diff options
Diffstat (limited to 'services')
96 files changed, 9595 insertions, 6295 deletions
diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk index fa49592..52834db 100644 --- a/services/audioflinger/Android.mk +++ b/services/audioflinger/Android.mk @@ -11,9 +11,11 @@ LOCAL_SRC_FILES:= \ AudioPolicyService.cpp LOCAL_C_INCLUDES := \ - system/media/audio_effects/include + system/media/audio_effects/include \ + system/media/audio_utils/include LOCAL_SHARED_LIBRARIES := \ + libaudioutils \ libcutils \ libutils \ libbinder \ diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index 5a46e44..f71ba0a 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -35,10 +35,10 @@ #include <cutils/bitops.h> #include <cutils/properties.h> +#include <cutils/compiler.h> -#include <media/AudioTrack.h> -#include <media/AudioRecord.h> #include <media/IMediaPlayerService.h> +#include <media/IMediaDeathNotifier.h> #include <private/media/AudioTrackShared.h> #include <private/media/AudioEffectShared.h> @@ -54,6 +54,8 @@ #include <audio_effects/effect_ns.h> #include <audio_effects/effect_aec.h> +#include <audio_utils/primitives.h> + #include <cpustats/ThreadCpuUsage.h> #include <powermanager/PowerManager.h> // #define DEBUG_CPU_USAGE 10 // log statistics every n wall clock seconds @@ -63,12 +65,12 @@ namespace android { -static const char* kDeadlockedString = "AudioFlinger may be deadlocked\n"; -static const char* kHardwareLockedString = "Hardware lock is taken\n"; +static const char kDeadlockedString[] = "AudioFlinger may be deadlocked\n"; +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; +static const uint32_t MAX_GAIN_INT = 0x1000; // retry counts for buffer fill timeout // 50 * ~20msecs = 1 second @@ -80,14 +82,16 @@ static const int8_t kMaxTrackStartupRetries = 50; static const int8_t kMaxTrackRetriesDirect = 2; static const int kDumpLockRetries = 50; -static const int kDumpLockSleep = 20000; +static const int kDumpLockSleepUs = 20000; -static const nsecs_t kWarningThrottle = seconds(5); +// don't warn about blocked writes or record buffer overflows more often than this +static const nsecs_t kWarningThrottleNs = seconds(5); // RecordThread loop sleep time upon application overrun or audio HAL read error static const int kRecordThreadSleepUs = 5000; -static const nsecs_t kSetParametersTimeout = seconds(2); +// maximum time to wait for setParameters to complete +static const nsecs_t kSetParametersTimeoutNs = seconds(2); // minimum sleep time for the mixer thread loop when tracks are active but in underrun static const uint32_t kMinThreadSleepTimeUs = 5000; @@ -113,11 +117,9 @@ static bool settingsAllowed() { // To collect the amplifier usage static void addBatteryData(uint32_t params) { - sp<IBinder> binder = - defaultServiceManager()->getService(String16("media.player")); - sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder); - if (service.get() == NULL) { - ALOGW("Cannot connect to the MediaPlayerService for battery tracking"); + sp<IMediaPlayerService> service = IMediaDeathNotifier::getMediaPlayerService(); + if (service == NULL) { + // it already logged return; } @@ -147,7 +149,7 @@ out: return rc; } -static const char *audio_interfaces[] = { +static const char * const audio_interfaces[] = { "primary", "a2dp", "usb", @@ -158,7 +160,10 @@ static const char *audio_interfaces[] = { AudioFlinger::AudioFlinger() : BnAudioFlinger(), - mPrimaryHardwareDev(0), mMasterVolume(1.0f), mMasterMute(false), mNextUniqueId(1), + mPrimaryHardwareDev(NULL), + mHardwareStatus(AUDIO_HW_IDLE), // see also onFirstRef() + mMasterVolume(1.0f), mMasterMute(false), mNextUniqueId(1), + mMode(AUDIO_MODE_INVALID), mBtNrecIsOff(false) { } @@ -170,7 +175,6 @@ void AudioFlinger::onFirstRef() Mutex::Autolock _l(mLock); /* TODO: move all this work into an Init() function */ - mHardwareStatus = AUDIO_HW_IDLE; for (size_t i = 0; i < ARRAY_SIZE(audio_interfaces); i++) { const hw_module_t *mod; @@ -263,13 +267,10 @@ status_t AudioFlinger::dumpClients(int fd, const Vector<String16>& args) result.append("Clients:\n"); for (size_t i = 0; i < mClients.size(); ++i) { - wp<Client> wClient = mClients.valueAt(i); - if (wClient != 0) { - sp<Client> client = wClient.promote(); - if (client != 0) { - snprintf(buffer, SIZE, " pid: %d\n", client->pid()); - result.append(buffer); - } + sp<Client> client = mClients.valueAt(i).promote(); + if (client != 0) { + snprintf(buffer, SIZE, " pid: %d\n", client->pid()); + result.append(buffer); } } @@ -290,7 +291,7 @@ status_t AudioFlinger::dumpInternals(int fd, const Vector<String16>& args) const size_t SIZE = 256; char buffer[SIZE]; String8 result; - int hardwareStatus = mHardwareStatus; + hardware_call_state hardwareStatus = mHardwareStatus; snprintf(buffer, SIZE, "Hardware status: %d\n", hardwareStatus); result.append(buffer); @@ -320,14 +321,14 @@ static bool tryLock(Mutex& mutex) locked = true; break; } - usleep(kDumpLockSleep); + usleep(kDumpLockSleepUs); } return locked; } status_t AudioFlinger::dump(int fd, const Vector<String16>& args) { - if (checkCallingPermission(String16("android.permission.DUMP")) == false) { + if (!checkCallingPermission(String16("android.permission.DUMP"))) { dumpPermissionDenial(fd, args); } else { // get state of hardware lock @@ -376,9 +377,9 @@ status_t AudioFlinger::dump(int fd, const Vector<String16>& args) sp<IAudioTrack> AudioFlinger::createTrack( pid_t pid, - int streamType, + audio_stream_type_t streamType, uint32_t sampleRate, - uint32_t format, + audio_format_t format, uint32_t channelMask, int frameCount, uint32_t flags, @@ -394,8 +395,10 @@ sp<IAudioTrack> AudioFlinger::createTrack( status_t lStatus; int lSessionId; - if (streamType >= AUDIO_STREAM_CNT) { - ALOGE("invalid stream type"); + // client AudioTrack::set already implements AUDIO_STREAM_DEFAULT => AUDIO_STREAM_MUSIC, + // but if someone uses binder directly they could bypass that and cause us to crash + if (uint32_t(streamType) >= AUDIO_STREAM_CNT) { + ALOGE("createTrack() invalid stream type %d", streamType); lStatus = BAD_VALUE; goto Exit; } @@ -427,6 +430,7 @@ sp<IAudioTrack> AudioFlinger::createTrack( // prevent same audio session on different output threads uint32_t sessions = t->hasAudioSession(*sessionId); if (sessions & PlaybackThread::TRACK_SESSION) { + ALOGE("createTrack() session ID %d already in use", *sessionId); lStatus = BAD_VALUE; goto Exit; } @@ -495,13 +499,13 @@ int AudioFlinger::channelCount(int output) const return thread->channelCount(); } -uint32_t AudioFlinger::format(int output) const +audio_format_t AudioFlinger::format(int output) const { Mutex::Autolock _l(mLock); PlaybackThread *thread = checkPlaybackThread_l(output); if (thread == NULL) { ALOGW("format() unknown thread %d", output); - return 0; + return AUDIO_FORMAT_INVALID; } return thread->format(); } @@ -558,7 +562,7 @@ status_t AudioFlinger::setMasterVolume(float value) return NO_ERROR; } -status_t AudioFlinger::setMode(int mode) +status_t AudioFlinger::setMode(audio_mode_t mode) { status_t ret = initCheck(); if (ret != NO_ERROR) { @@ -569,7 +573,7 @@ status_t AudioFlinger::setMode(int mode) if (!settingsAllowed()) { return PERMISSION_DENIED; } - if ((mode < 0) || (mode >= AUDIO_MODE_CNT)) { + if (uint32_t(mode) >= AUDIO_MODE_CNT) { ALOGW("Illegal value: setMode(%d)", mode); return BAD_VALUE; } @@ -641,22 +645,25 @@ status_t AudioFlinger::setMasterMute(bool muted) float AudioFlinger::masterVolume() const { - return mMasterVolume; + Mutex::Autolock _l(mLock); + return masterVolume_l(); } bool AudioFlinger::masterMute() const { - return mMasterMute; + Mutex::Autolock _l(mLock); + return masterMute_l(); } -status_t AudioFlinger::setStreamVolume(int stream, float value, int output) +status_t AudioFlinger::setStreamVolume(audio_stream_type_t stream, float value, int output) { // check calling permissions if (!settingsAllowed()) { return PERMISSION_DENIED; } - if (stream < 0 || uint32_t(stream) >= AUDIO_STREAM_CNT) { + if (uint32_t(stream) >= AUDIO_STREAM_CNT) { + ALOGE("setStreamVolume() invalid stream %d", stream); return BAD_VALUE; } @@ -682,15 +689,16 @@ status_t AudioFlinger::setStreamVolume(int stream, float value, int output) return NO_ERROR; } -status_t AudioFlinger::setStreamMute(int stream, bool muted) +status_t AudioFlinger::setStreamMute(audio_stream_type_t stream, bool muted) { // check calling permissions if (!settingsAllowed()) { return PERMISSION_DENIED; } - if (stream < 0 || uint32_t(stream) >= AUDIO_STREAM_CNT || + if (uint32_t(stream) >= AUDIO_STREAM_CNT || uint32_t(stream) == AUDIO_STREAM_ENFORCED_AUDIBLE) { + ALOGE("setStreamMute() invalid stream %d", stream); return BAD_VALUE; } @@ -702,9 +710,9 @@ status_t AudioFlinger::setStreamMute(int stream, bool muted) return NO_ERROR; } -float AudioFlinger::streamVolume(int stream, int output) const +float AudioFlinger::streamVolume(audio_stream_type_t stream, int output) const { - if (stream < 0 || uint32_t(stream) >= AUDIO_STREAM_CNT) { + if (uint32_t(stream) >= AUDIO_STREAM_CNT) { return 0.0f; } @@ -723,9 +731,9 @@ float AudioFlinger::streamVolume(int stream, int output) const return volume; } -bool AudioFlinger::streamMute(int stream) const +bool AudioFlinger::streamMute(audio_stream_type_t stream) const { - if (stream < 0 || stream >= (int)AUDIO_STREAM_CNT) { + if (uint32_t(stream) >= AUDIO_STREAM_CNT) { return true; } @@ -790,7 +798,7 @@ status_t AudioFlinger::setParameters(int ioHandle, const String8& keyValuePairs) thread = checkPlaybackThread_l(ioHandle); if (thread == NULL) { thread = checkRecordThread_l(ioHandle); - } else if (thread.get() == primaryPlaybackThread_l()) { + } else if (thread == primaryPlaybackThread_l()) { // indicate output device change to all input threads for pre processing AudioParameter param = AudioParameter(keyValuePairs); int value; @@ -838,7 +846,7 @@ String8 AudioFlinger::getParameters(int ioHandle, const String8& keys) return String8(""); } -size_t AudioFlinger::getInputBufferSize(uint32_t sampleRate, int format, int channelCount) +size_t AudioFlinger::getInputBufferSize(uint32_t sampleRate, audio_format_t format, int channelCount) { status_t ret = initCheck(); if (ret != NO_ERROR) { @@ -962,7 +970,8 @@ void AudioFlinger::audioConfigChanged_l(int event, int ioHandle, void *param2) { size_t size = mNotificationClients.size(); for (size_t i = 0; i < size; i++) { - mNotificationClients.valueAt(i)->client()->ioConfigChanged(event, ioHandle, param2); + mNotificationClients.valueAt(i)->audioFlingerClient()->ioConfigChanged(event, ioHandle, + param2); } } @@ -976,19 +985,24 @@ void AudioFlinger::removeClient_l(pid_t pid) // ---------------------------------------------------------------------------- -AudioFlinger::ThreadBase::ThreadBase(const sp<AudioFlinger>& audioFlinger, int id, uint32_t device) +AudioFlinger::ThreadBase::ThreadBase(const sp<AudioFlinger>& audioFlinger, int id, uint32_t device, + type_t type) : Thread(false), - mAudioFlinger(audioFlinger), mSampleRate(0), mFrameCount(0), mChannelCount(0), - mFrameSize(1), mFormat(0), mStandby(false), mId(id), mExiting(false), - mDevice(device) + mType(type), + mAudioFlinger(audioFlinger), mSampleRate(0), mFrameCount(0), + // mChannelMask + mChannelCount(0), + mFrameSize(1), mFormat(AUDIO_FORMAT_INVALID), + mParamStatus(NO_ERROR), + mStandby(false), mId(id), mExiting(false), + mDevice(device), + mDeathRecipient(new PMDeathRecipient(this)) { - mDeathRecipient = new PMDeathRecipient(this); } AudioFlinger::ThreadBase::~ThreadBase() { mParamCond.broadcast(); - mNewParameters.clear(); // do not lock the mutex in destructor releaseWakeLock_l(); if (mPowerManager != 0) { @@ -999,13 +1013,13 @@ AudioFlinger::ThreadBase::~ThreadBase() void AudioFlinger::ThreadBase::exit() { - // keep a strong ref on ourself so that we wont get + // keep a strong ref on ourself so that we won't get // destroyed in the middle of requestExitAndWait() sp <ThreadBase> strongMe = this; ALOGV("ThreadBase::exit"); { - AutoMutex lock(&mLock); + AutoMutex lock(mLock); mExiting = true; requestExit(); mWaitWorkCV.signal(); @@ -1023,7 +1037,7 @@ int AudioFlinger::ThreadBase::channelCount() const return (int)mChannelCount; } -uint32_t AudioFlinger::ThreadBase::format() const +audio_format_t AudioFlinger::ThreadBase::format() const { return mFormat; } @@ -1044,7 +1058,7 @@ status_t AudioFlinger::ThreadBase::setParameters(const String8& keyValuePairs) mWaitWorkCV.signal(); // wait condition with timeout in case the thread loop has exited // before the request could be processed - if (mParamCond.waitRelative(mLock, kSetParametersTimeout) == NO_ERROR) { + if (mParamCond.waitRelative(mLock, kSetParametersTimeoutNs) == NO_ERROR) { status = mParamStatus; mWaitWorkCV.signal(); } else { @@ -1062,9 +1076,9 @@ void AudioFlinger::ThreadBase::sendConfigEvent(int event, int param) // sendConfigEvent_l() must be called with ThreadBase::mLock held void AudioFlinger::ThreadBase::sendConfigEvent_l(int event, int param) { - ConfigEvent *configEvent = new ConfigEvent(); - configEvent->mEvent = event; - configEvent->mParam = param; + ConfigEvent configEvent; + configEvent.mEvent = event; + configEvent.mParam = param; mConfigEvents.add(configEvent); ALOGV("sendConfigEvent() num events %d event %d, param %d", mConfigEvents.size(), event, param); mWaitWorkCV.signal(); @@ -1075,15 +1089,14 @@ void AudioFlinger::ThreadBase::processConfigEvents() mLock.lock(); while(!mConfigEvents.isEmpty()) { ALOGV("processConfigEvents() remaining events %d", mConfigEvents.size()); - ConfigEvent *configEvent = mConfigEvents[0]; + ConfigEvent configEvent = mConfigEvents[0]; mConfigEvents.removeAt(0); // release mLock before locking AudioFlinger mLock: lock order is always // AudioFlinger then ThreadBase to avoid cross deadlock mLock.unlock(); mAudioFlinger->mLock.lock(); - audioConfigChanged_l(configEvent->mEvent, configEvent->mParam); + audioConfigChanged_l(configEvent.mEvent, configEvent.mParam); mAudioFlinger->mLock.unlock(); - delete configEvent; mLock.lock(); } mLock.unlock(); @@ -1113,7 +1126,7 @@ status_t AudioFlinger::ThreadBase::dumpBase(int fd, const Vector<String16>& args result.append(buffer); snprintf(buffer, SIZE, "Format: %d\n", mFormat); result.append(buffer); - snprintf(buffer, SIZE, "Frame size: %d\n", mFrameSize); + snprintf(buffer, SIZE, "Frame size: %u\n", mFrameSize); result.append(buffer); snprintf(buffer, SIZE, "\nPending setParameters commands: \n"); @@ -1130,7 +1143,7 @@ status_t AudioFlinger::ThreadBase::dumpBase(int fd, const Vector<String16>& args snprintf(buffer, SIZE, " Index event param\n"); result.append(buffer); for (size_t i = 0; i < mConfigEvents.size(); i++) { - snprintf(buffer, SIZE, " %02d %02d %d\n", i, mConfigEvents[i]->mEvent, mConfigEvents[i]->mParam); + snprintf(buffer, SIZE, " %02d %02d %d\n", i, mConfigEvents[i].mEvent, mConfigEvents[i].mParam); result.append(buffer); } result.append("\n"); @@ -1365,22 +1378,32 @@ void AudioFlinger::ThreadBase::checkSuspendOnEffectEnabled_l(const sp<EffectModu AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, - uint32_t device) - : ThreadBase(audioFlinger, id, device), - mMixBuffer(0), mSuspended(0), mBytesWritten(0), mOutput(output), + uint32_t device, + type_t type) + : ThreadBase(audioFlinger, id, device, type), + mMixBuffer(NULL), mSuspended(0), mBytesWritten(0), + // Assumes constructor is called by AudioFlinger with it's mLock held, + // but it would be safer to explicitly pass initial masterMute as parameter + mMasterMute(audioFlinger->masterMute_l()), + // mStreamTypes[] initialized in constructor body + mOutput(output), + // Assumes constructor is called by AudioFlinger with it's mLock held, + // but it would be safer to explicitly pass initial masterVolume as parameter + mMasterVolume(audioFlinger->masterVolume_l()), mLastWriteTime(0), mNumWrites(0), mNumDelayedWrites(0), mInWrite(false) { snprintf(mName, kNameLength, "AudioOut_%d", id); readOutputParameters(); - mMasterVolume = mAudioFlinger->masterVolume(); - mMasterMute = mAudioFlinger->masterMute(); - - for (int stream = 0; stream < AUDIO_STREAM_CNT; stream++) { + // mStreamTypes[AUDIO_STREAM_CNT] is initialized by stream_type_t default constructor + // There is no AUDIO_STREAM_MIN, and ++ operator does not compile + for (audio_stream_type_t stream = (audio_stream_type_t) 0; stream < AUDIO_STREAM_CNT; + stream = (audio_stream_type_t) (stream + 1)) { mStreamTypes[stream].volume = mAudioFlinger->streamVolumeInternal(stream); mStreamTypes[stream].mute = mAudioFlinger->streamMute(stream); - mStreamTypes[stream].valid = true; + // initialized by stream_type_t default constructor + // mStreamTypes[stream].valid = true; } } @@ -1418,13 +1441,10 @@ status_t AudioFlinger::PlaybackThread::dumpTracks(int fd, const Vector<String16> result.append(buffer); result.append(" Name Clien Typ Fmt Chn mask 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) { - sp<Track> track = wTrack.promote(); - if (track != 0) { - track->dump(buffer, SIZE); - result.append(buffer); - } + sp<Track> track = mActiveTracks[i].promote(); + if (track != 0) { + track->dump(buffer, SIZE); + result.append(buffer); } } write(fd, result.string(), result.size()); @@ -1478,9 +1498,9 @@ void AudioFlinger::PlaybackThread::onFirstRef() // PlaybackThread::createTrack_l() must be called with AudioFlinger::mLock held sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTrack_l( const sp<AudioFlinger::Client>& client, - int streamType, + audio_stream_type_t streamType, uint32_t sampleRate, - uint32_t format, + audio_format_t format, uint32_t channelMask, int frameCount, const sp<IMemory>& sharedBuffer, @@ -1526,8 +1546,10 @@ sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTra for (size_t i = 0; i < mTracks.size(); ++i) { sp<Track> t = mTracks[i]; if (t != 0) { - if (sessionId == t->sessionId() && - strategy != AudioSystem::getStrategyForStream((audio_stream_type_t)t->type())) { + uint32_t actual = AudioSystem::getStrategyForStream((audio_stream_type_t)t->type()); + if (sessionId == t->sessionId() && strategy != actual) { + ALOGE("createTrack_l() mismatched strategy; expected %u but found %u", + strategy, actual); lStatus = BAD_VALUE; goto Exit; } @@ -1599,24 +1621,24 @@ bool AudioFlinger::PlaybackThread::masterMute() const return mMasterMute; } -status_t AudioFlinger::PlaybackThread::setStreamVolume(int stream, float value) +status_t AudioFlinger::PlaybackThread::setStreamVolume(audio_stream_type_t stream, float value) { mStreamTypes[stream].volume = value; return NO_ERROR; } -status_t AudioFlinger::PlaybackThread::setStreamMute(int stream, bool muted) +status_t AudioFlinger::PlaybackThread::setStreamMute(audio_stream_type_t stream, bool muted) { mStreamTypes[stream].mute = muted; return NO_ERROR; } -float AudioFlinger::PlaybackThread::streamVolume(int stream) const +float AudioFlinger::PlaybackThread::streamVolume(audio_stream_type_t stream) const { return mStreamTypes[stream].volume; } -bool AudioFlinger::PlaybackThread::streamMute(int stream) const +bool AudioFlinger::PlaybackThread::streamMute(audio_stream_type_t stream) const { return mStreamTypes[stream].mute; } @@ -1690,7 +1712,7 @@ String8 AudioFlinger::PlaybackThread::getParameters(const String8& keys) // audioConfigChanged_l() must be called with AudioFlinger::mLock held void AudioFlinger::PlaybackThread::audioConfigChanged_l(int event, int param) { AudioSystem::OutputDescriptor desc; - void *param2 = 0; + void *param2 = NULL; ALOGV("PlaybackThread::audioConfigChanged_l, thread %p, event %d, param %d", this, event, param); @@ -1720,12 +1742,12 @@ void AudioFlinger::PlaybackThread::readOutputParameters() mChannelMask = mOutput->stream->common.get_channels(&mOutput->stream->common); mChannelCount = (uint16_t)popcount(mChannelMask); mFormat = mOutput->stream->common.get_format(&mOutput->stream->common); - mFrameSize = (uint16_t)audio_stream_frame_size(&mOutput->stream->common); + mFrameSize = audio_stream_frame_size(&mOutput->stream->common); mFrameCount = mOutput->stream->common.get_buffer_size(&mOutput->stream->common) / 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; + delete[] mMixBuffer; mMixBuffer = new int16_t[mFrameCount * 2]; memset(mMixBuffer, 0, mFrameCount * 2 * sizeof(int16_t)); @@ -1743,7 +1765,7 @@ void AudioFlinger::PlaybackThread::readOutputParameters() status_t AudioFlinger::PlaybackThread::getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames) { - if (halFrames == 0 || dspFrames == 0) { + if (halFrames == NULL || dspFrames == NULL) { return BAD_VALUE; } Mutex::Autolock _l(mLock); @@ -1793,7 +1815,7 @@ uint32_t AudioFlinger::PlaybackThread::getStrategyForSession_l(int sessionId) } -AudioFlinger::AudioStreamOut* AudioFlinger::PlaybackThread::getOutput() +AudioFlinger::AudioStreamOut* AudioFlinger::PlaybackThread::getOutput() const { Mutex::Autolock _l(mLock); return mOutput; @@ -1830,13 +1852,12 @@ uint32_t AudioFlinger::PlaybackThread::activeSleepTimeUs() // ---------------------------------------------------------------------------- -AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, uint32_t device) - : PlaybackThread(audioFlinger, output, id, device), - mAudioMixer(0), mPrevMixerStatus(MIXER_IDLE) +AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, + int id, uint32_t device, type_t type) + : PlaybackThread(audioFlinger, output, id, device, type), + mAudioMixer(new AudioMixer(mFrameCount, mSampleRate)), + mPrevMixerStatus(MIXER_IDLE) { - mType = ThreadBase::MIXER; - mAudioMixer = new AudioMixer(mFrameCount, mSampleRate); - // FIXME - Current mixer implementation only supports stereo output if (mChannelCount == 1) { ALOGE("Invalid audio hardware channel count"); @@ -1851,7 +1872,7 @@ AudioFlinger::MixerThread::~MixerThread() bool AudioFlinger::MixerThread::threadLoop() { Vector< sp<Track> > tracksToRemove; - uint32_t mixerStatus = MIXER_IDLE; + mixer_state mixerStatus = MIXER_IDLE; nsecs_t standbyTime = systemTime(); size_t mixBufferSize = mFrameCount * mFrameSize; // FIXME: Relaxed timing because of a certain device that can't meet latency @@ -1923,8 +1944,8 @@ bool AudioFlinger::MixerThread::threadLoop() const SortedVector< wp<Track> >& activeTracks = mActiveTracks; // put audio hardware into standby after short delay - if UNLIKELY((!activeTracks.size() && systemTime() > standbyTime) || - mSuspended) { + if (CC_UNLIKELY((!activeTracks.size() && systemTime() > standbyTime) || + mSuspended)) { if (!mStandby) { ALOGV("Audio hardware entering standby, mixer %p, mSuspended %d\n", this, mSuspended); mOutput->stream->common.standby(&mOutput->stream->common); @@ -1946,7 +1967,7 @@ bool AudioFlinger::MixerThread::threadLoop() acquireWakeLock_l(); mPrevMixerStatus = MIXER_IDLE; - if (mMasterMute == false) { + if (!mMasterMute) { char value[PROPERTY_VALUE_MAX]; property_get("ro.audio.silent", value, "0"); if (atoi(value)) { @@ -1968,9 +1989,9 @@ bool AudioFlinger::MixerThread::threadLoop() // 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)) { + if (CC_LIKELY(mixerStatus == MIXER_TRACKS_READY)) { // mix buffers... mAudioMixer->process(); // increase sleep time progressively when application underrun condition clears. @@ -2016,11 +2037,11 @@ bool AudioFlinger::MixerThread::threadLoop() } // 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); + for (size_t i = 0; i < effectChains.size(); i ++) { + effectChains[i]->process_l(); + } + // enable changes in effect chain + unlockEffectChains(effectChains); mLastWriteTime = systemTime(); mInWrite = true; mBytesWritten += mixBufferSize; @@ -2033,7 +2054,7 @@ bool AudioFlinger::MixerThread::threadLoop() nsecs_t delta = now - mLastWriteTime; if (!mStandby && delta > maxPeriod) { mNumDelayedWrites++; - if ((now - lastWarning) > kWarningThrottle) { + if ((now - lastWarning) > kWarningThrottleNs) { ALOGW("write blocked for %llu msecs, %d delayed writes, thread %p", ns2ms(delta), mNumDelayedWrites, this); lastWarning = now; @@ -2070,10 +2091,11 @@ bool AudioFlinger::MixerThread::threadLoop() } // prepareTracks_l() must be called with ThreadBase::mLock held -uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track> >& activeTracks, Vector< sp<Track> > *tracksToRemove) +AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTracks_l( + const SortedVector< wp<Track> >& activeTracks, Vector< sp<Track> > *tracksToRemove) { - uint32_t mixerStatus = MIXER_IDLE; + mixer_state mixerStatus = MIXER_IDLE; // find out which tracks need to be processed size_t count = activeTracks.size(); size_t mixedTracks = 0; @@ -2098,12 +2120,13 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track sp<Track> t = activeTracks[i].promote(); if (t == 0) continue; + // this const just means the local variable doesn't change Track* const track = t.get(); audio_track_cblk_t* cblk = track->cblk(); // The first time a track is added we wait // for all its buffers to be filled before processing it - mAudioMixer->setActiveTrack(track->name()); + int name = track->name(); // make sure that we have enough frames to mix one full buffer. // enforce this condition only once to enable draining the buffer in case the client // app does not call stop() and relies on underrun to stop: @@ -2123,13 +2146,13 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track // the minimum track buffer size is normally twice the number of frames necessary // to fill one buffer and the resampler should not leave more than one buffer worth // of unreleased frames after each pass, but just in case... - LOG_ASSERT(minFrames <= cblk->frameCount); + ALOG_ASSERT(minFrames <= cblk->frameCount); } } if ((cblk->framesReady() >= minFrames) && track->isReady() && !track->isPaused() && !track->isTerminated()) { - //ALOGV("track %d u=%08x, s=%08x [OK] on thread %p", track->name(), cblk->user, cblk->server, this); + //ALOGV("track %d u=%08x, s=%08x [OK] on thread %p", name, cblk->user, cblk->server, this); mixedTracks++; @@ -2142,8 +2165,8 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track if (chain != 0) { tracksWithEffect++; } else { - ALOGW("prepareTracks_l(): track %08x attached to effect but no chain found on session %d", - track->name(), track->sessionId()); + ALOGW("prepareTracks_l(): track %d attached to effect but no chain found on session %d", + name, track->sessionId()); } } @@ -2156,7 +2179,7 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track track->mState = TrackBase::ACTIVE; param = AudioMixer::RAMP_VOLUME; } - mAudioMixer->setParameter(AudioMixer::RESAMPLE, AudioMixer::RESET, NULL); + mAudioMixer->setParameter(name, AudioMixer::RESAMPLE, AudioMixer::RESET, NULL); } else if (cblk->server != 0) { // If the track is stopped before the first frame was mixed, // do not apply ramp @@ -2176,10 +2199,31 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track // read original volumes with volume control float typeVolume = mStreamTypes[track->type()].volume; float v = masterVolume * typeVolume; - vl = (uint32_t)(v * cblk->volume[0]) << 12; - vr = (uint32_t)(v * cblk->volume[1]) << 12; - - va = (uint32_t)(v * cblk->sendLevel); + uint32_t vlr = cblk->getVolumeLR(); + vl = vlr & 0xFFFF; + vr = vlr >> 16; + // track volumes come from shared memory, so can't be trusted and must be clamped + if (vl > MAX_GAIN_INT) { + ALOGV("Track left volume out of range: %04X", vl); + vl = MAX_GAIN_INT; + } + if (vr > MAX_GAIN_INT) { + ALOGV("Track right volume out of range: %04X", vr); + vr = MAX_GAIN_INT; + } + // now apply the master volume and stream type volume + vl = (uint32_t)(v * vl) << 12; + vr = (uint32_t)(v * vr) << 12; + // assuming master volume and stream type volume each go up to 1.0, + // vl and vr are now in 8.24 format + + uint16_t sendLevel = cblk->getSendLevel_U4_12(); + // send level comes from shared memory and so may be corrupt + if (sendLevel >= MAX_GAIN_INT) { + ALOGV("Track send level out of range: %04X", sendLevel); + sendLevel = MAX_GAIN_INT; + } + va = (uint32_t)(v * sendLevel); } // Delegate volume control to effect in track effect chain if needed if (chain != 0 && chain->setVolume_l(&vl, &vr)) { @@ -2197,6 +2241,7 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track // Convert volumes from 8.24 to 4.12 format int16_t left, right, aux; + // This additional clamping is needed in case chain->setVolume_l() overshot uint32_t v_clamped = (vl + (1 << 11)) >> 12; if (v_clamped > MAX_GAIN_INT) v_clamped = MAX_GAIN_INT; left = int16_t(v_clamped); @@ -2208,26 +2253,31 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track aux = int16_t(va); // XXX: these things DON'T need to be done each time - mAudioMixer->setBufferProvider(track); - mAudioMixer->enable(AudioMixer::MIXING); + mAudioMixer->setBufferProvider(name, track); + mAudioMixer->enable(name); - mAudioMixer->setParameter(param, AudioMixer::VOLUME0, (void *)left); - mAudioMixer->setParameter(param, AudioMixer::VOLUME1, (void *)right); - mAudioMixer->setParameter(param, AudioMixer::AUXLEVEL, (void *)aux); + mAudioMixer->setParameter(name, param, AudioMixer::VOLUME0, (void *)left); + mAudioMixer->setParameter(name, param, AudioMixer::VOLUME1, (void *)right); + mAudioMixer->setParameter(name, param, AudioMixer::AUXLEVEL, (void *)aux); mAudioMixer->setParameter( + name, AudioMixer::TRACK, AudioMixer::FORMAT, (void *)track->format()); mAudioMixer->setParameter( + name, AudioMixer::TRACK, AudioMixer::CHANNEL_MASK, (void *)track->channelMask()); mAudioMixer->setParameter( + name, AudioMixer::RESAMPLE, AudioMixer::SAMPLE_RATE, (void *)(cblk->sampleRate)); mAudioMixer->setParameter( + name, AudioMixer::TRACK, AudioMixer::MAIN_BUFFER, (void *)track->mainBuffer()); mAudioMixer->setParameter( + name, AudioMixer::TRACK, AudioMixer::AUX_BUFFER, (void *)track->auxBuffer()); @@ -2241,7 +2291,7 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track mixerStatus = MIXER_TRACKS_READY; } } else { - //ALOGV("track %d u=%08x, s=%08x [NOT READY] on thread %p", track->name(), cblk->user, cblk->server, this); + //ALOGV("track %d u=%08x, s=%08x [NOT READY] on thread %p", name, cblk->user, cblk->server, this); if (track->isStopped()) { track->reset(); } @@ -2253,7 +2303,7 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track // No buffers for this track. Give it a few chances to // fill a buffer, then remove it from active list. if (--(track->mRetryCount) <= 0) { - ALOGV("BUFFER TIMEOUT: remove(%d) from active list on thread %p", track->name(), this); + ALOGV("BUFFER TIMEOUT: remove(%d) from active list on thread %p", name, this); tracksToRemove->add(track); // indicate to client process that the track was disabled because of underrun android_atomic_or(CBLK_DISABLED_ON, &cblk->flags); @@ -2265,13 +2315,13 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track mixerStatus = MIXER_TRACKS_ENABLED; } } - mAudioMixer->disable(AudioMixer::MIXING); + mAudioMixer->disable(name); } } // remove all the tracks that need to be... count = tracksToRemove->size(); - if (UNLIKELY(count)) { + if (CC_UNLIKELY(count)) { for (size_t i=0 ; i<count ; i++) { const sp<Track>& track = tracksToRemove->itemAt(i); mActiveTracks.remove(track); @@ -2299,7 +2349,7 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track return mixerStatus; } -void AudioFlinger::MixerThread::invalidateTracks(int streamType) +void AudioFlinger::MixerThread::invalidateTracks(audio_stream_type_t streamType) { ALOGV ("MixerThread::invalidateTracks() mixer %p, streamType %d, mTracks.size %d", this, streamType, mTracks.size()); @@ -2315,7 +2365,7 @@ void AudioFlinger::MixerThread::invalidateTracks(int streamType) } } -void AudioFlinger::PlaybackThread::setStreamValid(int streamType, bool valid) +void AudioFlinger::PlaybackThread::setStreamValid(audio_stream_type_t streamType, bool valid) { ALOGV ("PlaybackThread::setStreamValid() thread %p, streamType %d, valid %d", this, streamType, valid); @@ -2352,7 +2402,7 @@ bool AudioFlinger::MixerThread::checkForNewParameters_l() reconfig = true; } if (param.getInt(String8(AudioParameter::keyFormat), value) == NO_ERROR) { - if (value != AUDIO_FORMAT_PCM_16_BIT) { + if ((audio_format_t) value != AUDIO_FORMAT_PCM_16_BIT) { status = BAD_VALUE; } else { reconfig = true; @@ -2367,7 +2417,7 @@ bool AudioFlinger::MixerThread::checkForNewParameters_l() } if (param.getInt(String8(AudioParameter::keyFrameCount), value) == NO_ERROR) { // do not accept frame count changes if tracks are open as the track buffer - // size depends on frame count and correct behavior would not be garantied + // size depends on frame count and correct behavior would not be guaranteed // if frame count is changed after track creation if (!mTracks.isEmpty()) { status = INVALID_OPERATION; @@ -2417,6 +2467,8 @@ bool AudioFlinger::MixerThread::checkForNewParameters_l() } if (status == NO_ERROR && reconfig) { delete mAudioMixer; + // for safety in case readOutputParameters() accesses mAudioMixer (it doesn't) + mAudioMixer = NULL; readOutputParameters(); mAudioMixer = new AudioMixer(mFrameCount, mSampleRate); for (size_t i = 0; i < mTracks.size() ; i++) { @@ -2438,7 +2490,7 @@ bool AudioFlinger::MixerThread::checkForNewParameters_l() mParamCond.signal(); // wait for condition with time out in case the thread calling ThreadBase::setParameters() // already timed out waiting for the status and will never signal the condition. - mWaitWorkCV.waitRelative(mLock, kSetParametersTimeout); + mWaitWorkCV.waitRelative(mLock, kSetParametersTimeoutNs); } return reconfig; } @@ -2469,23 +2521,16 @@ uint32_t AudioFlinger::MixerThread::suspendSleepTimeUs() // ---------------------------------------------------------------------------- AudioFlinger::DirectOutputThread::DirectOutputThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, uint32_t device) - : PlaybackThread(audioFlinger, output, id, device) + : PlaybackThread(audioFlinger, output, id, device, DIRECT) + // mLeftVolFloat, mRightVolFloat + // mLeftVolShort, mRightVolShort { - mType = ThreadBase::DIRECT; } 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) { @@ -2577,7 +2622,7 @@ void AudioFlinger::DirectOutputThread::applyVolume(uint16_t leftVol, uint16_t ri bool AudioFlinger::DirectOutputThread::threadLoop() { - uint32_t mixerStatus = MIXER_IDLE; + mixer_state mixerStatus = MIXER_IDLE; sp<Track> trackToRemove; sp<Track> activeTrack; nsecs_t standbyTime = systemTime(); @@ -2615,8 +2660,8 @@ bool AudioFlinger::DirectOutputThread::threadLoop() } // put audio hardware into standby after short delay - if UNLIKELY((!mActiveTracks.size() && systemTime() > standbyTime) || - mSuspended) { + if (CC_UNLIKELY((!mActiveTracks.size() && systemTime() > standbyTime) || + mSuspended)) { // wait until we have something to do... if (!mStandby) { ALOGV("Audio hardware entering standby, mixer %p\n", this); @@ -2637,7 +2682,7 @@ bool AudioFlinger::DirectOutputThread::threadLoop() ALOGV("DirectOutputThread %p TID %d waking up in active mode\n", this, gettid()); acquireWakeLock_l(); - if (mMasterMute == false) { + if (!mMasterMute) { char value[PROPERTY_VALUE_MAX]; property_get("ro.audio.silent", value, "0"); if (atoi(value)) { @@ -2693,10 +2738,11 @@ bool AudioFlinger::DirectOutputThread::threadLoop() } else { float typeVolume = mStreamTypes[track->type()].volume; float v = mMasterVolume * typeVolume; - float v_clamped = v * cblk->volume[0]; + uint32_t vlr = cblk->getVolumeLR(); + float v_clamped = v * (vlr & 0xFFFF); if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN; left = v_clamped/MAX_GAIN; - v_clamped = v * cblk->volume[1]; + v_clamped = v * (vlr >> 16); if (v_clamped > MAX_GAIN) v_clamped = MAX_GAIN; right = v_clamped/MAX_GAIN; } @@ -2766,7 +2812,7 @@ bool AudioFlinger::DirectOutputThread::threadLoop() } // remove all the tracks that need to be... - if (UNLIKELY(trackToRemove != 0)) { + if (CC_UNLIKELY(trackToRemove != 0)) { mActiveTracks.remove(trackToRemove); if (!effectChains.isEmpty()) { ALOGV("stopping track on chain %p for session Id: %d", effectChains[0].get(), @@ -2781,7 +2827,7 @@ bool AudioFlinger::DirectOutputThread::threadLoop() lockEffectChains_l(effectChains); } - if (LIKELY(mixerStatus == MIXER_TRACKS_READY)) { + if (CC_LIKELY(mixerStatus == MIXER_TRACKS_READY)) { AudioBufferProvider::Buffer buffer; size_t frameCount = mFrameCount; curBuf = (int8_t *)mMixBuffer; @@ -2789,7 +2835,7 @@ bool AudioFlinger::DirectOutputThread::threadLoop() while (frameCount) { buffer.frameCount = frameCount; activeTrack->getNextBuffer(&buffer); - if (UNLIKELY(buffer.raw == 0)) { + if (CC_UNLIKELY(buffer.raw == NULL)) { memset(curBuf, 0, frameCount * mFrameSize); break; } @@ -2914,7 +2960,7 @@ bool AudioFlinger::DirectOutputThread::checkForNewParameters_l() mParamCond.signal(); // wait for condition with time out in case the thread calling ThreadBase::setParameters() // already timed out waiting for the status and will never signal the condition. - mWaitWorkCV.waitRelative(mLock, kSetParametersTimeout); + mWaitWorkCV.waitRelative(mLock, kSetParametersTimeoutNs); } return reconfig; } @@ -2955,10 +3001,11 @@ uint32_t AudioFlinger::DirectOutputThread::suspendSleepTimeUs() // ---------------------------------------------------------------------------- -AudioFlinger::DuplicatingThread::DuplicatingThread(const sp<AudioFlinger>& audioFlinger, AudioFlinger::MixerThread* mainThread, int id) - : MixerThread(audioFlinger, mainThread->getOutput(), id, mainThread->device()), mWaitTimeMs(UINT_MAX) +AudioFlinger::DuplicatingThread::DuplicatingThread(const sp<AudioFlinger>& audioFlinger, + AudioFlinger::MixerThread* mainThread, int id) + : MixerThread(audioFlinger, mainThread->getOutput(), id, mainThread->device(), DUPLICATING), + mWaitTimeMs(UINT_MAX) { - mType = ThreadBase::DUPLICATING; addOutputTrack(mainThread); } @@ -2973,7 +3020,7 @@ AudioFlinger::DuplicatingThread::~DuplicatingThread() bool AudioFlinger::DuplicatingThread::threadLoop() { Vector< sp<Track> > tracksToRemove; - uint32_t mixerStatus = MIXER_IDLE; + mixer_state mixerStatus = MIXER_IDLE; nsecs_t standbyTime = systemTime(); size_t mixBufferSize = mFrameCount*mFrameSize; SortedVector< sp<OutputTrack> > outputTracks; @@ -3008,8 +3055,8 @@ bool AudioFlinger::DuplicatingThread::threadLoop() } // put audio hardware into standby after short delay - if UNLIKELY((!activeTracks.size() && systemTime() > standbyTime) || - mSuspended) { + if (CC_UNLIKELY((!activeTracks.size() && systemTime() > standbyTime) || + mSuspended)) { if (!mStandby) { for (size_t i = 0; i < outputTracks.size(); i++) { outputTracks[i]->stop(); @@ -3032,7 +3079,7 @@ bool AudioFlinger::DuplicatingThread::threadLoop() acquireWakeLock_l(); mPrevMixerStatus = MIXER_IDLE; - if (mMasterMute == false) { + if (!mMasterMute) { char value[PROPERTY_VALUE_MAX]; property_get("ro.audio.silent", value, "0"); if (atoi(value)) { @@ -3055,7 +3102,7 @@ bool AudioFlinger::DuplicatingThread::threadLoop() lockEffectChains_l(effectChains); } - if (LIKELY(mixerStatus == MIXER_TRACKS_READY)) { + if (CC_LIKELY(mixerStatus == MIXER_TRACKS_READY)) { // mix buffers... if (outputsReady(outputTracks)) { mAudioMixer->process(); @@ -3198,7 +3245,7 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( const wp<ThreadBase>& thread, const sp<Client>& client, uint32_t sampleRate, - uint32_t format, + audio_format_t format, uint32_t channelMask, int frameCount, uint32_t flags, @@ -3207,13 +3254,17 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( : RefBase(), mThread(thread), mClient(client), - mCblk(0), + mCblk(NULL), + // mBuffer + // mBufferEnd mFrameCount(0), mState(IDLE), mClientTid(-1), mFormat(format), mFlags(flags & ~SYSTEM_FLAGS_MASK), mSessionId(sessionId) + // mChannelCount + // mChannelMask { ALOGV_IF(sharedBuffer != 0, "sharedBuffer: %p, size: %d", sharedBuffer->pointer(), sharedBuffer->size()); @@ -3229,7 +3280,7 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( mCblkMemory = client->heap()->allocate(size); if (mCblkMemory != 0) { mCblk = static_cast<audio_track_cblk_t *>(mCblkMemory->pointer()); - if (mCblk) { // construct the shared structure in-place. + if (mCblk != NULL) { // construct the shared structure in-place. new(mCblk) audio_track_cblk_t(); // clear all buffers mCblk->frameCount = frameCount; @@ -3254,7 +3305,7 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( } } else { mCblk = (audio_track_cblk_t *)(new uint8_t[size]); - if (mCblk) { // construct the shared structure in-place. + // construct the shared structure in-place. new(mCblk) audio_track_cblk_t(); // clear all buffers mCblk->frameCount = frameCount; @@ -3267,13 +3318,12 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( // written to buffer (other flags are cleared) mCblk->flags = CBLK_UNDERRUN_ON; mBufferEnd = (uint8_t *)mBuffer + bufferSize; - } } } AudioFlinger::ThreadBase::TrackBase::~TrackBase() { - if (mCblk) { + if (mCblk != NULL) { mCblk->~audio_track_cblk_t(); // destroy our shared-structure. if (mClient == NULL) { delete mCblk; @@ -3281,6 +3331,7 @@ AudioFlinger::ThreadBase::TrackBase::~TrackBase() } mCblkMemory.clear(); // and free the shared memory if (mClient != NULL) { + // Client destructor must run with AudioFlinger mutex locked Mutex::Autolock _l(mClient->audioFlinger()->mLock); mClient.clear(); } @@ -3288,7 +3339,7 @@ AudioFlinger::ThreadBase::TrackBase::~TrackBase() void AudioFlinger::ThreadBase::TrackBase::releaseBuffer(AudioBufferProvider::Buffer* buffer) { - buffer->raw = 0; + buffer->raw = NULL; mFrameCount = buffer->frameCount; step(); buffer->frameCount = 0; @@ -3336,17 +3387,18 @@ uint32_t AudioFlinger::ThreadBase::TrackBase::channelMask() const { void* AudioFlinger::ThreadBase::TrackBase::getBuffer(uint32_t offset, uint32_t frames) const { audio_track_cblk_t* cblk = this->cblk(); - int8_t *bufferStart = (int8_t *)mBuffer + (offset-cblk->serverBase)*cblk->frameSize; - int8_t *bufferEnd = bufferStart + frames * cblk->frameSize; + size_t frameSize = cblk->frameSize; + int8_t *bufferStart = (int8_t *)mBuffer + (offset-cblk->serverBase)*frameSize; + int8_t *bufferEnd = bufferStart + frames * frameSize; // Check validity of returned pointer in case the track control block would have been corrupted. if (bufferStart < mBuffer || bufferStart > bufferEnd || bufferEnd > mBufferEnd || - ((unsigned long)bufferStart & (unsigned long)(cblk->frameSize - 1))) { + ((unsigned long)bufferStart & (unsigned long)(frameSize - 1))) { ALOGE("TrackBase::getBuffer buffer out of range:\n start: %p, end %p , mBuffer %p mBufferEnd %p\n \ server %d, serverBase %d, user %d, userBase %d", bufferStart, bufferEnd, mBuffer, mBufferEnd, cblk->server, cblk->serverBase, cblk->user, cblk->userBase); - return 0; + return NULL; } return bufferStart; @@ -3358,9 +3410,9 @@ void* AudioFlinger::ThreadBase::TrackBase::getBuffer(uint32_t offset, uint32_t f AudioFlinger::PlaybackThread::Track::Track( const wp<ThreadBase>& thread, const sp<Client>& client, - int streamType, + audio_stream_type_t streamType, uint32_t sampleRate, - uint32_t format, + audio_format_t format, uint32_t channelMask, int frameCount, const sp<IMemory>& sharedBuffer, @@ -3380,8 +3432,6 @@ AudioFlinger::PlaybackThread::Track::Track( if (mName < 0) { ALOGE("no more track names available"); } - mVolume[0] = 1.0f; - mVolume[1] = 1.0f; mStreamType = streamType; // NOTE: audio_track_cblk_t::frameSize for 8 bit PCM data is based on a sample size of // 16 bit because data is converted to 16 bit before being stored in buffer by AudioTrack @@ -3433,6 +3483,7 @@ void AudioFlinger::PlaybackThread::Track::destroy() void AudioFlinger::PlaybackThread::Track::dump(char* buffer, size_t size) { + uint32_t vlr = mCblk->getVolumeLR(); snprintf(buffer, size, " %05d %05d %03u %03u 0x%08x %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(), @@ -3445,8 +3496,8 @@ void AudioFlinger::PlaybackThread::Track::dump(char* buffer, size_t size) mMute, mFillingUpStatus, mCblk->sampleRate, - mCblk->volume[0], - mCblk->volume[1], + vlr & 0xFFFF, + vlr >> 16, mCblk->server, mCblk->user, (int)mMainBuffer, @@ -3468,7 +3519,7 @@ status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(AudioBufferProvider: framesReady = cblk->framesReady(); - if (LIKELY(framesReady)) { + if (CC_LIKELY(framesReady)) { uint32_t s = cblk->server; uint32_t bufferEnd = cblk->serverBase + cblk->frameCount; @@ -3481,14 +3532,14 @@ status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(AudioBufferProvider: } buffer->raw = getBuffer(s, framesReq); - if (buffer->raw == 0) goto getNextBuffer_exit; + if (buffer->raw == NULL) goto getNextBuffer_exit; buffer->frameCount = framesReq; return NO_ERROR; } getNextBuffer_exit: - buffer->raw = 0; + buffer->raw = NULL; buffer->frameCount = 0; ALOGV("getNextBuffer() no more data for track %d on thread %p", mName, mThread.unsafe_get()); return NOT_ENOUGH_DATA; @@ -3514,7 +3565,7 @@ status_t AudioFlinger::PlaybackThread::Track::start() sp<ThreadBase> thread = mThread.promote(); if (thread != 0) { Mutex::Autolock _l(thread->mLock); - int state = mState; + track_state state = mState; // here the track could be either new, or restarted // in both cases "unstop" the track if (mState == PAUSED) { @@ -3555,7 +3606,7 @@ void AudioFlinger::PlaybackThread::Track::stop() sp<ThreadBase> thread = mThread.promote(); if (thread != 0) { Mutex::Autolock _l(thread->mLock); - int state = mState; + track_state state = mState; if (mState > STOPPED) { mState = STOPPED; // If the track is not active (PAUSED and buffers full), flush buffers @@ -3643,12 +3694,6 @@ void AudioFlinger::PlaybackThread::Track::mute(bool muted) mMute = muted; } -void AudioFlinger::PlaybackThread::Track::setVolume(float left, float right) -{ - mVolume[0] = left; - mVolume[1] = right; -} - status_t AudioFlinger::PlaybackThread::Track::attachAuxEffect(int EffectId) { status_t status = DEAD_OBJECT; @@ -3673,7 +3718,7 @@ AudioFlinger::RecordThread::RecordTrack::RecordTrack( const wp<ThreadBase>& thread, const sp<Client>& client, uint32_t sampleRate, - uint32_t format, + audio_format_t format, uint32_t channelMask, int frameCount, uint32_t flags, @@ -3717,7 +3762,7 @@ status_t AudioFlinger::RecordThread::RecordTrack::getNextBuffer(AudioBufferProvi framesAvail = cblk->framesAvailable_l(); - if (LIKELY(framesAvail)) { + if (CC_LIKELY(framesAvail)) { uint32_t s = cblk->server; uint32_t bufferEnd = cblk->serverBase + cblk->frameCount; @@ -3729,14 +3774,14 @@ status_t AudioFlinger::RecordThread::RecordTrack::getNextBuffer(AudioBufferProvi } buffer->raw = getBuffer(s, framesReq); - if (buffer->raw == 0) goto getNextBuffer_exit; + if (buffer->raw == NULL) goto getNextBuffer_exit; buffer->frameCount = framesReq; return NO_ERROR; } getNextBuffer_exit: - buffer->raw = 0; + buffer->raw = NULL; buffer->frameCount = 0; return NOT_ENOUGH_DATA; } @@ -3786,7 +3831,7 @@ AudioFlinger::PlaybackThread::OutputTrack::OutputTrack( const wp<ThreadBase>& thread, DuplicatingThread *sourceThread, uint32_t sampleRate, - uint32_t format, + audio_format_t format, uint32_t channelMask, int frameCount) : Track(thread, NULL, AUDIO_STREAM_CNT, sampleRate, format, channelMask, frameCount, NULL, 0), @@ -3797,7 +3842,6 @@ AudioFlinger::PlaybackThread::OutputTrack::OutputTrack( if (mCblk != NULL) { 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); ALOGV("OutputTrack constructor mCblk %p, mBuffer %p, mCblk->buffers %p, " \ @@ -3881,7 +3925,7 @@ bool AudioFlinger::PlaybackThread::OutputTrack::write(int16_t* data, uint32_t fr if (mOutBuffer.frameCount == 0) { mOutBuffer.frameCount = pInBuffer->frameCount; nsecs_t startTime = systemTime(); - if (obtainBuffer(&mOutBuffer, waitTimeLeftMs) == (status_t)AudioTrack::NO_MORE_BUFFERS) { + if (obtainBuffer(&mOutBuffer, waitTimeLeftMs) == (status_t)NO_MORE_BUFFERS) { ALOGV ("OutputTrack::write() %p thread %p no more output buffers", this, mThread.unsafe_get()); outputBufferFull = true; break; @@ -3970,13 +4014,13 @@ status_t AudioFlinger::PlaybackThread::OutputTrack::obtainBuffer(AudioBufferProv goto start_loop_here; while (framesAvail == 0) { active = mActive; - if (UNLIKELY(!active)) { + if (CC_UNLIKELY(!active)) { ALOGV("Not active and NO_MORE_BUFFERS"); - return AudioTrack::NO_MORE_BUFFERS; + return NO_MORE_BUFFERS; } result = cblk->cv.waitRelative(cblk->lock, milliseconds(waitTimeMs)); if (result != NO_ERROR) { - return AudioTrack::NO_MORE_BUFFERS; + return NO_MORE_BUFFERS; } // read the server count again start_loop_here: @@ -3985,7 +4029,7 @@ status_t AudioFlinger::PlaybackThread::OutputTrack::obtainBuffer(AudioBufferProv } // if (framesAvail < framesReq) { -// return AudioTrack::NO_MORE_BUFFERS; +// return NO_MORE_BUFFERS; // } if (framesReq > framesAvail) { @@ -4035,7 +4079,7 @@ AudioFlinger::Client::~Client() mAudioFlinger->removeClient_l(mPid); } -const sp<MemoryDealer>& AudioFlinger::Client::heap() const +sp<MemoryDealer> AudioFlinger::Client::heap() const { return mMemoryDealer; } @@ -4045,13 +4089,12 @@ 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) + : mAudioFlinger(audioFlinger), mPid(pid), mAudioFlingerClient(client) { } AudioFlinger::NotificationClient::~NotificationClient() { - mClient.clear(); } void AudioFlinger::NotificationClient::binderDied(const wp<IBinder>& who) @@ -4078,6 +4121,10 @@ AudioFlinger::TrackHandle::~TrackHandle() { mTrack->destroy(); } +sp<IMemory> AudioFlinger::TrackHandle::getCblk() const { + return mTrack->getCblk(); +} + status_t AudioFlinger::TrackHandle::start() { return mTrack->start(); } @@ -4098,14 +4145,6 @@ void AudioFlinger::TrackHandle::pause() { mTrack->pause(); } -void AudioFlinger::TrackHandle::setVolume(float left, float right) { - mTrack->setVolume(left, right); -} - -sp<IMemory> AudioFlinger::TrackHandle::getCblk() const { - return mTrack->getCblk(); -} - status_t AudioFlinger::TrackHandle::attachAuxEffect(int EffectId) { return mTrack->attachAuxEffect(EffectId); @@ -4123,7 +4162,7 @@ sp<IAudioRecord> AudioFlinger::openRecord( pid_t pid, int input, uint32_t sampleRate, - uint32_t format, + audio_format_t format, uint32_t channelMask, int frameCount, uint32_t flags, @@ -4212,6 +4251,10 @@ AudioFlinger::RecordHandle::~RecordHandle() { stop(); } +sp<IMemory> AudioFlinger::RecordHandle::getCblk() const { + return mRecordTrack->getCblk(); +} + status_t AudioFlinger::RecordHandle::start() { ALOGV("RecordHandle::start()"); return mRecordTrack->start(); @@ -4222,10 +4265,6 @@ void AudioFlinger::RecordHandle::stop() { mRecordTrack->stop(); } -sp<IMemory> AudioFlinger::RecordHandle::getCblk() const { - return mRecordTrack->getCblk(); -} - status_t AudioFlinger::RecordHandle::onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { @@ -4240,15 +4279,16 @@ AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger, uint32_t channels, int id, uint32_t device) : - ThreadBase(audioFlinger, id, device), - mInput(input), mTrack(NULL), mResampler(0), mRsmpOutBuffer(0), mRsmpInBuffer(0) + ThreadBase(audioFlinger, id, device, RECORD), + mInput(input), mTrack(NULL), mResampler(NULL), mRsmpOutBuffer(NULL), mRsmpInBuffer(NULL), + // mRsmpInIndex and mInputBytes set by readInputParameters() + mReqChannelCount(popcount(channels)), + mReqSampleRate(sampleRate) + // mBytesRead is only meaningful while active, and so is cleared in start() + // (but might be better to also clear here for dump?) { - mType = ThreadBase::RECORD; - snprintf(mName, kNameLength, "AudioIn_%d", id); - mReqChannelCount = popcount(channels); - mReqSampleRate = sampleRate; readInputParameters(); } @@ -4256,10 +4296,8 @@ AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger, AudioFlinger::RecordThread::~RecordThread() { delete[] mRsmpInBuffer; - if (mResampler != 0) { - delete mResampler; - delete[] mRsmpOutBuffer; - } + delete mResampler; + delete[] mRsmpOutBuffer; } void AudioFlinger::RecordThread::onFirstRef() @@ -4348,9 +4386,9 @@ bool AudioFlinger::RecordThread::threadLoop() } buffer.frameCount = mFrameCount; - if (LIKELY(mActiveTrack->getNextBuffer(&buffer) == NO_ERROR)) { + if (CC_LIKELY(mActiveTrack->getNextBuffer(&buffer) == NO_ERROR)) { size_t framesOut = buffer.frameCount; - if (mResampler == 0) { + if (mResampler == NULL) { // no resampling while (framesOut) { size_t framesIn = mFrameCount - mRsmpInIndex; @@ -4415,7 +4453,7 @@ bool AudioFlinger::RecordThread::threadLoop() // ditherAndClamp() works as long as all buffers returned by mActiveTrack->getNextBuffer() // are 32 bit aligned which should be always true. if (mChannelCount == 2 && mReqChannelCount == 1) { - AudioMixer::ditherAndClamp(mRsmpOutBuffer, mRsmpOutBuffer, framesOut); + ditherAndClamp(mRsmpOutBuffer, mRsmpOutBuffer, framesOut); // the resampler always outputs stereo samples: do post stereo to mono conversion int16_t *src = (int16_t *)mRsmpOutBuffer; int16_t *dst = buffer.i16; @@ -4424,7 +4462,7 @@ bool AudioFlinger::RecordThread::threadLoop() src += 2; } } else { - AudioMixer::ditherAndClamp((int32_t *)buffer.raw, mRsmpOutBuffer, framesOut); + ditherAndClamp((int32_t *)buffer.raw, mRsmpOutBuffer, framesOut); } } @@ -4435,7 +4473,7 @@ bool AudioFlinger::RecordThread::threadLoop() else { if (!mActiveTrack->setOverflow()) { nsecs_t now = systemTime(); - if ((now - lastWarning) > kWarningThrottle) { + if ((now - lastWarning) > kWarningThrottleNs) { ALOGW("RecordThread: buffer overflow"); lastWarning = now; } @@ -4468,7 +4506,7 @@ bool AudioFlinger::RecordThread::threadLoop() sp<AudioFlinger::RecordThread::RecordTrack> AudioFlinger::RecordThread::createRecordTrack_l( const sp<AudioFlinger::Client>& client, uint32_t sampleRate, - int format, + audio_format_t format, int channelMask, int frameCount, uint32_t flags, @@ -4517,7 +4555,7 @@ status_t AudioFlinger::RecordThread::start(RecordThread::RecordTrack* recordTrac sp <ThreadBase> strongMe = this; status_t status = NO_ERROR; { - AutoMutex lock(&mLock); + AutoMutex lock(mLock); if (mActiveTrack != 0) { if (recordTrack != mActiveTrack.get()) { status = -EBUSY; @@ -4569,7 +4607,7 @@ void AudioFlinger::RecordThread::stop(RecordThread::RecordTrack* recordTrack) { ALOGV("RecordThread::stop"); sp <ThreadBase> strongMe = this; { - AutoMutex lock(&mLock); + AutoMutex lock(mLock); if (mActiveTrack != 0 && recordTrack == mActiveTrack.get()) { mActiveTrack->mState = TrackBase::PAUSING; // do not wait for mStartStopCond if exiting @@ -4608,7 +4646,7 @@ status_t AudioFlinger::RecordThread::dump(int fd, const Vector<String16>& args) result.append(buffer); snprintf(buffer, SIZE, "In size: %d\n", mInputBytes); result.append(buffer); - snprintf(buffer, SIZE, "Resampling: %d\n", (mResampler != 0)); + snprintf(buffer, SIZE, "Resampling: %d\n", (mResampler != NULL)); result.append(buffer); snprintf(buffer, SIZE, "Out channel count: %d\n", mReqChannelCount); result.append(buffer); @@ -4643,7 +4681,7 @@ status_t AudioFlinger::RecordThread::getNextBuffer(AudioBufferProvider::Buffer* mInput->stream->common.standby(&mInput->stream->common); usleep(kRecordThreadSleepUs); } - buffer->raw = 0; + buffer->raw = NULL; buffer->frameCount = 0; return NOT_ENOUGH_DATA; } @@ -4680,7 +4718,7 @@ bool AudioFlinger::RecordThread::checkForNewParameters_l() String8 keyValuePair = mNewParameters[0]; AudioParameter param = AudioParameter(keyValuePair); int value; - int reqFormat = mFormat; + audio_format_t reqFormat = mFormat; int reqSamplingRate = mReqSampleRate; int reqChannelCount = mReqChannelCount; @@ -4689,7 +4727,7 @@ bool AudioFlinger::RecordThread::checkForNewParameters_l() reconfig = true; } if (param.getInt(String8(AudioParameter::keyFormat), value) == NO_ERROR) { - reqFormat = value; + reqFormat = (audio_format_t) value; reconfig = true; } if (param.getInt(String8(AudioParameter::keyChannels), value) == NO_ERROR) { @@ -4758,7 +4796,7 @@ bool AudioFlinger::RecordThread::checkForNewParameters_l() mParamCond.signal(); // wait for condition with time out in case the thread calling ThreadBase::setParameters() // already timed out waiting for the status and will never signal the condition. - mWaitWorkCV.waitRelative(mLock, kSetParametersTimeout); + mWaitWorkCV.waitRelative(mLock, kSetParametersTimeoutNs); } return reconfig; } @@ -4781,7 +4819,7 @@ String8 AudioFlinger::RecordThread::getParameters(const String8& keys) void AudioFlinger::RecordThread::audioConfigChanged_l(int event, int param) { AudioSystem::OutputDescriptor desc; - void *param2 = 0; + void *param2 = NULL; switch (event) { case AudioSystem::INPUT_OPENED: @@ -4803,16 +4841,18 @@ void AudioFlinger::RecordThread::audioConfigChanged_l(int event, int param) { void AudioFlinger::RecordThread::readInputParameters() { - if (mRsmpInBuffer) delete mRsmpInBuffer; - if (mRsmpOutBuffer) delete mRsmpOutBuffer; - if (mResampler) delete mResampler; - mResampler = 0; + delete mRsmpInBuffer; + // mRsmpInBuffer is always assigned a new[] below + delete mRsmpOutBuffer; + mRsmpOutBuffer = NULL; + delete mResampler; + mResampler = NULL; mSampleRate = mInput->stream->common.get_sample_rate(&mInput->stream->common); mChannelMask = mInput->stream->common.get_channels(&mInput->stream->common); mChannelCount = (uint16_t)popcount(mChannelMask); mFormat = mInput->stream->common.get_format(&mInput->stream->common); - mFrameSize = (uint16_t)audio_stream_frame_size(&mInput->stream->common); + mFrameSize = audio_stream_frame_size(&mInput->stream->common); mInputBytes = mInput->stream->common.get_buffer_size(&mInput->stream->common); mFrameCount = mInputBytes / mFrameSize; mRsmpInBuffer = new int16_t[mFrameCount * mChannelCount]; @@ -4872,7 +4912,7 @@ AudioFlinger::RecordThread::RecordTrack* AudioFlinger::RecordThread::track() return mTrack; } -AudioFlinger::AudioStreamIn* AudioFlinger::RecordThread::getInput() +AudioFlinger::AudioStreamIn* AudioFlinger::RecordThread::getInput() const { Mutex::Autolock _l(mLock); return mInput; @@ -4900,7 +4940,7 @@ audio_stream_t* AudioFlinger::RecordThread::stream() int AudioFlinger::openOutput(uint32_t *pDevices, uint32_t *pSamplingRate, - uint32_t *pFormat, + audio_format_t *pFormat, uint32_t *pChannels, uint32_t *pLatencyMs, uint32_t flags) @@ -4909,7 +4949,7 @@ int AudioFlinger::openOutput(uint32_t *pDevices, PlaybackThread *thread = NULL; mHardwareStatus = AUDIO_HW_OUTPUT_OPEN; uint32_t samplingRate = pSamplingRate ? *pSamplingRate : 0; - uint32_t format = pFormat ? *pFormat : 0; + audio_format_t format = pFormat ? *pFormat : AUDIO_FORMAT_DEFAULT; uint32_t channels = pChannels ? *pChannels : 0; uint32_t latency = pLatencyMs ? *pLatencyMs : 0; audio_stream_out_t *outStream; @@ -4932,7 +4972,7 @@ int AudioFlinger::openOutput(uint32_t *pDevices, if (outHwDev == NULL) return 0; - status = outHwDev->open_output_stream(outHwDev, *pDevices, (int *)&format, + status = outHwDev->open_output_stream(outHwDev, *pDevices, &format, &channels, &samplingRate, &outStream); ALOGV("openOutput() openOutputStream returned output %p, SamplingRate %d, Format %d, Channels %x, status %d", outStream, @@ -4957,10 +4997,10 @@ int AudioFlinger::openOutput(uint32_t *pDevices, } mPlaybackThreads.add(id, thread); - if (pSamplingRate) *pSamplingRate = samplingRate; - if (pFormat) *pFormat = format; - if (pChannels) *pChannels = channels; - if (pLatencyMs) *pLatencyMs = thread->latency(); + if (pSamplingRate != NULL) *pSamplingRate = samplingRate; + if (pFormat != NULL) *pFormat = format; + if (pChannels != NULL) *pChannels = channels; + if (pLatencyMs != NULL) *pLatencyMs = thread->latency(); // notify client processes of the new output creation thread->audioConfigChanged_l(AudioSystem::OUTPUT_OPENED); @@ -5012,7 +5052,7 @@ status_t AudioFlinger::closeOutput(int output) } } } - void *param2 = 0; + void *param2 = NULL; audioConfigChanged_l(AudioSystem::OUTPUT_CLOSED, output, param2); mPlaybackThreads.removeItem(output); } @@ -5020,6 +5060,7 @@ status_t AudioFlinger::closeOutput(int output) if (thread->type() != ThreadBase::DUPLICATING) { AudioStreamOut *out = thread->clearOutput(); + assert(out != NULL); // from now on thread->mOutput is NULL out->hwDev->close_output_stream(out->hwDev, out->stream); delete out; @@ -5060,17 +5101,17 @@ status_t AudioFlinger::restoreOutput(int output) int AudioFlinger::openInput(uint32_t *pDevices, uint32_t *pSamplingRate, - uint32_t *pFormat, + audio_format_t *pFormat, uint32_t *pChannels, - uint32_t acoustics) + audio_in_acoustics_t acoustics) { status_t status; RecordThread *thread = NULL; uint32_t samplingRate = pSamplingRate ? *pSamplingRate : 0; - uint32_t format = pFormat ? *pFormat : 0; + audio_format_t format = pFormat ? *pFormat : AUDIO_FORMAT_DEFAULT; uint32_t channels = pChannels ? *pChannels : 0; uint32_t reqSamplingRate = samplingRate; - uint32_t reqFormat = format; + audio_format_t reqFormat = format; uint32_t reqChannels = channels; audio_stream_in_t *inStream; audio_hw_device_t *inHwDev; @@ -5085,9 +5126,9 @@ int AudioFlinger::openInput(uint32_t *pDevices, if (inHwDev == NULL) return 0; - status = inHwDev->open_input_stream(inHwDev, *pDevices, (int *)&format, + status = inHwDev->open_input_stream(inHwDev, *pDevices, &format, &channels, &samplingRate, - (audio_in_acoustics_t)acoustics, + acoustics, &inStream); ALOGV("openInput() openInputStream returned input %p, SamplingRate %d, Format %d, Channels %x, acoustics %x, status %d", inStream, @@ -5105,9 +5146,9 @@ int AudioFlinger::openInput(uint32_t *pDevices, (samplingRate <= 2 * reqSamplingRate) && (popcount(channels) < 3) && (popcount(reqChannels) < 3)) { ALOGV("openInput() reopening with proposed sampling rate and channels"); - status = inHwDev->open_input_stream(inHwDev, *pDevices, (int *)&format, + status = inHwDev->open_input_stream(inHwDev, *pDevices, &format, &channels, &samplingRate, - (audio_in_acoustics_t)acoustics, + acoustics, &inStream); } @@ -5127,9 +5168,9 @@ int AudioFlinger::openInput(uint32_t *pDevices, device); mRecordThreads.add(id, thread); ALOGV("openInput() created record thread: ID %d thread %p", id, thread); - if (pSamplingRate) *pSamplingRate = reqSamplingRate; - if (pFormat) *pFormat = format; - if (pChannels) *pChannels = reqChannels; + if (pSamplingRate != NULL) *pSamplingRate = reqSamplingRate; + if (pFormat != NULL) *pFormat = format; + if (pChannels != NULL) *pChannels = reqChannels; input->stream->common.standby(&input->stream->common); @@ -5154,13 +5195,14 @@ status_t AudioFlinger::closeInput(int input) } ALOGV("closeInput() %d", input); - void *param2 = 0; + void *param2 = NULL; audioConfigChanged_l(AudioSystem::INPUT_CLOSED, input, param2); mRecordThreads.removeItem(input); } thread->exit(); AudioStreamIn *in = thread->clearInput(); + assert(in != NULL); // from now on thread->mInput is NULL in->hwDev->close_input_stream(in->hwDev, in->stream); delete in; @@ -5168,7 +5210,7 @@ status_t AudioFlinger::closeInput(int input) return NO_ERROR; } -status_t AudioFlinger::setStreamOutput(uint32_t stream, int output) +status_t AudioFlinger::setStreamOutput(audio_stream_type_t stream, int output) { Mutex::Autolock _l(mLock); MixerThread *dstThread = checkMixerThread_l(output); @@ -5215,12 +5257,8 @@ void AudioFlinger::acquireAudioSessionId(int audioSession) return; } } - AudioSessionRef *ref = new AudioSessionRef(); - ref->sessionid = audioSession; - ref->pid = caller; - ref->cnt = 1; - mAudioSessionRefs.push(ref); - ALOGV(" added new entry for %d", ref->sessionid); + mAudioSessionRefs.push(new AudioSessionRef(audioSession, caller)); + ALOGV(" added new entry for %d", audioSession); } void AudioFlinger::releaseAudioSessionId(int audioSession) @@ -5736,7 +5774,7 @@ sp<AudioFlinger::EffectHandle> AudioFlinger::ThreadBase::createEffect_l( effect = chain->getEffectFromDesc_l(desc); } - ALOGV("createEffect_l() got effect %p on chain %p", effect == 0 ? 0 : effect.get(), chain.get()); + ALOGV("createEffect_l() got effect %p on chain %p", effect.get(), chain.get()); if (effect == 0) { int id = mAudioFlinger->nextUniqueId(); @@ -5764,7 +5802,7 @@ sp<AudioFlinger::EffectHandle> AudioFlinger::ThreadBase::createEffect_l( // create effect handle and connect it to effect module handle = new EffectHandle(effect, client, effectClient, priority); lStatus = effect->addHandle(handle); - if (enabled) { + if (enabled != NULL) { *enabled = (int)effect->isEnabled(); } } @@ -5895,7 +5933,7 @@ sp<AudioFlinger::EffectChain> AudioFlinger::ThreadBase::getEffectChain_l(int ses return chain; } -void AudioFlinger::ThreadBase::setMode(uint32_t mode) +void AudioFlinger::ThreadBase::setMode(audio_mode_t mode) { Mutex::Autolock _l(mLock); size_t size = mEffectChains.size(); @@ -6151,7 +6189,7 @@ AudioFlinger::EffectModule::~EffectModule() } } -status_t AudioFlinger::EffectModule::addHandle(sp<EffectHandle>& handle) +status_t AudioFlinger::EffectModule::addHandle(const sp<EffectHandle>& handle) { status_t status; @@ -6198,7 +6236,7 @@ size_t AudioFlinger::EffectModule::removeHandle(const wp<EffectHandle>& handle) bool enabled = false; EffectHandle *hdl = handle.unsafe_get(); - if (hdl) { + if (hdl != NULL) { ALOGV("removeHandle() unsafe_get OK"); enabled = hdl->enabled(); } @@ -6295,7 +6333,7 @@ void AudioFlinger::EffectModule::process() 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, + ditherAndClamp(mConfig.inputCfg.buffer.s32, mConfig.inputCfg.buffer.s32, mConfig.inputCfg.buffer.frameCount/2); } @@ -6400,7 +6438,7 @@ status_t AudioFlinger::EffectModule::configure() status_t cmdStatus; uint32_t size = sizeof(int); status_t status = (*mEffectInterface)->command(mEffectInterface, - EFFECT_CMD_CONFIGURE, + EFFECT_CMD_SET_CONFIG, sizeof(effect_config_t), &mConfig, &size, @@ -6693,7 +6731,7 @@ status_t AudioFlinger::EffectModule::setDevice(uint32_t device) return status; } -status_t AudioFlinger::EffectModule::setMode(uint32_t mode) +status_t AudioFlinger::EffectModule::setMode(audio_mode_t mode) { Mutex::Autolock _l(mLock); status_t status = NO_ERROR; @@ -6702,7 +6740,7 @@ status_t AudioFlinger::EffectModule::setMode(uint32_t mode) uint32_t size = sizeof(status_t); status = (*mEffectInterface)->command(mEffectInterface, EFFECT_CMD_SET_AUDIO_MODE, - sizeof(int), + sizeof(audio_mode_t), &mode, &size, &cmdStatus); @@ -6718,7 +6756,8 @@ void AudioFlinger::EffectModule::setSuspended(bool suspended) Mutex::Autolock _l(mLock); mSuspended = suspended; } -bool AudioFlinger::EffectModule::suspended() + +bool AudioFlinger::EffectModule::suspended() const { Mutex::Autolock _l(mLock); return mSuspended; @@ -6833,7 +6872,7 @@ AudioFlinger::EffectHandle::EffectHandle(const sp<EffectModule>& effect, if (mCblkMemory != 0) { mCblk = static_cast<effect_param_cblk_t *>(mCblkMemory->pointer()); - if (mCblk) { + if (mCblk != NULL) { new(mCblk) effect_param_cblk_t(); mBuffer = (uint8_t *)mCblk + bufOffset; } @@ -6930,7 +6969,7 @@ void AudioFlinger::EffectHandle::disconnect(bool unpiniflast) // release sp on module => module destructor can be called now mEffect.clear(); if (mClient != 0) { - if (mCblk) { + if (mCblk != NULL) { mCblk->~effect_param_cblk_t(); // destroy our shared-structure. } mCblkMemory.clear(); // and free the shared memory @@ -7060,7 +7099,7 @@ status_t AudioFlinger::EffectHandle::onTransact( void AudioFlinger::EffectHandle::dump(char* buffer, size_t size) { - bool locked = mCblk ? tryLock(mCblk->lock) : false; + bool locked = mCblk != NULL && tryLock(mCblk->lock); snprintf(buffer, size, "\t\t\t%05d %05d %01u %01u %05u %05u\n", (mClient == NULL) ? getpid() : mClient->pid(), @@ -7351,7 +7390,7 @@ void AudioFlinger::EffectChain::setDevice_l(uint32_t device) } // setMode_l() must be called with PlaybackThread::mLock held -void AudioFlinger::EffectChain::setMode_l(uint32_t mode) +void AudioFlinger::EffectChain::setMode_l(audio_mode_t mode) { size_t size = mEffects.size(); for (size_t i = 0; i < size; i++) { @@ -7522,7 +7561,8 @@ void AudioFlinger::EffectChain::setEffectSuspendedAll_l(bool suspend) ALOGV("setEffectSuspendedAll_l() add entry for 0"); } if (desc->mRefCount++ == 0) { - Vector< sp<EffectModule> > effects = getSuspendEligibleEffects(); + Vector< sp<EffectModule> > effects; + getSuspendEligibleEffects(effects); for (size_t i = 0; i < effects.size(); i++) { setEffectSuspended_l(&effects[i]->desc().type, true); } @@ -7573,16 +7613,14 @@ bool AudioFlinger::EffectChain::isEffectEligibleForSuspend(const effect_descript return true; } -Vector< sp<AudioFlinger::EffectModule> > AudioFlinger::EffectChain::getSuspendEligibleEffects() +void AudioFlinger::EffectChain::getSuspendEligibleEffects(Vector< sp<AudioFlinger::EffectModule> > &effects) { - Vector< sp<EffectModule> > effects; + effects.clear(); for (size_t i = 0; i < mEffects.size(); i++) { - if (!isEffectEligibleForSuspend(mEffects[i]->desc())) { - continue; + if (isEffectEligibleForSuspend(mEffects[i]->desc())) { + effects.add(mEffects[i]); } - effects.add(mEffects[i]); } - return effects; } sp<AudioFlinger::EffectModule> AudioFlinger::EffectChain::getEffectIfEnabled( diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h index 9bd2c7f..3f3188c 100644 --- a/services/audioflinger/AudioFlinger.h +++ b/services/audioflinger/AudioFlinger.h @@ -26,7 +26,7 @@ #include <media/IAudioFlingerClient.h> #include <media/IAudioTrack.h> #include <media/IAudioRecord.h> -#include <media/AudioTrack.h> +#include <media/AudioSystem.h> #include <utils/Atomic.h> #include <utils/Errors.h> @@ -55,12 +55,6 @@ class AudioResampler; // ---------------------------------------------------------------------------- -#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) -#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) - - -// ---------------------------------------------------------------------------- - static const nsecs_t kStandbyTimeInNsecs = seconds(3); class AudioFlinger : @@ -69,16 +63,16 @@ class AudioFlinger : { friend class BinderService<AudioFlinger>; public: - static char const* getServiceName() { return "media.audio_flinger"; } + static const char* getServiceName() { return "media.audio_flinger"; } virtual status_t dump(int fd, const Vector<String16>& args); // IAudioFlinger interface virtual sp<IAudioTrack> createTrack( pid_t pid, - int streamType, + audio_stream_type_t streamType, uint32_t sampleRate, - uint32_t format, + audio_format_t format, uint32_t channelMask, int frameCount, uint32_t flags, @@ -89,7 +83,7 @@ public: virtual uint32_t sampleRate(int output) const; virtual int channelCount(int output) const; - virtual uint32_t format(int output) const; + virtual audio_format_t format(int output) const; virtual size_t frameCount(int output) const; virtual uint32_t latency(int output) const; @@ -99,13 +93,13 @@ public: virtual float masterVolume() const; virtual bool masterMute() const; - virtual status_t setStreamVolume(int stream, float value, int output); - virtual status_t setStreamMute(int stream, bool muted); + virtual status_t setStreamVolume(audio_stream_type_t stream, float value, int output); + virtual status_t setStreamMute(audio_stream_type_t stream, bool muted); - virtual float streamVolume(int stream, int output) const; - virtual bool streamMute(int stream) const; + virtual float streamVolume(audio_stream_type_t stream, int output) const; + virtual bool streamMute(audio_stream_type_t stream) const; - virtual status_t setMode(int mode); + virtual status_t setMode(audio_mode_t mode); virtual status_t setMicMute(bool state); virtual bool getMicMute() const; @@ -115,12 +109,12 @@ public: virtual void registerClient(const sp<IAudioFlingerClient>& client); - virtual size_t getInputBufferSize(uint32_t sampleRate, int format, int channelCount); + virtual size_t getInputBufferSize(uint32_t sampleRate, audio_format_t format, int channelCount); virtual unsigned int getInputFramesLost(int ioHandle); virtual int openOutput(uint32_t *pDevices, uint32_t *pSamplingRate, - uint32_t *pFormat, + audio_format_t *pFormat, uint32_t *pChannels, uint32_t *pLatencyMs, uint32_t flags); @@ -135,13 +129,13 @@ public: virtual int openInput(uint32_t *pDevices, uint32_t *pSamplingRate, - uint32_t *pFormat, + audio_format_t *pFormat, uint32_t *pChannels, - uint32_t acoustics); + audio_in_acoustics_t acoustics); virtual status_t closeInput(int input); - virtual status_t setStreamOutput(uint32_t stream, int output); + virtual status_t setStreamOutput(audio_stream_type_t stream, int output); virtual status_t setVoiceVolume(float volume); @@ -195,7 +189,7 @@ public: pid_t pid, int input, uint32_t sampleRate, - uint32_t format, + audio_format_t format, uint32_t channelMask, int frameCount, uint32_t flags, @@ -208,11 +202,12 @@ public: Parcel* reply, uint32_t flags); - uint32_t getMode() { return mMode; } + audio_mode_t getMode() const { return mMode; } bool btNrecIsOff() { return mBtNrecIsOff; } private: + AudioFlinger(); virtual ~AudioFlinger(); @@ -231,16 +226,16 @@ private: public: Client(const sp<AudioFlinger>& audioFlinger, pid_t pid); virtual ~Client(); - const sp<MemoryDealer>& heap() const; + sp<MemoryDealer> heap() const; pid_t pid() const { return mPid; } sp<AudioFlinger> audioFlinger() { return mAudioFlinger; } private: Client(const Client&); Client& operator = (const Client&); - sp<AudioFlinger> mAudioFlinger; - sp<MemoryDealer> mMemoryDealer; - pid_t mPid; + const sp<AudioFlinger> mAudioFlinger; + const sp<MemoryDealer> mMemoryDealer; + const pid_t mPid; }; // --- Notification Client --- @@ -251,7 +246,7 @@ private: pid_t pid); virtual ~NotificationClient(); - sp<IAudioFlingerClient> client() { return mClient; } + sp<IAudioFlingerClient> audioFlingerClient() const { return mAudioFlingerClient; } // IBinder::DeathRecipient virtual void binderDied(const wp<IBinder>& who); @@ -260,9 +255,9 @@ private: NotificationClient(const NotificationClient&); NotificationClient& operator = (const NotificationClient&); - sp<AudioFlinger> mAudioFlinger; - pid_t mPid; - sp<IAudioFlingerClient> mClient; + const sp<AudioFlinger> mAudioFlinger; + const pid_t mPid; + const sp<IAudioFlingerClient> mAudioFlingerClient; }; class TrackHandle; @@ -282,17 +277,17 @@ private: class ThreadBase : public Thread { public: - ThreadBase (const sp<AudioFlinger>& audioFlinger, int id, uint32_t device); - virtual ~ThreadBase(); - - enum type { + enum type_t { MIXER, // Thread class is MixerThread DIRECT, // Thread class is DirectOutputThread DUPLICATING, // Thread class is DuplicatingThread RECORD // Thread class is RecordThread }; + ThreadBase (const sp<AudioFlinger>& audioFlinger, int id, uint32_t device, type_t type); + virtual ~ThreadBase(); + status_t dumpBase(int fd, const Vector<String16>& args); status_t dumpEffectChains(int fd, const Vector<String16>& args); @@ -321,7 +316,7 @@ private: TrackBase(const wp<ThreadBase>& thread, const sp<Client>& client, uint32_t sampleRate, - uint32_t format, + audio_format_t format, uint32_t channelMask, int frameCount, uint32_t flags, @@ -349,7 +344,7 @@ private: virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer) = 0; virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer); - uint32_t format() const { + audio_format_t format() const { return mFormat; } @@ -372,19 +367,19 @@ private: bool step(); void reset(); - wp<ThreadBase> mThread; - sp<Client> mClient; + const wp<ThreadBase> mThread; + /*const*/ sp<Client> mClient; // see explanation at ~TrackBase() why not const sp<IMemory> mCblkMemory; audio_track_cblk_t* mCblk; void* mBuffer; void* mBufferEnd; uint32_t mFrameCount; // we don't really need a lock for these - int mState; + track_state mState; int mClientTid; - uint32_t mFormat; + const audio_format_t mFormat; uint32_t mFlags; - int mSessionId; + const int mSessionId; uint8_t mChannelCount; uint32_t mChannelMask; }; @@ -413,10 +408,10 @@ private: }; virtual status_t initCheck() const = 0; - int type() const { return mType; } + type_t type() const { return mType; } uint32_t sampleRate() const; int channelCount() const; - uint32_t format() const; + audio_format_t format() const; size_t frameCount() const; void wakeUp() { mWaitWorkCV.broadcast(); } void exit(); @@ -467,7 +462,7 @@ private: // unlock effect chains after process void unlockEffectChains(Vector<sp <EffectChain> >& effectChains); // set audio mode to all effect chains - void setMode(uint32_t mode); + void setMode(audio_mode_t mode); // get effect module with corresponding ID on specified audio session sp<AudioFlinger::EffectModule> getEffect_l(int sessionId, int effectId); // add and effect module. Also creates the effect chain is none exists for @@ -535,19 +530,19 @@ private: friend class RecordThread; friend class RecordTrack; - int mType; + const type_t mType; Condition mWaitWorkCV; - sp<AudioFlinger> mAudioFlinger; + const sp<AudioFlinger> mAudioFlinger; uint32_t mSampleRate; size_t mFrameCount; uint32_t mChannelMask; uint16_t mChannelCount; - uint16_t mFrameSize; - uint32_t mFormat; + size_t mFrameSize; + audio_format_t mFormat; Condition mParamCond; Vector<String8> mNewParameters; status_t mParamStatus; - Vector<ConfigEvent *> mConfigEvents; + Vector<ConfigEvent> mConfigEvents; bool mStandby; int mId; bool mExiting; @@ -558,7 +553,7 @@ private: char mName[kNameLength]; sp<IPowerManager> mPowerManager; sp<IBinder> mWakeLockToken; - sp<PMDeathRecipient> mDeathRecipient; + const sp<PMDeathRecipient> mDeathRecipient; // list of suspended effects per session and per type. The first vector is // keyed by session ID, the second by type UUID timeLow field KeyedVector< int, KeyedVector< int, sp<SuspendedSessionDesc> > > mSuspendedSessions; @@ -579,9 +574,9 @@ private: public: Track( const wp<ThreadBase>& thread, const sp<Client>& client, - int streamType, + audio_stream_type_t streamType, uint32_t sampleRate, - uint32_t format, + audio_format_t format, uint32_t channelMask, int frameCount, const sp<IMemory>& sharedBuffer, @@ -596,12 +591,11 @@ private: void flush(); void destroy(); void mute(bool); - void setVolume(float left, float right); int name() const { return mName; } - int type() const { + audio_stream_type_t type() const { return mStreamType; } status_t attachAuxEffect(int EffectId); @@ -639,7 +633,6 @@ private: } // we don't really need a lock for these - float mVolume[2]; volatile bool mMute; // FILLED state is used for suppressing volume ramp at begin of playing enum {FS_FILLING, FS_FILLED, FS_ACTIVE}; @@ -647,7 +640,7 @@ private: int8_t mRetryCount; sp<IMemory> mSharedBuffer; bool mResetDone; - int mStreamType; + audio_stream_type_t mStreamType; int mName; int16_t *mMainBuffer; int32_t *mAuxBuffer; @@ -668,7 +661,7 @@ private: OutputTrack( const wp<ThreadBase>& thread, DuplicatingThread *sourceThread, uint32_t sampleRate, - uint32_t format, + audio_format_t format, uint32_t channelMask, int frameCount); ~OutputTrack(); @@ -678,10 +671,14 @@ private: bool write(int16_t* data, uint32_t frames); bool bufferQueueEmpty() { return (mBufferQueue.size() == 0) ? true : false; } bool isActive() { return mActive; } - wp<ThreadBase>& thread() { return mThread; } + const wp<ThreadBase>& thread() { return mThread; } private: + enum { + NO_MORE_BUFFERS = 0x80000001, // same in AudioTrack.h, ok to be different value + }; + status_t obtainBuffer(AudioBufferProvider::Buffer* buffer, uint32_t waitTimeMs); void clearBufferQueue(); @@ -691,10 +688,11 @@ private: Vector < Buffer* > mBufferQueue; AudioBufferProvider::Buffer mOutBuffer; bool mActive; - DuplicatingThread* mSourceThread; + DuplicatingThread* const mSourceThread; // for waitTimeMs() in write() }; // end of OutputTrack - PlaybackThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, uint32_t device); + PlaybackThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, + uint32_t device, type_t type); virtual ~PlaybackThread(); virtual status_t dump(int fd, const Vector<String16>& args); @@ -703,7 +701,7 @@ private: virtual status_t readyToRun(); virtual void onFirstRef(); - virtual status_t initCheck() const { return (mOutput == 0) ? NO_INIT : NO_ERROR; } + virtual status_t initCheck() const { return (mOutput == NULL) ? NO_INIT : NO_ERROR; } virtual uint32_t latency() const; @@ -713,30 +711,30 @@ private: virtual float masterVolume() const; virtual bool masterMute() const; - virtual status_t setStreamVolume(int stream, float value); - virtual status_t setStreamMute(int stream, bool muted); + virtual status_t setStreamVolume(audio_stream_type_t stream, float value); + virtual status_t setStreamMute(audio_stream_type_t stream, bool muted); - virtual float streamVolume(int stream) const; - virtual bool streamMute(int stream) const; + virtual float streamVolume(audio_stream_type_t stream) const; + virtual bool streamMute(audio_stream_type_t stream) const; sp<Track> createTrack_l( const sp<AudioFlinger::Client>& client, - int streamType, + audio_stream_type_t streamType, uint32_t sampleRate, - uint32_t format, + audio_format_t format, uint32_t channelMask, int frameCount, const sp<IMemory>& sharedBuffer, int sessionId, status_t *status); - AudioStreamOut* getOutput(); + AudioStreamOut* getOutput() const; AudioStreamOut* clearOutput(); virtual audio_stream_t* stream(); void suspend() { mSuspended++; } void restore() { if (mSuspended) mSuspended--; } - bool isSuspended() { return (mSuspended != 0); } + bool isSuspended() const { return (mSuspended != 0); } virtual String8 getParameters(const String8& keys); virtual void audioConfigChanged_l(int event, int param = 0); virtual status_t getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames); @@ -753,7 +751,7 @@ private: virtual uint32_t hasAudioSession(int sessionId); virtual uint32_t getStrategyForSession_l(int sessionId); - void setStreamValid(int streamType, bool valid); + void setStreamValid(audio_stream_type_t streamType, bool valid); struct stream_type_t { stream_type_t() @@ -771,7 +769,9 @@ private: int16_t* mMixBuffer; int mSuspended; int mBytesWritten; + private: bool mMasterMute; + protected: SortedVector< wp<Track> > mActiveTracks; virtual int getTrackName_l() = 0; @@ -803,9 +803,9 @@ private: status_t dumpTracks(int fd, const Vector<String16>& args); SortedVector< sp<Track> > mTracks; - // mStreamTypes[] uses 1 additionnal stream type internally for the OutputTrack used by DuplicatingThread + // mStreamTypes[] uses 1 additional stream type internally for the OutputTrack used by DuplicatingThread stream_type_t mStreamTypes[AUDIO_STREAM_CNT + 1]; - AudioStreamOut* mOutput; + AudioStreamOut *mOutput; float mMasterVolume; nsecs_t mLastWriteTime; int mNumWrites; @@ -818,18 +818,19 @@ private: MixerThread (const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output, int id, - uint32_t device); + uint32_t device, + type_t type = MIXER); virtual ~MixerThread(); // Thread virtuals virtual bool threadLoop(); - void invalidateTracks(int streamType); + void invalidateTracks(audio_stream_type_t 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, + mixer_state prepareTracks_l(const SortedVector< wp<Track> >& activeTracks, Vector< sp<Track> > *tracksToRemove); virtual int getTrackName_l(); virtual void deleteTrackName_l(int name); @@ -837,8 +838,7 @@ private: virtual uint32_t suspendSleepTimeUs(); AudioMixer* mAudioMixer; - uint32_t mPrevMixerStatus; // previous status (mixer_state) returned by - // prepareTracks_l() + mixer_state mPrevMixerStatus; // previous status returned by prepareTracks_l() }; class DirectOutputThread : public PlaybackThread { @@ -892,7 +892,7 @@ private: PlaybackThread *checkPlaybackThread_l(int output) const; MixerThread *checkMixerThread_l(int output) const; RecordThread *checkRecordThread_l(int input) const; - float streamVolumeInternal(int stream) const { return mStreamTypes[stream].volume; } + float streamVolumeInternal(audio_stream_type_t stream) const { return mStreamTypes[stream].volume; } void audioConfigChanged_l(int event, int ioHandle, void *param2); uint32_t nextUniqueId(); @@ -909,18 +909,17 @@ private: public: TrackHandle(const sp<PlaybackThread::Track>& track); virtual ~TrackHandle(); + virtual sp<IMemory> getCblk() const; virtual status_t start(); virtual void stop(); virtual void flush(); virtual void mute(bool); 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: - sp<PlaybackThread::Track> mTrack; + const sp<PlaybackThread::Track> mTrack; }; friend class Client; @@ -942,7 +941,7 @@ private: RecordTrack(const wp<ThreadBase>& thread, const sp<Client>& client, uint32_t sampleRate, - uint32_t format, + audio_format_t format, uint32_t channelMask, int frameCount, uint32_t flags, @@ -982,11 +981,11 @@ private: virtual status_t readyToRun(); virtual void onFirstRef(); - virtual status_t initCheck() const { return (mInput == 0) ? NO_INIT : NO_ERROR; } + virtual status_t initCheck() const { return (mInput == NULL) ? NO_INIT : NO_ERROR; } sp<AudioFlinger::RecordThread::RecordTrack> createRecordTrack_l( const sp<AudioFlinger::Client>& client, uint32_t sampleRate, - int format, + audio_format_t format, int channelMask, int frameCount, uint32_t flags, @@ -996,7 +995,7 @@ private: status_t start(RecordTrack* recordTrack); void stop(RecordTrack* recordTrack); status_t dump(int fd, const Vector<String16>& args); - AudioStreamIn* getInput(); + AudioStreamIn* getInput() const; AudioStreamIn* clearInput(); virtual audio_stream_t* stream(); @@ -1024,8 +1023,8 @@ private: int16_t *mRsmpInBuffer; size_t mRsmpInIndex; size_t mInputBytes; - int mReqChannelCount; - uint32_t mReqSampleRate; + const int mReqChannelCount; + const uint32_t mReqSampleRate; ssize_t mBytesRead; }; @@ -1033,13 +1032,13 @@ private: public: RecordHandle(const sp<RecordThread::RecordTrack>& recordTrack); virtual ~RecordHandle(); + virtual sp<IMemory> getCblk() const; virtual status_t start(); virtual void stop(); - virtual sp<IMemory> getCblk() const; virtual status_t onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags); private: - sp<RecordThread::RecordTrack> mRecordTrack; + const sp<RecordThread::RecordTrack> mRecordTrack; }; //--- Audio Effect Management @@ -1089,7 +1088,7 @@ private: void reset_l(); status_t configure(); status_t init(); - uint32_t state() { + effect_state state() const { return mState; } uint32_t status() { @@ -1108,9 +1107,9 @@ private: int16_t *outBuffer() { return mConfig.outputCfg.buffer.s16; } void setChain(const wp<EffectChain>& chain) { mChain = chain; } void setThread(const wp<ThreadBase>& thread) { mThread = thread; } - wp<ThreadBase>& thread() { return mThread; } + const wp<ThreadBase>& thread() { return mThread; } - status_t addHandle(sp<EffectHandle>& handle); + status_t addHandle(const sp<EffectHandle>& handle); void disconnect(const wp<EffectHandle>& handle, bool unpiniflast); size_t removeHandle (const wp<EffectHandle>& handle); @@ -1119,11 +1118,11 @@ private: 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 setMode(audio_mode_t mode); status_t start(); status_t stop(); void setSuspended(bool suspended); - bool suspended(); + bool suspended() const; sp<EffectHandle> controlHandle(); @@ -1146,7 +1145,7 @@ private: status_t start_l(); status_t stop_l(); - Mutex mLock; // mutex for process, commands and handles list protection +mutable 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 @@ -1154,8 +1153,8 @@ private: effect_descriptor_t mDescriptor;// effect descriptor received from effect engine effect_config_t mConfig; // input and output audio configuration effect_handle_t mEffectInterface; // Effect module C API - status_t mStatus; // initialization status - uint32_t mState; // current activation state (effect_state) + status_t mStatus; // initialization status + effect_state mState; // current activation state Vector< wp<EffectHandle> > mHandles; // list of client handles uint32_t mMaxDisableWaitCnt; // maximum grace period before forcing an effect off after // sending disable command. @@ -1272,7 +1271,7 @@ private: sp<EffectModule> getEffectFromType_l(const effect_uuid_t *type); bool setVolume_l(uint32_t *left, uint32_t *right); void setDevice_l(uint32_t device); - void setMode_l(uint32_t mode); + void setMode_l(audio_mode_t mode); void setInBuffer(int16_t *buffer, bool ownsBuffer = false) { mInBuffer = buffer; @@ -1328,7 +1327,8 @@ private: // get a list of effect modules to suspend when an effect of the type // passed is enabled. - Vector< sp<EffectModule> > getSuspendEligibleEffects(); + void getSuspendEligibleEffects(Vector< sp<EffectModule> > &effects); + // get an effect module if it is currently enable sp<EffectModule> getEffectIfEnabled(const effect_uuid_t *type); // true if the effect whose descriptor is passed can be suspended @@ -1359,25 +1359,32 @@ private: KeyedVector< int, sp<SuspendedEffectDesc> > mSuspendedEffects; }; + // AudioStreamOut and AudioStreamIn are immutable, so their fields are const. + // For emphasis, we could also make all pointers to them be "const *", + // but that would clutter the code unnecessarily. + struct AudioStreamOut { - audio_hw_device_t *hwDev; - audio_stream_out_t *stream; + audio_hw_device_t* const hwDev; + audio_stream_out_t* const stream; AudioStreamOut(audio_hw_device_t *dev, audio_stream_out_t *out) : hwDev(dev), stream(out) {} }; struct AudioStreamIn { - audio_hw_device_t *hwDev; - audio_stream_in_t *stream; + audio_hw_device_t* const hwDev; + audio_stream_in_t* const stream; AudioStreamIn(audio_hw_device_t *dev, audio_stream_in_t *in) : hwDev(dev), stream(in) {} }; struct AudioSessionRef { - int sessionid; - pid_t pid; + // FIXME rename parameter names when fields get "m" prefix + AudioSessionRef(int sessionid_, pid_t pid_) : + sessionid(sessionid_), pid(pid_), cnt(1) {} + const int sessionid; + const pid_t pid; int cnt; }; @@ -1391,11 +1398,13 @@ private: mutable Mutex mHardwareLock; audio_hw_device_t* mPrimaryHardwareDev; Vector<audio_hw_device_t*> mAudioHwDevs; - mutable int mHardwareStatus; + mutable hardware_call_state mHardwareStatus; // for dump only DefaultKeyedVector< int, sp<PlaybackThread> > mPlaybackThreads; PlaybackThread::stream_type_t mStreamTypes[AUDIO_STREAM_CNT]; + + // both are protected by mLock float mMasterVolume; bool mMasterMute; @@ -1403,10 +1412,13 @@ private: DefaultKeyedVector< pid_t, sp<NotificationClient> > mNotificationClients; volatile int32_t mNextUniqueId; - uint32_t mMode; + audio_mode_t mMode; bool mBtNrecIsOff; Vector<AudioSessionRef*> mAudioSessionRefs; + + float masterVolume_l() const { return mMasterVolume; } + bool masterMute_l() const { return mMasterMute; } }; diff --git a/services/audioflinger/AudioMixer.cpp b/services/audioflinger/AudioMixer.cpp index 9dda256..0b9f8ba 100644 --- a/services/audioflinger/AudioMixer.cpp +++ b/services/audioflinger/AudioMixer.cpp @@ -18,6 +18,7 @@ #define LOG_TAG "AudioMixer" //#define LOG_NDEBUG 0 +#include <assert.h> #include <stdint.h> #include <string.h> #include <stdlib.h> @@ -27,240 +28,229 @@ #include <utils/Log.h> #include <cutils/bitops.h> +#include <cutils/compiler.h> #include <system/audio.h> +#include <audio_utils/primitives.h> + #include "AudioMixer.h" namespace android { -// ---------------------------------------------------------------------------- - -static inline int16_t clamp16(int32_t sample) -{ - if ((sample>>15) ^ (sample>>31)) - sample = 0x7FFF ^ (sample>>31); - return sample; -} // ---------------------------------------------------------------------------- AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate) - : mActiveTrack(0), mTrackNames(0), mSampleRate(sampleRate) + : mTrackNames(0), mSampleRate(sampleRate) { + // AudioMixer is not yet capable of multi-channel beyond stereo + assert(2 == MAX_NUM_CHANNELS); mState.enabledTracks= 0; mState.needsChanged = 0; mState.frameCount = frameCount; - mState.outputTemp = 0; - mState.resampleTemp = 0; mState.hook = process__nop; + mState.outputTemp = NULL; + mState.resampleTemp = NULL; + // mState.reserved track_t* t = mState.tracks; - for (int i=0 ; i<32 ; i++) { + for (unsigned i=0 ; i < MAX_NUM_TRACKS ; i++) { t->needs = 0; t->volume[0] = UNITY_GAIN; t->volume[1] = UNITY_GAIN; + // no initialization needed + // t->prevVolume[0] + // t->prevVolume[1] t->volumeInc[0] = 0; t->volumeInc[1] = 0; t->auxLevel = 0; t->auxInc = 0; + // no initialization needed + // t->prevAuxLevel + // t->frameCount t->channelCount = 2; t->enabled = 0; t->format = 16; t->channelMask = AUDIO_CHANNEL_OUT_STEREO; - t->buffer.raw = 0; - t->bufferProvider = 0; - t->hook = 0; - t->resampler = 0; + t->bufferProvider = NULL; + t->buffer.raw = NULL; + // t->buffer.frameCount + t->hook = NULL; + t->in = NULL; + t->resampler = NULL; t->sampleRate = mSampleRate; - t->in = 0; t->mainBuffer = NULL; t->auxBuffer = NULL; t++; } } - AudioMixer::~AudioMixer() - { - track_t* t = mState.tracks; - for (int i=0 ; i<32 ; i++) { - delete t->resampler; - t++; - } - delete [] mState.outputTemp; - delete [] mState.resampleTemp; - } - - int AudioMixer::getTrackName() - { - uint32_t names = mTrackNames; - uint32_t mask = 1; - int n = 0; - while (names & mask) { - mask <<= 1; - n++; +AudioMixer::~AudioMixer() +{ + track_t* t = mState.tracks; + for (unsigned i=0 ; i < MAX_NUM_TRACKS ; i++) { + delete t->resampler; + t++; } - if (mask) { + delete [] mState.outputTemp; + delete [] mState.resampleTemp; +} + +int AudioMixer::getTrackName() +{ + uint32_t names = ~mTrackNames; + if (names != 0) { + int n = __builtin_ctz(names); ALOGV("add track (%d)", n); - mTrackNames |= mask; + mTrackNames |= 1 << n; return TRACK0 + n; } return -1; - } +} - void AudioMixer::invalidateState(uint32_t mask) - { +void AudioMixer::invalidateState(uint32_t mask) +{ if (mask) { mState.needsChanged |= mask; mState.hook = process__validate; } } - void AudioMixer::deleteTrackName(int name) - { +void AudioMixer::deleteTrackName(int name) +{ name -= TRACK0; - if (uint32_t(name) < MAX_NUM_TRACKS) { - ALOGV("deleteTrackName(%d)", name); - track_t& track(mState.tracks[ name ]); - if (track.enabled != 0) { - track.enabled = 0; - invalidateState(1<<name); - } - if (track.resampler) { - // delete the resampler - delete track.resampler; - track.resampler = 0; - track.sampleRate = mSampleRate; - invalidateState(1<<name); - } - track.volumeInc[0] = 0; - track.volumeInc[1] = 0; - mTrackNames &= ~(1<<name); + assert(uint32_t(name) < MAX_NUM_TRACKS); + ALOGV("deleteTrackName(%d)", name); + track_t& track(mState.tracks[ name ]); + if (track.enabled != 0) { + track.enabled = 0; + invalidateState(1<<name); } - } - -status_t AudioMixer::enable(int name) -{ - switch (name) { - case MIXING: { - if (mState.tracks[ mActiveTrack ].enabled != 1) { - mState.tracks[ mActiveTrack ].enabled = 1; - ALOGV("enable(%d)", mActiveTrack); - invalidateState(1<<mActiveTrack); - } - } break; - default: - return NAME_NOT_FOUND; + if (track.resampler != NULL) { + // delete the resampler + delete track.resampler; + track.resampler = NULL; + track.sampleRate = mSampleRate; + invalidateState(1<<name); } - return NO_ERROR; + track.volumeInc[0] = 0; + track.volumeInc[1] = 0; + mTrackNames &= ~(1<<name); } -status_t AudioMixer::disable(int name) +void AudioMixer::enable(int name) { - switch (name) { - case MIXING: { - if (mState.tracks[ mActiveTrack ].enabled != 0) { - mState.tracks[ mActiveTrack ].enabled = 0; - ALOGV("disable(%d)", mActiveTrack); - invalidateState(1<<mActiveTrack); - } - } break; - default: - return NAME_NOT_FOUND; + name -= TRACK0; + assert(uint32_t(name) < MAX_NUM_TRACKS); + track_t& track = mState.tracks[name]; + + if (track.enabled != 1) { + track.enabled = 1; + ALOGV("enable(%d)", name); + invalidateState(1 << name); } - return NO_ERROR; } -status_t AudioMixer::setActiveTrack(int track) +void AudioMixer::disable(int name) { - if (uint32_t(track-TRACK0) >= MAX_NUM_TRACKS) { - return BAD_VALUE; + name -= TRACK0; + assert(uint32_t(name) < MAX_NUM_TRACKS); + track_t& track = mState.tracks[name]; + + if (track.enabled != 0) { + track.enabled = 0; + ALOGV("disable(%d)", name); + invalidateState(1 << name); } - mActiveTrack = track - TRACK0; - return NO_ERROR; } -status_t AudioMixer::setParameter(int target, int name, void *value) +void AudioMixer::setParameter(int name, int target, int param, void *value) { + name -= TRACK0; + assert(uint32_t(name) < MAX_NUM_TRACKS); + track_t& track = mState.tracks[name]; + int valueInt = (int)value; int32_t *valueBuf = (int32_t *)value; switch (target) { + case TRACK: - if (name == CHANNEL_MASK) { + switch (param) { + case CHANNEL_MASK: { uint32_t mask = (uint32_t)value; - if (mState.tracks[ mActiveTrack ].channelMask != mask) { + if (track.channelMask != mask) { uint8_t channelCount = popcount(mask); - if ((channelCount <= MAX_NUM_CHANNELS) && (channelCount)) { - mState.tracks[ mActiveTrack ].channelMask = mask; - mState.tracks[ mActiveTrack ].channelCount = channelCount; - ALOGV("setParameter(TRACK, CHANNEL_MASK, %x)", mask); - invalidateState(1<<mActiveTrack); - return NO_ERROR; - } - } else { - return NO_ERROR; + assert((channelCount <= MAX_NUM_CHANNELS) && (channelCount)); + track.channelMask = mask; + track.channelCount = channelCount; + ALOGV("setParameter(TRACK, CHANNEL_MASK, %x)", mask); + invalidateState(1 << name); } - } - if (name == MAIN_BUFFER) { - if (mState.tracks[ mActiveTrack ].mainBuffer != valueBuf) { - mState.tracks[ mActiveTrack ].mainBuffer = valueBuf; + } break; + case MAIN_BUFFER: + if (track.mainBuffer != valueBuf) { + track.mainBuffer = valueBuf; ALOGV("setParameter(TRACK, MAIN_BUFFER, %p)", valueBuf); - invalidateState(1<<mActiveTrack); + invalidateState(1 << name); } - return NO_ERROR; - } - if (name == AUX_BUFFER) { - if (mState.tracks[ mActiveTrack ].auxBuffer != valueBuf) { - mState.tracks[ mActiveTrack ].auxBuffer = valueBuf; + break; + case AUX_BUFFER: + if (track.auxBuffer != valueBuf) { + track.auxBuffer = valueBuf; ALOGV("setParameter(TRACK, AUX_BUFFER, %p)", valueBuf); - invalidateState(1<<mActiveTrack); + invalidateState(1 << name); } - return NO_ERROR; + break; + default: + // bad param + assert(false); } - break; + case RESAMPLE: - if (name == SAMPLE_RATE) { - if (valueInt > 0) { - track_t& track = mState.tracks[ mActiveTrack ]; - if (track.setResampler(uint32_t(valueInt), mSampleRate)) { - ALOGV("setParameter(RESAMPLE, SAMPLE_RATE, %u)", - uint32_t(valueInt)); - invalidateState(1<<mActiveTrack); - } - return NO_ERROR; + switch (param) { + case SAMPLE_RATE: + assert(valueInt > 0); + if (track.setResampler(uint32_t(valueInt), mSampleRate)) { + ALOGV("setParameter(RESAMPLE, SAMPLE_RATE, %u)", + uint32_t(valueInt)); + invalidateState(1 << name); } - } - if (name == RESET) { - track_t& track = mState.tracks[ mActiveTrack ]; + break; + case RESET: track.resetResampler(); - invalidateState(1<<mActiveTrack); - return NO_ERROR; + invalidateState(1 << name); + break; + default: + // bad param + assert(false); } break; + case RAMP_VOLUME: case VOLUME: - if ((uint32_t(name-VOLUME0) < MAX_NUM_CHANNELS)) { - track_t& track = mState.tracks[ mActiveTrack ]; - if (track.volume[name-VOLUME0] != valueInt) { + switch (param) { + case VOLUME0: + case VOLUME1: + if (track.volume[param-VOLUME0] != valueInt) { ALOGV("setParameter(VOLUME, VOLUME0/1: %04x)", valueInt); - track.prevVolume[name-VOLUME0] = track.volume[name-VOLUME0] << 16; - track.volume[name-VOLUME0] = valueInt; + track.prevVolume[param-VOLUME0] = track.volume[param-VOLUME0] << 16; + track.volume[param-VOLUME0] = valueInt; if (target == VOLUME) { - track.prevVolume[name-VOLUME0] = valueInt << 16; - track.volumeInc[name-VOLUME0] = 0; + track.prevVolume[param-VOLUME0] = valueInt << 16; + track.volumeInc[param-VOLUME0] = 0; } else { - int32_t d = (valueInt<<16) - track.prevVolume[name-VOLUME0]; + int32_t d = (valueInt<<16) - track.prevVolume[param-VOLUME0]; int32_t volInc = d / int32_t(mState.frameCount); - track.volumeInc[name-VOLUME0] = volInc; + track.volumeInc[param-VOLUME0] = volInc; if (volInc == 0) { - track.prevVolume[name-VOLUME0] = valueInt << 16; + track.prevVolume[param-VOLUME0] = valueInt << 16; } } - invalidateState(1<<mActiveTrack); + invalidateState(1 << name); } - return NO_ERROR; - } else if (name == AUXLEVEL) { - track_t& track = mState.tracks[ mActiveTrack ]; + break; + case AUXLEVEL: if (track.auxLevel != valueInt) { ALOGV("setParameter(VOLUME, AUXLEVEL: %04x)", valueInt); track.prevAuxLevel = track.auxLevel << 16; @@ -276,13 +266,19 @@ status_t AudioMixer::setParameter(int target, int name, void *value) track.prevAuxLevel = valueInt << 16; } } - invalidateState(1<<mActiveTrack); + invalidateState(1 << name); } - return NO_ERROR; + break; + default: + // bad param + assert(false); } break; + + default: + // bad target + assert(false); } - return BAD_VALUE; } bool AudioMixer::track_t::setResampler(uint32_t value, uint32_t devSampleRate) @@ -290,7 +286,7 @@ bool AudioMixer::track_t::setResampler(uint32_t value, uint32_t devSampleRate) if (value!=devSampleRate || resampler) { if (sampleRate != value) { sampleRate = value; - if (resampler == 0) { + if (resampler == NULL) { resampler = AudioResampler::create( format, channelCount, devSampleRate); } @@ -302,12 +298,12 @@ bool AudioMixer::track_t::setResampler(uint32_t value, uint32_t devSampleRate) bool AudioMixer::track_t::doesResample() const { - return resampler != 0; + return resampler != NULL; } void AudioMixer::track_t::resetResampler() { - if (resampler != 0) { + if (resampler != NULL) { resampler->reset(); } } @@ -315,7 +311,7 @@ void AudioMixer::track_t::resetResampler() inline void AudioMixer::track_t::adjustVolumeRamp(bool aux) { - for (int i=0 ; i<2 ; i++) { + for (uint32_t i=0 ; i<MAX_NUM_CHANNELS ; i++) { if (((volumeInc[i]>0) && (((prevVolume[i]+volumeInc[i])>>16) >= volume[i])) || ((volumeInc[i]<0) && (((prevVolume[i]+volumeInc[i])>>16) <= volume[i]))) { volumeInc[i] = 0; @@ -349,10 +345,11 @@ size_t AudioMixer::getUnreleasedFrames(int name) return 0; } -status_t AudioMixer::setBufferProvider(AudioBufferProvider* buffer) +void AudioMixer::setBufferProvider(int name, AudioBufferProvider* buffer) { - mState.tracks[ mActiveTrack ].bufferProvider = buffer; - return NO_ERROR; + name -= TRACK0; + assert(uint32_t(name) < MAX_NUM_TRACKS); + mState.tracks[name].bufferProvider = buffer; } @@ -447,11 +444,11 @@ void AudioMixer::process__validate(state_t* state) } else { if (state->outputTemp) { delete [] state->outputTemp; - state->outputTemp = 0; + state->outputTemp = NULL; } if (state->resampleTemp) { delete [] state->resampleTemp; - state->resampleTemp = 0; + state->resampleTemp = NULL; } state->hook = process__genericNoResampling; if (all16BitsStereoNoResample && !volumeRamp) { @@ -467,115 +464,33 @@ void AudioMixer::process__validate(state_t* state) countActiveTracks, state->enabledTracks, all16BitsStereoNoResample, resampling, volumeRamp); - state->hook(state); - - // Now that the volume ramp has been done, set optimal state and - // track hooks for subsequent mixer process - if (countActiveTracks) { - int allMuted = 1; - uint32_t en = state->enabledTracks; - while (en) { - const int i = 31 - __builtin_clz(en); - en &= ~(1<<i); - track_t& t = state->tracks[i]; - if (!t.doesResample() && t.volumeRL == 0) - { - t.needs |= NEEDS_MUTE_ENABLED; - t.hook = track__nop; - } else { - allMuted = 0; - } - } - if (allMuted) { - state->hook = process__nop; - } else if (all16BitsStereoNoResample) { - if (countActiveTracks == 1) { - state->hook = process__OneTrack16BitsStereoNoResampling; - } - } - } -} - -static inline -int32_t mulAdd(int16_t in, int16_t v, int32_t a) -{ -#if defined(__arm__) && !defined(__thumb__) - int32_t out; - asm( "smlabb %[out], %[in], %[v], %[a] \n" - : [out]"=r"(out) - : [in]"%r"(in), [v]"r"(v), [a]"r"(a) - : ); - return out; -#else - return a + in * int32_t(v); -#endif -} - -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 -} + state->hook(state); -static inline -int32_t mulAddRL(int left, uint32_t inRL, uint32_t vRL, int32_t a) -{ -#if defined(__arm__) && !defined(__thumb__) - int32_t out; - if (left) { - asm( "smlabb %[out], %[inRL], %[vRL], %[a] \n" - : [out]"=r"(out) - : [inRL]"%r"(inRL), [vRL]"r"(vRL), [a]"r"(a) - : ); - } else { - asm( "smlatt %[out], %[inRL], %[vRL], %[a] \n" - : [out]"=r"(out) - : [inRL]"%r"(inRL), [vRL]"r"(vRL), [a]"r"(a) - : ); - } - return out; -#else - if (left) { - return a + int16_t(inRL&0xFFFF) * int16_t(vRL&0xFFFF); - } else { - return a + int16_t(inRL>>16) * int16_t(vRL>>16); - } -#endif -} - -static inline -int32_t mulRL(int left, uint32_t inRL, uint32_t vRL) -{ -#if defined(__arm__) && !defined(__thumb__) - int32_t out; - if (left) { - asm( "smulbb %[out], %[inRL], %[vRL] \n" - : [out]"=r"(out) - : [inRL]"%r"(inRL), [vRL]"r"(vRL) - : ); - } else { - asm( "smultt %[out], %[inRL], %[vRL] \n" - : [out]"=r"(out) - : [inRL]"%r"(inRL), [vRL]"r"(vRL) - : ); - } - return out; -#else - if (left) { - return int16_t(inRL&0xFFFF) * int16_t(vRL&0xFFFF); - } else { - return int16_t(inRL>>16) * int16_t(vRL>>16); + // Now that the volume ramp has been done, set optimal state and + // track hooks for subsequent mixer process + if (countActiveTracks) { + int allMuted = 1; + uint32_t en = state->enabledTracks; + while (en) { + const int i = 31 - __builtin_clz(en); + en &= ~(1<<i); + track_t& t = state->tracks[i]; + if (!t.doesResample() && t.volumeRL == 0) + { + t.needs |= NEEDS_MUTE_ENABLED; + t.hook = track__nop; + } else { + allMuted = 0; + } + } + if (allMuted) { + state->hook = process__nop; + } else if (all16BitsStereoNoResample) { + if (countActiveTracks == 1) { + state->hook = process__OneTrack16BitsStereoNoResampling; + } + } } -#endif } @@ -591,13 +506,13 @@ void AudioMixer::track__genericResample(track_t* t, int32_t* out, size_t outFram t->resampler->setVolume(UNITY_GAIN, UNITY_GAIN); memset(temp, 0, outFrameCount * MAX_NUM_CHANNELS * sizeof(int32_t)); t->resampler->resample(temp, outFrameCount, t->bufferProvider); - if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc) { + if (CC_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]) { + if (CC_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); @@ -628,7 +543,7 @@ void AudioMixer::volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, i // (vl + vlInc*frameCount)/65536.0f, frameCount); // ramp volume - if UNLIKELY(aux != NULL) { + if (CC_UNLIKELY(aux != NULL)) { int32_t va = t->prevAuxLevel; const int32_t vaInc = t->auxInc; int32_t l; @@ -663,7 +578,7 @@ void AudioMixer::volumeStereo(track_t* t, int32_t* out, size_t frameCount, int32 const int16_t vl = t->volume[0]; const int16_t vr = t->volume[1]; - if UNLIKELY(aux != NULL) { + if (CC_UNLIKELY(aux != NULL)) { const int16_t va = (int16_t)t->auxLevel; do { int16_t l = (int16_t)(*temp++ >> 12); @@ -688,13 +603,13 @@ void AudioMixer::volumeStereo(track_t* t, int32_t* out, size_t frameCount, int32 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); + const int16_t *in = static_cast<const int16_t *>(t->in); - if UNLIKELY(aux != NULL) { + if (CC_UNLIKELY(aux != NULL)) { int32_t l; int32_t r; // ramp gain - if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc) { + if (CC_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; @@ -727,7 +642,7 @@ void AudioMixer::track__16BitsStereo(track_t* t, int32_t* out, size_t frameCount const uint32_t vrl = t->volumeRL; const int16_t va = (int16_t)t->auxLevel; do { - uint32_t rl = *reinterpret_cast<uint32_t const *>(in); + uint32_t rl = *reinterpret_cast<const uint32_t *>(in); int16_t a = (int16_t)(((int32_t)in[0] + in[1]) >> 1); in += 2; out[0] = mulAddRL(1, rl, vrl, out[0]); @@ -739,7 +654,7 @@ void AudioMixer::track__16BitsStereo(track_t* t, int32_t* out, size_t frameCount } } else { // ramp gain - if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]) { + if (CC_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]; @@ -765,7 +680,7 @@ void AudioMixer::track__16BitsStereo(track_t* t, int32_t* out, size_t frameCount else { const uint32_t vrl = t->volumeRL; do { - uint32_t rl = *reinterpret_cast<uint32_t const *>(in); + uint32_t rl = *reinterpret_cast<const uint32_t *>(in); in += 2; out[0] = mulAddRL(1, rl, vrl, out[0]); out[1] = mulAddRL(0, rl, vrl, out[1]); @@ -778,11 +693,11 @@ void AudioMixer::track__16BitsStereo(track_t* t, int32_t* out, size_t frameCount 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); + const int16_t *in = static_cast<int16_t const *>(t->in); - if UNLIKELY(aux != NULL) { + if (CC_UNLIKELY(aux != NULL)) { // ramp gain - if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]|t->auxInc) { + if (CC_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; @@ -825,7 +740,7 @@ void AudioMixer::track__16BitsMono(track_t* t, int32_t* out, size_t frameCount, } } else { // ramp gain - if UNLIKELY(t->volumeInc[0]|t->volumeInc[1]) { + if (CC_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]; @@ -862,19 +777,6 @@ void AudioMixer::track__16BitsMono(track_t* t, int32_t* out, size_t frameCount, t->in = in; } -void AudioMixer::ditherAndClamp(int32_t* out, int32_t const *sums, size_t c) -{ - for (size_t i=0 ; i<c ; i++) { - int32_t l = *sums++; - int32_t r = *sums++; - int32_t nl = l >> 12; - int32_t nr = r >> 12; - l = clamp16(nl); - r = clamp16(nr); - *out++ = (r<<16) | (l & 0xFFFF); - } -} - // no-op case void AudioMixer::process__nop(state_t* state) { @@ -891,7 +793,7 @@ void AudioMixer::process__nop(state_t* state) i = 31 - __builtin_clz(e2); e2 &= ~(1<<i); track_t& t2 = state->tracks[i]; - if UNLIKELY(t2.mainBuffer != t1.mainBuffer) { + if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) { e1 &= ~(1<<i); } } @@ -907,7 +809,7 @@ void AudioMixer::process__nop(state_t* state) while (outFrames) { t1.buffer.frameCount = outFrames; t1.bufferProvider->getNextBuffer(&t1.buffer); - if (!t1.buffer.raw) break; + if (t1.buffer.raw == NULL) break; outFrames -= t1.buffer.frameCount; t1.bufferProvider->releaseBuffer(&t1.buffer); } @@ -949,7 +851,7 @@ void AudioMixer::process__genericNoResampling(state_t* state) j = 31 - __builtin_clz(e2); e2 &= ~(1<<j); track_t& t2 = state->tracks[j]; - if UNLIKELY(t2.mainBuffer != t1.mainBuffer) { + if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) { e1 &= ~(1<<j); } } @@ -966,7 +868,7 @@ void AudioMixer::process__genericNoResampling(state_t* state) track_t& t = state->tracks[i]; size_t outFrames = BLOCKSIZE; int32_t *aux = NULL; - if UNLIKELY((t.needs & NEEDS_AUX__MASK) == NEEDS_AUX_ENABLED) { + if (CC_UNLIKELY((t.needs & NEEDS_AUX__MASK) == NEEDS_AUX_ENABLED)) { aux = t.auxBuffer + numFrames; } while (outFrames) { @@ -975,7 +877,7 @@ void AudioMixer::process__genericNoResampling(state_t* state) (t.hook)(&t, outTemp + (BLOCKSIZE-outFrames)*MAX_NUM_CHANNELS, inFrames, state->resampleTemp, aux); t.frameCount -= inFrames; outFrames -= inFrames; - if UNLIKELY(aux != NULL) { + if (CC_UNLIKELY(aux != NULL)) { aux += inFrames; } } @@ -1010,9 +912,10 @@ void AudioMixer::process__genericNoResampling(state_t* state) } - // generic code with resampling +// generic code with resampling void AudioMixer::process__genericResampling(state_t* state) { + // this const just means that local variable outTemp doesn't change int32_t* const outTemp = state->outputTemp; const size_t size = sizeof(int32_t) * MAX_NUM_CHANNELS * state->frameCount; @@ -1030,7 +933,7 @@ void AudioMixer::process__genericResampling(state_t* state) j = 31 - __builtin_clz(e2); e2 &= ~(1<<j); track_t& t2 = state->tracks[j]; - if UNLIKELY(t2.mainBuffer != t1.mainBuffer) { + if (CC_UNLIKELY(t2.mainBuffer != t1.mainBuffer)) { e1 &= ~(1<<j); } } @@ -1042,7 +945,7 @@ void AudioMixer::process__genericResampling(state_t* state) e1 &= ~(1<<i); track_t& t = state->tracks[i]; int32_t *aux = NULL; - if UNLIKELY((t.needs & NEEDS_AUX__MASK) == NEEDS_AUX_ENABLED) { + if (CC_UNLIKELY((t.needs & NEEDS_AUX__MASK) == NEEDS_AUX_ENABLED)) { aux = t.auxBuffer; } @@ -1063,7 +966,7 @@ void AudioMixer::process__genericResampling(state_t* state) // been enabled for mixing. if (t.in == NULL) break; - if UNLIKELY(aux != NULL) { + if (CC_UNLIKELY(aux != NULL)) { aux += outFrames; } (t.hook)(&t, outTemp + outFrames*MAX_NUM_CHANNELS, t.buffer.frameCount, state->resampleTemp, aux); @@ -1093,7 +996,7 @@ void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state) while (numFrames) { b.frameCount = numFrames; t.bufferProvider->getNextBuffer(&b); - int16_t const *in = b.i16; + const int16_t *in = b.i16; // in == NULL can happen if the track was flushed just after having // been enabled for mixing. @@ -1105,11 +1008,11 @@ void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state) } size_t outFrames = b.frameCount; - if (UNLIKELY(uint32_t(vl) > UNITY_GAIN || uint32_t(vr) > UNITY_GAIN)) { + if (CC_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. do { - uint32_t rl = *reinterpret_cast<uint32_t const *>(in); + uint32_t rl = *reinterpret_cast<const uint32_t *>(in); in += 2; int32_t l = mulRL(1, rl, vrl) >> 12; int32_t r = mulRL(0, rl, vrl) >> 12; @@ -1120,7 +1023,7 @@ void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state) } while (--outFrames); } else { do { - uint32_t rl = *reinterpret_cast<uint32_t const *>(in); + uint32_t rl = *reinterpret_cast<const uint32_t *>(in); in += 2; int32_t l = mulRL(1, rl, vrl) >> 12; int32_t r = mulRL(0, rl, vrl) >> 12; @@ -1132,6 +1035,7 @@ void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state) } } +#if 0 // 2 tracks is also a common case // NEVER used in current implementation of process__validate() // only use if the 2 tracks have the same output buffer @@ -1149,12 +1053,12 @@ void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state) const track_t& t1 = state->tracks[i]; AudioBufferProvider::Buffer& b1(t1.buffer); - int16_t const *in0; + const int16_t *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 *in1; const int16_t vl1 = t1.volume[0]; const int16_t vr1 = t1.volume[1]; size_t frameCount1 = 0; @@ -1162,7 +1066,7 @@ void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state) //FIXME: only works if two tracks use same buffer int32_t* out = t0.mainBuffer; size_t numFrames = state->frameCount; - int16_t const *buff = NULL; + const int16_t *buff = NULL; while (numFrames) { @@ -1190,7 +1094,7 @@ void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state) } in1 = buff; b1.frameCount = numFrames; - } else { + } else { in1 = b1.i16; } frameCount1 = b1.frameCount; @@ -1225,11 +1129,9 @@ void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state) } } - if (buff != NULL) { - delete [] buff; - } + delete [] buff; } +#endif // ---------------------------------------------------------------------------- }; // namespace android - diff --git a/services/audioflinger/AudioMixer.h b/services/audioflinger/AudioMixer.h index 0137185..84f6330 100644 --- a/services/audioflinger/AudioMixer.h +++ b/services/audioflinger/AudioMixer.h @@ -28,11 +28,6 @@ namespace android { // ---------------------------------------------------------------------------- -#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) -#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) - -// ---------------------------------------------------------------------------- - class AudioMixer { public: @@ -47,11 +42,10 @@ public: enum { // names - // track units (32 units) + // track names (MAX_NUM_TRACKS units) TRACK0 = 0x1000, - // enable/disable - MIXING = 0x2000, + // 0x2000 is unused // setParameter targets TRACK = 0x3000, @@ -65,32 +59,30 @@ public: FORMAT = 0x4001, MAIN_BUFFER = 0x4002, AUX_BUFFER = 0x4003, - // for TARGET RESAMPLE + // for target RESAMPLE SAMPLE_RATE = 0x4100, RESET = 0x4101, - // for TARGET VOLUME (8 channels max) + // for target RAMP_VOLUME and VOLUME (8 channels max) VOLUME0 = 0x4200, VOLUME1 = 0x4201, AUXLEVEL = 0x4210, }; + // For all APIs with "name": TRACK0 <= name < TRACK0 + MAX_NUM_TRACKS int getTrackName(); void deleteTrackName(int name); - status_t enable(int name); - status_t disable(int name); + void enable(int name); + void disable(int name); - status_t setActiveTrack(int track); - status_t setParameter(int target, int name, void *value); + void setParameter(int name, int target, int param, void *value); - status_t setBufferProvider(AudioBufferProvider* bufferProvider); + void setBufferProvider(int name, AudioBufferProvider* bufferProvider); void process(); uint32_t trackNames() const { return mTrackNames; } - static void ditherAndClamp(int32_t* out, int32_t const *sums, size_t c); - size_t getUnreleasedFrames(int name); private: @@ -119,11 +111,6 @@ private: NEEDS_AUX_ENABLED = 0x00010000, }; - static inline int32_t applyVolume(int32_t in, int32_t v) { - return in * v; - } - - struct state_t; struct track_t; @@ -135,13 +122,13 @@ private: uint32_t needs; union { - int16_t volume[2]; // [0]3.12 fixed point + int16_t volume[MAX_NUM_CHANNELS]; // [0]3.12 fixed point int32_t volumeRL; }; - int32_t prevVolume[2]; + int32_t prevVolume[MAX_NUM_CHANNELS]; - int32_t volumeInc[2]; + int32_t volumeInc[MAX_NUM_CHANNELS]; int32_t auxLevel; int32_t auxInc; int32_t prevAuxLevel; @@ -158,7 +145,7 @@ private: mutable AudioBufferProvider::Buffer buffer; hook_t hook; - void const* in; // current location in buffer + const void* in; // current location in buffer AudioResampler* resampler; uint32_t sampleRate; @@ -181,10 +168,10 @@ private: int32_t *outputTemp; int32_t *resampleTemp; int32_t reserved[2]; - track_t tracks[32]; __attribute__((aligned(32))); + track_t tracks[MAX_NUM_TRACKS]; __attribute__((aligned(32))); }; - int mActiveTrack; + // bitmask of allocated track names, where bit 0 corresponds to TRACK0 etc. uint32_t mTrackNames; const uint32_t mSampleRate; @@ -204,7 +191,9 @@ private: static void process__genericNoResampling(state_t* state); static void process__genericResampling(state_t* state); static void process__OneTrack16BitsStereoNoResampling(state_t* state); +#if 0 static void process__TwoTracks16BitsStereoNoResampling(state_t* state); +#endif }; // ---------------------------------------------------------------------------- diff --git a/services/audioflinger/AudioPolicyService.cpp b/services/audioflinger/AudioPolicyService.cpp index 6be669b..1dddbb3 100644 --- a/services/audioflinger/AudioPolicyService.cpp +++ b/services/audioflinger/AudioPolicyService.cpp @@ -31,7 +31,6 @@ #include <utils/threads.h> #include "AudioPolicyService.h" #include <cutils/properties.h> -#include <dlfcn.h> #include <hardware_legacy/power.h> #include <media/AudioEffect.h> #include <media/EffectsFactoryApi.h> @@ -44,11 +43,11 @@ namespace android { -static const char *kDeadlockedString = "AudioPolicyService may be deadlocked\n"; -static const char *kCmdDeadlockedString = "AudioPolicyService command thread may be deadlocked\n"; +static const char kDeadlockedString[] = "AudioPolicyService may be deadlocked\n"; +static const char kCmdDeadlockedString[] = "AudioPolicyService command thread may be deadlocked\n"; static const int kDumpLockRetries = 50; -static const int kDumpLockSleep = 20000; +static const int kDumpLockSleepUs = 20000; static bool checkPermission() { if (getpid() == IPCThreadState::self()->getCallingPid()) return true; @@ -145,9 +144,9 @@ AudioPolicyService::~AudioPolicyService() } mInputs.clear(); - if (mpAudioPolicy && mpAudioPolicyDev) + if (mpAudioPolicy != NULL && mpAudioPolicyDev != NULL) mpAudioPolicyDev->destroy_audio_policy(mpAudioPolicyDev, mpAudioPolicy); - if (mpAudioPolicyDev) + if (mpAudioPolicyDev != NULL) audio_policy_dev_close(mpAudioPolicyDev); } @@ -186,7 +185,7 @@ audio_policy_dev_state_t AudioPolicyService::getDeviceConnectionState( device_address); } -status_t AudioPolicyService::setPhoneState(int state) +status_t AudioPolicyService::setPhoneState(audio_mode_t state) { if (mpAudioPolicy == NULL) { return NO_INIT; @@ -194,7 +193,7 @@ status_t AudioPolicyService::setPhoneState(int state) if (!checkPermission()) { return PERMISSION_DENIED; } - if (state < 0 || state >= AUDIO_MODE_CNT) { + if (uint32_t(state) >= AUDIO_MODE_CNT) { return BAD_VALUE; } @@ -208,19 +207,6 @@ status_t AudioPolicyService::setPhoneState(int state) return NO_ERROR; } -status_t AudioPolicyService::setRingerMode(uint32_t mode, uint32_t mask) -{ - if (mpAudioPolicy == NULL) { - return NO_INIT; - } - if (!checkPermission()) { - return PERMISSION_DENIED; - } - - mpAudioPolicy->set_ringer_mode(mpAudioPolicy, mode, mask); - return NO_ERROR; -} - status_t AudioPolicyService::setForceUse(audio_policy_force_use_t usage, audio_policy_forced_cfg_t config) { @@ -255,7 +241,7 @@ audio_policy_forced_cfg_t AudioPolicyService::getForceUse(audio_policy_force_use audio_io_handle_t AudioPolicyService::getOutput(audio_stream_type_t stream, uint32_t samplingRate, - uint32_t format, + audio_format_t format, uint32_t channels, audio_policy_output_flags_t flags) { @@ -301,9 +287,9 @@ void AudioPolicyService::releaseOutput(audio_io_handle_t output) mpAudioPolicy->release_output(mpAudioPolicy, output); } -audio_io_handle_t AudioPolicyService::getInput(int inputSource, +audio_io_handle_t AudioPolicyService::getInput(audio_source_t inputSource, uint32_t samplingRate, - uint32_t format, + audio_format_t format, uint32_t channels, audio_in_acoustics_t acoustics, int audioSession) @@ -311,6 +297,10 @@ audio_io_handle_t AudioPolicyService::getInput(int inputSource, if (mpAudioPolicy == NULL) { return 0; } + // already checked by client, but double-check in case the client wrapper is bypassed + if (uint32_t(inputSource) >= AUDIO_SOURCE_CNT) { + return 0; + } Mutex::Autolock _l(mLock); audio_io_handle_t input = mpAudioPolicy->get_input(mpAudioPolicy, inputSource, samplingRate, format, channels, acoustics); @@ -319,7 +309,7 @@ audio_io_handle_t AudioPolicyService::getInput(int inputSource, return input; } // create audio pre processors according to input source - ssize_t index = mInputSources.indexOfKey((audio_source_t)inputSource); + ssize_t index = mInputSources.indexOfKey(inputSource); if (index < 0) { return input; } @@ -401,14 +391,16 @@ status_t AudioPolicyService::initStreamVolume(audio_stream_type_t stream, if (!checkPermission()) { return PERMISSION_DENIED; } - if (stream < 0 || stream >= AUDIO_STREAM_CNT) { + if (uint32_t(stream) >= AUDIO_STREAM_CNT) { return BAD_VALUE; } mpAudioPolicy->init_stream_volume(mpAudioPolicy, stream, indexMin, indexMax); return NO_ERROR; } -status_t AudioPolicyService::setStreamVolumeIndex(audio_stream_type_t stream, int index) +status_t AudioPolicyService::setStreamVolumeIndex(audio_stream_type_t stream, + int index, + audio_devices_t device) { if (mpAudioPolicy == NULL) { return NO_INIT; @@ -416,22 +408,38 @@ status_t AudioPolicyService::setStreamVolumeIndex(audio_stream_type_t stream, in if (!checkPermission()) { return PERMISSION_DENIED; } - if (stream < 0 || stream >= AUDIO_STREAM_CNT) { + if (uint32_t(stream) >= AUDIO_STREAM_CNT) { return BAD_VALUE; } - return mpAudioPolicy->set_stream_volume_index(mpAudioPolicy, stream, index); + if (mpAudioPolicy->set_stream_volume_index_for_device) { + return mpAudioPolicy->set_stream_volume_index_for_device(mpAudioPolicy, + stream, + index, + device); + } else { + return mpAudioPolicy->set_stream_volume_index(mpAudioPolicy, stream, index); + } } -status_t AudioPolicyService::getStreamVolumeIndex(audio_stream_type_t stream, int *index) +status_t AudioPolicyService::getStreamVolumeIndex(audio_stream_type_t stream, + int *index, + audio_devices_t device) { if (mpAudioPolicy == NULL) { return NO_INIT; } - if (stream < 0 || stream >= AUDIO_STREAM_CNT) { + if (uint32_t(stream) >= AUDIO_STREAM_CNT) { return BAD_VALUE; } - return mpAudioPolicy->get_stream_volume_index(mpAudioPolicy, stream, index); + if (mpAudioPolicy->get_stream_volume_index_for_device) { + return mpAudioPolicy->get_stream_volume_index_for_device(mpAudioPolicy, + stream, + index, + device); + } else { + return mpAudioPolicy->get_stream_volume_index(mpAudioPolicy, stream, index); + } } uint32_t AudioPolicyService::getStrategyForStream(audio_stream_type_t stream) @@ -487,7 +495,7 @@ status_t AudioPolicyService::setEffectEnabled(int id, bool enabled) return mpAudioPolicy->set_effect_enabled(mpAudioPolicy, id, enabled); } -bool AudioPolicyService::isStreamActive(int stream, uint32_t inPastMs) const +bool AudioPolicyService::isStreamActive(audio_stream_type_t stream, uint32_t inPastMs) const { if (mpAudioPolicy == NULL) { return 0; @@ -546,7 +554,7 @@ static bool tryLock(Mutex& mutex) locked = true; break; } - usleep(kDumpLockSleep); + usleep(kDumpLockSleepUs); } return locked; } @@ -570,7 +578,7 @@ status_t AudioPolicyService::dumpInternals(int fd) status_t AudioPolicyService::dump(int fd, const Vector<String16>& args) { - if (checkCallingPermission(String16("android.permission.DUMP")) == false) { + if (!checkCallingPermission(String16("android.permission.DUMP"))) { dumpPermissionDenial(fd); } else { bool locked = tryLock(mLock); @@ -641,7 +649,7 @@ AudioPolicyService::AudioCommandThread::~AudioCommandThread() release_wake_lock(mName.string()); } mAudioCommands.clear(); - if (mpToneGenerator != NULL) delete mpToneGenerator; + delete mpToneGenerator; } void AudioPolicyService::AudioCommandThread::onFirstRef() @@ -674,8 +682,7 @@ bool AudioPolicyService::AudioCommandThread::threadLoop() ToneData *data = (ToneData *)command->mParam; ALOGV("AudioCommandThread() processing start tone %d on stream %d", data->mType, data->mStream); - if (mpToneGenerator != NULL) - delete mpToneGenerator; + delete mpToneGenerator; mpToneGenerator = new ToneGenerator(data->mStream, 1.0); mpToneGenerator->startTone(data->mType); delete data; @@ -782,7 +789,8 @@ status_t AudioPolicyService::AudioCommandThread::dump(int fd) return NO_ERROR; } -void AudioPolicyService::AudioCommandThread::startToneCommand(int type, int stream) +void AudioPolicyService::AudioCommandThread::startToneCommand(ToneGenerator::tone_type type, + audio_stream_type_t stream) { AudioCommand *command = new AudioCommand(); command->mCommand = START_TONE; @@ -809,7 +817,7 @@ void AudioPolicyService::AudioCommandThread::stopToneCommand() mWaitWorkCV.signal(); } -status_t AudioPolicyService::AudioCommandThread::volumeCommand(int stream, +status_t AudioPolicyService::AudioCommandThread::volumeCommand(audio_stream_type_t stream, float volume, int output, int delayMs) @@ -1020,7 +1028,7 @@ int AudioPolicyService::setStreamVolume(audio_stream_type_t stream, audio_io_handle_t output, int delayMs) { - return (int)mAudioCommandThread->volumeCommand((int)stream, volume, + return (int)mAudioCommandThread->volumeCommand(stream, volume, (int)output, delayMs); } @@ -1052,7 +1060,7 @@ int AudioPolicyService::setVoiceVolume(float volume, int delayMs) // Audio pre-processing configuration // ---------------------------------------------------------------------------- -const char *AudioPolicyService::kInputSourceNames[AUDIO_SOURCE_CNT -1] = { +/*static*/ const char * const AudioPolicyService::kInputSourceNames[AUDIO_SOURCE_CNT -1] = { MIC_SRC_TAG, VOICE_UL_SRC_TAG, VOICE_DL_SRC_TAG, @@ -1152,7 +1160,7 @@ effect_param_t *AudioPolicyService::loadEffectParameter(cnode *root) if (param == NULL && value == NULL) { // try to parse simple parameter form {int int} param = root->first_child; - if (param) { + if (param != NULL) { // Note: that a pair of random strings is read as 0 0 int *ptr = (int *)fx_param->data; int *ptr2 = (int *)((char *)param + sizeof(effect_param_t)); @@ -1348,7 +1356,7 @@ extern "C" { static audio_io_handle_t aps_open_output(void *service, uint32_t *pDevices, uint32_t *pSamplingRate, - uint32_t *pFormat, + audio_format_t *pFormat, uint32_t *pChannels, uint32_t *pLatencyMs, audio_policy_output_flags_t flags) @@ -1409,9 +1417,9 @@ static int aps_restore_output(void *service, audio_io_handle_t output) static audio_io_handle_t aps_open_input(void *service, uint32_t *pDevices, uint32_t *pSamplingRate, - uint32_t *pFormat, + audio_format_t *pFormat, uint32_t *pChannels, - uint32_t acoustics) + audio_in_acoustics_t acoustics) { sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); if (af == NULL) { diff --git a/services/audioflinger/AudioPolicyService.h b/services/audioflinger/AudioPolicyService.h index d898a53..62219e5 100644 --- a/services/audioflinger/AudioPolicyService.h +++ b/services/audioflinger/AudioPolicyService.h @@ -19,6 +19,7 @@ #include <cutils/misc.h> #include <cutils/config_utils.h> +#include <utils/String8.h> #include <utils/Vector.h> #include <utils/SortedVector.h> #include <binder/BinderService.h> @@ -31,8 +32,6 @@ namespace android { -class String8; - // ---------------------------------------------------------------------------- class AudioPolicyService : @@ -59,13 +58,12 @@ public: virtual audio_policy_dev_state_t getDeviceConnectionState( audio_devices_t device, const char *device_address); - virtual status_t setPhoneState(int state); - virtual status_t setRingerMode(uint32_t mode, uint32_t mask); + virtual status_t setPhoneState(audio_mode_t state); virtual status_t setForceUse(audio_policy_force_use_t usage, audio_policy_forced_cfg_t config); virtual audio_policy_forced_cfg_t getForceUse(audio_policy_force_use_t usage); virtual audio_io_handle_t getOutput(audio_stream_type_t stream, uint32_t samplingRate = 0, - uint32_t format = AUDIO_FORMAT_DEFAULT, + audio_format_t format = AUDIO_FORMAT_DEFAULT, uint32_t channels = 0, audio_policy_output_flags_t flags = AUDIO_POLICY_OUTPUT_FLAG_INDIRECT); @@ -76,12 +74,12 @@ public: audio_stream_type_t stream, int session = 0); virtual void releaseOutput(audio_io_handle_t output); - virtual audio_io_handle_t getInput(int inputSource, + virtual audio_io_handle_t getInput(audio_source_t inputSource, uint32_t samplingRate = 0, - uint32_t format = AUDIO_FORMAT_DEFAULT, + audio_format_t format = AUDIO_FORMAT_DEFAULT, uint32_t channels = 0, audio_in_acoustics_t acoustics = - (audio_in_acoustics_t)0, + (audio_in_acoustics_t)0 /*AUDIO_IN_ACOUSTICS_NONE*/, int audioSession = 0); virtual status_t startInput(audio_io_handle_t input); virtual status_t stopInput(audio_io_handle_t input); @@ -89,8 +87,12 @@ public: virtual status_t initStreamVolume(audio_stream_type_t stream, int indexMin, int indexMax); - virtual status_t setStreamVolumeIndex(audio_stream_type_t stream, int index); - virtual status_t getStreamVolumeIndex(audio_stream_type_t stream, int *index); + virtual status_t setStreamVolumeIndex(audio_stream_type_t stream, + int index, + audio_devices_t device); + virtual status_t getStreamVolumeIndex(audio_stream_type_t stream, + int *index, + audio_devices_t device); virtual uint32_t getStrategyForStream(audio_stream_type_t stream); virtual uint32_t getDevicesForStream(audio_stream_type_t stream); @@ -103,7 +105,7 @@ public: int id); virtual status_t unregisterEffect(int id); virtual status_t setEffectEnabled(int id, bool enabled); - virtual bool isStreamActive(int stream, uint32_t inPastMs = 0) const; + virtual bool isStreamActive(audio_stream_type_t stream, uint32_t inPastMs = 0) const; virtual status_t queryDefaultPreProcessing(int audioSession, effect_descriptor_t *descriptors, @@ -169,9 +171,10 @@ private: virtual bool threadLoop(); void exit(); - void startToneCommand(int type = 0, int stream = 0); + void startToneCommand(ToneGenerator::tone_type type, + audio_stream_type_t stream); void stopToneCommand(); - status_t volumeCommand(int stream, float volume, int output, int delayMs = 0); + status_t volumeCommand(audio_stream_type_t stream, float volume, int output, int delayMs = 0); status_t parametersCommand(int ioHandle, const char *keyValuePairs, int delayMs = 0); status_t voiceVolumeCommand(float volume, int delayMs = 0); void insertCommand_l(AudioCommand *command, int delayMs = 0); @@ -196,13 +199,13 @@ private: class ToneData { public: - int mType; // tone type (START_TONE only) - int mStream; // stream type (START_TONE only) + ToneGenerator::tone_type mType; // tone type (START_TONE only) + audio_stream_type_t mStream; // stream type (START_TONE only) }; class VolumeData { public: - int mStream; + audio_stream_type_t mStream; float mVolume; int mIO; }; @@ -251,7 +254,7 @@ private: Vector< sp<AudioEffect> >mEffects; }; - static const char *kInputSourceNames[AUDIO_SOURCE_CNT -1]; + static const char * const kInputSourceNames[AUDIO_SOURCE_CNT -1]; void setPreProcessorEnabled(InputDesc *inputDesc, bool enabled); status_t loadPreProcessorConfig(const char *path); diff --git a/services/audioflinger/AudioResampler.cpp b/services/audioflinger/AudioResampler.cpp index 4586b54..feacd96 100644 --- a/services/audioflinger/AudioResampler.cpp +++ b/services/audioflinger/AudioResampler.cpp @@ -390,6 +390,7 @@ resampleMono16_exit: * phaseFraction : phase fraction for next interpolation * *******************************************************************/ +__attribute__((noinline)) void AudioResamplerOrder1::AsmMono16Loop(int16_t *in, int32_t* maxOutPt, int32_t maxInIdx, size_t &outputIndex, int32_t* out, size_t &inputIndex, int32_t vl, int32_t vr, uint32_t &phaseFraction, uint32_t phaseIncrement) @@ -500,6 +501,7 @@ void AudioResamplerOrder1::AsmMono16Loop(int16_t *in, int32_t* maxOutPt, int32_t * phaseFraction : phase fraction for next interpolation * *******************************************************************/ +__attribute__((noinline)) void AudioResamplerOrder1::AsmStereo16Loop(int16_t *in, int32_t* maxOutPt, int32_t maxInIdx, size_t &outputIndex, int32_t* out, size_t &inputIndex, int32_t vl, int32_t vr, uint32_t &phaseFraction, uint32_t phaseIncrement) @@ -600,6 +602,5 @@ void AudioResamplerOrder1::AsmStereo16Loop(int16_t *in, int32_t* maxOutPt, int32 // ---------------------------------------------------------------------------- -} -; // namespace android +} // namespace android diff --git a/services/audioflinger/AudioResamplerSinc.cpp b/services/audioflinger/AudioResamplerSinc.cpp index 9e5e254..d012433 100644 --- a/services/audioflinger/AudioResamplerSinc.cpp +++ b/services/audioflinger/AudioResamplerSinc.cpp @@ -284,7 +284,7 @@ template<int CHANNELS> **/ void AudioResamplerSinc::read( int16_t*& impulse, uint32_t& phaseFraction, - int16_t const* in, size_t inputIndex) + const int16_t* in, size_t inputIndex) { const uint32_t phaseIndex = phaseFraction >> kNumPhaseBits; impulse += CHANNELS; @@ -302,7 +302,7 @@ void AudioResamplerSinc::read( template<int CHANNELS> void AudioResamplerSinc::filterCoefficient( - int32_t& l, int32_t& r, uint32_t phase, int16_t const *samples) + int32_t& l, int32_t& r, uint32_t phase, const int16_t *samples) { // compute the index of the coefficient on the positive side and // negative side @@ -317,9 +317,9 @@ void AudioResamplerSinc::filterCoefficient( l = 0; r = 0; - int32_t const* coefs = mFirCoefs; - int16_t const *sP = samples; - int16_t const *sN = samples+CHANNELS; + const int32_t* coefs = mFirCoefs; + const int16_t *sP = samples; + const int16_t *sN = samples+CHANNELS; for (unsigned int i=0 ; i<halfNumCoefs/4 ; i++) { interpolate<CHANNELS>(l, r, coefs+indexP, lerpP, sP); interpolate<CHANNELS>(l, r, coefs+indexN, lerpN, sN); @@ -339,13 +339,13 @@ void AudioResamplerSinc::filterCoefficient( template<int CHANNELS> void AudioResamplerSinc::interpolate( int32_t& l, int32_t& r, - int32_t const* coefs, int16_t lerp, int16_t const* samples) + const int32_t* coefs, int16_t lerp, const int16_t* samples) { int32_t c0 = coefs[0]; int32_t c1 = coefs[1]; int32_t sinc = mulAdd(lerp, (c1-c0)<<1, c0); if (CHANNELS == 2) { - uint32_t rl = *reinterpret_cast<uint32_t const*>(samples); + uint32_t rl = *reinterpret_cast<const uint32_t*>(samples); l = mulAddRL(1, rl, sinc, l); r = mulAddRL(0, rl, sinc, r); } else { diff --git a/services/audioflinger/AudioResamplerSinc.h b/services/audioflinger/AudioResamplerSinc.h index e6cb90b..0e1bc44 100644 --- a/services/audioflinger/AudioResamplerSinc.h +++ b/services/audioflinger/AudioResamplerSinc.h @@ -44,22 +44,22 @@ private: template<int CHANNELS> inline void filterCoefficient( - int32_t& l, int32_t& r, uint32_t phase, int16_t const *samples); + int32_t& l, int32_t& r, uint32_t phase, const int16_t *samples); template<int CHANNELS> inline void interpolate( int32_t& l, int32_t& r, - int32_t const* coefs, int16_t lerp, int16_t const* samples); + const int32_t* coefs, int16_t lerp, const int16_t* samples); template<int CHANNELS> inline void read(int16_t*& impulse, uint32_t& phaseFraction, - int16_t const* in, size_t inputIndex); + const int16_t* in, size_t inputIndex); int16_t *mState; int16_t *mImpulse; int16_t *mRingFull; - int32_t const * mFirCoefs; + const int32_t * mFirCoefs; static const int32_t mFirCoefsDown[]; static const int32_t mFirCoefsUp[]; diff --git a/services/camera/libcameraservice/CameraHardwareInterface.h b/services/camera/libcameraservice/CameraHardwareInterface.h index 34087b5..2ac69f7 100644 --- a/services/camera/libcameraservice/CameraHardwareInterface.h +++ b/services/camera/libcameraservice/CameraHardwareInterface.h @@ -635,6 +635,12 @@ private: return native_window_set_crop(a, &crop); } + static int __set_timestamp(struct preview_stream_ops *w, + int64_t timestamp) { + ANativeWindow *a = anw(w); + return native_window_set_buffers_timestamp(a, timestamp); + } + static int __set_usage(struct preview_stream_ops* w, int usage) { ANativeWindow *a = anw(w); @@ -664,6 +670,7 @@ private: mHalPreviewWindow.nw.set_buffer_count = __set_buffer_count; mHalPreviewWindow.nw.set_buffers_geometry = __set_buffers_geometry; mHalPreviewWindow.nw.set_crop = __set_crop; + mHalPreviewWindow.nw.set_timestamp = __set_timestamp; mHalPreviewWindow.nw.set_usage = __set_usage; mHalPreviewWindow.nw.set_swap_interval = __set_swap_interval; diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp index 918f31e..06fc708 100644 --- a/services/camera/libcameraservice/CameraService.cpp +++ b/services/camera/libcameraservice/CameraService.cpp @@ -351,7 +351,7 @@ CameraService::Client::Client(const sp<CameraService>& cameraService, // Enable zoom, error, focus, and metadata messages by default enableMsgType(CAMERA_MSG_ERROR | CAMERA_MSG_ZOOM | CAMERA_MSG_FOCUS | - CAMERA_MSG_PREVIEW_METADATA); + CAMERA_MSG_PREVIEW_METADATA | CAMERA_MSG_FOCUS_MOVE); // Callback is disabled by default mPreviewCallbackFlag = CAMERA_FRAME_CALLBACK_FLAG_NOOP; diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp index 68bbb570..296c95e 100644 --- a/services/input/EventHub.cpp +++ b/services/input/EventHub.cpp @@ -156,8 +156,6 @@ EventHub::EventHub(void) : mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) { acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID); - mNumCpus = sysconf(_SC_NPROCESSORS_ONLN); - mEpollFd = epoll_create(EPOLL_SIZE_HINT); LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno); @@ -648,8 +646,9 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz sizeof(struct input_event) * capacity); if (readSize == 0 || (readSize < 0 && errno == ENODEV)) { // Device was removed before INotify noticed. - ALOGW("could not get event, removed? (fd: %d size: %d bufferSize: %d capacity: %d errno: %d)\n", - device->fd, readSize, bufferSize, capacity, errno); + ALOGW("could not get event, removed? (fd: %d size: %d bufferSize: %d " + "capacity: %d errno: %d)\n", + device->fd, readSize, bufferSize, capacity, errno); deviceChanged = true; closeDeviceLocked(device); } else if (readSize < 0) { @@ -774,19 +773,6 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz } else { // Some events occurred. mPendingEventCount = size_t(pollResult); - - // On an SMP system, it is possible for the framework to read input events - // faster than the kernel input device driver can produce a complete packet. - // Because poll() wakes up as soon as the first input event becomes available, - // the framework will often end up reading one event at a time until the - // packet is complete. Instead of one call to read() returning 71 events, - // it could take 71 calls to read() each returning 1 event. - // - // Sleep for a short period of time after waking up from the poll() to give - // the kernel time to finish writing the entire packet of input events. - if (mNumCpus > 1) { - usleep(250); - } } } @@ -845,7 +831,7 @@ status_t EventHub::openDeviceLocked(const char *devicePath) { ALOGV("Opening device: %s", devicePath); - int fd = open(devicePath, O_RDWR); + int fd = open(devicePath, O_RDWR | O_CLOEXEC); if(fd < 0) { ALOGE("could not open %s, %s\n", devicePath, strerror(errno)); return -1; @@ -1077,14 +1063,20 @@ status_t EventHub::openDeviceLocked(const char *devicePath) { return -1; } + // Enable wake-lock behavior on kernels that support it. + // TODO: Only need this for devices that can really wake the system. + bool usingSuspendBlock = ioctl(fd, EVIOCSSUSPENDBLOCK, 1) == 0; + ALOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=0x%x, " - "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s", + "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s, " + "usingSuspendBlock=%s", deviceId, fd, devicePath, device->identifier.name.string(), device->classes, device->configurationFile.string(), device->keyMap.keyLayoutFile.string(), device->keyMap.keyCharacterMapFile.string(), - toString(mBuiltInKeyboardId == deviceId)); + toString(mBuiltInKeyboardId == deviceId), + toString(usingSuspendBlock)); mDevices.add(deviceId, device); diff --git a/services/input/EventHub.h b/services/input/EventHub.h index 9d8252e..8a2afd3 100644 --- a/services/input/EventHub.h +++ b/services/input/EventHub.h @@ -367,9 +367,6 @@ private: size_t mPendingEventCount; size_t mPendingEventIndex; bool mPendingINotify; - - // Set to the number of CPUs. - int32_t mNumCpus; }; }; // namespace android diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp index 0bef0db..ad64ccd 100644 --- a/services/input/InputDispatcher.cpp +++ b/services/input/InputDispatcher.cpp @@ -177,14 +177,6 @@ static bool validateMotionEvent(int32_t action, size_t pointerCount, return true; } -static void scalePointerCoords(const PointerCoords* inCoords, size_t count, float scaleFactor, - PointerCoords* outCoords) { - for (size_t i = 0; i < count; i++) { - outCoords[i] = inCoords[i]; - outCoords[i].scale(scaleFactor); - } -} - static void dumpRegion(String8& dump, const SkRegion& region) { if (region.isEmpty()) { dump.append("<empty>"); @@ -246,6 +238,8 @@ void InputDispatcher::dispatchOnce() { nsecs_t nextWakeupTime = LONG_LONG_MAX; { // acquire lock AutoMutex _l(mLock); + mDispatcherIsAliveCondition.broadcast(); + dispatchOnceInnerLocked(&nextWakeupTime); if (runCommandsLockedInterruptible()) { @@ -4094,6 +4088,8 @@ void InputDispatcher::dump(String8& dump) { void InputDispatcher::monitor() { // Acquire and release the lock to ensure that the dispatcher has not deadlocked. mLock.lock(); + mLooper->wake(); + mDispatcherIsAliveCondition.wait(mLock); mLock.unlock(); } diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h index 1478d67..a1d42e1 100644 --- a/services/input/InputDispatcher.h +++ b/services/input/InputDispatcher.h @@ -862,6 +862,8 @@ private: Mutex mLock; + Condition mDispatcherIsAliveCondition; + sp<Looper> mLooper; EventEntry* mPendingEvent; diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp index 8324d95..4be06e4 100644 --- a/services/input/InputReader.cpp +++ b/services/input/InputReader.cpp @@ -278,17 +278,20 @@ void InputReader::loopOnce() { { // acquire lock AutoMutex _l(mLock); + mReaderIsAliveCondition.broadcast(); if (count) { processEventsLocked(mEventBuffer, count); } if (!count || timeoutMillis == 0) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + if (now >= mNextTimeout) { #if DEBUG_RAW_EVENTS - ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f); + ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f); #endif - mNextTimeout = LLONG_MAX; - timeoutExpiredLocked(now); + mNextTimeout = LLONG_MAX; + timeoutExpiredLocked(now); + } } } // release lock @@ -644,9 +647,13 @@ int32_t InputReader::getStateLocked(int32_t deviceId, uint32_t sourceMask, int32 for (size_t i = 0; i < numDevices; i++) { InputDevice* device = mDevices.valueAt(i); if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) { - result = (device->*getStateFunc)(sourceMask, code); - if (result >= AKEY_STATE_DOWN) { - return result; + // If any device reports AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL, return that + // value. Otherwise, return AKEY_STATE_UP as long as one device reports it. + int32_t currentResult = (device->*getStateFunc)(sourceMask, code); + if (currentResult >= AKEY_STATE_DOWN) { + return currentResult; + } else if (currentResult == AKEY_STATE_UP) { + result = currentResult; } } } @@ -768,6 +775,8 @@ void InputReader::dump(String8& dump) { void InputReader::monitor() { // Acquire and release the lock to ensure that the reader has not deadlocked. mLock.lock(); + mEventHub->wake(); + mReaderIsAliveCondition.wait(mLock); mLock.unlock(); // Check the EventHub @@ -1000,9 +1009,13 @@ int32_t InputDevice::getState(uint32_t sourceMask, int32_t code, GetStateFunc ge for (size_t i = 0; i < numMappers; i++) { InputMapper* mapper = mMappers[i]; if (sourcesMatchMask(mapper->getSources(), sourceMask)) { - result = (mapper->*getStateFunc)(sourceMask, code); - if (result >= AKEY_STATE_DOWN) { - return result; + // If any mapper reports AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL, return that + // value. Otherwise, return AKEY_STATE_UP as long as one mapper reports it. + int32_t currentResult = (mapper->*getStateFunc)(sourceMask, code); + if (currentResult >= AKEY_STATE_DOWN) { + return currentResult; + } else if (currentResult == AKEY_STATE_UP) { + result = currentResult; } } } diff --git a/services/input/InputReader.h b/services/input/InputReader.h index a122c97..ad89a22 100644 --- a/services/input/InputReader.h +++ b/services/input/InputReader.h @@ -363,6 +363,8 @@ protected: private: Mutex mLock; + Condition mReaderIsAliveCondition; + sp<EventHubInterface> mEventHub; sp<InputReaderPolicyInterface> mPolicy; sp<QueuedInputListener> mQueuedListener; diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index 4f81178..081f1f4 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -24,67 +24,35 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.Intent.FilterComparison; import android.content.IntentFilter; import android.content.ServiceConnection; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.net.Uri; import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.util.Log; import android.util.Pair; import android.util.Slog; -import android.util.TypedValue; -import android.util.Xml; +import android.util.SparseArray; import android.widget.RemoteViews; import com.android.internal.appwidget.IAppWidgetHost; import com.android.internal.appwidget.IAppWidgetService; -import com.android.internal.os.AtomicFile; -import com.android.internal.util.FastXmlSerializer; import com.android.internal.widget.IRemoteViewsAdapterConnection; -import com.android.internal.widget.IRemoteViewsFactory; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - -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; import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Locale; -import java.util.Set; + +/** + * Redirects calls to this service to the instance of the service for the appropriate user. + */ class AppWidgetService extends IAppWidgetService.Stub { private static final String TAG = "AppWidgetService"; - private static final String SETTINGS_FILENAME = "appwidgets.xml"; - private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes - /* * When identifying a Host or Provider based on the calling process, use the uid field. * When identifying a Host or Provider based on a package manager broadcast, use the @@ -125,11 +93,9 @@ class AppWidgetService extends IAppWidgetService.Stub * globally and may lead us to leak AppWidgetService instances (if there were more than one). */ static class ServiceConnectionProxy implements ServiceConnection { - private final Pair<Integer, Intent.FilterComparison> mKey; private final IBinder mConnectionCb; ServiceConnectionProxy(Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) { - mKey = key; mConnectionCb = connectionCb; } public void onServiceConnected(ComponentName name, IBinder service) { @@ -155,13 +121,6 @@ class AppWidgetService extends IAppWidgetService.Stub } } - // Manages active connections to RemoteViewsServices - private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection> - mBoundRemoteViewsServices = new HashMap<Pair<Integer,FilterComparison>,ServiceConnection>(); - // Manages persistent references to RemoteViewsServices from different App Widgets - private final HashMap<FilterComparison, HashSet<Integer>> - mRemoteViewsServicesAppWidgets = new HashMap<FilterComparison, HashSet<Integer>>(); - Context mContext; Locale mLocale; PackageManager mPackageManager; @@ -171,35 +130,32 @@ class AppWidgetService extends IAppWidgetService.Stub final ArrayList<AppWidgetId> mAppWidgetIds = new ArrayList<AppWidgetId>(); ArrayList<Host> mHosts = new ArrayList<Host>(); boolean mSafeMode; - boolean mStateLoaded; - // These are for debugging only -- widgets are going missing in some rare instances - ArrayList<Provider> mDeletedProviders = new ArrayList<Provider>(); - ArrayList<Host> mDeletedHosts = new ArrayList<Host>(); + + private final SparseArray<AppWidgetServiceImpl> mAppWidgetServices; AppWidgetService(Context context) { mContext = context; - mPackageManager = context.getPackageManager(); - mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + mAppWidgetServices = new SparseArray<AppWidgetServiceImpl>(5); + AppWidgetServiceImpl primary = new AppWidgetServiceImpl(context, 0); + mAppWidgetServices.append(0, primary); } public void systemReady(boolean safeMode) { mSafeMode = safeMode; - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - } + mAppWidgetServices.get(0).systemReady(safeMode); // Register for the boot completed broadcast, so we can send the - // ENABLE broacasts. If we try to send them now, they time out, + // ENABLE broacasts. If we try to send them now, they time out, // because the system isn't ready to handle them yet. mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null); // Register for configuration changes so we can update the names // of the widgets when the locale changes. - mContext.registerReceiver(mBroadcastReceiver, - new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED), null, null); + mContext.registerReceiver(mBroadcastReceiver, new IntentFilter( + Intent.ACTION_CONFIGURATION_CHANGED), null, null); // Register for broadcasts about package install, etc., so we can // update the provider list. @@ -216,216 +172,24 @@ class AppWidgetService extends IAppWidgetService.Stub mContext.registerReceiver(mBroadcastReceiver, sdFilter); } - private void ensureStateLoadedLocked() { - if (!mStateLoaded) { - loadAppWidgetList(); - loadStateLocked(); - mStateLoaded = true; - } - } - - private void dumpProvider(Provider p, int index, PrintWriter pw) { - AppWidgetProviderInfo info = p.info; - pw.print(" ["); pw.print(index); pw.print("] provider "); - pw.print(info.provider.flattenToShortString()); - pw.println(':'); - pw.print(" min=("); pw.print(info.minWidth); - pw.print("x"); pw.print(info.minHeight); - pw.print(") minResize=("); pw.print(info.minResizeWidth); - pw.print("x"); pw.print(info.minResizeHeight); - pw.print(") updatePeriodMillis="); - pw.print(info.updatePeriodMillis); - pw.print(" resizeMode="); - pw.print(info.resizeMode); - pw.print(" autoAdvanceViewId="); - pw.print(info.autoAdvanceViewId); - pw.print(" initialLayout=#"); - pw.print(Integer.toHexString(info.initialLayout)); - pw.print(" zombie="); pw.println(p.zombie); - } - - private void dumpHost(Host host, int index, PrintWriter pw) { - pw.print(" ["); pw.print(index); pw.print("] hostId="); - pw.print(host.hostId); pw.print(' '); - pw.print(host.packageName); pw.print('/'); - pw.print(host.uid); pw.println(':'); - pw.print(" callbacks="); pw.println(host.callbacks); - pw.print(" instances.size="); pw.print(host.instances.size()); - pw.print(" zombie="); pw.println(host.zombie); - } - - private void dumpAppWidgetId(AppWidgetId id, int index, PrintWriter pw) { - pw.print(" ["); pw.print(index); pw.print("] id="); - pw.println(id.appWidgetId); - pw.print(" hostId="); - pw.print(id.host.hostId); pw.print(' '); - pw.print(id.host.packageName); pw.print('/'); - pw.println(id.host.uid); - if (id.provider != null) { - pw.print(" provider="); - pw.println(id.provider.info.provider.flattenToShortString()); - } - if (id.host != null) { - pw.print(" host.callbacks="); pw.println(id.host.callbacks); - } - if (id.views != null) { - pw.print(" views="); pw.println(id.views); - } - } - @Override - public 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 from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid()); - return; - } - - synchronized (mAppWidgetIds) { - int N = mInstalledProviders.size(); - pw.println("Providers:"); - for (int i=0; i<N; i++) { - dumpProvider(mInstalledProviders.get(i), i, pw); - } - - N = mAppWidgetIds.size(); - pw.println(" "); - pw.println("AppWidgetIds:"); - for (int i=0; i<N; i++) { - dumpAppWidgetId(mAppWidgetIds.get(i), i, pw); - } - - N = mHosts.size(); - pw.println(" "); - pw.println("Hosts:"); - for (int i=0; i<N; i++) { - dumpHost(mHosts.get(i), i, pw); - } - - N = mDeletedProviders.size(); - pw.println(" "); - pw.println("Deleted Providers:"); - for (int i=0; i<N; i++) { - dumpProvider(mDeletedProviders.get(i), i, pw); - } - - N = mDeletedHosts.size(); - pw.println(" "); - pw.println("Deleted Hosts:"); - for (int i=0; i<N; i++) { - dumpHost(mDeletedHosts.get(i), i, pw); - } - } - } - - public int allocateAppWidgetId(String packageName, int hostId) { - int callingUid = enforceCallingUid(packageName); - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - int appWidgetId = mNextAppWidgetId++; - - Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); - - AppWidgetId id = new AppWidgetId(); - id.appWidgetId = appWidgetId; - id.host = host; - - host.instances.add(id); - mAppWidgetIds.add(id); - - saveStateLocked(); - - return appWidgetId; - } + public int allocateAppWidgetId(String packageName, int hostId) throws RemoteException { + return getImplForUser().allocateAppWidgetId(packageName, hostId); } - - public void deleteAppWidgetId(int appWidgetId) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id != null) { - deleteAppWidgetLocked(id); - saveStateLocked(); - } - } - } - - public void deleteHost(int hostId) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - int callingUid = getCallingUid(); - Host host = lookupHostLocked(callingUid, hostId); - if (host != null) { - deleteHostLocked(host); - saveStateLocked(); - } - } - } - - public void deleteAllHosts() { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - int callingUid = getCallingUid(); - final int N = mHosts.size(); - boolean changed = false; - for (int i=N-1; i>=0; i--) { - Host host = mHosts.get(i); - if (host.uid == callingUid) { - deleteHostLocked(host); - changed = true; - } - } - if (changed) { - saveStateLocked(); - } - } + + @Override + public void deleteAppWidgetId(int appWidgetId) throws RemoteException { + getImplForUser().deleteAppWidgetId(appWidgetId); } - void deleteHostLocked(Host host) { - final int N = host.instances.size(); - for (int i=N-1; i>=0; i--) { - AppWidgetId id = host.instances.get(i); - deleteAppWidgetLocked(id); - } - host.instances.clear(); - mHosts.remove(host); - mDeletedHosts.add(host); - // it's gone or going away, abruptly drop the callback connection - host.callbacks = null; + @Override + public void deleteHost(int hostId) throws RemoteException { + getImplForUser().deleteHost(hostId); } - void deleteAppWidgetLocked(AppWidgetId id) { - // We first unbind all services that are bound to this id - unbindAppWidgetRemoteViewsServicesLocked(id); - - Host host = id.host; - host.instances.remove(id); - pruneHostLocked(host); - - mAppWidgetIds.remove(id); - - Provider p = id.provider; - if (p != null) { - p.instances.remove(id); - if (!p.zombie) { - // send the broacast saying that this appWidgetId has been deleted - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED); - intent.setComponent(p.info.provider); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId); - mContext.sendBroadcast(intent); - if (p.instances.size() == 0) { - // cancel the future updates - cancelBroadcasts(p); - - // send the broacast saying that the provider is not in use any more - intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED); - intent.setComponent(p.info.provider); - mContext.sendBroadcast(intent); - } - } - } + @Override + public void deleteAllHosts() throws RemoteException { + getImplForUser().deleteAllHosts(); } void cancelBroadcasts(Provider p) { @@ -441,617 +205,58 @@ class AppWidgetService extends IAppWidgetService.Stub } } - public void bindAppWidgetId(int appWidgetId, ComponentName provider) { - mContext.enforceCallingPermission(android.Manifest.permission.BIND_APPWIDGET, - "bindGagetId appWidgetId=" + appWidgetId + " provider=" + provider); - - final long ident = Binder.clearCallingIdentity(); - try { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id == null) { - throw new IllegalArgumentException("bad appWidgetId"); - } - if (id.provider != null) { - throw new IllegalArgumentException("appWidgetId " + appWidgetId + " already bound to " - + id.provider.info.provider); - } - Provider p = lookupProviderLocked(provider); - if (p == null) { - throw new IllegalArgumentException("not a appwidget provider: " + provider); - } - if (p.zombie) { - throw new IllegalArgumentException("can't bind to a 3rd party provider in" - + " safe mode: " + provider); - } - - id.provider = p; - p.instances.add(id); - int instancesSize = p.instances.size(); - if (instancesSize == 1) { - // tell the provider that it's ready - sendEnableIntentLocked(p); - } - - // send an update now -- We need this update now, and just for this appWidgetId. - // It's less critical when the next one happens, so when we schdule the next one, - // we add updatePeriodMillis to its start time. That time will have some slop, - // but that's okay. - sendUpdateIntentLocked(p, new int[] { appWidgetId }); - - // schedule the future updates - registerForBroadcastsLocked(p, getAppWidgetIds(p)); - saveStateLocked(); - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - // Binds to a specific RemoteViewsService - public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id == null) { - throw new IllegalArgumentException("bad appWidgetId"); - } - final ComponentName componentName = intent.getComponent(); - try { - final ServiceInfo si = mContext.getPackageManager().getServiceInfo(componentName, - PackageManager.GET_PERMISSIONS); - if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(si.permission)) { - throw new SecurityException("Selected service does not require " - + android.Manifest.permission.BIND_REMOTEVIEWS - + ": " + componentName); - } - } catch (PackageManager.NameNotFoundException e) { - throw new IllegalArgumentException("Unknown component " + componentName); - } - - // If there is already a connection made for this service intent, then disconnect from - // that first. (This does not allow multiple connections to the same service under - // the same key) - ServiceConnectionProxy conn = null; - FilterComparison fc = new FilterComparison(intent); - Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, fc); - if (mBoundRemoteViewsServices.containsKey(key)) { - conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key); - conn.disconnect(); - mContext.unbindService(conn); - mBoundRemoteViewsServices.remove(key); - } - - // Bind to the RemoteViewsService (which will trigger a callback to the - // RemoteViewsAdapter.onServiceConnected()) - final long token = Binder.clearCallingIdentity(); - try { - conn = new ServiceConnectionProxy(key, connection); - mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); - mBoundRemoteViewsServices.put(key, conn); - } finally { - Binder.restoreCallingIdentity(token); - } - - // Add it to the mapping of RemoteViewsService to appWidgetIds so that we can determine - // when we can call back to the RemoteViewsService later to destroy associated - // factories. - incrementAppWidgetServiceRefCount(appWidgetId, fc); - } - } - - // Unbinds from a specific RemoteViewsService - public void unbindRemoteViewsService(int appWidgetId, Intent intent) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - // Unbind from the RemoteViewsService (which will trigger a callback to the bound - // RemoteViewsAdapter) - Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, - new FilterComparison(intent)); - if (mBoundRemoteViewsServices.containsKey(key)) { - // We don't need to use the appWidgetId until after we are sure there is something - // to unbind. Note that this may mask certain issues with apps calling unbind() - // more than necessary. - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id == null) { - throw new IllegalArgumentException("bad appWidgetId"); - } - - ServiceConnectionProxy conn = - (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key); - conn.disconnect(); - mContext.unbindService(conn); - mBoundRemoteViewsServices.remove(key); - } else { - Log.e("AppWidgetService", "Error (unbindRemoteViewsService): Connection not bound"); - } - } - } - - // Unbinds from a RemoteViewsService when we delete an app widget - private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) { - int appWidgetId = id.appWidgetId; - // Unbind all connections to Services bound to this AppWidgetId - Iterator<Pair<Integer, Intent.FilterComparison>> it = - mBoundRemoteViewsServices.keySet().iterator(); - while (it.hasNext()) { - final Pair<Integer, Intent.FilterComparison> key = it.next(); - if (key.first.intValue() == appWidgetId) { - final ServiceConnectionProxy conn = (ServiceConnectionProxy) - mBoundRemoteViewsServices.get(key); - conn.disconnect(); - mContext.unbindService(conn); - it.remove(); - } - } - - // Check if we need to destroy any services (if no other app widgets are - // referencing the same service) - decrementAppWidgetServiceRefCount(appWidgetId); - } - - // Destroys the cached factory on the RemoteViewsService's side related to the specified intent - private void destroyRemoteViewsService(final Intent intent) { - final ServiceConnection conn = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - final IRemoteViewsFactory cb = - IRemoteViewsFactory.Stub.asInterface(service); - try { - cb.onDestroy(intent); - } catch (RemoteException e) { - e.printStackTrace(); - } catch (RuntimeException e) { - e.printStackTrace(); - } - mContext.unbindService(this); - } - @Override - public void onServiceDisconnected(android.content.ComponentName name) { - // Do nothing - } - }; - - // Bind to the service and remove the static intent->factory mapping in the - // RemoteViewsService. - final long token = Binder.clearCallingIdentity(); - try { - mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); - } finally { - Binder.restoreCallingIdentity(token); - } - } - - // Adds to the ref-count for a given RemoteViewsService intent - private void incrementAppWidgetServiceRefCount(int appWidgetId, FilterComparison fc) { - HashSet<Integer> appWidgetIds = null; - if (mRemoteViewsServicesAppWidgets.containsKey(fc)) { - appWidgetIds = mRemoteViewsServicesAppWidgets.get(fc); - } else { - appWidgetIds = new HashSet<Integer>(); - mRemoteViewsServicesAppWidgets.put(fc, appWidgetIds); - } - appWidgetIds.add(appWidgetId); - } - - // Subtracts from the ref-count for a given RemoteViewsService intent, prompting a delete if - // the ref-count reaches zero. - private void decrementAppWidgetServiceRefCount(int appWidgetId) { - Iterator<FilterComparison> it = - mRemoteViewsServicesAppWidgets.keySet().iterator(); - while (it.hasNext()) { - final FilterComparison key = it.next(); - final HashSet<Integer> ids = mRemoteViewsServicesAppWidgets.get(key); - if (ids.remove(appWidgetId)) { - // If we have removed the last app widget referencing this service, then we - // should destroy it and remove it from this set - if (ids.isEmpty()) { - destroyRemoteViewsService(key.getIntent()); - it.remove(); - } - } - } - } - - public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id != null && id.provider != null && !id.provider.zombie) { - return id.provider.info; - } - return null; - } - } - - public RemoteViews getAppWidgetViews(int appWidgetId) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); - if (id != null) { - return id.views; - } - return null; - } - } - - public List<AppWidgetProviderInfo> getInstalledProviders() { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - final int N = mInstalledProviders.size(); - ArrayList<AppWidgetProviderInfo> result = new ArrayList<AppWidgetProviderInfo>(N); - for (int i=0; i<N; i++) { - Provider p = mInstalledProviders.get(i); - if (!p.zombie) { - result.add(p.info); - } - } - return result; - } - } - - public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { - if (appWidgetIds == null) { - return; - } - if (appWidgetIds.length == 0) { - return; - } - final int N = appWidgetIds.length; - - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - for (int i=0; i<N; i++) { - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); - updateAppWidgetInstanceLocked(id, views); - } - } - } - - public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { - if (appWidgetIds == null) { - return; - } - if (appWidgetIds.length == 0) { - return; - } - final int N = appWidgetIds.length; - - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - for (int i=0; i<N; i++) { - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); - updateAppWidgetInstanceLocked(id, views, true); - } - } - } - - public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) { - if (appWidgetIds == null) { - return; - } - if (appWidgetIds.length == 0) { - return; - } - final int N = appWidgetIds.length; - - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - for (int i=0; i<N; i++) { - AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); - notifyAppWidgetViewDataChangedInstanceLocked(id, viewId); - } - } - } - - public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - Provider p = lookupProviderLocked(provider); - if (p == null) { - Slog.w(TAG, "updateAppWidgetProvider: provider doesn't exist: " + provider); - return; - } - ArrayList<AppWidgetId> instances = p.instances; - final int callingUid = getCallingUid(); - final int N = instances.size(); - for (int i=0; i<N; i++) { - AppWidgetId id = instances.get(i); - if (canAccessAppWidgetId(id, callingUid)) { - updateAppWidgetInstanceLocked(id, views); - } - } - } - } - - void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) { - updateAppWidgetInstanceLocked(id, views, false); - } - - void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) { - // allow for stale appWidgetIds and other badness - // lookup also checks that the calling process can access the appWidgetId - // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) - if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { - - // We do not want to save this RemoteViews - if (!isPartialUpdate) id.views = views; - - // is anyone listening? - if (id.host.callbacks != null) { - try { - // the lock is held, but this is a oneway call - id.host.callbacks.updateAppWidget(id.appWidgetId, views); - } catch (RemoteException e) { - // It failed; remove the callback. No need to prune because - // we know that this host is still referenced by this instance. - id.host.callbacks = null; - } - } - } - } - - void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, int viewId) { - // allow for stale appWidgetIds and other badness - // lookup also checks that the calling process can access the appWidgetId - // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) - if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { - // is anyone listening? - if (id.host.callbacks != null) { - try { - // the lock is held, but this is a oneway call - id.host.callbacks.viewDataChanged(id.appWidgetId, viewId); - } catch (RemoteException e) { - // It failed; remove the callback. No need to prune because - // we know that this host is still referenced by this instance. - id.host.callbacks = null; - } - } - - // If the host is unavailable, then we call the associated - // RemoteViewsFactory.onDataSetChanged() directly - if (id.host.callbacks == null) { - Set<FilterComparison> keys = mRemoteViewsServicesAppWidgets.keySet(); - for (FilterComparison key : keys) { - if (mRemoteViewsServicesAppWidgets.get(key).contains(id.appWidgetId)) { - Intent intent = key.getIntent(); - - final ServiceConnection conn = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - IRemoteViewsFactory cb = - IRemoteViewsFactory.Stub.asInterface(service); - try { - cb.onDataSetChangedAsync(); - } catch (RemoteException e) { - e.printStackTrace(); - } catch (RuntimeException e) { - e.printStackTrace(); - } - mContext.unbindService(this); - } - @Override - public void onServiceDisconnected(android.content.ComponentName name) { - // Do nothing - } - }; - - // Bind to the service and call onDataSetChanged() - final long token = Binder.clearCallingIdentity(); - try { - mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } - } - } - } - - public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId, - List<RemoteViews> updatedViews) { - int callingUid = enforceCallingUid(packageName); - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); - host.callbacks = callbacks; - - updatedViews.clear(); - - ArrayList<AppWidgetId> instances = host.instances; - int N = instances.size(); - int[] updatedIds = new int[N]; - for (int i=0; i<N; i++) { - AppWidgetId id = instances.get(i); - updatedIds[i] = id.appWidgetId; - updatedViews.add(id.views); - } - return updatedIds; - } - } - - public void stopListening(int hostId) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - Host host = lookupHostLocked(getCallingUid(), hostId); - if (host != null) { - host.callbacks = null; - pruneHostLocked(host); - } - } - } - - boolean canAccessAppWidgetId(AppWidgetId id, int callingUid) { - if (id.host.uid == callingUid) { - // Apps hosting the AppWidget have access to it. - return true; - } - if (id.provider != null && id.provider.uid == callingUid) { - // Apps providing the AppWidget have access to it (if the appWidgetId has been bound) - return true; - } - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET) - == PackageManager.PERMISSION_GRANTED) { - // Apps that can bind have access to all appWidgetIds. - return true; - } - // Nobody else can access it. - return false; - } - - AppWidgetId lookupAppWidgetIdLocked(int appWidgetId) { - int callingUid = getCallingUid(); - final int N = mAppWidgetIds.size(); - for (int i=0; i<N; i++) { - AppWidgetId id = mAppWidgetIds.get(i); - if (id.appWidgetId == appWidgetId && canAccessAppWidgetId(id, callingUid)) { - return id; - } - } - return null; - } - - Provider lookupProviderLocked(ComponentName provider) { - final int N = mInstalledProviders.size(); - for (int i=0; i<N; i++) { - Provider p = mInstalledProviders.get(i); - if (p.info.provider.equals(provider)) { - return p; - } - } - return null; + @Override + public void bindAppWidgetId(int appWidgetId, ComponentName provider) throws RemoteException { + getImplForUser().bindAppWidgetId(appWidgetId, provider); } - Host lookupHostLocked(int uid, int hostId) { - final int N = mHosts.size(); - for (int i=0; i<N; i++) { - Host h = mHosts.get(i); - if (h.uid == uid && h.hostId == hostId) { - return h; - } - } - return null; + @Override + public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) + throws RemoteException { + getImplForUser().bindRemoteViewsService(appWidgetId, intent, connection); } - Host lookupOrAddHostLocked(int uid, String packageName, int hostId) { - final int N = mHosts.size(); - for (int i=0; i<N; i++) { - Host h = mHosts.get(i); - if (h.hostId == hostId && h.packageName.equals(packageName)) { - return h; - } - } - Host host = new Host(); - host.packageName = packageName; - host.uid = uid; - host.hostId = hostId; - mHosts.add(host); - return host; + @Override + public int[] startListening(IAppWidgetHost host, String packageName, int hostId, + List<RemoteViews> updatedViews) throws RemoteException { + return getImplForUser().startListening(host, packageName, hostId, updatedViews); } - void pruneHostLocked(Host host) { - if (host.instances.size() == 0 && host.callbacks == null) { - mHosts.remove(host); - } + // TODO: Call this from PackageManagerService when a user is removed + public void removeUser(int userId) { } - void loadAppWidgetList() { - PackageManager pm = mPackageManager; - - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - List<ResolveInfo> broadcastReceivers = pm.queryBroadcastReceivers(intent, - PackageManager.GET_META_DATA); - - final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); - for (int i=0; i<N; i++) { - ResolveInfo ri = broadcastReceivers.get(i); - addProviderLocked(ri); + private AppWidgetServiceImpl getImplForUser() { + final int userId = Binder.getOrigCallingUser(); + AppWidgetServiceImpl service = mAppWidgetServices.get(userId); + if (service == null) { + Slog.e(TAG, "Unable to find AppWidgetServiceImpl for the current user"); + // TODO: Verify that it's a valid user + service = new AppWidgetServiceImpl(mContext, userId); + service.systemReady(mSafeMode); + // Assume that BOOT_COMPLETED was received, as this is a non-primary user. + service.sendInitialBroadcasts(); + mAppWidgetServices.append(userId, service); } - } - boolean addProviderLocked(ResolveInfo ri) { - if ((ri.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { - return false; - } - if (!ri.activityInfo.isEnabled()) { - return false; - } - Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName, - ri.activityInfo.name), ri); - if (p != null) { - mInstalledProviders.add(p); - return true; - } else { - return false; - } + return service; } - void removeProviderLocked(int index, Provider p) { - int N = p.instances.size(); - for (int i=0; i<N; i++) { - AppWidgetId id = p.instances.get(i); - // Call back with empty RemoteViews - updateAppWidgetInstanceLocked(id, null); - // Stop telling the host about updates for this from now on - cancelBroadcasts(p); - // clear out references to this appWidgetId - id.host.instances.remove(id); - mAppWidgetIds.remove(id); - id.provider = null; - pruneHostLocked(id.host); - id.host = null; - } - p.instances.clear(); - mInstalledProviders.remove(index); - mDeletedProviders.add(p); - // no need to send the DISABLE broadcast, since the receiver is gone anyway - cancelBroadcasts(p); + @Override + public int[] getAppWidgetIds(ComponentName provider) throws RemoteException { + return getImplForUser().getAppWidgetIds(provider); } - void sendEnableIntentLocked(Provider p) { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED); - intent.setComponent(p.info.provider); - mContext.sendBroadcast(intent); + @Override + public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) throws RemoteException { + return getImplForUser().getAppWidgetInfo(appWidgetId); } - void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) { - if (appWidgetIds != null && appWidgetIds.length > 0) { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); - intent.setComponent(p.info.provider); - mContext.sendBroadcast(intent); - } + @Override + public RemoteViews getAppWidgetViews(int appWidgetId) throws RemoteException { + return getImplForUser().getAppWidgetViews(appWidgetId); } - void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) { - if (p.info.updatePeriodMillis > 0) { - // if this is the first instance, set the alarm. otherwise, - // rely on the fact that we've already set it and that - // PendingIntent.getBroadcast will update the extras. - boolean alreadyRegistered = p.broadcast != null; - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); - intent.setComponent(p.info.provider); - long token = Binder.clearCallingIdentity(); - try { - p.broadcast = PendingIntent.getBroadcast(mContext, 1, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - } finally { - Binder.restoreCallingIdentity(token); - } - if (!alreadyRegistered) { - long period = p.info.updatePeriodMillis; - if (period < MIN_UPDATE_PERIOD) { - period = MIN_UPDATE_PERIOD; - } - mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + period, period, p.broadcast); - } - } - } - static int[] getAppWidgetIds(Provider p) { int instancesSize = p.instances.size(); int appWidgetIds[] = new int[instancesSize]; @@ -1060,570 +265,70 @@ class AppWidgetService extends IAppWidgetService.Stub } return appWidgetIds; } - - public int[] getAppWidgetIds(ComponentName provider) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - Provider p = lookupProviderLocked(provider); - if (p != null && getCallingUid() == p.uid) { - return getAppWidgetIds(p); - } else { - return new int[0]; - } - } - } - - private Provider parseProviderInfoXml(ComponentName component, ResolveInfo ri) { - Provider p = null; - - ActivityInfo activityInfo = ri.activityInfo; - XmlResourceParser parser = null; - try { - parser = activityInfo.loadXmlMetaData(mPackageManager, - AppWidgetManager.META_DATA_APPWIDGET_PROVIDER); - if (parser == null) { - Slog.w(TAG, "No " + AppWidgetManager.META_DATA_APPWIDGET_PROVIDER + " meta-data for " - + "AppWidget provider '" + component + '\''); - return null; - } - - AttributeSet attrs = Xml.asAttributeSet(parser); - - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && type != XmlPullParser.START_TAG) { - // drain whitespace, comments, etc. - } - - String nodeName = parser.getName(); - if (!"appwidget-provider".equals(nodeName)) { - Slog.w(TAG, "Meta-data does not start with appwidget-provider tag for" - + " AppWidget provider '" + component + '\''); - return null; - } - - p = new Provider(); - AppWidgetProviderInfo info = p.info = new AppWidgetProviderInfo(); - info.provider = component; - p.uid = activityInfo.applicationInfo.uid; - - Resources res = mPackageManager.getResourcesForApplication( - activityInfo.applicationInfo); - - TypedArray sa = res.obtainAttributes(attrs, - com.android.internal.R.styleable.AppWidgetProviderInfo); - // These dimensions has to be resolved in the application's context. - // We simply send back the raw complex data, which will be - // converted to dp in {@link AppWidgetManager#getAppWidgetInfo}. - TypedValue value = sa.peekValue( - com.android.internal.R.styleable.AppWidgetProviderInfo_minWidth); - info.minWidth = value != null ? value.data : 0; - value = sa.peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minHeight); - info.minHeight = value != null ? value.data : 0; - value = sa.peekValue( - com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeWidth); - info.minResizeWidth = value != null ? value.data : info.minWidth; - value = sa.peekValue( - com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeHeight); - info.minResizeHeight = value != null ? value.data : info.minHeight; - - info.updatePeriodMillis = sa.getInt( - com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0); - info.initialLayout = sa.getResourceId( - com.android.internal.R.styleable.AppWidgetProviderInfo_initialLayout, 0); - String className = sa.getString( - com.android.internal.R.styleable.AppWidgetProviderInfo_configure); - if (className != null) { - info.configure = new ComponentName(component.getPackageName(), className); - } - info.label = activityInfo.loadLabel(mPackageManager).toString(); - info.icon = ri.getIconResource(); - info.previewImage = sa.getResourceId( - com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0); - info.autoAdvanceViewId = sa.getResourceId( - com.android.internal.R.styleable.AppWidgetProviderInfo_autoAdvanceViewId, -1); - info.resizeMode = sa.getInt( - com.android.internal.R.styleable.AppWidgetProviderInfo_resizeMode, - AppWidgetProviderInfo.RESIZE_NONE); - - sa.recycle(); - } catch (Exception e) { - // Ok to catch Exception here, because anything going wrong because - // of what a client process passes to us should not be fatal for the - // system process. - Slog.w(TAG, "XML parsing failed for AppWidget provider '" + component + '\'', e); - return null; - } finally { - if (parser != null) parser.close(); - } - return p; + @Override + public List<AppWidgetProviderInfo> getInstalledProviders() throws RemoteException { + return getImplForUser().getInstalledProviders(); } - int getUidForPackage(String packageName) throws PackageManager.NameNotFoundException { - PackageInfo pkgInfo = mPackageManager.getPackageInfo(packageName, 0); - if (pkgInfo == null || pkgInfo.applicationInfo == null) { - throw new PackageManager.NameNotFoundException(); - } - return pkgInfo.applicationInfo.uid; + @Override + public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) + throws RemoteException { + getImplForUser().notifyAppWidgetViewDataChanged(appWidgetIds, viewId); } - int enforceCallingUid(String packageName) throws IllegalArgumentException { - int callingUid = getCallingUid(); - int packageUid; - try { - packageUid = getUidForPackage(packageName); - } catch (PackageManager.NameNotFoundException ex) { - throw new IllegalArgumentException("packageName and uid don't match packageName=" - + packageName); - } - if (callingUid != packageUid) { - throw new IllegalArgumentException("packageName and uid don't match packageName=" - + packageName); - } - return callingUid; + @Override + public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) + throws RemoteException { + getImplForUser().partiallyUpdateAppWidgetIds(appWidgetIds, views); } - void sendInitialBroadcasts() { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - final int N = mInstalledProviders.size(); - for (int i=0; i<N; i++) { - Provider p = mInstalledProviders.get(i); - if (p.instances.size() > 0) { - sendEnableIntentLocked(p); - int[] appWidgetIds = getAppWidgetIds(p); - sendUpdateIntentLocked(p, appWidgetIds); - registerForBroadcastsLocked(p, appWidgetIds); - } - } - } + @Override + public void stopListening(int hostId) throws RemoteException { + getImplForUser().stopListening(hostId); } - // only call from initialization -- it assumes that the data structures are all empty - void loadStateLocked() { - AtomicFile file = savedStateFile(); - try { - FileInputStream stream = file.openRead(); - readStateFromFileLocked(stream); - - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - Slog.w(TAG, "Failed to close state FileInputStream " + e); - } - } - } catch (FileNotFoundException e) { - Slog.w(TAG, "Failed to read state: " + e); - } + @Override + public void unbindRemoteViewsService(int appWidgetId, Intent intent) throws RemoteException { + getImplForUser().unbindRemoteViewsService(appWidgetId, intent); } - void saveStateLocked() { - AtomicFile file = savedStateFile(); - FileOutputStream stream; - try { - stream = file.startWrite(); - if (writeStateToFileLocked(stream)) { - file.finishWrite(stream); - } else { - file.failWrite(stream); - Slog.w(TAG, "Failed to save state, restoring backup."); - } - } catch (IOException e) { - Slog.w(TAG, "Failed open state file for write: " + e); - } + @Override + public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) throws RemoteException { + getImplForUser().updateAppWidgetIds(appWidgetIds, views); } - boolean writeStateToFileLocked(FileOutputStream stream) { - int N; - - try { - XmlSerializer out = new FastXmlSerializer(); - out.setOutput(stream, "utf-8"); - out.startDocument(null, true); - out.startTag(null, "gs"); - - int providerIndex = 0; - N = mInstalledProviders.size(); - for (int i=0; i<N; i++) { - Provider p = mInstalledProviders.get(i); - if (p.instances.size() > 0) { - out.startTag(null, "p"); - out.attribute(null, "pkg", p.info.provider.getPackageName()); - out.attribute(null, "cl", p.info.provider.getClassName()); - out.endTag(null, "p"); - p.tag = providerIndex; - providerIndex++; - } - } - - N = mHosts.size(); - for (int i=0; i<N; i++) { - Host host = mHosts.get(i); - out.startTag(null, "h"); - out.attribute(null, "pkg", host.packageName); - out.attribute(null, "id", Integer.toHexString(host.hostId)); - out.endTag(null, "h"); - host.tag = i; - } - - N = mAppWidgetIds.size(); - for (int i=0; i<N; i++) { - AppWidgetId id = mAppWidgetIds.get(i); - out.startTag(null, "g"); - out.attribute(null, "id", Integer.toHexString(id.appWidgetId)); - out.attribute(null, "h", Integer.toHexString(id.host.tag)); - if (id.provider != null) { - out.attribute(null, "p", Integer.toHexString(id.provider.tag)); - } - out.endTag(null, "g"); - } - - out.endTag(null, "gs"); - - out.endDocument(); - return true; - } catch (IOException e) { - Slog.w(TAG, "Failed to write state: " + e); - return false; - } + @Override + public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) + throws RemoteException { + getImplForUser().updateAppWidgetProvider(provider, views); } - void readStateFromFileLocked(FileInputStream stream) { - boolean success = false; - - try { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(stream, null); - - int type; - int providerIndex = 0; - HashMap<Integer,Provider> loadedProviders = new HashMap<Integer, Provider>(); - do { - type = parser.next(); - if (type == XmlPullParser.START_TAG) { - String tag = parser.getName(); - if ("p".equals(tag)) { - // TODO: do we need to check that this package has the same signature - // as before? - String pkg = parser.getAttributeValue(null, "pkg"); - String cl = parser.getAttributeValue(null, "cl"); - - final PackageManager packageManager = mContext.getPackageManager(); - try { - packageManager.getReceiverInfo(new ComponentName(pkg, cl), 0); - } catch (PackageManager.NameNotFoundException e) { - String[] pkgs = packageManager.currentToCanonicalPackageNames( - new String[] { pkg }); - pkg = pkgs[0]; - } - - Provider p = lookupProviderLocked(new ComponentName(pkg, cl)); - if (p == null && mSafeMode) { - // if we're in safe mode, make a temporary one - p = new Provider(); - p.info = new AppWidgetProviderInfo(); - p.info.provider = new ComponentName(pkg, cl); - p.zombie = true; - mInstalledProviders.add(p); - } - if (p != null) { - // if it wasn't uninstalled or something - loadedProviders.put(providerIndex, p); - } - providerIndex++; - } - else if ("h".equals(tag)) { - Host host = new Host(); - - // TODO: do we need to check that this package has the same signature - // as before? - host.packageName = parser.getAttributeValue(null, "pkg"); - try { - host.uid = getUidForPackage(host.packageName); - } catch (PackageManager.NameNotFoundException ex) { - host.zombie = true; - } - if (!host.zombie || mSafeMode) { - // In safe mode, we don't discard the hosts we don't recognize - // so that they're not pruned from our list. Otherwise, we do. - host.hostId = Integer.parseInt( - parser.getAttributeValue(null, "id"), 16); - mHosts.add(host); - } - } - else if ("g".equals(tag)) { - AppWidgetId id = new AppWidgetId(); - id.appWidgetId = Integer.parseInt(parser.getAttributeValue(null, "id"), 16); - if (id.appWidgetId >= mNextAppWidgetId) { - mNextAppWidgetId = id.appWidgetId + 1; - } - - String providerString = parser.getAttributeValue(null, "p"); - if (providerString != null) { - // there's no provider if it hasn't been bound yet. - // maybe we don't have to save this, but it brings the system - // to the state it was in. - int pIndex = Integer.parseInt(providerString, 16); - id.provider = loadedProviders.get(pIndex); - if (false) { - Slog.d(TAG, "bound appWidgetId=" + id.appWidgetId + " to provider " - + pIndex + " which is " + id.provider); - } - if (id.provider == null) { - // This provider is gone. We just let the host figure out - // that this happened when it fails to load it. - continue; - } - } - - int hIndex = Integer.parseInt(parser.getAttributeValue(null, "h"), 16); - id.host = mHosts.get(hIndex); - if (id.host == null) { - // This host is gone. - continue; - } - - if (id.provider != null) { - id.provider.instances.add(id); - } - id.host.instances.add(id); - mAppWidgetIds.add(id); - } - } - } while (type != XmlPullParser.END_DOCUMENT); - success = true; - } catch (NullPointerException e) { - Slog.w(TAG, "failed parsing " + e); - } catch (NumberFormatException e) { - Slog.w(TAG, "failed parsing " + e); - } catch (XmlPullParserException e) { - Slog.w(TAG, "failed parsing " + e); - } catch (IOException e) { - Slog.w(TAG, "failed parsing " + e); - } catch (IndexOutOfBoundsException e) { - Slog.w(TAG, "failed parsing " + e); - } - - if (success) { - // delete any hosts that didn't manage to get connected (should happen) - // if it matters, they'll be reconnected. - for (int i=mHosts.size()-1; i>=0; i--) { - pruneHostLocked(mHosts.get(i)); - } - } else { - // failed reading, clean up - Slog.w(TAG, "Failed to read state, clearing widgets and hosts."); - - mAppWidgetIds.clear(); - mHosts.clear(); - final int N = mInstalledProviders.size(); - for (int i=0; i<N; i++) { - mInstalledProviders.get(i).instances.clear(); - } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + // Dump the state of all the app widget providers + for (int i = 0; i < mAppWidgetServices.size(); i++) { + AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i); + service.dump(fd, pw, args); } } - AtomicFile savedStateFile() { - return new AtomicFile(new File("/data/system/" + SETTINGS_FILENAME)); - } - BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - //Slog.d(TAG, "received " + action); + // Slog.d(TAG, "received " + action); if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { - sendInitialBroadcasts(); + getImplForUser().sendInitialBroadcasts(); } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { - Locale revised = Locale.getDefault(); - if (revised == null || mLocale == null || - !(revised.equals(mLocale))) { - mLocale = revised; - - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - int N = mInstalledProviders.size(); - for (int i=N-1; i>=0; i--) { - Provider p = mInstalledProviders.get(i); - String pkgName = p.info.provider.getPackageName(); - updateProvidersForPackageLocked(pkgName); - } - saveStateLocked(); - } + for (int i = 0; i < mAppWidgetServices.size(); i++) { + AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i); + service.onConfigurationChanged(); } } else { - boolean added = false; - boolean changed = false; - String pkgList[] = null; - if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - added = true; - } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { - pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); - added = false; - } else { - Uri uri = intent.getData(); - if (uri == null) { - return; - } - String pkgName = uri.getSchemeSpecificPart(); - if (pkgName == null) { - return; - } - pkgList = new String[] { pkgName }; - added = Intent.ACTION_PACKAGE_ADDED.equals(action); - changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); - } - if (pkgList == null || pkgList.length == 0) { - return; - } - if (added || changed) { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - Bundle extras = intent.getExtras(); - if (changed || (extras != null && - extras.getBoolean(Intent.EXTRA_REPLACING, false))) { - for (String pkgName : pkgList) { - // The package was just upgraded - updateProvidersForPackageLocked(pkgName); - } - } else { - // The package was just added - for (String pkgName : pkgList) { - addProvidersForPackageLocked(pkgName); - } - } - saveStateLocked(); - } - } else { - Bundle extras = intent.getExtras(); - if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) { - // The package is being updated. We'll receive a PACKAGE_ADDED shortly. - } else { - synchronized (mAppWidgetIds) { - ensureStateLoadedLocked(); - for (String pkgName : pkgList) { - removeProvidersForPackageLocked(pkgName); - saveStateLocked(); - } - } - } - } + // TODO: Verify that this only needs to be delivered for the related user and not + // all the users + getImplForUser().onBroadcastReceived(intent); } } }; - - void addProvidersForPackageLocked(String pkgName) { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - intent.setPackage(pkgName); - List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, - PackageManager.GET_META_DATA); - - final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); - 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); - } - } - } - - void updateProvidersForPackageLocked(String pkgName) { - HashSet<String> keep = new HashSet<String>(); - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - intent.setPackage(pkgName); - List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, - PackageManager.GET_META_DATA); - - // add the missing ones and collect which ones to keep - int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); - 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); - if (p == null) { - if (addProviderLocked(ri)) { - keep.add(ai.name); - } - } else { - Provider parsed = parseProviderInfoXml(component, ri); - if (parsed != null) { - keep.add(ai.name); - // Use the new AppWidgetProviderInfo. - p.info = parsed.info; - // If it's enabled - final int M = p.instances.size(); - if (M > 0) { - int[] appWidgetIds = getAppWidgetIds(p); - // Reschedule for the new updatePeriodMillis (don't worry about handling - // it specially if updatePeriodMillis didn't change because we just sent - // an update, and the next one will be updatePeriodMillis from now). - cancelBroadcasts(p); - registerForBroadcastsLocked(p, appWidgetIds); - // 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); - } catch (RemoteException ex) { - // It failed; remove the callback. No need to prune because - // we know that this host is still referenced by this - // instance. - id.host.callbacks = null; - } - } - } - // Now that we've told the host, push out an update. - sendUpdateIntentLocked(p, appWidgetIds); - } - } - } - } - } - - // prune the ones we don't want to keep - N = mInstalledProviders.size(); - for (int i=N-1; i>=0; i--) { - Provider p = mInstalledProviders.get(i); - if (pkgName.equals(p.info.provider.getPackageName()) - && !keep.contains(p.info.provider.getClassName())) { - removeProviderLocked(i, p); - } - } - } - - void removeProvidersForPackageLocked(String pkgName) { - int N = mInstalledProviders.size(); - for (int i=N-1; i>=0; i--) { - Provider p = mInstalledProviders.get(i); - if (pkgName.equals(p.info.provider.getPackageName())) { - removeProviderLocked(i, p); - } - } - - // Delete the hosts for this package too - // - // By now, we have removed any AppWidgets that were in any hosts here, - // so we don't need to worry about sending DISABLE broadcasts to them. - N = mHosts.size(); - for (int i=N-1; i>=0; i--) { - Host host = mHosts.get(i); - if (pkgName.equals(host.packageName)) { - deleteHostLocked(host); - } - } - } } - diff --git a/services/java/com/android/server/AppWidgetServiceImpl.java b/services/java/com/android/server/AppWidgetServiceImpl.java new file mode 100644 index 0000000..250386f --- /dev/null +++ b/services/java/com/android/server/AppWidgetServiceImpl.java @@ -0,0 +1,1606 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.Intent.FilterComparison; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserId; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Pair; +import android.util.Slog; +import android.util.TypedValue; +import android.util.Xml; +import android.widget.RemoteViews; + +import com.android.internal.appwidget.IAppWidgetHost; +import com.android.internal.os.AtomicFile; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.widget.IRemoteViewsAdapterConnection; +import com.android.internal.widget.IRemoteViewsFactory; +import com.android.server.am.ActivityManagerService; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +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; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +class AppWidgetServiceImpl { + + private static final String TAG = "AppWidgetServiceImpl"; + private static final String SETTINGS_FILENAME = "appwidgets.xml"; + private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes + + /* + * When identifying a Host or Provider based on the calling process, use the uid field. When + * identifying a Host or Provider based on a package manager broadcast, use the package given. + */ + + static class Provider { + int uid; + AppWidgetProviderInfo info; + ArrayList<AppWidgetId> instances = new ArrayList<AppWidgetId>(); + PendingIntent broadcast; + boolean zombie; // if we're in safe mode, don't prune this just because nobody references it + + int tag; // for use while saving state (the index) + } + + static class Host { + int uid; + int hostId; + String packageName; + ArrayList<AppWidgetId> instances = new ArrayList<AppWidgetId>(); + IAppWidgetHost callbacks; + boolean zombie; // if we're in safe mode, don't prune this just because nobody references it + + int tag; // for use while saving state (the index) + } + + static class AppWidgetId { + int appWidgetId; + Provider provider; + RemoteViews views; + Host host; + } + + /** + * Acts as a proxy between the ServiceConnection and the RemoteViewsAdapterConnection. This + * needs to be a static inner class since a reference to the ServiceConnection is held globally + * and may lead us to leak AppWidgetService instances (if there were more than one). + */ + static class ServiceConnectionProxy implements ServiceConnection { + private final IBinder mConnectionCb; + + ServiceConnectionProxy(Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) { + mConnectionCb = connectionCb; + } + + public void onServiceConnected(ComponentName name, IBinder service) { + final IRemoteViewsAdapterConnection cb = IRemoteViewsAdapterConnection.Stub + .asInterface(mConnectionCb); + try { + cb.onServiceConnected(service); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void onServiceDisconnected(ComponentName name) { + disconnect(); + } + + public void disconnect() { + final IRemoteViewsAdapterConnection cb = IRemoteViewsAdapterConnection.Stub + .asInterface(mConnectionCb); + try { + cb.onServiceDisconnected(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + // Manages active connections to RemoteViewsServices + private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection> mBoundRemoteViewsServices = new HashMap<Pair<Integer, FilterComparison>, ServiceConnection>(); + // Manages persistent references to RemoteViewsServices from different App Widgets + private final HashMap<FilterComparison, HashSet<Integer>> mRemoteViewsServicesAppWidgets = new HashMap<FilterComparison, HashSet<Integer>>(); + + Context mContext; + Locale mLocale; + PackageManager mPackageManager; + AlarmManager mAlarmManager; + ArrayList<Provider> mInstalledProviders = new ArrayList<Provider>(); + int mNextAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + 1; + final ArrayList<AppWidgetId> mAppWidgetIds = new ArrayList<AppWidgetId>(); + ArrayList<Host> mHosts = new ArrayList<Host>(); + boolean mSafeMode; + int mUserId; + boolean mStateLoaded; + + // These are for debugging only -- widgets are going missing in some rare instances + ArrayList<Provider> mDeletedProviders = new ArrayList<Provider>(); + ArrayList<Host> mDeletedHosts = new ArrayList<Host>(); + + AppWidgetServiceImpl(Context context, int userId) { + mContext = context; + mPackageManager = context.getPackageManager(); + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + mUserId = userId; + } + + public void systemReady(boolean safeMode) { + mSafeMode = safeMode; + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + } + } + + void onConfigurationChanged() { + Locale revised = Locale.getDefault(); + if (revised == null || mLocale == null || !(revised.equals(mLocale))) { + mLocale = revised; + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + int N = mInstalledProviders.size(); + for (int i = N - 1; i >= 0; i--) { + Provider p = mInstalledProviders.get(i); + String pkgName = p.info.provider.getPackageName(); + updateProvidersForPackageLocked(pkgName); + } + saveStateLocked(); + } + } + } + + void onBroadcastReceived(Intent intent) { + final String action = intent.getAction(); + boolean added = false; + boolean changed = false; + String pkgList[] = null; + if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + added = true; + } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { + pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + added = false; + } else { + Uri uri = intent.getData(); + if (uri == null) { + return; + } + String pkgName = uri.getSchemeSpecificPart(); + if (pkgName == null) { + return; + } + pkgList = new String[] { pkgName }; + added = Intent.ACTION_PACKAGE_ADDED.equals(action); + changed = Intent.ACTION_PACKAGE_CHANGED.equals(action); + } + if (pkgList == null || pkgList.length == 0) { + return; + } + if (added || changed) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + Bundle extras = intent.getExtras(); + if (changed + || (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false))) { + for (String pkgName : pkgList) { + // The package was just upgraded + updateProvidersForPackageLocked(pkgName); + } + } else { + // The package was just added + for (String pkgName : pkgList) { + addProvidersForPackageLocked(pkgName); + } + } + saveStateLocked(); + } + } else { + Bundle extras = intent.getExtras(); + if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) { + // The package is being updated. We'll receive a PACKAGE_ADDED shortly. + } else { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + for (String pkgName : pkgList) { + removeProvidersForPackageLocked(pkgName); + saveStateLocked(); + } + } + } + } + } + + private void dumpProvider(Provider p, int index, PrintWriter pw) { + AppWidgetProviderInfo info = p.info; + pw.print(" ["); pw.print(index); pw.print("] provider "); + pw.print(info.provider.flattenToShortString()); + pw.println(':'); + pw.print(" min=("); pw.print(info.minWidth); + pw.print("x"); pw.print(info.minHeight); + pw.print(") minResize=("); pw.print(info.minResizeWidth); + pw.print("x"); pw.print(info.minResizeHeight); + pw.print(") updatePeriodMillis="); + pw.print(info.updatePeriodMillis); + pw.print(" resizeMode="); + pw.print(info.resizeMode); + pw.print(" autoAdvanceViewId="); + pw.print(info.autoAdvanceViewId); + pw.print(" initialLayout=#"); + pw.print(Integer.toHexString(info.initialLayout)); + pw.print(" zombie="); pw.println(p.zombie); + } + + private void dumpHost(Host host, int index, PrintWriter pw) { + pw.print(" ["); pw.print(index); pw.print("] hostId="); + pw.print(host.hostId); pw.print(' '); + pw.print(host.packageName); pw.print('/'); + pw.print(host.uid); pw.println(':'); + pw.print(" callbacks="); pw.println(host.callbacks); + pw.print(" instances.size="); pw.print(host.instances.size()); + pw.print(" zombie="); pw.println(host.zombie); + } + + private void dumpAppWidgetId(AppWidgetId id, int index, PrintWriter pw) { + pw.print(" ["); pw.print(index); pw.print("] id="); + pw.println(id.appWidgetId); + pw.print(" hostId="); + pw.print(id.host.hostId); pw.print(' '); + pw.print(id.host.packageName); pw.print('/'); + pw.println(id.host.uid); + if (id.provider != null) { + pw.print(" provider="); + pw.println(id.provider.info.provider.flattenToShortString()); + } + if (id.host != null) { + pw.print(" host.callbacks="); pw.println(id.host.callbacks); + } + if (id.views != null) { + pw.print(" views="); pw.println(id.views); + } + } + + 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 from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + synchronized (mAppWidgetIds) { + int N = mInstalledProviders.size(); + pw.println("Providers:"); + for (int i=0; i<N; i++) { + dumpProvider(mInstalledProviders.get(i), i, pw); + } + + N = mAppWidgetIds.size(); + pw.println(" "); + pw.println("AppWidgetIds:"); + for (int i=0; i<N; i++) { + dumpAppWidgetId(mAppWidgetIds.get(i), i, pw); + } + + N = mHosts.size(); + pw.println(" "); + pw.println("Hosts:"); + for (int i=0; i<N; i++) { + dumpHost(mHosts.get(i), i, pw); + } + + N = mDeletedProviders.size(); + pw.println(" "); + pw.println("Deleted Providers:"); + for (int i=0; i<N; i++) { + dumpProvider(mDeletedProviders.get(i), i, pw); + } + + N = mDeletedHosts.size(); + pw.println(" "); + pw.println("Deleted Hosts:"); + for (int i=0; i<N; i++) { + dumpHost(mDeletedHosts.get(i), i, pw); + } + } + } + + private void ensureStateLoadedLocked() { + if (!mStateLoaded) { + loadAppWidgetList(); + loadStateLocked(); + mStateLoaded = true; + } + } + + public int allocateAppWidgetId(String packageName, int hostId) { + int callingUid = enforceCallingUid(packageName); + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + int appWidgetId = mNextAppWidgetId++; + + Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); + + AppWidgetId id = new AppWidgetId(); + id.appWidgetId = appWidgetId; + id.host = host; + + host.instances.add(id); + mAppWidgetIds.add(id); + + saveStateLocked(); + + return appWidgetId; + } + } + + public void deleteAppWidgetId(int appWidgetId) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id != null) { + deleteAppWidgetLocked(id); + saveStateLocked(); + } + } + } + + public void deleteHost(int hostId) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + int callingUid = Binder.getCallingUid(); + Host host = lookupHostLocked(callingUid, hostId); + if (host != null) { + deleteHostLocked(host); + saveStateLocked(); + } + } + } + + public void deleteAllHosts() { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + int callingUid = Binder.getCallingUid(); + final int N = mHosts.size(); + boolean changed = false; + for (int i = N - 1; i >= 0; i--) { + Host host = mHosts.get(i); + if (host.uid == callingUid) { + deleteHostLocked(host); + changed = true; + } + } + if (changed) { + saveStateLocked(); + } + } + } + + void deleteHostLocked(Host host) { + final int N = host.instances.size(); + for (int i = N - 1; i >= 0; i--) { + AppWidgetId id = host.instances.get(i); + deleteAppWidgetLocked(id); + } + host.instances.clear(); + mHosts.remove(host); + mDeletedHosts.add(host); + // it's gone or going away, abruptly drop the callback connection + host.callbacks = null; + } + + void deleteAppWidgetLocked(AppWidgetId id) { + // We first unbind all services that are bound to this id + unbindAppWidgetRemoteViewsServicesLocked(id); + + Host host = id.host; + host.instances.remove(id); + pruneHostLocked(host); + + mAppWidgetIds.remove(id); + + Provider p = id.provider; + if (p != null) { + p.instances.remove(id); + if (!p.zombie) { + // send the broacast saying that this appWidgetId has been deleted + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED); + intent.setComponent(p.info.provider); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id.appWidgetId); + mContext.sendBroadcast(intent); + if (p.instances.size() == 0) { + // cancel the future updates + cancelBroadcasts(p); + + // send the broacast saying that the provider is not in use any more + intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED); + intent.setComponent(p.info.provider); + mContext.sendBroadcast(intent); + } + } + } + } + + void cancelBroadcasts(Provider p) { + if (p.broadcast != null) { + mAlarmManager.cancel(p.broadcast); + long token = Binder.clearCallingIdentity(); + try { + p.broadcast.cancel(); + } finally { + Binder.restoreCallingIdentity(token); + } + p.broadcast = null; + } + } + + public void bindAppWidgetId(int appWidgetId, ComponentName provider) { + mContext.enforceCallingPermission(android.Manifest.permission.BIND_APPWIDGET, + "bindGagetId appWidgetId=" + appWidgetId + " provider=" + provider); + + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id == null) { + throw new IllegalArgumentException("bad appWidgetId"); + } + if (id.provider != null) { + throw new IllegalArgumentException("appWidgetId " + appWidgetId + + " already bound to " + id.provider.info.provider); + } + Provider p = lookupProviderLocked(provider); + if (p == null) { + throw new IllegalArgumentException("not a appwidget provider: " + provider); + } + if (p.zombie) { + throw new IllegalArgumentException("can't bind to a 3rd party provider in" + + " safe mode: " + provider); + } + + Binder.restoreCallingIdentity(ident); + + id.provider = p; + p.instances.add(id); + int instancesSize = p.instances.size(); + if (instancesSize == 1) { + // tell the provider that it's ready + sendEnableIntentLocked(p); + } + + // send an update now -- We need this update now, and just for this appWidgetId. + // It's less critical when the next one happens, so when we schedule the next one, + // we add updatePeriodMillis to its start time. That time will have some slop, + // but that's okay. + sendUpdateIntentLocked(p, new int[] { appWidgetId }); + + // schedule the future updates + registerForBroadcastsLocked(p, getAppWidgetIds(p)); + saveStateLocked(); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + // Binds to a specific RemoteViewsService + public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id == null) { + throw new IllegalArgumentException("bad appWidgetId"); + } + final ComponentName componentName = intent.getComponent(); + try { + final ServiceInfo si = mContext.getPackageManager().getServiceInfo(componentName, + PackageManager.GET_PERMISSIONS); + if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(si.permission)) { + throw new SecurityException("Selected service does not require " + + android.Manifest.permission.BIND_REMOTEVIEWS + ": " + componentName); + } + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException("Unknown component " + componentName); + } + + // If there is already a connection made for this service intent, then disconnect from + // that first. (This does not allow multiple connections to the same service under + // the same key) + ServiceConnectionProxy conn = null; + FilterComparison fc = new FilterComparison(intent); + Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, fc); + if (mBoundRemoteViewsServices.containsKey(key)) { + conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key); + conn.disconnect(); + mContext.unbindService(conn); + mBoundRemoteViewsServices.remove(key); + } + + // Bind to the RemoteViewsService (which will trigger a callback to the + // RemoteViewsAdapter.onServiceConnected()) + final long token = Binder.clearCallingIdentity(); + try { + conn = new ServiceConnectionProxy(key, connection); + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); + mBoundRemoteViewsServices.put(key, conn); + } finally { + Binder.restoreCallingIdentity(token); + } + + // Add it to the mapping of RemoteViewsService to appWidgetIds so that we can determine + // when we can call back to the RemoteViewsService later to destroy associated + // factories. + incrementAppWidgetServiceRefCount(appWidgetId, fc); + } + } + + // Unbinds from a specific RemoteViewsService + public void unbindRemoteViewsService(int appWidgetId, Intent intent) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + // Unbind from the RemoteViewsService (which will trigger a callback to the bound + // RemoteViewsAdapter) + Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, new FilterComparison( + intent)); + if (mBoundRemoteViewsServices.containsKey(key)) { + // We don't need to use the appWidgetId until after we are sure there is something + // to unbind. Note that this may mask certain issues with apps calling unbind() + // more than necessary. + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id == null) { + throw new IllegalArgumentException("bad appWidgetId"); + } + + ServiceConnectionProxy conn = (ServiceConnectionProxy) mBoundRemoteViewsServices + .get(key); + conn.disconnect(); + mContext.unbindService(conn); + mBoundRemoteViewsServices.remove(key); + } else { + Log.e("AppWidgetService", "Error (unbindRemoteViewsService): Connection not bound"); + } + } + } + + // Unbinds from a RemoteViewsService when we delete an app widget + private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) { + int appWidgetId = id.appWidgetId; + // Unbind all connections to Services bound to this AppWidgetId + Iterator<Pair<Integer, Intent.FilterComparison>> it = mBoundRemoteViewsServices.keySet() + .iterator(); + while (it.hasNext()) { + final Pair<Integer, Intent.FilterComparison> key = it.next(); + if (key.first.intValue() == appWidgetId) { + final ServiceConnectionProxy conn = (ServiceConnectionProxy) mBoundRemoteViewsServices + .get(key); + conn.disconnect(); + mContext.unbindService(conn); + it.remove(); + } + } + + // Check if we need to destroy any services (if no other app widgets are + // referencing the same service) + decrementAppWidgetServiceRefCount(appWidgetId); + } + + // Destroys the cached factory on the RemoteViewsService's side related to the specified intent + private void destroyRemoteViewsService(final Intent intent) { + final ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + final IRemoteViewsFactory cb = IRemoteViewsFactory.Stub.asInterface(service); + try { + cb.onDestroy(intent); + } catch (RemoteException e) { + e.printStackTrace(); + } catch (RuntimeException e) { + e.printStackTrace(); + } + mContext.unbindService(this); + } + + @Override + public void onServiceDisconnected(android.content.ComponentName name) { + // Do nothing + } + }; + + // Bind to the service and remove the static intent->factory mapping in the + // RemoteViewsService. + final long token = Binder.clearCallingIdentity(); + try { + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Adds to the ref-count for a given RemoteViewsService intent + private void incrementAppWidgetServiceRefCount(int appWidgetId, FilterComparison fc) { + HashSet<Integer> appWidgetIds = null; + if (mRemoteViewsServicesAppWidgets.containsKey(fc)) { + appWidgetIds = mRemoteViewsServicesAppWidgets.get(fc); + } else { + appWidgetIds = new HashSet<Integer>(); + mRemoteViewsServicesAppWidgets.put(fc, appWidgetIds); + } + appWidgetIds.add(appWidgetId); + } + + // Subtracts from the ref-count for a given RemoteViewsService intent, prompting a delete if + // the ref-count reaches zero. + private void decrementAppWidgetServiceRefCount(int appWidgetId) { + Iterator<FilterComparison> it = mRemoteViewsServicesAppWidgets.keySet().iterator(); + while (it.hasNext()) { + final FilterComparison key = it.next(); + final HashSet<Integer> ids = mRemoteViewsServicesAppWidgets.get(key); + if (ids.remove(appWidgetId)) { + // If we have removed the last app widget referencing this service, then we + // should destroy it and remove it from this set + if (ids.isEmpty()) { + destroyRemoteViewsService(key.getIntent()); + it.remove(); + } + } + } + } + + public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id != null && id.provider != null && !id.provider.zombie) { + return id.provider.info; + } + return null; + } + } + + public RemoteViews getAppWidgetViews(int appWidgetId) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId); + if (id != null) { + return id.views; + } + return null; + } + } + + public List<AppWidgetProviderInfo> getInstalledProviders() { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + final int N = mInstalledProviders.size(); + ArrayList<AppWidgetProviderInfo> result = new ArrayList<AppWidgetProviderInfo>(N); + for (int i = 0; i < N; i++) { + Provider p = mInstalledProviders.get(i); + if (!p.zombie) { + result.add(p.info); + } + } + return result; + } + } + + public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + for (int i = 0; i < N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + updateAppWidgetInstanceLocked(id, views); + } + } + } + + public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + for (int i = 0; i < N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + updateAppWidgetInstanceLocked(id, views, true); + } + } + } + + public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + for (int i = 0; i < N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + notifyAppWidgetViewDataChangedInstanceLocked(id, viewId); + } + } + } + + public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + Provider p = lookupProviderLocked(provider); + if (p == null) { + Slog.w(TAG, "updateAppWidgetProvider: provider doesn't exist: " + provider); + return; + } + ArrayList<AppWidgetId> instances = p.instances; + final int callingUid = Binder.getCallingUid(); + final int N = instances.size(); + for (int i = 0; i < N; i++) { + AppWidgetId id = instances.get(i); + if (canAccessAppWidgetId(id, callingUid)) { + updateAppWidgetInstanceLocked(id, views); + } + } + } + } + + void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) { + updateAppWidgetInstanceLocked(id, views, false); + } + + void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) { + // allow for stale appWidgetIds and other badness + // lookup also checks that the calling process can access the appWidgetId + // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) + if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { + + // We do not want to save this RemoteViews + if (!isPartialUpdate) + id.views = views; + + // is anyone listening? + if (id.host.callbacks != null) { + try { + // the lock is held, but this is a oneway call + id.host.callbacks.updateAppWidget(id.appWidgetId, views); + } catch (RemoteException e) { + // It failed; remove the callback. No need to prune because + // we know that this host is still referenced by this instance. + id.host.callbacks = null; + } + } + } + } + + void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, int viewId) { + // allow for stale appWidgetIds and other badness + // lookup also checks that the calling process can access the appWidgetId + // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) + if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { + // is anyone listening? + if (id.host.callbacks != null) { + try { + // the lock is held, but this is a oneway call + id.host.callbacks.viewDataChanged(id.appWidgetId, viewId); + } catch (RemoteException e) { + // It failed; remove the callback. No need to prune because + // we know that this host is still referenced by this instance. + id.host.callbacks = null; + } + } + + // If the host is unavailable, then we call the associated + // RemoteViewsFactory.onDataSetChanged() directly + if (id.host.callbacks == null) { + Set<FilterComparison> keys = mRemoteViewsServicesAppWidgets.keySet(); + for (FilterComparison key : keys) { + if (mRemoteViewsServicesAppWidgets.get(key).contains(id.appWidgetId)) { + Intent intent = key.getIntent(); + + final ServiceConnection conn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + IRemoteViewsFactory cb = IRemoteViewsFactory.Stub + .asInterface(service); + try { + cb.onDataSetChangedAsync(); + } catch (RemoteException e) { + e.printStackTrace(); + } catch (RuntimeException e) { + e.printStackTrace(); + } + mContext.unbindService(this); + } + + @Override + public void onServiceDisconnected(android.content.ComponentName name) { + // Do nothing + } + }; + + // Bind to the service and call onDataSetChanged() + final long token = Binder.clearCallingIdentity(); + try { + mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + } + } + } + + public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId, + List<RemoteViews> updatedViews) { + int callingUid = enforceCallingUid(packageName); + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + Host host = lookupOrAddHostLocked(callingUid, packageName, hostId); + host.callbacks = callbacks; + + updatedViews.clear(); + + ArrayList<AppWidgetId> instances = host.instances; + int N = instances.size(); + int[] updatedIds = new int[N]; + for (int i = 0; i < N; i++) { + AppWidgetId id = instances.get(i); + updatedIds[i] = id.appWidgetId; + updatedViews.add(id.views); + } + return updatedIds; + } + } + + public void stopListening(int hostId) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + Host host = lookupHostLocked(Binder.getCallingUid(), hostId); + if (host != null) { + host.callbacks = null; + pruneHostLocked(host); + } + } + } + + boolean canAccessAppWidgetId(AppWidgetId id, int callingUid) { + if (id.host.uid == callingUid) { + // Apps hosting the AppWidget have access to it. + return true; + } + if (id.provider != null && id.provider.uid == callingUid) { + // Apps providing the AppWidget have access to it (if the appWidgetId has been bound) + return true; + } + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET) == PackageManager.PERMISSION_GRANTED) { + // Apps that can bind have access to all appWidgetIds. + return true; + } + // Nobody else can access it. + return false; + } + + AppWidgetId lookupAppWidgetIdLocked(int appWidgetId) { + int callingUid = Binder.getCallingUid(); + final int N = mAppWidgetIds.size(); + for (int i = 0; i < N; i++) { + AppWidgetId id = mAppWidgetIds.get(i); + if (id.appWidgetId == appWidgetId && canAccessAppWidgetId(id, callingUid)) { + return id; + } + } + return null; + } + + Provider lookupProviderLocked(ComponentName provider) { + final int N = mInstalledProviders.size(); + for (int i = 0; i < N; i++) { + Provider p = mInstalledProviders.get(i); + if (p.info.provider.equals(provider)) { + return p; + } + } + return null; + } + + Host lookupHostLocked(int uid, int hostId) { + final int N = mHosts.size(); + for (int i = 0; i < N; i++) { + Host h = mHosts.get(i); + if (h.uid == uid && h.hostId == hostId) { + return h; + } + } + return null; + } + + Host lookupOrAddHostLocked(int uid, String packageName, int hostId) { + final int N = mHosts.size(); + for (int i = 0; i < N; i++) { + Host h = mHosts.get(i); + if (h.hostId == hostId && h.packageName.equals(packageName)) { + return h; + } + } + Host host = new Host(); + host.packageName = packageName; + host.uid = uid; + host.hostId = hostId; + mHosts.add(host); + return host; + } + + void pruneHostLocked(Host host) { + if (host.instances.size() == 0 && host.callbacks == null) { + mHosts.remove(host); + } + } + + void loadAppWidgetList() { + PackageManager pm = mPackageManager; + + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + List<ResolveInfo> broadcastReceivers = pm.queryBroadcastReceivers(intent, + PackageManager.GET_META_DATA); + + final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); + for (int i = 0; i < N; i++) { + ResolveInfo ri = broadcastReceivers.get(i); + addProviderLocked(ri); + } + } + + boolean addProviderLocked(ResolveInfo ri) { + if ((ri.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + return false; + } + if (!ri.activityInfo.isEnabled()) { + return false; + } + Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName, + ri.activityInfo.name), ri); + if (p != null) { + mInstalledProviders.add(p); + return true; + } else { + return false; + } + } + + void removeProviderLocked(int index, Provider p) { + int N = p.instances.size(); + for (int i = 0; i < N; i++) { + AppWidgetId id = p.instances.get(i); + // Call back with empty RemoteViews + updateAppWidgetInstanceLocked(id, null); + // Stop telling the host about updates for this from now on + cancelBroadcasts(p); + // clear out references to this appWidgetId + id.host.instances.remove(id); + mAppWidgetIds.remove(id); + id.provider = null; + pruneHostLocked(id.host); + id.host = null; + } + p.instances.clear(); + mInstalledProviders.remove(index); + mDeletedProviders.add(p); + // no need to send the DISABLE broadcast, since the receiver is gone anyway + cancelBroadcasts(p); + } + + void sendEnableIntentLocked(Provider p) { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED); + intent.setComponent(p.info.provider); + mContext.sendBroadcast(intent); + } + + void sendUpdateIntentLocked(Provider p, int[] appWidgetIds) { + if (appWidgetIds != null && appWidgetIds.length > 0) { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); + intent.setComponent(p.info.provider); + mContext.sendBroadcast(intent); + } + } + + void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) { + if (p.info.updatePeriodMillis > 0) { + // if this is the first instance, set the alarm. otherwise, + // rely on the fact that we've already set it and that + // PendingIntent.getBroadcast will update the extras. + boolean alreadyRegistered = p.broadcast != null; + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); + intent.setComponent(p.info.provider); + long token = Binder.clearCallingIdentity(); + try { + p.broadcast = PendingIntent.getBroadcast(mContext, 1, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + } finally { + Binder.restoreCallingIdentity(token); + } + if (!alreadyRegistered) { + long period = p.info.updatePeriodMillis; + if (period < MIN_UPDATE_PERIOD) { + period = MIN_UPDATE_PERIOD; + } + mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock + .elapsedRealtime() + + period, period, p.broadcast); + } + } + } + + static int[] getAppWidgetIds(Provider p) { + int instancesSize = p.instances.size(); + int appWidgetIds[] = new int[instancesSize]; + for (int i = 0; i < instancesSize; i++) { + appWidgetIds[i] = p.instances.get(i).appWidgetId; + } + return appWidgetIds; + } + + public int[] getAppWidgetIds(ComponentName provider) { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + Provider p = lookupProviderLocked(provider); + if (p != null && Binder.getCallingUid() == p.uid) { + return getAppWidgetIds(p); + } else { + return new int[0]; + } + } + } + + private Provider parseProviderInfoXml(ComponentName component, ResolveInfo ri) { + Provider p = null; + + ActivityInfo activityInfo = ri.activityInfo; + XmlResourceParser parser = null; + try { + parser = activityInfo.loadXmlMetaData(mPackageManager, + AppWidgetManager.META_DATA_APPWIDGET_PROVIDER); + if (parser == null) { + Slog.w(TAG, "No " + AppWidgetManager.META_DATA_APPWIDGET_PROVIDER + + " meta-data for " + "AppWidget provider '" + component + '\''); + return null; + } + + AttributeSet attrs = Xml.asAttributeSet(parser); + + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + // drain whitespace, comments, etc. + } + + String nodeName = parser.getName(); + if (!"appwidget-provider".equals(nodeName)) { + Slog.w(TAG, "Meta-data does not start with appwidget-provider tag for" + + " AppWidget provider '" + component + '\''); + return null; + } + + p = new Provider(); + AppWidgetProviderInfo info = p.info = new AppWidgetProviderInfo(); + info.provider = component; + p.uid = activityInfo.applicationInfo.uid; + + Resources res = mPackageManager + .getResourcesForApplication(activityInfo.applicationInfo); + + TypedArray sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AppWidgetProviderInfo); + + // These dimensions has to be resolved in the application's context. + // We simply send back the raw complex data, which will be + // converted to dp in {@link AppWidgetManager#getAppWidgetInfo}. + TypedValue value = sa + .peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minWidth); + info.minWidth = value != null ? value.data : 0; + value = sa.peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minHeight); + info.minHeight = value != null ? value.data : 0; + value = sa.peekValue( + com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeWidth); + info.minResizeWidth = value != null ? value.data : info.minWidth; + value = sa.peekValue( + com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeHeight); + info.minResizeHeight = value != null ? value.data : info.minHeight; + info.updatePeriodMillis = sa.getInt( + com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0); + info.initialLayout = sa.getResourceId( + com.android.internal.R.styleable.AppWidgetProviderInfo_initialLayout, 0); + String className = sa + .getString(com.android.internal.R.styleable.AppWidgetProviderInfo_configure); + if (className != null) { + info.configure = new ComponentName(component.getPackageName(), className); + } + info.label = activityInfo.loadLabel(mPackageManager).toString(); + info.icon = ri.getIconResource(); + info.previewImage = sa.getResourceId( + com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0); + info.autoAdvanceViewId = sa.getResourceId( + com.android.internal.R.styleable.AppWidgetProviderInfo_autoAdvanceViewId, -1); + info.resizeMode = sa.getInt( + com.android.internal.R.styleable.AppWidgetProviderInfo_resizeMode, + AppWidgetProviderInfo.RESIZE_NONE); + + sa.recycle(); + } catch (Exception e) { + // Ok to catch Exception here, because anything going wrong because + // of what a client process passes to us should not be fatal for the + // system process. + Slog.w(TAG, "XML parsing failed for AppWidget provider '" + component + '\'', e); + return null; + } finally { + if (parser != null) + parser.close(); + } + return p; + } + + int getUidForPackage(String packageName) throws PackageManager.NameNotFoundException { + PackageInfo pkgInfo = mPackageManager.getPackageInfo(packageName, 0); + if (pkgInfo == null || pkgInfo.applicationInfo == null) { + throw new PackageManager.NameNotFoundException(); + } + return pkgInfo.applicationInfo.uid; + } + + int enforceCallingUid(String packageName) throws IllegalArgumentException { + int callingUid = Binder.getCallingUid(); + int packageUid; + try { + packageUid = getUidForPackage(packageName); + } catch (PackageManager.NameNotFoundException ex) { + throw new IllegalArgumentException("packageName and uid don't match packageName=" + + packageName); + } + if (!UserId.isSameApp(callingUid, packageUid)) { + throw new IllegalArgumentException("packageName and uid don't match packageName=" + + packageName); + } + return callingUid; + } + + void sendInitialBroadcasts() { + synchronized (mAppWidgetIds) { + ensureStateLoadedLocked(); + final int N = mInstalledProviders.size(); + for (int i = 0; i < N; i++) { + Provider p = mInstalledProviders.get(i); + if (p.instances.size() > 0) { + sendEnableIntentLocked(p); + int[] appWidgetIds = getAppWidgetIds(p); + sendUpdateIntentLocked(p, appWidgetIds); + registerForBroadcastsLocked(p, appWidgetIds); + } + } + } + } + + // only call from initialization -- it assumes that the data structures are all empty + void loadStateLocked() { + AtomicFile file = savedStateFile(); + try { + FileInputStream stream = file.openRead(); + readStateFromFileLocked(stream); + + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + Slog.w(TAG, "Failed to close state FileInputStream " + e); + } + } + } catch (FileNotFoundException e) { + Slog.w(TAG, "Failed to read state: " + e); + } + } + + void saveStateLocked() { + AtomicFile file = savedStateFile(); + FileOutputStream stream; + try { + stream = file.startWrite(); + if (writeStateToFileLocked(stream)) { + file.finishWrite(stream); + } else { + file.failWrite(stream); + Slog.w(TAG, "Failed to save state, restoring backup."); + } + } catch (IOException e) { + Slog.w(TAG, "Failed open state file for write: " + e); + } + } + + boolean writeStateToFileLocked(FileOutputStream stream) { + int N; + + try { + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(stream, "utf-8"); + out.startDocument(null, true); + out.startTag(null, "gs"); + + int providerIndex = 0; + N = mInstalledProviders.size(); + for (int i = 0; i < N; i++) { + Provider p = mInstalledProviders.get(i); + if (p.instances.size() > 0) { + out.startTag(null, "p"); + out.attribute(null, "pkg", p.info.provider.getPackageName()); + out.attribute(null, "cl", p.info.provider.getClassName()); + out.endTag(null, "p"); + p.tag = providerIndex; + providerIndex++; + } + } + + N = mHosts.size(); + for (int i = 0; i < N; i++) { + Host host = mHosts.get(i); + out.startTag(null, "h"); + out.attribute(null, "pkg", host.packageName); + out.attribute(null, "id", Integer.toHexString(host.hostId)); + out.endTag(null, "h"); + host.tag = i; + } + + N = mAppWidgetIds.size(); + for (int i = 0; i < N; i++) { + AppWidgetId id = mAppWidgetIds.get(i); + out.startTag(null, "g"); + out.attribute(null, "id", Integer.toHexString(id.appWidgetId)); + out.attribute(null, "h", Integer.toHexString(id.host.tag)); + if (id.provider != null) { + out.attribute(null, "p", Integer.toHexString(id.provider.tag)); + } + out.endTag(null, "g"); + } + + out.endTag(null, "gs"); + + out.endDocument(); + return true; + } catch (IOException e) { + Slog.w(TAG, "Failed to write state: " + e); + return false; + } + } + + void readStateFromFileLocked(FileInputStream stream) { + boolean success = false; + + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(stream, null); + + int type; + int providerIndex = 0; + HashMap<Integer, Provider> loadedProviders = new HashMap<Integer, Provider>(); + do { + type = parser.next(); + if (type == XmlPullParser.START_TAG) { + String tag = parser.getName(); + if ("p".equals(tag)) { + // TODO: do we need to check that this package has the same signature + // as before? + String pkg = parser.getAttributeValue(null, "pkg"); + String cl = parser.getAttributeValue(null, "cl"); + + final PackageManager packageManager = mContext.getPackageManager(); + try { + packageManager.getReceiverInfo(new ComponentName(pkg, cl), 0); + } catch (PackageManager.NameNotFoundException e) { + String[] pkgs = packageManager + .currentToCanonicalPackageNames(new String[] { pkg }); + pkg = pkgs[0]; + } + + Provider p = lookupProviderLocked(new ComponentName(pkg, cl)); + if (p == null && mSafeMode) { + // if we're in safe mode, make a temporary one + p = new Provider(); + p.info = new AppWidgetProviderInfo(); + p.info.provider = new ComponentName(pkg, cl); + p.zombie = true; + mInstalledProviders.add(p); + } + if (p != null) { + // if it wasn't uninstalled or something + loadedProviders.put(providerIndex, p); + } + providerIndex++; + } else if ("h".equals(tag)) { + Host host = new Host(); + + // TODO: do we need to check that this package has the same signature + // as before? + host.packageName = parser.getAttributeValue(null, "pkg"); + try { + host.uid = getUidForPackage(host.packageName); + } catch (PackageManager.NameNotFoundException ex) { + host.zombie = true; + } + if (!host.zombie || mSafeMode) { + // In safe mode, we don't discard the hosts we don't recognize + // so that they're not pruned from our list. Otherwise, we do. + host.hostId = Integer + .parseInt(parser.getAttributeValue(null, "id"), 16); + mHosts.add(host); + } + } else if ("g".equals(tag)) { + AppWidgetId id = new AppWidgetId(); + id.appWidgetId = Integer.parseInt(parser.getAttributeValue(null, "id"), 16); + if (id.appWidgetId >= mNextAppWidgetId) { + mNextAppWidgetId = id.appWidgetId + 1; + } + + String providerString = parser.getAttributeValue(null, "p"); + if (providerString != null) { + // there's no provider if it hasn't been bound yet. + // maybe we don't have to save this, but it brings the system + // to the state it was in. + int pIndex = Integer.parseInt(providerString, 16); + id.provider = loadedProviders.get(pIndex); + if (false) { + Slog.d(TAG, "bound appWidgetId=" + id.appWidgetId + " to provider " + + pIndex + " which is " + id.provider); + } + if (id.provider == null) { + // This provider is gone. We just let the host figure out + // that this happened when it fails to load it. + continue; + } + } + + int hIndex = Integer.parseInt(parser.getAttributeValue(null, "h"), 16); + id.host = mHosts.get(hIndex); + if (id.host == null) { + // This host is gone. + continue; + } + + if (id.provider != null) { + id.provider.instances.add(id); + } + id.host.instances.add(id); + mAppWidgetIds.add(id); + } + } + } while (type != XmlPullParser.END_DOCUMENT); + success = true; + } catch (NullPointerException e) { + Slog.w(TAG, "failed parsing " + e); + } catch (NumberFormatException e) { + Slog.w(TAG, "failed parsing " + e); + } catch (XmlPullParserException e) { + Slog.w(TAG, "failed parsing " + e); + } catch (IOException e) { + Slog.w(TAG, "failed parsing " + e); + } catch (IndexOutOfBoundsException e) { + Slog.w(TAG, "failed parsing " + e); + } + + if (success) { + // delete any hosts that didn't manage to get connected (should happen) + // if it matters, they'll be reconnected. + for (int i = mHosts.size() - 1; i >= 0; i--) { + pruneHostLocked(mHosts.get(i)); + } + } else { + // failed reading, clean up + Slog.w(TAG, "Failed to read state, clearing widgets and hosts."); + + mAppWidgetIds.clear(); + mHosts.clear(); + final int N = mInstalledProviders.size(); + for (int i = 0; i < N; i++) { + mInstalledProviders.get(i).instances.clear(); + } + } + } + + AtomicFile savedStateFile() { + int userId = Binder.getOrigCallingUser(); + File dir = new File("/data/system/users/" + userId); + File settingsFile = new File(dir, SETTINGS_FILENAME); + if (!dir.exists()) { + dir.mkdirs(); + if (userId == 0) { + // Migrate old data + File oldFile = new File("/data/system/" + SETTINGS_FILENAME); + // Method doesn't throw an exception on failure. Ignore any errors + // in moving the file (like non-existence) + oldFile.renameTo(settingsFile); + } + } + return new AtomicFile(settingsFile); + } + + void addProvidersForPackageLocked(String pkgName) { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + intent.setPackage(pkgName); + List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, + PackageManager.GET_META_DATA); + + final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); + 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); + } + } + } + + void updateProvidersForPackageLocked(String pkgName) { + HashSet<String> keep = new HashSet<String>(); + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + intent.setPackage(pkgName); + List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent, + PackageManager.GET_META_DATA); + + // add the missing ones and collect which ones to keep + int N = broadcastReceivers == null ? 0 : broadcastReceivers.size(); + 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); + if (p == null) { + if (addProviderLocked(ri)) { + keep.add(ai.name); + } + } else { + Provider parsed = parseProviderInfoXml(component, ri); + if (parsed != null) { + keep.add(ai.name); + // Use the new AppWidgetProviderInfo. + p.info = parsed.info; + // If it's enabled + final int M = p.instances.size(); + if (M > 0) { + int[] appWidgetIds = getAppWidgetIds(p); + // Reschedule for the new updatePeriodMillis (don't worry about handling + // it specially if updatePeriodMillis didn't change because we just sent + // an update, and the next one will be updatePeriodMillis from now). + cancelBroadcasts(p); + registerForBroadcastsLocked(p, appWidgetIds); + // 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); + } catch (RemoteException ex) { + // It failed; remove the callback. No need to prune because + // we know that this host is still referenced by this + // instance. + id.host.callbacks = null; + } + } + } + // Now that we've told the host, push out an update. + sendUpdateIntentLocked(p, appWidgetIds); + } + } + } + } + } + + // prune the ones we don't want to keep + N = mInstalledProviders.size(); + for (int i = N - 1; i >= 0; i--) { + Provider p = mInstalledProviders.get(i); + if (pkgName.equals(p.info.provider.getPackageName()) + && !keep.contains(p.info.provider.getClassName())) { + removeProviderLocked(i, p); + } + } + } + + void removeProvidersForPackageLocked(String pkgName) { + int N = mInstalledProviders.size(); + for (int i = N - 1; i >= 0; i--) { + Provider p = mInstalledProviders.get(i); + if (pkgName.equals(p.info.provider.getPackageName())) { + removeProviderLocked(i, p); + } + } + + // Delete the hosts for this package too + // + // By now, we have removed any AppWidgets that were in any hosts here, + // so we don't need to worry about sending DISABLE broadcasts to them. + N = mHosts.size(); + for (int i = N - 1; i >= 0; i--) { + Host host = mHosts.get(i); + if (pkgName.equals(host.packageName)) { + deleteHostLocked(host); + } + } + } +} diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index 4d5e0a6..a7b08f5 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -140,6 +140,8 @@ class BackupManagerService extends IBackupManager.Stub { static final int BACKUP_FILE_VERSION = 1; static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production + static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup"; + // How often we perform a backup pass. Privileged external callers can // trigger an immediate pass. private static final long BACKUP_INTERVAL = AlarmManager.INTERVAL_HOUR; @@ -235,6 +237,10 @@ class BackupManagerService extends IBackupManager.Stub { volatile long mLastBackupPass; volatile long mNextBackupPass; + // For debugging, we maintain a progress trace of operations during backup + static final boolean DEBUG_BACKUP_TRACE = true; + final List<String> mBackupTrace = new ArrayList<String>(); + // A similar synchronization mechanism around clearing apps' data for restore final Object mClearDataLock = new Object(); volatile boolean mClearingData; @@ -652,6 +658,23 @@ class BackupManagerService extends IBackupManager.Stub { } } + // ----- Debug-only backup operation trace ----- + void addBackupTrace(String s) { + if (DEBUG_BACKUP_TRACE) { + synchronized (mBackupTrace) { + mBackupTrace.add(s); + } + } + } + + void clearBackupTrace() { + if (DEBUG_BACKUP_TRACE) { + synchronized (mBackupTrace) { + mBackupTrace.clear(); + } + } + } + // ----- Main service implementation ----- public BackupManagerService(Context context) { @@ -1612,6 +1635,7 @@ class BackupManagerService extends IBackupManager.Stub { mAgentConnectLock.wait(5000); } catch (InterruptedException e) { // just bail + if (DEBUG) Slog.w(TAG, "Interrupted: " + e); return null; } } @@ -1621,6 +1645,7 @@ class BackupManagerService extends IBackupManager.Stub { Slog.w(TAG, "Timeout waiting for agent " + app); return null; } + if (DEBUG) Slog.i(TAG, "got agent " + mConnectedAgent); agent = mConnectedAgent; } } catch (RemoteException e) { @@ -1650,7 +1675,8 @@ class BackupManagerService extends IBackupManager.Stub { synchronized(mClearDataLock) { mClearingData = true; try { - mActivityManager.clearApplicationUserData(packageName, observer); + mActivityManager.clearApplicationUserData(packageName, observer, + Binder.getOrigCallingUser()); } catch (RemoteException e) { // can't happen because the activity manager is in this process } @@ -1814,6 +1840,8 @@ class BackupManagerService extends IBackupManager.Stub { mCurrentState = BackupState.INITIAL; mFinished = false; + + addBackupTrace("STATE => INITIAL"); } // Main entry point: perform one chunk of work, updating the state as appropriate @@ -1842,11 +1870,25 @@ class BackupManagerService extends IBackupManager.Stub { // We're starting a backup pass. Initialize the transport and send // the PM metadata blob if we haven't already. void beginBackup() { + if (DEBUG_BACKUP_TRACE) { + clearBackupTrace(); + StringBuilder b = new StringBuilder(256); + b.append("beginBackup: ["); + for (BackupRequest req : mOriginalQueue) { + b.append(' '); + b.append(req.packageName); + } + b.append(" ]"); + addBackupTrace(b.toString()); + } + mStatus = BackupConstants.TRANSPORT_OK; // Sanity check: if the queue is empty we have no work to do. if (mOriginalQueue.isEmpty()) { Slog.w(TAG, "Backup begun with an empty queue - nothing to do."); + addBackupTrace("queue empty at begin"); + executeNextState(BackupState.FINAL); return; } @@ -1859,13 +1901,17 @@ class BackupManagerService extends IBackupManager.Stub { File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL); try { - EventLog.writeEvent(EventLogTags.BACKUP_START, mTransport.transportDirName()); + final String transportName = mTransport.transportDirName(); + EventLog.writeEvent(EventLogTags.BACKUP_START, transportName); // If we haven't stored package manager metadata yet, we must init the transport. if (mStatus == BackupConstants.TRANSPORT_OK && pmState.length() <= 0) { Slog.i(TAG, "Initializing (wiping) backup state and transport storage"); + addBackupTrace("initializing transport " + transportName); resetBackupState(mStateDir); // Just to make sure. mStatus = mTransport.initializeDevice(); + + addBackupTrace("transport.initializeDevice() == " + mStatus); if (mStatus == BackupConstants.TRANSPORT_OK) { EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE); } else { @@ -1884,6 +1930,7 @@ class BackupManagerService extends IBackupManager.Stub { mPackageManager, allAgentPackages()); mStatus = invokeAgentForBackup(PACKAGE_MANAGER_SENTINEL, IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport); + addBackupTrace("PMBA invoke: " + mStatus); } if (mStatus == BackupConstants.TRANSPORT_NOT_INITIALIZED) { @@ -1894,11 +1941,13 @@ class BackupManagerService extends IBackupManager.Stub { } } catch (Exception e) { Slog.e(TAG, "Error in backup thread", e); + addBackupTrace("Exception in backup thread: " + e); mStatus = BackupConstants.TRANSPORT_ERROR; } finally { // If we've succeeded so far, invokeAgentForBackup() will have run the PM // metadata and its completion/timeout callback will continue the state // machine chain. If it failed that won't happen; we handle that now. + addBackupTrace("exiting prelim: " + mStatus); if (mStatus != BackupConstants.TRANSPORT_OK) { // if things went wrong at this point, we need to // restage everything and try again later. @@ -1912,11 +1961,12 @@ class BackupManagerService extends IBackupManager.Stub { // if that was warranted. Now we process the single next thing in the queue. void invokeNextAgent() { mStatus = BackupConstants.TRANSPORT_OK; + addBackupTrace("invoke q=" + mQueue.size()); // Sanity check that we have work to do. If not, skip to the end where // we reestablish the wakelock invariants etc. if (mQueue.isEmpty()) { - Slog.e(TAG, "Running queue but it's empty!"); + if (DEBUG) Slog.i(TAG, "queue now empty"); executeNextState(BackupState.FINAL); return; } @@ -1926,6 +1976,7 @@ class BackupManagerService extends IBackupManager.Stub { mQueue.remove(0); Slog.d(TAG, "starting agent for backup of " + request); + addBackupTrace("launch agent for " + request.packageName); // Verify that the requested app exists; it might be something that // requested a backup but was then uninstalled. The request was @@ -1941,6 +1992,7 @@ class BackupManagerService extends IBackupManager.Stub { mWakelock.setWorkSource(new WorkSource(mCurrentPackage.applicationInfo.uid)); agent = bindToAgentSynchronous(mCurrentPackage.applicationInfo, IApplicationThread.BACKUP_MODE_INCREMENTAL); + addBackupTrace("agent bound; a? = " + (agent != null)); if (agent != null) { mStatus = invokeAgentForBackup(request.packageName, agent, mTransport); // at this point we'll either get a completion callback from the @@ -1954,14 +2006,17 @@ class BackupManagerService extends IBackupManager.Stub { // Try for the next one. Slog.d(TAG, "error in bind/backup", ex); mStatus = BackupConstants.AGENT_ERROR; + addBackupTrace("agent SE"); } } catch (NameNotFoundException e) { Slog.d(TAG, "Package does not exist; skipping"); + addBackupTrace("no such package"); + mStatus = BackupConstants.AGENT_UNKNOWN; } finally { mWakelock.setWorkSource(null); // If there was an agent error, no timeout/completion handling will occur. - // That means we need to deal with the next state ourselves. + // That means we need to direct to the next state ourselves. if (mStatus != BackupConstants.TRANSPORT_OK) { BackupState nextState = BackupState.RUNNING_QUEUE; @@ -1973,18 +2028,26 @@ class BackupManagerService extends IBackupManager.Stub { dataChangedImpl(request.packageName); mStatus = BackupConstants.TRANSPORT_OK; if (mQueue.isEmpty()) nextState = BackupState.FINAL; - } else if (mStatus != BackupConstants.TRANSPORT_OK) { + } else if (mStatus == BackupConstants.AGENT_UNKNOWN) { + // Failed lookup of the app, so we couldn't bring up an agent, but + // we're otherwise fine. Just drop it and go on to the next as usual. + mStatus = BackupConstants.TRANSPORT_OK; + } else { // Transport-level failure means we reenqueue everything revertAndEndBackup(); nextState = BackupState.FINAL; } executeNextState(nextState); + } else { + addBackupTrace("expecting completion/timeout callback"); } } } void finalizeBackup() { + addBackupTrace("finishing"); + // Either backup was successful, in which case we of course do not need // this pass's journal any more; or it failed, in which case we just // re-enqueued all of these packages in the current active journal. @@ -1997,6 +2060,7 @@ class BackupManagerService extends IBackupManager.Stub { // done a backup, we can now record what the current backup dataset token // is. if ((mCurrentToken == 0) && (mStatus == BackupConstants.TRANSPORT_OK)) { + addBackupTrace("success; recording token"); try { mCurrentToken = mTransport.getCurrentRestoreSet(); } catch (RemoteException e) {} // can't happen @@ -2012,11 +2076,13 @@ class BackupManagerService extends IBackupManager.Stub { // Make sure we back up everything and perform the one-time init clearMetadata(); if (DEBUG) Slog.d(TAG, "Server requires init; rerunning"); + addBackupTrace("init required; rerunning"); backupNow(); } } // Only once we're entirely finished do we release the wakelock + clearBackupTrace(); Slog.i(TAG, "Backup pass finished."); mWakelock.release(); } @@ -2031,7 +2097,8 @@ class BackupManagerService extends IBackupManager.Stub { // handler in case it doesn't get back to us. int invokeAgentForBackup(String packageName, IBackupAgent agent, IBackupTransport transport) { - if (DEBUG) Slog.d(TAG, "processOneBackup doBackup() on " + packageName); + if (DEBUG) Slog.d(TAG, "invokeAgentForBackup on " + packageName); + addBackupTrace("invoking " + packageName); mSavedStateName = new File(mStateDir, packageName); mBackupDataName = new File(mDataDir, packageName + ".data"); @@ -2071,10 +2138,13 @@ class BackupManagerService extends IBackupManager.Stub { ParcelFileDescriptor.MODE_TRUNCATE); // Initiate the target's backup pass + addBackupTrace("setting timeout"); prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL, this); + addBackupTrace("calling agent doBackup()"); agent.doBackup(mSavedState, mBackupData, mNewState, token, mBackupManagerBinder); } catch (Exception e) { Slog.e(TAG, "Error invoking for backup on " + packageName); + addBackupTrace("exception: " + e); EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, packageName, e.toString()); agentErrorCleanup(); @@ -2085,6 +2155,7 @@ class BackupManagerService extends IBackupManager.Stub { // either be a callback from the agent, at which point we'll process its data // for transport, or a timeout. Either way the next phase will happen in // response to the TimeoutHandler interface callbacks. + addBackupTrace("invoke success"); return BackupConstants.TRANSPORT_OK; } @@ -2096,6 +2167,7 @@ class BackupManagerService extends IBackupManager.Stub { + mCurrentPackage.packageName); mBackupHandler.removeMessages(MSG_TIMEOUT); clearAgentState(); + addBackupTrace("operation complete"); ParcelFileDescriptor backupData = null; mStatus = BackupConstants.TRANSPORT_OK; @@ -2105,6 +2177,7 @@ class BackupManagerService extends IBackupManager.Stub { if (mStatus == BackupConstants.TRANSPORT_OK) { backupData = ParcelFileDescriptor.open(mBackupDataName, ParcelFileDescriptor.MODE_READ_ONLY); + addBackupTrace("sending data to transport"); mStatus = mTransport.performBackup(mCurrentPackage, backupData); } @@ -2113,11 +2186,15 @@ class BackupManagerService extends IBackupManager.Stub { // hold off on finishBackup() until the end, which implies holding off on // renaming *all* the output state files (see below) until that happens. + addBackupTrace("data delivered: " + mStatus); if (mStatus == BackupConstants.TRANSPORT_OK) { + addBackupTrace("finishing op on transport"); mStatus = mTransport.finishBackup(); + addBackupTrace("finished: " + mStatus); } } else { if (DEBUG) Slog.i(TAG, "no backup data written; not calling transport"); + addBackupTrace("no data to send"); } // After successful transport, delete the now-stale data @@ -2165,12 +2242,14 @@ class BackupManagerService extends IBackupManager.Stub { Slog.e(TAG, "Timeout backing up " + mCurrentPackage.packageName); EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, mCurrentPackage.packageName, "timeout"); + addBackupTrace("timeout of " + mCurrentPackage.packageName); agentErrorCleanup(); dataChangedImpl(mCurrentPackage.packageName); } void revertAndEndBackup() { if (MORE_DEBUG) Slog.i(TAG, "Reverting backup queue - restaging everything"); + addBackupTrace("transport error; reverting"); for (BackupRequest request : mOriginalQueue) { dataChangedImpl(request.packageName); } @@ -2199,6 +2278,7 @@ class BackupManagerService extends IBackupManager.Stub { // If this was a pseudopackage there's no associated Activity Manager state if (mCurrentPackage.applicationInfo != null) { + addBackupTrace("unbinding " + mCurrentPackage.packageName); try { // unbind even on timeout, just in case mActivityManager.unbindBackupAgent(mCurrentPackage.applicationInfo); } catch (RemoteException e) {} @@ -2206,6 +2286,7 @@ class BackupManagerService extends IBackupManager.Stub { } void restartBackupAlarm() { + addBackupTrace("setting backup trigger"); synchronized (mQueueLock) { try { startBackupAlarmsLocked(mTransport.requestBackupTime()); @@ -2216,6 +2297,7 @@ class BackupManagerService extends IBackupManager.Stub { void executeNextState(BackupState nextState) { if (MORE_DEBUG) Slog.i(TAG, " => executing next step on " + this + " nextState=" + nextState); + addBackupTrace("executeNextState => " + nextState); mCurrentState = nextState; Message msg = mBackupHandler.obtainMessage(MSG_BACKUP_RESTORE_STEP, this); mBackupHandler.sendMessage(msg); @@ -2246,14 +2328,16 @@ class BackupManagerService extends IBackupManager.Stub { ParcelFileDescriptor mPipe; int mToken; boolean mSendApk; + boolean mWriteManifest; FullBackupRunner(PackageInfo pack, IBackupAgent agent, ParcelFileDescriptor pipe, - int token, boolean sendApk) throws IOException { + int token, boolean sendApk, boolean writeManifest) throws IOException { mPackage = pack; mAgent = agent; mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor()); mToken = token; mSendApk = sendApk; + mWriteManifest = writeManifest; } @Override @@ -2262,12 +2346,14 @@ class BackupManagerService extends IBackupManager.Stub { BackupDataOutput output = new BackupDataOutput( mPipe.getFileDescriptor()); - if (MORE_DEBUG) Slog.d(TAG, "Writing manifest for " + mPackage.packageName); - writeAppManifest(mPackage, mManifestFile, mSendApk); - FullBackup.backupToTar(mPackage.packageName, null, null, - mFilesDir.getAbsolutePath(), - mManifestFile.getAbsolutePath(), - output); + if (mWriteManifest) { + if (MORE_DEBUG) Slog.d(TAG, "Writing manifest for " + mPackage.packageName); + writeAppManifest(mPackage, mManifestFile, mSendApk); + FullBackup.backupToTar(mPackage.packageName, null, null, + mFilesDir.getAbsolutePath(), + mManifestFile.getAbsolutePath(), + output); + } if (mSendApk) { writeApkToBackup(mPackage, output); @@ -2354,10 +2440,13 @@ class BackupManagerService extends IBackupManager.Stub { } } - // Cull any packages that have indicated that backups are not permitted. + // Cull any packages that have indicated that backups are not permitted, as well + // as any explicit mention of the 'special' shared-storage agent package (we + // handle that one at the end). for (int i = 0; i < packagesToBackup.size(); ) { PackageInfo pkg = packagesToBackup.get(i); - if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) { + if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0 + || pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE)) { packagesToBackup.remove(i); } else { i++; @@ -2437,6 +2526,16 @@ class BackupManagerService extends IBackupManager.Stub { return; } + // Shared storage if requested + if (mIncludeShared) { + try { + pkg = mPackageManager.getPackageInfo(SHARED_BACKUP_AGENT_PACKAGE, 0); + packagesToBackup.add(pkg); + } catch (NameNotFoundException e) { + Slog.e(TAG, "Unable to find shared-storage backup handler"); + } + } + // Now back up the app data via the agent mechanism int N = packagesToBackup.size(); for (int i = 0; i < N; i++) { @@ -2444,11 +2543,6 @@ class BackupManagerService extends IBackupManager.Stub { backupOnePackage(pkg, out); } - // Shared storage if requested - if (mIncludeShared) { - backupSharedStorage(); - } - // Done! finalizeBackup(out); } catch (RemoteException e) { @@ -2554,19 +2648,21 @@ class BackupManagerService extends IBackupManager.Stub { if (agent != null) { ParcelFileDescriptor[] pipes = null; try { - pipes = ParcelFileDescriptor.createPipe(); + pipes = ParcelFileDescriptor.createPipe(); ApplicationInfo app = pkg.applicationInfo; + final boolean isSharedStorage = pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE); final boolean sendApk = mIncludeApks + && !isSharedStorage && ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0) && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 || (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); - sendOnBackupPackage(pkg.packageName); + sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName); final int token = generateToken(); FullBackupRunner runner = new FullBackupRunner(pkg, agent, pipes[1], - token, sendApk); + token, sendApk, !isSharedStorage); pipes[1].close(); // the runner has dup'd it pipes[1] = null; Thread t = new Thread(runner); @@ -2641,33 +2737,6 @@ class BackupManagerService extends IBackupManager.Stub { } } - private void backupSharedStorage() throws RemoteException { - PackageInfo pkg = null; - try { - pkg = mPackageManager.getPackageInfo("com.android.sharedstoragebackup", 0); - IBackupAgent agent = bindToAgentSynchronous(pkg.applicationInfo, - IApplicationThread.BACKUP_MODE_FULL); - if (agent != null) { - sendOnBackupPackage("Shared storage"); - - final int token = generateToken(); - prepareOperationTimeout(token, TIMEOUT_SHARED_BACKUP_INTERVAL, null); - agent.doFullBackup(mOutputFile, token, mBackupManagerBinder); - if (!waitUntilOperationComplete(token)) { - Slog.e(TAG, "Full backup failed on shared storage"); - } else { - if (DEBUG) Slog.d(TAG, "Full shared storage backup success"); - } - } else { - Slog.e(TAG, "Could not bind to shared storage backup agent"); - } - } catch (NameNotFoundException e) { - Slog.e(TAG, "Shared storage backup package not found"); - } finally { - tearDown(pkg); - } - } - private void finalizeBackup(OutputStream out) { try { // A standard 'tar' EOF sequence: two 512-byte blocks of all zeroes. @@ -2893,7 +2962,7 @@ class BackupManagerService extends IBackupManager.Stub { // Are we able to restore shared-storage data? if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - mPackagePolicies.put("com.android.sharedstoragebackup", RestorePolicy.ACCEPT); + mPackagePolicies.put(SHARED_BACKUP_AGENT_PACKAGE, RestorePolicy.ACCEPT); } FileInputStream rawInStream = null; @@ -3771,7 +3840,7 @@ class BackupManagerService extends IBackupManager.Stub { info.path, 0, FullBackup.SHARED_PREFIX.length())) { // File in shared storage. !!! TODO: implement this. info.path = info.path.substring(FullBackup.SHARED_PREFIX.length()); - info.packageName = "com.android.sharedstoragebackup"; + info.packageName = SHARED_BACKUP_AGENT_PACKAGE; info.domain = FullBackup.SHARED_STORAGE_TOKEN; if (DEBUG) Slog.i(TAG, "File in shared storage: " + info.path); } else if (FullBackup.APPS_PREFIX.regionMatches(0, @@ -4715,6 +4784,8 @@ class BackupManagerService extends IBackupManager.Stub { // one already there, then overwrite it, but no harm done. BackupRequest req = new BackupRequest(packageName); if (mPendingBackups.put(app.packageName, req) == null) { + if (DEBUG) Slog.d(TAG, "Now staging backup of " + packageName); + // 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. @@ -5736,6 +5807,17 @@ class BackupManagerService extends IBackupManager.Stub { pw.println(" " + s); } + if (DEBUG_BACKUP_TRACE) { + synchronized (mBackupTrace) { + if (!mBackupTrace.isEmpty()) { + pw.println("Most recent backup trace:"); + for (String s : mBackupTrace) { + pw.println(" " + s); + } + } + } + } + int N = mBackupParticipants.size(); pw.println("Participants:"); for (int i=0; i<N; i++) { diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 97fb0b0..a372fb8 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -992,11 +992,15 @@ private NetworkStateTracker makeWimaxStateTracker() { NetworkInfo ni = network.getNetworkInfo(); if (ni.isAvailable() == false) { - if (DBG) log("special network not available"); if (!TextUtils.equals(feature,Phone.FEATURE_ENABLE_DUN_ALWAYS)) { + if (DBG) log("special network not available ni=" + ni.getTypeName()); return Phone.APN_TYPE_NOT_AVAILABLE; } else { // else make the attempt anyway - probably giving REQUEST_STARTED below + if (DBG) { + log("special network not available, but try anyway ni=" + + ni.getTypeName()); + } } } @@ -1031,9 +1035,14 @@ private NetworkStateTracker makeWimaxStateTracker() { if ((ni.isConnectedOrConnecting() == true) && !network.isTeardownRequested()) { if (ni.isConnected() == true) { - // add the pid-specific dns - handleDnsConfigurationChange(usedNetworkType); - if (VDBG) log("special network already active"); + final long token = Binder.clearCallingIdentity(); + try { + // add the pid-specific dns + handleDnsConfigurationChange(usedNetworkType); + if (VDBG) log("special network already active"); + } finally { + Binder.restoreCallingIdentity(token); + } return Phone.APN_ALREADY_ACTIVE; } if (VDBG) log("special network already connecting"); @@ -1221,6 +1230,7 @@ private NetworkStateTracker makeWimaxStateTracker() { } if (!ConnectivityManager.isNetworkTypeValid(networkType)) { + if (DBG) log("requestRouteToHostAddress on invalid network: " + networkType); return false; } NetworkStateTracker tracker = mNetTrackers[networkType]; @@ -1233,11 +1243,16 @@ private NetworkStateTracker makeWimaxStateTracker() { } return false; } + final long token = Binder.clearCallingIdentity(); try { InetAddress addr = InetAddress.getByAddress(hostAddress); LinkProperties lp = tracker.getLinkProperties(); return addRouteToAddress(lp, addr); - } catch (UnknownHostException e) {} + } catch (UnknownHostException e) { + if (DBG) log("requestRouteToHostAddress got " + e.toString()); + } finally { + Binder.restoreCallingIdentity(token); + } return false; } @@ -1277,7 +1292,10 @@ private NetworkStateTracker makeWimaxStateTracker() { private boolean modifyRoute(String ifaceName, LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd, boolean toDefaultTable) { - if ((ifaceName == null) || (lp == null) || (r == null)) return false; + if ((ifaceName == null) || (lp == null) || (r == null)) { + if (DBG) log("modifyRoute got unexpected null: " + ifaceName + ", " + lp + ", " + r); + return false; + } if (cycleCount > MAX_HOSTROUTE_CYCLE_COUNT) { loge("Error modifying route - too much recursion"); @@ -1309,7 +1327,7 @@ private NetworkStateTracker makeWimaxStateTracker() { } } catch (Exception e) { // never crash - catch them all - if (VDBG) loge("Exception trying to add a route: " + e); + if (DBG) loge("Exception trying to add a route: " + e); return false; } } else { @@ -1323,7 +1341,7 @@ private NetworkStateTracker makeWimaxStateTracker() { mNetd.removeRoute(ifaceName, r); } catch (Exception e) { // never crash - catch them all - if (VDBG) loge("Exception trying to remove a route: " + e); + if (DBG) loge("Exception trying to remove a route: " + e); return false; } } else { @@ -1335,7 +1353,7 @@ private NetworkStateTracker makeWimaxStateTracker() { mNetd.removeSecondaryRoute(ifaceName, r); } catch (Exception e) { // never crash - catch them all - if (VDBG) loge("Exception trying to remove a route: " + e); + if (DBG) loge("Exception trying to remove a route: " + e); return false; } } @@ -2004,7 +2022,7 @@ private NetworkStateTracker makeWimaxStateTracker() { mNetd.removeRoute(ifaceName, r); } catch (Exception e) { // never crash - catch them all - if (VDBG) loge("Exception trying to remove a route: " + e); + if (DBG) loge("Exception trying to remove a route: " + e); } } } @@ -2218,7 +2236,7 @@ private NetworkStateTracker makeWimaxStateTracker() { mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses)); mNetd.setDefaultInterfaceForDns(iface); } catch (Exception e) { - if (VDBG) loge("exception setting default dns interface: " + e); + if (DBG) loge("exception setting default dns interface: " + e); } } if (!domains.equals(SystemProperties.get("net.dns.search"))) { @@ -2248,7 +2266,7 @@ private NetworkStateTracker makeWimaxStateTracker() { mNetd.setDnsServersForInterface(p.getInterfaceName(), NetworkUtils.makeStrings(dnses)); } catch (Exception e) { - if (VDBG) loge("exception setting dns servers: " + e); + if (DBG) loge("exception setting dns servers: " + e); } // set per-pid dns for attached secondary nets List pids = mNetRequestersPids[netType]; diff --git a/services/java/com/android/server/EntropyService.java b/services/java/com/android/server/EntropyMixer.java index 788a2f5..b63a70e 100644 --- a/services/java/com/android/server/EntropyService.java +++ b/services/java/com/android/server/EntropyMixer.java @@ -47,8 +47,8 @@ import android.util.Slog; * <p>TODO: Investigate attempting to write entropy data at shutdown time * instead of periodically. */ -public class EntropyService extends Binder { - private static final String TAG = "EntropyService"; +public class EntropyMixer extends Binder { + private static final String TAG = "EntropyMixer"; private static final int ENTROPY_WHAT = 1; private static final int ENTROPY_WRITE_PERIOD = 3 * 60 * 60 * 1000; // 3 hrs private static final long START_TIME = System.currentTimeMillis(); @@ -72,12 +72,12 @@ public class EntropyService extends Binder { } }; - public EntropyService() { + public EntropyMixer() { this(getSystemDir() + "/entropy.dat", "/dev/urandom"); } /** Test only interface, not for public use */ - public EntropyService(String entropyFile, String randomDevice) { + public EntropyMixer(String entropyFile, String randomDevice) { if (randomDevice == null) { throw new NullPointerException("randomDevice"); } if (entropyFile == null) { throw new NullPointerException("entropyFile"); } diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags index 4dad209..0bcec2e 100644 --- a/services/java/com/android/server/EventLogTags.logtags +++ b/services/java/com/android/server/EventLogTags.logtags @@ -142,5 +142,5 @@ option java_package com.android.server # --------------------------- # NetworkStatsService.java # --------------------------- -51100 netstats_mobile_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3),(dev_history_start|2|3) -51101 netstats_wifi_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3),(dev_history_start|2|3) +51100 netstats_mobile_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3) +51101 netstats_wifi_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3) diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index f5c4ed4..aba1bc6 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -142,6 +142,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; private static final String SUBTYPE_MODE_VOICE = "voice"; private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher"; + private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE = + "EnabledWhenDefaultIsNotAsciiCapable"; + private static final String TAG_ASCII_CAPABLE = "AsciiCapable"; final Context mContext; final Resources mRes; @@ -2163,6 +2166,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) { + final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtype(); + if (currentSubtype != null) { + final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId); + lastInputMethodSubtypeId = + getSubtypeIdFromHashCode(currentImi, currentSubtype.hashCode()); + } + } + final int N = imList.size(); mIms = new InputMethodInfo[N]; mSubtypeIds = new int[N]; @@ -2472,7 +2484,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = new HashMap<String, InputMethodSubtype>(); final int N = subtypes.size(); - boolean containsKeyboardSubtype = false; for (int i = 0; i < N; ++i) { // scan overriding implicitly enabled subtypes. InputMethodSubtype subtype = subtypes.get(i); @@ -2506,15 +2517,23 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!systemLocale.equals(locale)) continue; } applicableModeAndSubtypesMap.put(mode, subtype); - if (!containsKeyboardSubtype - && SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())) { - containsKeyboardSubtype = true; - } } } + final InputMethodSubtype keyboardSubtype + = applicableModeAndSubtypesMap.get(SUBTYPE_MODE_KEYBOARD); final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>( applicableModeAndSubtypesMap.values()); - if (!containsKeyboardSubtype) { + if (keyboardSubtype != null && !keyboardSubtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) { + for (int i = 0; i < N; ++i) { + final InputMethodSubtype subtype = subtypes.get(i); + final String mode = subtype.getMode(); + if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey( + TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) { + applicableSubtypes.add(subtype); + } + } + } + if (keyboardSubtype == null) { InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked( res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true); if (lastResortKeyboardSubtype != null) { diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 56afe7f..8cb9d99b 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -1962,8 +1962,10 @@ public class LocationManagerService extends ILocationManager.Stub implements Run } else { mNetworkState = LocationProvider.TEMPORARILY_UNAVAILABLE; } - NetworkInfo info = - (NetworkInfo)intent.getExtra(ConnectivityManager.EXTRA_NETWORK_INFO); + + final ConnectivityManager connManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + final NetworkInfo info = connManager.getActiveNetworkInfo(); // Notify location providers of current network state synchronized (mLock) { diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index 5425813..366160b 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -20,6 +20,7 @@ import com.android.internal.app.IMediaContainerService; import com.android.internal.util.XmlUtils; import com.android.server.am.ActivityManagerService; import com.android.server.pm.PackageManagerService; +import com.android.server.NativeDaemonConnector.Command; import android.Manifest; import android.content.BroadcastReceiver; @@ -564,8 +565,7 @@ class MountService extends IMountService.Stub } try { - mConnector.doCommand(String.format( - "volume %sshare %s %s", (enable ? "" : "un"), path, method)); + mConnector.execute("volume", enable ? "share" : "unshare", path, method); } catch (NativeDaemonConnectorException e) { Slog.e(TAG, "Failed to share/unshare", e); } @@ -633,8 +633,9 @@ class MountService extends IMountService.Stub * Determine media state and UMS detection status */ try { - String[] vols = mConnector.doListCommand( - "volume list", VoldResponseCode.VolumeListResult); + final String[] vols = NativeDaemonEvent.filterMessageList( + mConnector.executeForList("volume", "list"), + VoldResponseCode.VolumeListResult); for (String volstr : vols) { String[] tok = volstr.split(" "); // FMT: <label> <mountpoint> <state> @@ -839,7 +840,7 @@ class MountService extends IMountService.Stub if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path); try { - mConnector.doCommand(String.format("volume mount %s", path)); + mConnector.execute("volume", "mount", path); } catch (NativeDaemonConnectorException e) { /* * Mount failed for some reason @@ -909,10 +910,13 @@ class MountService extends IMountService.Stub // Redundant probably. But no harm in updating state again. mPms.updateExternalMediaStatus(false, false); try { - String arg = removeEncryption - ? " force_and_revert" - : (force ? " force" : ""); - mConnector.doCommand(String.format("volume unmount %s%s", path, arg)); + final Command cmd = new Command("volume", "unmount", path); + if (removeEncryption) { + cmd.appendArg("force_and_revert"); + } else if (force) { + cmd.appendArg("force"); + } + mConnector.execute(cmd); // We unmounted the volume. None of the asec containers are available now. synchronized (mAsecMountSet) { mAsecMountSet.clear(); @@ -934,8 +938,7 @@ class MountService extends IMountService.Stub private int doFormatVolume(String path) { try { - String cmd = String.format("volume format %s", path); - mConnector.doCommand(cmd); + mConnector.execute("volume", "format", path); return StorageResultCode.OperationSucceeded; } catch (NativeDaemonConnectorException e) { int code = e.getCode(); @@ -950,39 +953,19 @@ 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; - + final NativeDaemonEvent event; try { - rsp = mConnector.doCommand(cmd); + event = mConnector.execute("volume", "shared", path, method); } 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(" "); - 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]); - } catch (NumberFormatException nfe) { - Slog.e(TAG, String.format("Error parsing code %s", tok[0])); - return false; - } - if (code == VoldResponseCode.ShareEnabledResult) { - return "enabled".equals(tok[2]); - } else { - Slog.e(TAG, String.format("Unexpected response code %d", code)); - return false; - } + if (event.getCode() == VoldResponseCode.ShareEnabledResult) { + return event.getMessage().endsWith("enabled"); + } else { + return false; } - Slog.e(TAG, "Got an empty response"); - return false; } private void notifyShareAvailabilityChange(final boolean avail) { @@ -1186,7 +1169,7 @@ class MountService extends IMountService.Stub * amount of containers we'd ever expect to have. This keeps an * "asec list" from blocking a thread repeatedly. */ - mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG); + mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25); mReady = false; Thread thread = new Thread(mConnector, VOLD_TAG); thread.start(); @@ -1410,13 +1393,14 @@ class MountService extends IMountService.Stub validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); waitForReady(); try { - String[] r = mConnector.doListCommand( - String.format("storage users %s", path), - VoldResponseCode.StorageUsersListResult); + final String[] r = NativeDaemonEvent.filterMessageList( + mConnector.executeForList("storage", "users", path), + VoldResponseCode.StorageUsersListResult); + // FMT: <pid> <process name> int[] data = new int[r.length]; for (int i = 0; i < r.length; i++) { - String []tok = r[i].split(" "); + String[] tok = r[i].split(" "); try { data[i] = Integer.parseInt(tok[0]); } catch (NumberFormatException nfe) { @@ -1443,7 +1427,8 @@ class MountService extends IMountService.Stub warnOnNotMounted(); try { - return mConnector.doListCommand("asec list", VoldResponseCode.AsecListResult); + return NativeDaemonEvent.filterMessageList( + mConnector.executeForList("asec", "list"), VoldResponseCode.AsecListResult); } catch (NativeDaemonConnectorException e) { return new String[0]; } @@ -1456,9 +1441,8 @@ class MountService extends IMountService.Stub warnOnNotMounted(); int rc = StorageResultCode.OperationSucceeded; - String cmd = String.format("asec create %s %d %s %s %d", id, sizeMb, fstype, key, ownerUid); try { - mConnector.doCommand(cmd); + mConnector.execute("asec", "create", id, sizeMb, fstype, key, ownerUid); } catch (NativeDaemonConnectorException e) { rc = StorageResultCode.OperationFailedInternalError; } @@ -1477,7 +1461,7 @@ class MountService extends IMountService.Stub int rc = StorageResultCode.OperationSucceeded; try { - mConnector.doCommand(String.format("asec finalize %s", id)); + mConnector.execute("asec", "finalize", id); /* * Finalization does a remount, so no need * to update mAsecMountSet @@ -1503,7 +1487,11 @@ class MountService extends IMountService.Stub int rc = StorageResultCode.OperationSucceeded; try { - mConnector.doCommand(String.format("asec destroy %s%s", id, (force ? " force" : ""))); + final Command cmd = new Command("asec", "destroy", id); + if (force) { + cmd.appendArg("force"); + } + mConnector.execute(cmd); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code == VoldResponseCode.OpFailedStorageBusy) { @@ -1536,9 +1524,8 @@ class MountService extends IMountService.Stub } int rc = StorageResultCode.OperationSucceeded; - String cmd = String.format("asec mount %s %s %d", id, key, ownerUid); try { - mConnector.doCommand(cmd); + mConnector.execute("asec", "mount", id, key, ownerUid); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code != VoldResponseCode.OpFailedStorageBusy) { @@ -1574,9 +1561,12 @@ class MountService extends IMountService.Stub Runtime.getRuntime().gc(); int rc = StorageResultCode.OperationSucceeded; - String cmd = String.format("asec unmount %s%s", id, (force ? " force" : "")); try { - mConnector.doCommand(cmd); + final Command cmd = new Command("asec", "unmount", id); + if (force) { + cmd.appendArg("force"); + } + mConnector.execute(cmd); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code == VoldResponseCode.OpFailedStorageBusy) { @@ -1620,9 +1610,8 @@ class MountService extends IMountService.Stub } int rc = StorageResultCode.OperationSucceeded; - String cmd = String.format("asec rename %s %s", oldId, newId); try { - mConnector.doCommand(cmd); + mConnector.execute("asec", "rename", oldId, newId); } catch (NativeDaemonConnectorException e) { rc = StorageResultCode.OperationFailedInternalError; } @@ -1635,14 +1624,11 @@ class MountService extends IMountService.Stub waitForReady(); warnOnNotMounted(); + final NativeDaemonEvent event; try { - ArrayList<String> rsp = mConnector.doCommand(String.format("asec path %s", id)); - 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]; + event = mConnector.execute("asec", "path", id); + event.checkCode(VoldResponseCode.AsecPathResult); + return event.getMessage(); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code == VoldResponseCode.OpFailedStorageNotFound) { @@ -1659,14 +1645,11 @@ class MountService extends IMountService.Stub waitForReady(); warnOnNotMounted(); + final NativeDaemonEvent event; try { - ArrayList<String> rsp = mConnector.doCommand(String.format("asec fspath %s", id)); - 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]; + event = mConnector.execute("asec", "fspath", id); + event.checkCode(VoldResponseCode.AsecPathResult); + return event.getMessage(); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code == VoldResponseCode.OpFailedStorageNotFound) { @@ -1709,14 +1692,11 @@ class MountService extends IMountService.Stub waitForReady(); warnOnNotMounted(); + final NativeDaemonEvent event; 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]; + event = mConnector.execute("obb", "path", filename); + event.checkCode(VoldResponseCode.AsecPathResult); + return event.getMessage(); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code == VoldResponseCode.OpFailedStorageNotFound) { @@ -1778,18 +1758,10 @@ class MountService extends IMountService.Stub waitForReady(); + final NativeDaemonEvent event; try { - ArrayList<String> rsp = mConnector.doCommand("cryptfs cryptocomplete"); - String[] tokens = rsp.get(0).split(" "); - - if (tokens == null || tokens.length != 2) { - // Unexpected. - Slog.w(TAG, "Unexpected result from cryptfs cryptocomplete"); - return ENCRYPTION_STATE_ERROR_UNKNOWN; - } - - return Integer.parseInt(tokens[1]); - + event = mConnector.execute("cryptfs", "cryptocomplete"); + return Integer.parseInt(event.getMessage()); } catch (NumberFormatException e) { // Bad result - unexpected. Slog.w(TAG, "Unable to parse result from cryptfs cryptocomplete"); @@ -1816,22 +1788,21 @@ class MountService extends IMountService.Stub Slog.i(TAG, "decrypting storage..."); } + final NativeDaemonEvent event; try { - ArrayList<String> rsp = mConnector.doCommand("cryptfs checkpw " + password); - String[] tokens = rsp.get(0).split(" "); - - if (tokens == null || tokens.length != 2) { - return -1; - } - - int code = Integer.parseInt(tokens[1]); + event = mConnector.execute("cryptfs", "checkpw", password); + final int code = Integer.parseInt(event.getMessage()); if (code == 0) { // Decrypt was successful. Post a delayed message before restarting in order // to let the UI to clear itself mHandler.postDelayed(new Runnable() { public void run() { - mConnector.doCommand(String.format("cryptfs restart")); + try { + mConnector.execute("cryptfs", "restart"); + } catch (NativeDaemonConnectorException e) { + Slog.e(TAG, "problem executing in background", e); + } } }, 1000); // 1 second } @@ -1858,7 +1829,7 @@ class MountService extends IMountService.Stub } try { - mConnector.doCommand(String.format("cryptfs enablecrypto inplace %s", password)); + mConnector.execute("cryptfs", "enablecrypto", "inplace", password); } catch (NativeDaemonConnectorException e) { // Encryption failed return e.getCode(); @@ -1881,16 +1852,10 @@ class MountService extends IMountService.Stub Slog.i(TAG, "changing encryption password..."); } + final NativeDaemonEvent event; try { - ArrayList<String> response = mConnector.doCommand("cryptfs changepw " + password); - - String[] tokens = response.get(0).split(" "); - - if (tokens == null || tokens.length != 2) { - return -1; - } - - return Integer.parseInt(tokens[1]); + event = mConnector.execute("cryptfs", "changepw", password); + return Integer.parseInt(event.getMessage()); } catch (NativeDaemonConnectorException e) { // Encryption failed return e.getCode(); @@ -1920,24 +1885,11 @@ class MountService extends IMountService.Stub Slog.i(TAG, "validating encryption password..."); } + final NativeDaemonEvent event; try { - ArrayList<String> response = mConnector.doCommand("cryptfs verifypw " + password); - String[] tokens = response.get(0).split(" "); - - if (tokens == null || tokens.length != 2) { - String msg = "Unexpected result from cryptfs verifypw: {"; - if (tokens == null) msg += "null"; - else for (int i = 0; i < tokens.length; i++) { - if (i != 0) msg += ','; - msg += tokens[i]; - } - msg += '}'; - Slog.e(TAG, msg); - return -1; - } - - Slog.i(TAG, "cryptfs verifypw => " + tokens[1]); - return Integer.parseInt(tokens[1]); + event = mConnector.execute("cryptfs", "verifypw", password); + Slog.i(TAG, "cryptfs verifypw => " + event.getMessage()); + return Integer.parseInt(event.getMessage()); } catch (NativeDaemonConnectorException e) { // Encryption failed return e.getCode(); @@ -2296,10 +2248,9 @@ class MountService extends IMountService.Stub } int rc = StorageResultCode.OperationSucceeded; - String cmd = String.format("obb mount %s %s %d", mObbState.filename, hashedKey, - mObbState.callerUid); try { - mConnector.doCommand(cmd); + mConnector.execute( + "obb", "mount", mObbState.filename, hashedKey, mObbState.callerUid); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code != VoldResponseCode.OpFailedStorageBusy) { @@ -2380,10 +2331,12 @@ class MountService extends IMountService.Stub mObbState.filename = obbInfo.filename; int rc = StorageResultCode.OperationSucceeded; - String cmd = String.format("obb unmount %s%s", mObbState.filename, - (mForceUnmount ? " force" : "")); try { - mConnector.doCommand(cmd); + final Command cmd = new Command("obb", "unmount", mObbState.filename); + if (mForceUnmount) { + cmd.appendArg("force"); + } + mConnector.execute(cmd); } catch (NativeDaemonConnectorException e) { int code = e.getCode(); if (code == VoldResponseCode.OpFailedStorageBusy) { @@ -2476,6 +2429,10 @@ class MountService extends IMountService.Stub pw.println(v.toString()); } } + + pw.println(); + pw.println(" mConnection:"); + mConnector.dump(fd, pw, args); } /** {@inheritDoc} */ diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java index 28013bd..308661f 100644 --- a/services/java/com/android/server/NativeDaemonConnector.java +++ b/services/java/com/android/server/NativeDaemonConnector.java @@ -22,59 +22,51 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.os.SystemClock; +import android.util.LocalLog; import android.util.Slog; +import com.google.android.collect.Lists; + +import java.nio.charset.Charsets; +import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; /** - * Generic connector class for interfacing with a native - * daemon which uses the libsysutils FrameworkListener - * protocol. + * Generic connector class for interfacing with a native daemon which uses the + * {@code libsysutils} FrameworkListener protocol. */ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor { - private static final boolean LOCAL_LOGD = false; + private static final boolean LOGD = false; - private BlockingQueue<String> mResponseQueue; - private OutputStream mOutputStream; - private String TAG = "NativeDaemonConnector"; - private String mSocket; - private INativeDaemonConnectorCallbacks mCallbacks; - private Handler mCallbackHandler; + private final String TAG; - /** Lock held whenever communicating with native daemon. */ - private Object mDaemonLock = new Object(); + private String mSocket; + private OutputStream mOutputStream; + private LocalLog mLocalLog; - private final int BUFFER_SIZE = 4096; + private final BlockingQueue<NativeDaemonEvent> mResponseQueue; - class ResponseCode { - public static final int ActionInitiated = 100; - - public static final int CommandOkay = 200; - - // The range of 400 -> 599 is reserved for cmd failures - public static final int OperationFailed = 400; - public static final int CommandSyntaxError = 500; - public static final int CommandParameterError = 501; + private INativeDaemonConnectorCallbacks mCallbacks; + private Handler mCallbackHandler; - public static final int UnsolicitedInformational = 600; + /** Lock held whenever communicating with native daemon. */ + private final Object mDaemonLock = new Object(); - // - public static final int FailedRangeStart = 400; - public static final int FailedRangeEnd = 599; - } + private final int BUFFER_SIZE = 4096; - NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, - String socket, int responseQueueSize, String logTag) { + NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket, + int responseQueueSize, String logTag, int maxLogSize) { mCallbacks = callbacks; - if (logTag != null) - TAG = logTag; mSocket = socket; - mResponseQueue = new LinkedBlockingQueue<String>(responseQueueSize); + mResponseQueue = new LinkedBlockingQueue<NativeDaemonEvent>(responseQueueSize); + TAG = logTag != null ? logTag : "NativeDaemonConnector"; + mLocalLog = new LocalLog(maxLogSize); } @Override @@ -136,29 +128,35 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo for (int i = 0; i < count; i++) { if (buffer[i] == 0) { - String event = new String(buffer, start, i - start); - if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event)); + final String rawEvent = new String( + buffer, start, i - start, Charsets.UTF_8); + log("RCV <- {" + rawEvent + "}"); - String[] tokens = event.split(" ", 2); try { - int code = Integer.parseInt(tokens[0]); - - if (code >= ResponseCode.UnsolicitedInformational) { - mCallbackHandler.sendMessage( - mCallbackHandler.obtainMessage(code, event)); + final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent( + rawEvent); + if (event.isClassUnsolicited()) { + // TODO: migrate to sending NativeDaemonEvent instances + mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage( + event.getCode(), event.getRawEvent())); } else { try { mResponseQueue.put(event); } catch (InterruptedException ex) { - Slog.e(TAG, "Failed to put response onto queue", ex); + Slog.e(TAG, "Failed to put response onto queue: " + ex); } } - } catch (NumberFormatException nfe) { - Slog.w(TAG, String.format("Bad msg (%s)", event)); + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Problem parsing message: " + rawEvent, e); } + start = i + 1; } } + if (start == 0) { + final String rawEvent = new String(buffer, start, count, Charsets.UTF_8); + log("RCV incomplete <- {" + rawEvent + "}"); + } // We should end at the amount we read. If not, compact then // buffer and read again. @@ -195,137 +193,265 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo } } - private void sendCommandLocked(String command) throws NativeDaemonConnectorException { - sendCommandLocked(command, null); - } - /** - * Sends a command to the daemon with a single argument + * Send command to daemon, escaping arguments as needed. * - * @param command The command to send to the daemon - * @param argument The argument to send with the command (or null) + * @return the final command issued. */ - private void sendCommandLocked(String command, String argument) + private String sendCommandLocked(String cmd, Object... args) throws NativeDaemonConnectorException { - if (command != null && command.indexOf('\0') >= 0) { - throw new IllegalArgumentException("unexpected command: " + command); + // TODO: eventually enforce that cmd doesn't contain arguments + if (cmd.indexOf('\0') >= 0) { + throw new IllegalArgumentException("unexpected command: " + cmd); } - if (argument != null && argument.indexOf('\0') >= 0) { - throw new IllegalArgumentException("unexpected argument: " + argument); + + final StringBuilder builder = new StringBuilder(cmd); + for (Object arg : args) { + final String argString = String.valueOf(arg); + if (argString.indexOf('\0') >= 0) { + throw new IllegalArgumentException("unexpected argument: " + arg); + } + + builder.append(' '); + appendEscaped(builder, argString); } - if (LOCAL_LOGD) Slog.d(TAG, String.format("SND -> {%s} {%s}", command, argument)); + final String unterminated = builder.toString(); + log("SND -> {" + unterminated + "}"); + + builder.append('\0'); + if (mOutputStream == null) { - Slog.e(TAG, "No connection to daemon", new IllegalStateException()); - throw new NativeDaemonConnectorException("No output stream!"); + throw new NativeDaemonConnectorException("missing output stream"); } else { - StringBuilder builder = new StringBuilder(command); - if (argument != null) { - builder.append(argument); - } - builder.append('\0'); - try { - mOutputStream.write(builder.toString().getBytes()); - } catch (IOException ex) { - Slog.e(TAG, "IOException in sendCommand", ex); + mOutputStream.write(builder.toString().getBytes(Charsets.UTF_8)); + } catch (IOException e) { + throw new NativeDaemonConnectorException("problem sending command", e); } } + + return unterminated; + } + + /** + * Issue the given command to the native daemon and return a single expected + * response. + * + * @throws NativeDaemonConnectorException when problem communicating with + * native daemon, or if the response matches + * {@link NativeDaemonEvent#isClassClientError()} or + * {@link NativeDaemonEvent#isClassServerError()}. + */ + public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException { + return execute(cmd.mCmd, cmd.mArguments.toArray()); + } + + /** + * Issue the given command to the native daemon and return a single expected + * response. + * + * @throws NativeDaemonConnectorException when problem communicating with + * native daemon, or if the response matches + * {@link NativeDaemonEvent#isClassClientError()} or + * {@link NativeDaemonEvent#isClassServerError()}. + */ + public NativeDaemonEvent execute(String cmd, Object... args) + throws NativeDaemonConnectorException { + final NativeDaemonEvent[] events = executeForList(cmd, args); + if (events.length != 1) { + throw new NativeDaemonConnectorException( + "Expected exactly one response, but received " + events.length); + } + return events[0]; } /** - * Issue a command to the native daemon and return the responses + * Issue the given command to the native daemon and return any + * {@link NativeDaemonEvent#isClassContinue()} responses, including the + * final terminal response. + * + * @throws NativeDaemonConnectorException when problem communicating with + * native daemon, or if the response matches + * {@link NativeDaemonEvent#isClassClientError()} or + * {@link NativeDaemonEvent#isClassServerError()}. */ - public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException { + public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException { + return executeForList(cmd.mCmd, cmd.mArguments.toArray()); + } + + /** + * Issue the given command to the native daemon and return any + * {@link NativeDaemonEvent#isClassContinue()} responses, including the + * final terminal response. + * + * @throws NativeDaemonConnectorException when problem communicating with + * native daemon, or if the response matches + * {@link NativeDaemonEvent#isClassClientError()} or + * {@link NativeDaemonEvent#isClassServerError()}. + */ + public NativeDaemonEvent[] executeForList(String cmd, Object... args) + throws NativeDaemonConnectorException { synchronized (mDaemonLock) { - return doCommandLocked(cmd); + return executeLocked(cmd, args); } } - private ArrayList<String> doCommandLocked(String cmd) throws NativeDaemonConnectorException { - mResponseQueue.clear(); - sendCommandLocked(cmd); - - ArrayList<String> response = new ArrayList<String>(); - boolean complete = false; - int code = -1; + private NativeDaemonEvent[] executeLocked(String cmd, Object... args) + throws NativeDaemonConnectorException { + final ArrayList<NativeDaemonEvent> events = Lists.newArrayList(); - while (!complete) { + while (mResponseQueue.size() > 0) { 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(" "); - try { - code = Integer.parseInt(tokens[0]); - } catch (NumberFormatException nfe) { - throw new NativeDaemonConnectorException( - String.format("Invalid response from daemon (%s)", line)); - } + log("ignoring {" + mResponseQueue.take() + "}"); + } catch (Exception e) {} + } - if ((code >= 200) && (code < 600)) { - complete = true; - } - response.add(line); - } catch (InterruptedException ex) { - Slog.e(TAG, "Failed to process response", ex); + final String sentCommand = sendCommandLocked(cmd, args); + + NativeDaemonEvent event = null; + do { + try { + event = mResponseQueue.take(); + } catch (InterruptedException e) { + Slog.w(TAG, "interrupted waiting for event line"); + continue; } + events.add(event); + } while (event.isClassContinue()); + + if (event.isClassClientError()) { + throw new NativeDaemonArgumentException(sentCommand, event); + } + if (event.isClassServerError()) { + throw new NativeDaemonFailureException(sentCommand, event); } - if (code >= ResponseCode.FailedRangeStart && - code <= ResponseCode.FailedRangeEnd) { - /* - * Note: The format of the last response in this case is - * "NNN <errmsg>" - */ - throw new NativeDaemonConnectorException( - code, cmd, response.get(response.size()-1).substring(4)); + return events.toArray(new NativeDaemonEvent[events.size()]); + } + + /** + * Issue a command to the native daemon and return the raw responses. + * + * @deprecated callers should move to {@link #execute(String, Object...)} + * which returns parsed {@link NativeDaemonEvent}. + */ + @Deprecated + public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException { + final ArrayList<String> rawEvents = Lists.newArrayList(); + final NativeDaemonEvent[] events = executeForList(cmd); + for (NativeDaemonEvent event : events) { + rawEvents.add(event.getRawEvent()); } - return response; + return rawEvents; } /** - * Issues a list command and returns the cooked list + * Issues a list command and returns the cooked list of all + * {@link NativeDaemonEvent#getMessage()} which match requested code. */ - public String[] doListCommand(String cmd, int expectedResponseCode) + @Deprecated + public String[] doListCommand(String cmd, int expectedCode) throws NativeDaemonConnectorException { + final ArrayList<String> list = Lists.newArrayList(); + + final NativeDaemonEvent[] events = executeForList(cmd); + for (int i = 0; i < events.length - 1; i++) { + final NativeDaemonEvent event = events[i]; + final int code = event.getCode(); + if (code == expectedCode) { + list.add(event.getMessage()); + } else { + throw new NativeDaemonConnectorException( + "unexpected list response " + code + " instead of " + expectedCode); + } + } - ArrayList<String> rsp = doCommand(cmd); - String[] rdata = new String[rsp.size()-1]; - int idx = 0; + final NativeDaemonEvent finalEvent = events[events.length - 1]; + if (!finalEvent.isClassOk()) { + throw new NativeDaemonConnectorException("unexpected final event: " + finalEvent); + } - for (int i = 0; i < rsp.size(); i++) { - String line = rsp.get(i); - try { - String[] tok = line.split(" "); - int code = Integer.parseInt(tok[0]); - if (code == expectedResponseCode) { - rdata[idx++] = line.substring(tok[0].length() + 1); - } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) { - if (LOCAL_LOGD) Slog.d(TAG, String.format("List terminated with {%s}", line)); - int last = rsp.size() -1; - if (i != last) { - Slog.w(TAG, String.format("Recv'd %d lines after end of list {%s}", (last-i), cmd)); - for (int j = i; j <= last ; j++) { - Slog.w(TAG, String.format("ExtraData <%s>", rsp.get(i))); - } - } - return rdata; - } else { - throw new NativeDaemonConnectorException( - String.format("Expected list response %d, but got %d", - expectedResponseCode, code)); - } - } catch (NumberFormatException nfe) { - throw new NativeDaemonConnectorException( - String.format("Error reading code '%s'", line)); + return list.toArray(new String[list.size()]); + } + + /** + * Append the given argument to {@link StringBuilder}, escaping as needed, + * and surrounding with quotes when it contains spaces. + */ + // @VisibleForTesting + static void appendEscaped(StringBuilder builder, String arg) { + final boolean hasSpaces = arg.indexOf(' ') >= 0; + if (hasSpaces) { + builder.append('"'); + } + + final int length = arg.length(); + for (int i = 0; i < length; i++) { + final char c = arg.charAt(i); + + if (c == '"') { + builder.append("\\\""); + } else if (c == '\\') { + builder.append("\\\\"); + } else { + builder.append(c); } } - throw new NativeDaemonConnectorException("Got an empty response"); + + if (hasSpaces) { + builder.append('"'); + } + } + + private static class NativeDaemonArgumentException extends NativeDaemonConnectorException { + public NativeDaemonArgumentException(String command, NativeDaemonEvent event) { + super(command, event); + } + + @Override + public IllegalArgumentException rethrowAsParcelableException() { + throw new IllegalArgumentException(getMessage(), this); + } + } + + private static class NativeDaemonFailureException extends NativeDaemonConnectorException { + public NativeDaemonFailureException(String command, NativeDaemonEvent event) { + super(command, event); + } + } + + /** + * Command builder that handles argument list building. + */ + public static class Command { + private String mCmd; + private ArrayList<Object> mArguments = Lists.newArrayList(); + + public Command(String cmd, Object... args) { + mCmd = cmd; + for (Object arg : args) { + appendArg(arg); + } + } + + public Command appendArg(Object arg) { + mArguments.add(arg); + return this; + } } /** {@inheritDoc} */ public void monitor() { synchronized (mDaemonLock) { } } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mLocalLog.dump(fd, pw, args); + } + + private void log(String logstring) { + if (LOGD) Slog.d(TAG, logstring); + mLocalLog.log(logstring); + } } diff --git a/services/java/com/android/server/NativeDaemonConnectorException.java b/services/java/com/android/server/NativeDaemonConnectorException.java index 426742b..590bbcc 100644 --- a/services/java/com/android/server/NativeDaemonConnectorException.java +++ b/services/java/com/android/server/NativeDaemonConnectorException.java @@ -16,33 +16,43 @@ package com.android.server; +import android.os.Parcel; + /** - * An exception that indicates there was an error with a NativeDaemonConnector operation + * An exception that indicates there was an error with a + * {@link NativeDaemonConnector} operation. */ -public class NativeDaemonConnectorException extends RuntimeException -{ - private int mCode = -1; +public class NativeDaemonConnectorException extends Exception { private String mCmd; + private NativeDaemonEvent mEvent; - public NativeDaemonConnectorException() {} + public NativeDaemonConnectorException(String detailMessage) { + super(detailMessage); + } - public NativeDaemonConnectorException(String error) - { - super(error); + public NativeDaemonConnectorException(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); } - public NativeDaemonConnectorException(int code, String cmd, String error) - { - super(String.format("Cmd {%s} failed with code %d : {%s}", cmd, code, error)); - mCode = code; + public NativeDaemonConnectorException(String cmd, NativeDaemonEvent event) { + super("command '" + cmd + "' failed with '" + event + "'"); mCmd = cmd; + mEvent = event; } public int getCode() { - return mCode; + return mEvent.getCode(); } public String getCmd() { return mCmd; } + + /** + * Rethrow as a {@link RuntimeException} subclass that is handled by + * {@link Parcel#writeException(Exception)}. + */ + public IllegalArgumentException rethrowAsParcelableException() { + throw new IllegalStateException(getMessage(), this); + } } diff --git a/services/java/com/android/server/NativeDaemonEvent.java b/services/java/com/android/server/NativeDaemonEvent.java new file mode 100644 index 0000000..62084c0 --- /dev/null +++ b/services/java/com/android/server/NativeDaemonEvent.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import com.google.android.collect.Lists; + +import java.util.ArrayList; + +/** + * Parsed event from native side of {@link NativeDaemonConnector}. + */ +public class NativeDaemonEvent { + + // TODO: keep class ranges in sync with ResponseCode.h + // TODO: swap client and server error ranges to roughly mirror HTTP spec + + private final int mCode; + private final String mMessage; + private final String mRawEvent; + + private NativeDaemonEvent(int code, String message, String rawEvent) { + mCode = code; + mMessage = message; + mRawEvent = rawEvent; + } + + public int getCode() { + return mCode; + } + + public String getMessage() { + return mMessage; + } + + @Deprecated + public String getRawEvent() { + return mRawEvent; + } + + @Override + public String toString() { + return mRawEvent; + } + + /** + * Test if event represents a partial response which is continued in + * additional subsequent events. + */ + public boolean isClassContinue() { + return mCode >= 100 && mCode < 200; + } + + /** + * Test if event represents a command success. + */ + public boolean isClassOk() { + return mCode >= 200 && mCode < 300; + } + + /** + * Test if event represents a remote native daemon error. + */ + public boolean isClassServerError() { + return mCode >= 400 && mCode < 500; + } + + /** + * Test if event represents a command syntax or argument error. + */ + public boolean isClassClientError() { + return mCode >= 500 && mCode < 600; + } + + /** + * Test if event represents an unsolicited event from native daemon. + */ + public boolean isClassUnsolicited() { + return mCode >= 600 && mCode < 700; + } + + /** + * Verify this event matches the given code. + * + * @throws IllegalStateException if {@link #getCode()} doesn't match. + */ + public void checkCode(int code) { + if (mCode != code) { + throw new IllegalStateException("Expected " + code + " but was: " + this); + } + } + + /** + * Parse the given raw event into {@link NativeDaemonEvent} instance. + * + * @throws IllegalArgumentException when line doesn't match format expected + * from native side. + */ + public static NativeDaemonEvent parseRawEvent(String rawEvent) { + final int splitIndex = rawEvent.indexOf(' '); + if (splitIndex == -1) { + throw new IllegalArgumentException("unable to find ' ' separator"); + } + + final int code; + try { + code = Integer.parseInt(rawEvent.substring(0, splitIndex)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("problem parsing code", e); + } + + final String message = rawEvent.substring(splitIndex + 1); + return new NativeDaemonEvent(code, message, rawEvent); + } + + /** + * Filter the given {@link NativeDaemonEvent} list, returning + * {@link #getMessage()} for any events matching the requested code. + */ + public static String[] filterMessageList(NativeDaemonEvent[] events, int matchCode) { + final ArrayList<String> result = Lists.newArrayList(); + for (NativeDaemonEvent event : events) { + if (event.getCode() == matchCode) { + result.add(event.getMessage()); + } + } + return result.toArray(new String[result.size()]); + } +} diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index 75e5366..7bb7938 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -16,19 +16,27 @@ package com.android.server; -import static android.Manifest.permission.ACCESS_NETWORK_STATE; -import static android.Manifest.permission.CHANGE_NETWORK_STATE; +import static android.Manifest.permission.CONNECTIVITY_INTERNAL; import static android.Manifest.permission.DUMP; -import static android.Manifest.permission.MANAGE_NETWORK_POLICY; +import static android.Manifest.permission.SHUTDOWN; import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.TrafficStats.UID_TETHERING; import static android.provider.Settings.Secure.NETSTATS_ENABLED; +import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceRxThrottleResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceTxThrottleResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.IpFwdStatusResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.TetherDnsFwdTgtListResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.TetherInterfaceListResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.TetherStatusResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.TetheringStatsResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.TtyListResult; import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED; import android.content.Context; -import android.content.pm.PackageManager; import android.net.INetworkManagementEventObserver; import android.net.InterfaceConfiguration; import android.net.LinkAddress; @@ -37,8 +45,9 @@ import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; -import android.os.Binder; import android.os.INetworkManagementService; +import android.os.RemoteCallbackList; +import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; @@ -47,6 +56,7 @@ import android.util.Slog; import android.util.SparseBooleanArray; import com.android.internal.net.NetworkStatsFactory; +import com.android.server.NativeDaemonConnector.Command; import com.google.android.collect.Sets; import java.io.BufferedReader; @@ -78,8 +88,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub private static final boolean DBG = false; private static final String NETD_TAG = "NetdConnector"; - private static final int ADD = 1; - private static final int REMOVE = 2; + private static final String ADD = "add"; + private static final String REMOVE = "remove"; private static final String DEFAULT = "default"; private static final String SECONDARY = "secondary"; @@ -125,8 +135,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub private Thread mThread; private final CountDownLatch mConnectedSignal = new CountDownLatch(1); - // TODO: replace with RemoteCallbackList - private ArrayList<INetworkManagementEventObserver> mObservers; + private final RemoteCallbackList<INetworkManagementEventObserver> mObservers = + new RemoteCallbackList<INetworkManagementEventObserver>(); private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory(); @@ -147,14 +157,13 @@ public class NetworkManagementService extends INetworkManagementService.Stub */ private NetworkManagementService(Context context) { mContext = context; - mObservers = new ArrayList<INetworkManagementEventObserver>(); if ("simulator".equals(SystemProperties.get("ro.product.device"))) { return; } mConnector = new NativeDaemonConnector( - new NetdCallbackReceiver(), "netd", 10, NETD_TAG); + new NetdCallbackReceiver(), "netd", 10, NETD_TAG, 50); mThread = new Thread(mConnector, NETD_TAG); // Add ourself to the Watchdog monitors. @@ -181,7 +190,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub if (hasKernelSupport && shouldEnable) { Slog.d(TAG, "enabling bandwidth control"); try { - mConnector.doCommand("bandwidth enable"); + mConnector.execute("bandwidth", "enable"); mBandwidthControlEnabled = true; } catch (NativeDaemonConnectorException e) { Log.wtf(TAG, "problem enabling bandwidth controls", e); @@ -193,27 +202,30 @@ public class NetworkManagementService extends INetworkManagementService.Stub SystemProperties.set(PROP_QTAGUID_ENABLED, mBandwidthControlEnabled ? "1" : "0"); } - public void registerObserver(INetworkManagementEventObserver obs) { - Slog.d(TAG, "Registering observer"); - mObservers.add(obs); + @Override + public void registerObserver(INetworkManagementEventObserver observer) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + mObservers.register(observer); } - public void unregisterObserver(INetworkManagementEventObserver obs) { - Slog.d(TAG, "Unregistering observer"); - mObservers.remove(mObservers.indexOf(obs)); + @Override + public void unregisterObserver(INetworkManagementEventObserver observer) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + mObservers.unregister(observer); } /** * Notify our observers of an interface status change */ private void notifyInterfaceStatusChanged(String iface, boolean up) { - for (INetworkManagementEventObserver obs : mObservers) { + final int length = mObservers.beginBroadcast(); + for (int i = 0; i < length; i++) { try { - obs.interfaceStatusChanged(iface, up); - } catch (Exception ex) { - Slog.w(TAG, "Observer notifier failed", ex); + mObservers.getBroadcastItem(i).interfaceStatusChanged(iface, up); + } catch (RemoteException e) { } } + mObservers.finishBroadcast(); } /** @@ -221,26 +233,28 @@ public class NetworkManagementService extends INetworkManagementService.Stub * (typically, an Ethernet cable has been plugged-in or unplugged). */ private void notifyInterfaceLinkStateChanged(String iface, boolean up) { - for (INetworkManagementEventObserver obs : mObservers) { + final int length = mObservers.beginBroadcast(); + for (int i = 0; i < length; i++) { try { - obs.interfaceLinkStateChanged(iface, up); - } catch (Exception ex) { - Slog.w(TAG, "Observer notifier failed", ex); + mObservers.getBroadcastItem(i).interfaceLinkStateChanged(iface, up); + } catch (RemoteException e) { } } + mObservers.finishBroadcast(); } /** * Notify our observers of an interface addition. */ private void notifyInterfaceAdded(String iface) { - for (INetworkManagementEventObserver obs : mObservers) { + final int length = mObservers.beginBroadcast(); + for (int i = 0; i < length; i++) { try { - obs.interfaceAdded(iface); - } catch (Exception ex) { - Slog.w(TAG, "Observer notifier failed", ex); + mObservers.getBroadcastItem(i).interfaceAdded(iface); + } catch (RemoteException e) { } } + mObservers.finishBroadcast(); } /** @@ -252,33 +266,34 @@ public class NetworkManagementService extends INetworkManagementService.Stub mActiveAlertIfaces.remove(iface); mActiveQuotaIfaces.remove(iface); - for (INetworkManagementEventObserver obs : mObservers) { + final int length = mObservers.beginBroadcast(); + for (int i = 0; i < length; i++) { try { - obs.interfaceRemoved(iface); - } catch (Exception ex) { - Slog.w(TAG, "Observer notifier failed", ex); + mObservers.getBroadcastItem(i).interfaceRemoved(iface); + } catch (RemoteException e) { } } + mObservers.finishBroadcast(); } /** * Notify our observers of a limit reached. */ private void notifyLimitReached(String limitName, String iface) { - for (INetworkManagementEventObserver obs : mObservers) { + final int length = mObservers.beginBroadcast(); + for (int i = 0; i < length; i++) { try { - obs.limitReached(limitName, iface); - } catch (Exception ex) { - Slog.w(TAG, "Observer notifier failed", ex); + mObservers.getBroadcastItem(i).limitReached(limitName, iface); + } catch (RemoteException e) { } } + mObservers.finishBroadcast(); } /** * Let us know the daemon is connected */ protected void onDaemonConnected() { - if (DBG) Slog.d(TAG, "onConnected"); mConnectedSignal.countDown(); } @@ -351,233 +366,188 @@ public class NetworkManagementService extends INetworkManagementService.Stub // INetworkManagementService members // - public String[] listInterfaces() throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - + @Override + public String[] listInterfaces() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - return mConnector.doListCommand("interface list", NetdResponseCode.InterfaceListResult); + return NativeDaemonEvent.filterMessageList( + mConnector.executeForList("interface", "list"), InterfaceListResult); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Cannot communicate with native daemon to list interfaces"); + throw e.rethrowAsParcelableException(); } } - public InterfaceConfiguration getInterfaceConfig(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG); - String rsp; + @Override + public InterfaceConfiguration getInterfaceConfig(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + final NativeDaemonEvent event; try { - rsp = mConnector.doCommand("interface getcfg " + iface).get(0); + event = mConnector.execute("interface", "getcfg", iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Cannot communicate with native daemon to get interface config"); + throw e.rethrowAsParcelableException(); } - Slog.d(TAG, String.format("rsp <%s>", rsp)); - // Rsp: 213 xx:xx:xx:xx:xx:xx yyy.yyy.yyy.yyy zzz [flag1 flag2 flag3] - StringTokenizer st = new StringTokenizer(rsp); + event.checkCode(InterfaceGetCfgResult); + + // Rsp: 213 xx:xx:xx:xx:xx:xx yyy.yyy.yyy.yyy zzz flag1 flag2 flag3 + final StringTokenizer st = new StringTokenizer(event.getMessage()); InterfaceConfiguration cfg; try { - 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("Invalid response from daemon (%s)", rsp)); - } - cfg = new InterfaceConfiguration(); - cfg.hwAddr = st.nextToken(" "); + cfg.setHardwareAddress(st.nextToken(" ")); InetAddress addr = null; int prefixLength = 0; try { - addr = NetworkUtils.numericToInetAddress(st.nextToken(" ")); + addr = NetworkUtils.numericToInetAddress(st.nextToken()); } catch (IllegalArgumentException iae) { Slog.e(TAG, "Failed to parse ipaddr", iae); } try { - prefixLength = Integer.parseInt(st.nextToken(" ")); + prefixLength = Integer.parseInt(st.nextToken()); } catch (NumberFormatException nfe) { Slog.e(TAG, "Failed to parse prefixLength", nfe); } - cfg.addr = new LinkAddress(addr, prefixLength); - cfg.interfaceFlags = st.nextToken("]").trim() +"]"; + cfg.setLinkAddress(new LinkAddress(addr, prefixLength)); + while (st.hasMoreTokens()) { + cfg.setFlag(st.nextToken()); + } } catch (NoSuchElementException nsee) { - throw new IllegalStateException( - String.format("Invalid response from daemon (%s)", rsp)); + throw new IllegalStateException("Invalid response from daemon: " + event); } - Slog.d(TAG, String.format("flags <%s>", cfg.interfaceFlags)); return cfg; } - public void setInterfaceConfig( - String iface, InterfaceConfiguration cfg) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); - LinkAddress linkAddr = cfg.addr; + @Override + public void setInterfaceConfig(String iface, InterfaceConfiguration cfg) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + LinkAddress linkAddr = cfg.getLinkAddress(); if (linkAddr == null || linkAddr.getAddress() == null) { throw new IllegalStateException("Null LinkAddress given"); } - String cmd = String.format("interface setcfg %s %s %d %s", iface, + + final Command cmd = new Command("interface", "setcfg", iface, linkAddr.getAddress().getHostAddress(), - linkAddr.getNetworkPrefixLength(), - cfg.interfaceFlags); - try { - mConnector.doCommand(cmd); - } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate with native daemon to interface setcfg - " + e); + linkAddr.getNetworkPrefixLength()); + for (String flag : cfg.getFlags()) { + cmd.appendArg(flag); } - } - public void setInterfaceDown(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); try { - InterfaceConfiguration ifcg = getInterfaceConfig(iface); - ifcg.interfaceFlags = ifcg.interfaceFlags.replace("up", "down"); - setInterfaceConfig(iface, ifcg); + mConnector.execute(cmd); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate with native daemon for interface down - " + e); + throw e.rethrowAsParcelableException(); } } - public void setInterfaceUp(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); - try { - InterfaceConfiguration ifcg = getInterfaceConfig(iface); - ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up"); - setInterfaceConfig(iface, ifcg); - } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate with native daemon for interface up - " + e); - } + @Override + public void setInterfaceDown(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + final InterfaceConfiguration ifcg = getInterfaceConfig(iface); + ifcg.setInterfaceDown(); + setInterfaceConfig(iface, ifcg); } - public void setInterfaceIpv6PrivacyExtensions(String iface, boolean enable) - throws IllegalStateException { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); - String cmd = String.format("interface ipv6privacyextensions %s %s", iface, - enable ? "enable" : "disable"); + @Override + public void setInterfaceUp(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + final InterfaceConfiguration ifcg = getInterfaceConfig(iface); + ifcg.setInterfaceUp(); + setInterfaceConfig(iface, ifcg); + } + + @Override + public void setInterfaceIpv6PrivacyExtensions(String iface, boolean enable) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand(cmd); + mConnector.execute( + "interface", "ipv6privacyextensions", iface, enable ? "enable" : "disable"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate with native daemon to set ipv6privacyextensions - " + e); + throw e.rethrowAsParcelableException(); } } - - /* TODO: This is right now a IPv4 only function. Works for wifi which loses its IPv6 addresses on interface down, but we need to do full clean up here */ - public void clearInterfaceAddresses(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); - String cmd = String.format("interface clearaddrs %s", iface); + @Override + public void clearInterfaceAddresses(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand(cmd); + mConnector.execute("interface", "clearaddrs", iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate with native daemon to interface clearallips - " + e); + throw e.rethrowAsParcelableException(); } } - public void enableIpv6(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void enableIpv6(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand(String.format("interface ipv6 %s enable", iface)); + mConnector.execute("interface", "ipv6", iface, "enable"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon for enabling ipv6"); + throw e.rethrowAsParcelableException(); } } - public void disableIpv6(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void disableIpv6(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand(String.format("interface ipv6 %s disable", iface)); + mConnector.execute("interface", "ipv6", iface, "disable"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon for disabling ipv6"); + throw e.rethrowAsParcelableException(); } } + @Override public void addRoute(String interfaceName, RouteInfo route) { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); modifyRoute(interfaceName, ADD, route, DEFAULT); } + @Override public void removeRoute(String interfaceName, RouteInfo route) { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); modifyRoute(interfaceName, REMOVE, route, DEFAULT); } + @Override public void addSecondaryRoute(String interfaceName, RouteInfo route) { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); modifyRoute(interfaceName, ADD, route, SECONDARY); } + @Override public void removeSecondaryRoute(String interfaceName, RouteInfo route) { - mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); modifyRoute(interfaceName, REMOVE, route, SECONDARY); } - private void modifyRoute(String interfaceName, int action, RouteInfo route, String type) { - ArrayList<String> rsp; - - StringBuilder cmd; - - switch (action) { - case ADD: - { - cmd = new StringBuilder("interface route add " + interfaceName + " " + type); - break; - } - case REMOVE: - { - cmd = new StringBuilder("interface route remove " + interfaceName + " " + type); - break; - } - default: - throw new IllegalStateException("Unknown action type " + action); - } + private void modifyRoute(String interfaceName, String action, RouteInfo route, String type) { + final Command cmd = new Command("interface", "route", action, interfaceName, type); // create triplet: dest-ip-addr prefixlength gateway-ip-addr - LinkAddress la = route.getDestination(); - cmd.append(' '); - cmd.append(la.getAddress().getHostAddress()); - cmd.append(' '); - cmd.append(la.getNetworkPrefixLength()); - cmd.append(' '); + final LinkAddress la = route.getDestination(); + cmd.appendArg(la.getAddress().getHostAddress()); + cmd.appendArg(la.getNetworkPrefixLength()); + if (route.getGateway() == null) { if (la.getAddress() instanceof Inet4Address) { - cmd.append("0.0.0.0"); + cmd.appendArg("0.0.0.0"); } else { - cmd.append ("::0"); + cmd.appendArg("::0"); } } else { - cmd.append(route.getGateway().getHostAddress()); + cmd.appendArg(route.getGateway().getHostAddress()); } + try { - rsp = mConnector.doCommand(cmd.toString()); + mConnector.execute(cmd); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate with native dameon to add routes - " - + e); - } - - if (DBG) { - for (String line : rsp) { - Log.v(TAG, "add route response is " + line); - } + throw e.rethrowAsParcelableException(); } } @@ -609,8 +579,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub return list; } + @Override public RouteInfo[] getRoutes(String interfaceName) { - mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); ArrayList<RouteInfo> routes = new ArrayList<RouteInfo>(); // v4 routes listed as: @@ -680,308 +651,248 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } } - return (RouteInfo[]) routes.toArray(new RouteInfo[0]); + return routes.toArray(new RouteInfo[routes.size()]); } + @Override public void shutdown() { - if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.SHUTDOWN) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires SHUTDOWN permission"); - } + // TODO: remove from aidl if nobody calls externally + mContext.enforceCallingOrSelfPermission(SHUTDOWN, TAG); Slog.d(TAG, "Shutting down"); } + @Override public boolean getIpForwardingEnabled() throws IllegalStateException{ - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - ArrayList<String> rsp; + final NativeDaemonEvent event; try { - rsp = mConnector.doCommand("ipfwd status"); + event = mConnector.execute("ipfwd", "status"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate with native daemon to ipfwd status"); + throw e.rethrowAsParcelableException(); } - for (String line : rsp) { - 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> - return "enabled".equals(tok[2]); - } else { - throw new IllegalStateException(String.format("Unexpected response code %d", code)); - } - } - throw new IllegalStateException("Got an empty response"); + // 211 Forwarding enabled + event.checkCode(IpFwdStatusResult); + return event.getMessage().endsWith("enabled"); } - public void setIpForwardingEnabled(boolean enable) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand(String.format("ipfwd %sable", (enable ? "en" : "dis"))); + @Override + public void setIpForwardingEnabled(boolean enable) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + try { + mConnector.execute("ipfwd", enable ? "enable" : "disable"); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } } - public void startTethering(String[] dhcpRange) - throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void startTethering(String[] dhcpRange) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); // cmd is "tether start first_start first_stop second_start second_stop ..." // an odd number of addrs will fail - String cmd = "tether start"; + + final Command cmd = new Command("tether", "start"); for (String d : dhcpRange) { - cmd += " " + d; + cmd.appendArg(d); } try { - mConnector.doCommand(cmd); + mConnector.execute(cmd); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Unable to communicate to native daemon"); + throw e.rethrowAsParcelableException(); } } - public void stopTethering() throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void stopTethering() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand("tether stop"); + mConnector.execute("tether", "stop"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Unable to communicate to native daemon to stop tether"); + throw e.rethrowAsParcelableException(); } } - public boolean isTetheringStarted() throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + @Override + public boolean isTetheringStarted() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - ArrayList<String> rsp; + final NativeDaemonEvent event; try { - rsp = mConnector.doCommand("tether status"); + event = mConnector.execute("tether", "status"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon to get tether status"); + throw e.rethrowAsParcelableException(); } - for (String line : rsp) { - 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>... - return "started".equals(tok[2]); - } else { - throw new IllegalStateException(String.format("Unexpected response code %d", code)); - } - } - throw new IllegalStateException("Got an empty response"); + // 210 Tethering services started + event.checkCode(TetherStatusResult); + return event.getMessage().endsWith("started"); } - public void tetherInterface(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void tetherInterface(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand("tether interface add " + iface); + mConnector.execute("tether", "interface", "add", iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon for adding tether interface"); + throw e.rethrowAsParcelableException(); } } + @Override public void untetherInterface(String iface) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand("tether interface remove " + iface); + mConnector.execute("tether", "interface", "remove", iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon for removing tether interface"); + throw e.rethrowAsParcelableException(); } } - public String[] listTetheredInterfaces() throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + @Override + public String[] listTetheredInterfaces() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - return mConnector.doListCommand( - "tether interface list", NetdResponseCode.TetherInterfaceListResult); + return NativeDaemonEvent.filterMessageList( + mConnector.executeForList("tether", "interface", "list"), + TetherInterfaceListResult); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon for listing tether interfaces"); + throw e.rethrowAsParcelableException(); } } - public void setDnsForwarders(String[] dns) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void setDnsForwarders(String[] dns) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + final Command cmd = new Command("tether", "dns", "set"); + for (String s : dns) { + cmd.appendArg(NetworkUtils.numericToInetAddress(s).getHostAddress()); + } + try { - String cmd = "tether dns set"; - for (String s : dns) { - cmd += " " + NetworkUtils.numericToInetAddress(s).getHostAddress(); - } - try { - mConnector.doCommand(cmd); - } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon for setting tether dns"); - } - } catch (IllegalArgumentException e) { - throw new IllegalStateException("Error resolving dns name", e); + mConnector.execute(cmd); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); } } - public String[] getDnsForwarders() throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + @Override + public String[] getDnsForwarders() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - return mConnector.doListCommand( - "tether dns list", NetdResponseCode.TetherDnsFwdTgtListResult); + return NativeDaemonEvent.filterMessageList( + mConnector.executeForList("tether", "dns", "list"), TetherDnsFwdTgtListResult); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon for listing tether dns"); + throw e.rethrowAsParcelableException(); } } - private void modifyNat(String cmd, String internalInterface, String externalInterface) + private void modifyNat(String action, String internalInterface, String externalInterface) throws SocketException { - cmd = String.format("nat %s %s %s", cmd, internalInterface, externalInterface); + final Command cmd = new Command("nat", action, internalInterface, externalInterface); - NetworkInterface internalNetworkInterface = - NetworkInterface.getByName(internalInterface); + final NetworkInterface internalNetworkInterface = NetworkInterface.getByName( + internalInterface); if (internalNetworkInterface == null) { - cmd += " 0"; + cmd.appendArg("0"); } else { - Collection<InterfaceAddress>interfaceAddresses = - internalNetworkInterface.getInterfaceAddresses(); - cmd += " " + interfaceAddresses.size(); + Collection<InterfaceAddress> interfaceAddresses = internalNetworkInterface + .getInterfaceAddresses(); + cmd.appendArg(interfaceAddresses.size()); for (InterfaceAddress ia : interfaceAddresses) { - InetAddress addr = NetworkUtils.getNetworkPart(ia.getAddress(), - ia.getNetworkPrefixLength()); - cmd = cmd + " " + addr.getHostAddress() + "/" + ia.getNetworkPrefixLength(); + InetAddress addr = NetworkUtils.getNetworkPart( + ia.getAddress(), ia.getNetworkPrefixLength()); + cmd.appendArg(addr.getHostAddress() + "/" + ia.getNetworkPrefixLength()); } } - mConnector.doCommand(cmd); + try { + mConnector.execute(cmd); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } } - public void enableNat(String internalInterface, String externalInterface) - throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - if (DBG) Log.d(TAG, "enableNat(" + internalInterface + ", " + externalInterface + ")"); + @Override + public void enableNat(String internalInterface, String externalInterface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { modifyNat("enable", internalInterface, externalInterface); - } catch (Exception e) { - Log.e(TAG, "enableNat got Exception " + e.toString()); - throw new IllegalStateException( - "Unable to communicate to native daemon for enabling NAT interface"); + } catch (SocketException e) { + throw new IllegalStateException(e); } } - public void disableNat(String internalInterface, String externalInterface) - throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - if (DBG) Log.d(TAG, "disableNat(" + internalInterface + ", " + externalInterface + ")"); + @Override + public void disableNat(String internalInterface, String externalInterface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { modifyNat("disable", internalInterface, externalInterface); - } catch (Exception e) { - Log.e(TAG, "disableNat got Exception " + e.toString()); - throw new IllegalStateException( - "Unable to communicate to native daemon for disabling NAT interface"); + } catch (SocketException e) { + throw new IllegalStateException(e); } } - public String[] listTtys() throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + @Override + public String[] listTtys() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - return mConnector.doListCommand("list_ttys", NetdResponseCode.TtyListResult); + return NativeDaemonEvent.filterMessageList( + mConnector.executeForList("list_ttys"), TtyListResult); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Unable to communicate to native daemon for listing TTYs"); + throw e.rethrowAsParcelableException(); } } - public void attachPppd(String tty, String localAddr, String remoteAddr, String dns1Addr, - String dns2Addr) throws IllegalStateException { + @Override + public void attachPppd( + String tty, String localAddr, String remoteAddr, String dns1Addr, String dns2Addr) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mConnector.doCommand(String.format("pppd attach %s %s %s %s %s", tty, + mConnector.execute("pppd", "attach", tty, NetworkUtils.numericToInetAddress(localAddr).getHostAddress(), NetworkUtils.numericToInetAddress(remoteAddr).getHostAddress(), NetworkUtils.numericToInetAddress(dns1Addr).getHostAddress(), - NetworkUtils.numericToInetAddress(dns2Addr).getHostAddress())); - } catch (IllegalArgumentException e) { - throw new IllegalStateException("Error resolving addr", e); + NetworkUtils.numericToInetAddress(dns2Addr).getHostAddress()); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon to attach pppd", e); + throw e.rethrowAsParcelableException(); } } - public void detachPppd(String tty) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void detachPppd(String tty) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand(String.format("pppd detach %s", tty)); + mConnector.execute("pppd", "detach", tty); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon to detach pppd", e); + throw e.rethrowAsParcelableException(); } } - public void startAccessPoint(WifiConfiguration wifiConfig, String wlanIface, String softapIface) - throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService"); + @Override + public void startAccessPoint( + WifiConfiguration wifiConfig, String wlanIface, String softapIface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { wifiFirmwareReload(wlanIface, "AP"); - mConnector.doCommand(String.format("softap start " + wlanIface)); + mConnector.execute("softap", "start", wlanIface); if (wifiConfig == null) { - mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface)); + mConnector.execute("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), - getSecurityType(wifiConfig), - convertQuotedString(wifiConfig.preSharedKey)); - mConnector.doCommand(str); + mConnector.execute("softap", "set", wlanIface, softapIface, wifiConfig.SSID, + getSecurityType(wifiConfig), wifiConfig.preSharedKey); } - mConnector.doCommand(String.format("softap startap")); + mConnector.execute("softap", "startap"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon to start softap", e); + throw e.rethrowAsParcelableException(); } } - private String convertQuotedString(String s) { - if (s == null) { - return s; - } - /* Replace \ with \\, then " with \" and add quotes at end */ - return '"' + s.replaceAll("\\\\","\\\\\\\\").replaceAll("\"","\\\\\"") + '"'; - } - - private String getSecurityType(WifiConfiguration wifiConfig) { + private static String getSecurityType(WifiConfiguration wifiConfig) { switch (wifiConfig.getAuthType()) { case KeyMgmt.WPA_PSK: return "wpa-psk"; @@ -993,113 +904,58 @@ public class NetworkManagementService extends INetworkManagementService.Stub } /* @param mode can be "AP", "STA" or "P2P" */ - public void wifiFirmwareReload(String wlanIface, String mode) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService"); - + @Override + public void wifiFirmwareReload(String wlanIface, String mode) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand(String.format("softap fwreload " + wlanIface + " " + mode)); + mConnector.execute("softap", "fwreload", wlanIface, mode); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon ", e); + throw e.rethrowAsParcelableException(); } } - public void stopAccessPoint(String wlanIface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService"); + @Override + public void stopAccessPoint(String wlanIface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand("softap stopap"); - mConnector.doCommand("softap stop " + wlanIface); + mConnector.execute("softap", "stopap"); + mConnector.execute("softap", "stop", wlanIface); wifiFirmwareReload(wlanIface, "STA"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon to stop soft AP", - e); + throw e.rethrowAsParcelableException(); } } - public void setAccessPoint(WifiConfiguration wifiConfig, String wlanIface, String softapIface) - throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_WIFI_STATE, "NetworkManagementService"); + @Override + public void setAccessPoint(WifiConfiguration wifiConfig, String wlanIface, String softapIface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { if (wifiConfig == null) { - mConnector.doCommand(String.format("softap set " + wlanIface + " " + softapIface)); + mConnector.execute("softap", "set", wlanIface, softapIface); } else { - String str = String.format("softap set " + wlanIface + " " + softapIface - + " %s %s %s", convertQuotedString(wifiConfig.SSID), - getSecurityType(wifiConfig), - convertQuotedString(wifiConfig.preSharedKey)); - mConnector.doCommand(str); + mConnector.execute("softap", "set", wlanIface, softapIface, wifiConfig.SSID, + getSecurityType(wifiConfig), wifiConfig.preSharedKey); } } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon to set soft AP", - e); - } - } - - private long getInterfaceCounter(String iface, boolean rx) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - try { - 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]); - } catch (NumberFormatException nfe) { - Slog.e(TAG, String.format("Error parsing code %s", tok[0])); - return -1; - } - if ((rx && code != NetdResponseCode.InterfaceRxCounterResult) || ( - !rx && code != NetdResponseCode.InterfaceTxCounterResult)) { - Slog.e(TAG, String.format("Unexpected response code %d", code)); - return -1; - } - return Long.parseLong(tok[1]); - } catch (Exception e) { - Slog.e(TAG, String.format( - "Failed to read interface %s counters", (rx ? "rx" : "tx")), e); + throw e.rethrowAsParcelableException(); } - return -1; } @Override public NetworkStats getNetworkStatsSummary() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); return mStatsFactory.readNetworkStatsSummary(); } @Override public NetworkStats getNetworkStatsDetail() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); return mStatsFactory.readNetworkStatsDetail(UID_ALL); } @Override public void setInterfaceQuota(String iface, long quotaBytes) { - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); // silently discard when control disabled // TODO: eventually migrate to be always enabled @@ -1110,22 +966,19 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException("iface " + iface + " already has quota"); } - final StringBuilder command = new StringBuilder(); - command.append("bandwidth setiquota ").append(iface).append(" ").append(quotaBytes); - try { // TODO: support quota shared across interfaces - mConnector.doCommand(command.toString()); + mConnector.execute("bandwidth", "setiquota", iface, quotaBytes); mActiveQuotaIfaces.add(iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon", e); + throw e.rethrowAsParcelableException(); } } } @Override public void removeInterfaceQuota(String iface) { - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); // silently discard when control disabled // TODO: eventually migrate to be always enabled @@ -1137,25 +990,21 @@ public class NetworkManagementService extends INetworkManagementService.Stub return; } - final StringBuilder command = new StringBuilder(); - command.append("bandwidth removeiquota ").append(iface); - mActiveQuotaIfaces.remove(iface); mActiveAlertIfaces.remove(iface); try { // TODO: support quota shared across interfaces - mConnector.doCommand(command.toString()); + mConnector.execute("bandwidth", "removeiquota", iface); } catch (NativeDaemonConnectorException e) { - // TODO: include current iptables state - throw new IllegalStateException("Error communicating to native daemon", e); + throw e.rethrowAsParcelableException(); } } } @Override public void setInterfaceAlert(String iface, long alertBytes) { - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); // silently discard when control disabled // TODO: eventually migrate to be always enabled @@ -1171,23 +1020,19 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException("iface " + iface + " already has alert"); } - final StringBuilder command = new StringBuilder(); - command.append("bandwidth setinterfacealert ").append(iface).append(" ").append( - alertBytes); - try { // TODO: support alert shared across interfaces - mConnector.doCommand(command.toString()); + mConnector.execute("bandwidth", "setinterfacealert", iface, alertBytes); mActiveAlertIfaces.add(iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon", e); + throw e.rethrowAsParcelableException(); } } } @Override public void removeInterfaceAlert(String iface) { - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); // silently discard when control disabled // TODO: eventually migrate to be always enabled @@ -1199,40 +1044,34 @@ public class NetworkManagementService extends INetworkManagementService.Stub return; } - final StringBuilder command = new StringBuilder(); - command.append("bandwidth removeinterfacealert ").append(iface); - try { // TODO: support alert shared across interfaces - mConnector.doCommand(command.toString()); + mConnector.execute("bandwidth", "removeinterfacealert", iface); mActiveAlertIfaces.remove(iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon", e); + throw e.rethrowAsParcelableException(); } } } @Override public void setGlobalAlert(long alertBytes) { - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); // silently discard when control disabled // TODO: eventually migrate to be always enabled if (!mBandwidthControlEnabled) return; - final StringBuilder command = new StringBuilder(); - command.append("bandwidth setglobalalert ").append(alertBytes); - try { - mConnector.doCommand(command.toString()); + mConnector.execute("bandwidth", "setglobalalert", alertBytes); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon", e); + throw e.rethrowAsParcelableException(); } } @Override public void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces) { - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); // silently discard when control disabled // TODO: eventually migrate to be always enabled @@ -1245,47 +1084,35 @@ public class NetworkManagementService extends INetworkManagementService.Stub return; } - final StringBuilder command = new StringBuilder(); - command.append("bandwidth"); - if (rejectOnQuotaInterfaces) { - command.append(" addnaughtyapps"); - } else { - command.append(" removenaughtyapps"); - } - command.append(" ").append(uid); - try { - mConnector.doCommand(command.toString()); + mConnector.execute("bandwidth", + rejectOnQuotaInterfaces ? "addnaughtyapps" : "removenaughtyapps", uid); if (rejectOnQuotaInterfaces) { mUidRejectOnQuota.put(uid, true); } else { mUidRejectOnQuota.delete(uid); } } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon", e); + throw e.rethrowAsParcelableException(); } } } @Override public boolean isBandwidthControlEnabled() { - mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); return mBandwidthControlEnabled; } @Override public NetworkStats getNetworkStatsUidDetail(int uid) { - if (Binder.getCallingUid() != uid) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); - } + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); return mStatsFactory.readNetworkStatsDetail(uid); } @Override public NetworkStats getNetworkStatsTethering(String[] ifacePairs) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); if (ifacePairs.length % 2 != 0) { throw new IllegalArgumentException( @@ -1304,33 +1131,19 @@ public class NetworkManagementService extends INetworkManagementService.Stub } private NetworkStats.Entry getNetworkStatsTethering(String ifaceIn, String ifaceOut) { - final StringBuilder command = new StringBuilder(); - command.append("bandwidth gettetherstats ").append(ifaceIn).append(" ").append(ifaceOut); - - final String rsp; + final NativeDaemonEvent event; try { - rsp = mConnector.doCommand(command.toString()).get(0); + event = mConnector.execute("bandwidth", "gettetherstats", ifaceIn, ifaceOut); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException("Error communicating to native daemon", e); + throw e.rethrowAsParcelableException(); } - final String[] tok = rsp.split(" "); - /* Expecting: "code ifaceIn ifaceOut rx_bytes rx_packets tx_bytes tx_packets" */ - if (tok.length != 7) { - throw new IllegalStateException("Native daemon returned unexpected result: " + rsp); - } + event.checkCode(TetheringStatsResult); - final int code; - try { - code = Integer.parseInt(tok[0]); - } catch (NumberFormatException e) { - throw new IllegalStateException( - "Failed to parse native daemon return code for " + ifaceIn + " " + ifaceOut); - } - if (code != NetdResponseCode.TetheringStatsResult) { - throw new IllegalStateException( - "Unexpected return code from native daemon for " + ifaceIn + " " + ifaceOut); - } + // 221 ifaceIn ifaceOut rx_bytes rx_packets tx_bytes tx_packets + final StringTokenizer tok = new StringTokenizer(event.getMessage()); + tok.nextToken(); + tok.nextToken(); try { final NetworkStats.Entry entry = new NetworkStats.Entry(); @@ -1338,10 +1151,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub entry.uid = UID_TETHERING; entry.set = SET_DEFAULT; entry.tag = TAG_NONE; - entry.rxBytes = Long.parseLong(tok[3]); - entry.rxPackets = Long.parseLong(tok[4]); - entry.txBytes = Long.parseLong(tok[5]); - entry.txPackets = Long.parseLong(tok[6]); + entry.rxBytes = Long.parseLong(tok.nextToken()); + entry.rxPackets = Long.parseLong(tok.nextToken()); + entry.txBytes = Long.parseLong(tok.nextToken()); + entry.txPackets = Long.parseLong(tok.nextToken()); return entry; } catch (NumberFormatException e) { throw new IllegalStateException( @@ -1349,122 +1162,95 @@ public class NetworkManagementService extends INetworkManagementService.Stub } } + @Override public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.doCommand(String.format( - "interface setthrottle %s %d %d", iface, rxKbps, txKbps)); + mConnector.execute("interface", "setthrottle", iface, rxKbps, txKbps); } catch (NativeDaemonConnectorException e) { - Slog.e(TAG, "Error communicating with native daemon to set throttle", e); + throw e.rethrowAsParcelableException(); } } private int getInterfaceThrottle(String iface, boolean rx) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + final NativeDaemonEvent event; try { - 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; - } + event = mConnector.execute("interface", "getthrottle", iface, rx ? "rx" : "tx"); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } - String[] tok = rsp.split(" "); - if (tok.length < 2) { - Slog.e(TAG, "Malformed response to getthrottle command"); - return -1; - } + if (rx) { + event.checkCode(InterfaceRxThrottleResult); + } else { + event.checkCode(InterfaceTxThrottleResult); + } - int code; - try { - code = Integer.parseInt(tok[0]); - } catch (NumberFormatException nfe) { - Slog.e(TAG, String.format("Error parsing code %s", tok[0])); - return -1; - } - if ((rx && code != NetdResponseCode.InterfaceRxThrottleResult) || ( - !rx && code != NetdResponseCode.InterfaceTxThrottleResult)) { - Slog.e(TAG, String.format("Unexpected response code %d", code)); - return -1; - } - return Integer.parseInt(tok[1]); - } catch (Exception e) { - Slog.e(TAG, String.format( - "Failed to read interface %s throttle value", (rx ? "rx" : "tx")), e); + try { + return Integer.parseInt(event.getMessage()); + } catch (NumberFormatException e) { + throw new IllegalStateException("unexpected response:" + event); } - return -1; } + @Override public int getInterfaceRxThrottle(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); return getInterfaceThrottle(iface, true); } + @Override public int getInterfaceTxThrottle(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); return getInterfaceThrottle(iface, false); } - public void setDefaultInterfaceForDns(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void setDefaultInterfaceForDns(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - String cmd = "resolver setdefaultif " + iface; - - mConnector.doCommand(cmd); + mConnector.execute("resolver", "setdefaultif", iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Error communicating with native daemon to set default interface", e); + throw e.rethrowAsParcelableException(); } } - public void setDnsServersForInterface(String iface, String[] servers) - throws IllegalStateException { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_NETWORK_STATE, - "NetworkManagementService"); - try { - String cmd = "resolver setifdns " + iface; - for (String s : servers) { - InetAddress a = NetworkUtils.numericToInetAddress(s); - if (a.isAnyLocalAddress() == false) { - cmd += " " + a.getHostAddress(); - } + @Override + public void setDnsServersForInterface(String iface, String[] servers) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + final Command cmd = new Command("resolver", "setifdns", iface); + for (String s : servers) { + InetAddress a = NetworkUtils.numericToInetAddress(s); + if (a.isAnyLocalAddress() == false) { + cmd.appendArg(a.getHostAddress()); } - mConnector.doCommand(cmd); - } catch (IllegalArgumentException e) { - throw new IllegalStateException("Error setting dnsn for interface", e); + } + + try { + mConnector.execute(cmd); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Error communicating with native daemon to set dns for interface", e); + throw e.rethrowAsParcelableException(); } } - public void flushDefaultDnsCache() throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void flushDefaultDnsCache() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - String cmd = "resolver flushdefaultif"; - - mConnector.doCommand(cmd); + mConnector.execute("resolver", "flushdefaultif"); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Error communicating with native deamon to flush default interface", e); + throw e.rethrowAsParcelableException(); } } - public void flushInterfaceDnsCache(String iface) throws IllegalStateException { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService"); + @Override + public void flushInterfaceDnsCache(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - String cmd = "resolver flushif " + iface; - - mConnector.doCommand(cmd); + mConnector.execute("resolver", "flushif", iface); } catch (NativeDaemonConnectorException e) { - throw new IllegalStateException( - "Error communicating with native daemon to flush interface " + iface, e); + throw e.rethrowAsParcelableException(); } } @@ -1479,6 +1265,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { mContext.enforceCallingOrSelfPermission(DUMP, TAG); + pw.println("NetworkManagementService NativeDaemonConnector Log:"); + mConnector.dump(fd, pw, args); + pw.println(); + pw.print("Bandwidth control enabled: "); pw.println(mBandwidthControlEnabled); synchronized (mQuotaLock) { diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java index f7fe39e..a7d1992 100644 --- a/services/java/com/android/server/NetworkTimeUpdateService.java +++ b/services/java/com/android/server/NetworkTimeUpdateService.java @@ -234,8 +234,9 @@ public class NetworkTimeUpdateService { String action = intent.getAction(); if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) { // There is connectivity - NetworkInfo netInfo = (NetworkInfo)intent.getParcelableExtra( - ConnectivityManager.EXTRA_NETWORK_INFO); + final ConnectivityManager connManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + final NetworkInfo netInfo = connManager.getActiveNetworkInfo(); if (netInfo != null) { // Verify that it's a WIFI connection if (netInfo.getState() == NetworkInfo.State.CONNECTED && diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 5039294..34a8a02 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -45,6 +45,7 @@ import android.os.IBinder; import android.os.Message; import android.os.Process; import android.os.RemoteException; +import android.os.UserId; import android.os.Vibrator; import android.provider.Settings; import android.telephony.TelephonyManager; @@ -1034,7 +1035,7 @@ public class NotificationManagerService extends INotificationManager.Stub try { ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo( pkg, 0); - if (ai.uid != uid) { + if (!UserId.isSameApp(ai.uid, uid)) { throw new SecurityException("Calling uid " + uid + " gave package" + pkg + " which is owned by uid " + ai.uid); } diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index 2a0d2a0..bb0ac3e 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -51,7 +51,6 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.WorkSource; -import android.provider.Settings.SettingNotFoundException; import android.provider.Settings; import android.util.EventLog; import android.util.Log; @@ -60,6 +59,7 @@ import android.view.WindowManagerPolicy; import static android.provider.Settings.System.DIM_SCREEN; import static android.provider.Settings.System.SCREEN_BRIGHTNESS; import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE; +import static android.provider.Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ; 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; @@ -106,6 +106,14 @@ public class PowerManagerService extends IPowerManager.Stub // light sensor events rate in microseconds private static final int LIGHT_SENSOR_RATE = 1000000; + // Expansion of range of light values when applying scale from light + // sensor brightness setting, in the [0..255] brightness range. + private static final int LIGHT_SENSOR_RANGE_EXPANSION = 20; + + // Scaling factor of the light sensor brightness setting when applying + // it to the final brightness. + private static final int LIGHT_SENSOR_OFFSET_SCALE = 8; + // For debouncing the proximity sensor in milliseconds private static final int PROXIMITY_SENSOR_DELAY = 1000; @@ -118,6 +126,9 @@ public class PowerManagerService extends IPowerManager.Stub // Default timeout for screen off, if not found in settings database = 15 seconds. private static final int DEFAULT_SCREEN_OFF_TIMEOUT = 15000; + // Screen brightness should always have a value, but just in case... + private static final int DEFAULT_SCREEN_BRIGHTNESS = 192; + // flags for setPowerState private static final int SCREEN_ON_BIT = 0x00000001; private static final int SCREEN_BRIGHT_BIT = 0x00000002; @@ -150,6 +161,8 @@ public class PowerManagerService extends IPowerManager.Stub static final int ANIM_STEPS = 60/4; // Slower animation for autobrightness changes static final int AUTOBRIGHTNESS_ANIM_STEPS = 60; + // Number of steps when performing a more immediate brightness change. + static final int IMMEDIATE_ANIM_STEPS = 4; // These magic numbers are the initial state of the LEDs at boot. Ideally // we should read them from the driver, but our current hardware returns 0 @@ -227,6 +240,7 @@ public class PowerManagerService extends IPowerManager.Stub private boolean mLightSensorPendingDecrease = false; private boolean mLightSensorPendingIncrease = false; private float mLightSensorPendingValue = -1; + private float mLightSensorAdjustSetting = 0; private int mLightSensorScreenBrightness = -1; private int mLightSensorButtonBrightness = -1; private int mLightSensorKeyboardBrightness = -1; @@ -240,6 +254,7 @@ public class PowerManagerService extends IPowerManager.Stub // mLastScreenOnTime is the time the screen was last turned on private long mLastScreenOnTime; private boolean mPreventScreenOn; + private int mScreenBrightnessSetting = DEFAULT_SCREEN_BRIGHTNESS; private int mScreenBrightnessOverride = -1; private int mButtonBrightnessOverride = -1; private int mScreenBrightnessDim; @@ -460,6 +475,9 @@ public class PowerManagerService extends IPowerManager.Stub // DIM_SCREEN //mDimScreen = getInt(DIM_SCREEN) != 0; + mScreenBrightnessSetting = getInt(SCREEN_BRIGHTNESS, DEFAULT_SCREEN_BRIGHTNESS); + mLightSensorAdjustSetting = getFloat(SCREEN_AUTO_BRIGHTNESS_ADJ, 0); + // SCREEN_BRIGHTNESS_MODE, default to manual setScreenBrightnessMode(getInt(SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL)); @@ -624,9 +642,12 @@ public 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, WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE}, + new String[]{STAY_ON_WHILE_PLUGGED_IN, SCREEN_OFF_TIMEOUT, DIM_SCREEN, SCREEN_BRIGHTNESS, + SCREEN_BRIGHTNESS_MODE, SCREEN_AUTO_BRIGHTNESS_ADJ, + WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE}, null); mSettings = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, mHandler); SettingsObserver settingsObserver = new SettingsObserver(); @@ -1163,7 +1184,8 @@ public class PowerManagerService extends IPowerManager.Stub pw.println(" mProximitySensorActive=" + mProximitySensorActive); pw.println(" mProximityPendingValue=" + mProximityPendingValue); pw.println(" mLastProximityEventTime=" + mLastProximityEventTime); - pw.println(" mLightSensorEnabled=" + mLightSensorEnabled); + pw.println(" mLightSensorEnabled=" + mLightSensorEnabled + + " mLightSensorAdjustSetting=" + mLightSensorAdjustSetting); pw.println(" mLightSensorValue=" + mLightSensorValue + " mLightSensorPendingValue=" + mLightSensorPendingValue); pw.println(" mLightSensorPendingDecrease=" + mLightSensorPendingDecrease @@ -1706,11 +1728,6 @@ public class PowerManagerService extends IPowerManager.Stub // make sure button and key backlights are off too mButtonLight.turnOff(); mKeyboardLight.turnOff(); - // clear current value so we will update based on the new conditions - // when the sensor is reenabled. - mLightSensorValue = -1; - // reset our highest light sensor value when the screen turns off - mHighestLightSensorValue = -1; } } } @@ -2235,20 +2252,15 @@ public class PowerManagerService extends IPowerManager.Stub } private int getPreferredBrightness() { - try { - if (mScreenBrightnessOverride >= 0) { - return mScreenBrightnessOverride; - } else if (mLightSensorScreenBrightness >= 0 && mUseSoftwareAutoBrightness - && mAutoBrightessEnabled) { - return mLightSensorScreenBrightness; - } - final int brightness = Settings.System.getInt(mContext.getContentResolver(), - SCREEN_BRIGHTNESS); - // Don't let applications turn the screen all the way off - return Math.max(brightness, mScreenBrightnessDim); - } catch (SettingNotFoundException snfe) { - return Power.BRIGHTNESS_ON; + if (mScreenBrightnessOverride >= 0) { + return mScreenBrightnessOverride; + } else if (mLightSensorScreenBrightness >= 0 && mUseSoftwareAutoBrightness + && mAutoBrightessEnabled) { + return mLightSensorScreenBrightness; } + final int brightness = mScreenBrightnessSetting; + // Don't let applications turn the screen all the way off + return Math.max(brightness, mScreenBrightnessDim); } private int applyButtonState(int state) { @@ -2444,7 +2456,34 @@ public class PowerManagerService extends IPowerManager.Stub break; } } - return values[i]; + // This is the range of brightness values that we can use. + final int minval = values[0]; + final int maxval = values[mAutoBrightnessLevels.length]; + // This is the range we will be scaling. We put some padding + // at the low and high end to give the adjustment a little better + // impact on the actual observed value. + final int range = (maxval-minval) + LIGHT_SENSOR_RANGE_EXPANSION; + // This is the desired brightness value from 0.0 to 1.0. + float valf = ((values[i]-minval+(LIGHT_SENSOR_RANGE_EXPANSION/2))/(float)range); + // Apply a scaling to the value based on the adjustment. + if (mLightSensorAdjustSetting > 0 && mLightSensorAdjustSetting <= 1) { + float adj = (float)Math.sqrt(1.0f-mLightSensorAdjustSetting); + if (adj <= .00001) { + valf = 1; + } else { + valf /= adj; + } + } else if (mLightSensorAdjustSetting < 0 && mLightSensorAdjustSetting >= -1) { + float adj = (float)Math.sqrt(1.0f+mLightSensorAdjustSetting); + valf *= adj; + } + // Apply an additional offset to the value based on the adjustment. + valf += mLightSensorAdjustSetting/LIGHT_SENSOR_OFFSET_SCALE; + // Convert the 0.0-1.0 value back to a brightness integer. + int val = (int)((valf*range)+minval) - (LIGHT_SENSOR_RANGE_EXPANSION/2); + if (val < minval) val = minval; + else if (val > maxval) val = maxval; + return val; } catch (Exception e) { // guard against null pointer or index out of bounds errors Slog.e(TAG, "getAutoBrightnessValue", e); @@ -2473,7 +2512,7 @@ public class PowerManagerService extends IPowerManager.Stub int value = (int)mLightSensorPendingValue; mLightSensorPendingDecrease = false; mLightSensorPendingIncrease = false; - lightSensorChangedLocked(value); + lightSensorChangedLocked(value, false); } } } @@ -2483,18 +2522,19 @@ public class PowerManagerService extends IPowerManager.Stub synchronized (mLocks) { mIsDocked = (state != Intent.EXTRA_DOCK_STATE_UNDOCKED); if (mIsDocked) { + // allow brightness to decrease when docked mHighestLightSensorValue = -1; } if ((mPowerState & SCREEN_ON_BIT) != 0) { // force lights recalculation int value = (int)mLightSensorValue; mLightSensorValue = -1; - lightSensorChangedLocked(value); + lightSensorChangedLocked(value, false); } } } - private void lightSensorChangedLocked(int value) { + private void lightSensorChangedLocked(int value, boolean immediate) { if (mDebugLightSensor) { Slog.d(TAG, "lightSensorChangedLocked " + value); } @@ -2540,7 +2580,8 @@ public class PowerManagerService extends IPowerManager.Stub if (mAutoBrightessEnabled && mScreenBrightnessOverride < 0) { if (!mSkippedScreenOn) { - mScreenBrightness.setTargetLocked(lcdValue, AUTOBRIGHTNESS_ANIM_STEPS, + mScreenBrightness.setTargetLocked(lcdValue, + immediate ? IMMEDIATE_ANIM_STEPS : AUTOBRIGHTNESS_ANIM_STEPS, INITIAL_SCREEN_BRIGHTNESS, (int)mScreenBrightness.curValue); } } @@ -2688,7 +2729,7 @@ public class PowerManagerService extends IPowerManager.Stub if (mLightSensorValue >= 0) { int value = (int)mLightSensorValue; mLightSensorValue = -1; - lightSensorChangedLocked(value); + lightSensorChangedLocked(value, false); } } userActivity(SystemClock.uptimeMillis(), false, BUTTON_EVENT, true); @@ -2948,10 +2989,28 @@ public class PowerManagerService extends IPowerManager.Stub Binder.restoreCallingIdentity(identity); } - // update our animation state - synchronized (mLocks) { - mScreenBrightness.targetValue = brightness; - mScreenBrightness.jumpToTargetLocked(); + mScreenBrightness.targetValue = brightness; + mScreenBrightness.jumpToTargetLocked(); + } + } + + public void setAutoBrightnessAdjustment(float adj) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); + synchronized (mLocks) { + mLightSensorAdjustSetting = adj; + if (mSensorManager != null && mLightSensorEnabled) { + // clear calling identity so sensor manager battery stats are accurate + long identity = Binder.clearCallingIdentity(); + try { + // force recompute of backlight values + if (mLightSensorValue >= 0) { + int value = (int)mLightSensorValue; + mLightSensorValue = -1; + handleLightSensorValue(value, true); + } + } finally { + Binder.restoreCallingIdentity(identity); + } } } } @@ -3056,20 +3115,25 @@ public class PowerManagerService extends IPowerManager.Stub } if (mSensorManager != null && mLightSensorEnabled != enable) { mLightSensorEnabled = enable; - // clear previous values so we will adjust to current brightness when - // auto-brightness is reenabled - mHighestLightSensorValue = -1; - mLightSensorValue = -1; - // clear calling identity so sensor manager battery stats are accurate long identity = Binder.clearCallingIdentity(); try { if (enable) { + // reset our highest value when reenabling + mHighestLightSensorValue = -1; + // force recompute of backlight values + if (mLightSensorValue >= 0) { + int value = (int)mLightSensorValue; + mLightSensorValue = -1; + handleLightSensorValue(value, true); + } mSensorManager.registerListener(mLightListener, mLightSensor, LIGHT_SENSOR_RATE); } else { mSensorManager.unregisterListener(mLightListener); mHandler.removeCallbacks(mAutoBrightnessTask); + mLightSensorPendingDecrease = false; + mLightSensorPendingIncrease = false; } } finally { Binder.restoreCallingIdentity(identity); @@ -3121,43 +3185,45 @@ public class PowerManagerService extends IPowerManager.Stub } }; + private void handleLightSensorValue(int value, boolean immediate) { + long milliseconds = SystemClock.elapsedRealtime(); + if (mLightSensorValue == -1 || + milliseconds < mLastScreenOnTime + mLightSensorWarmupTime) { + // process the value immediately if screen has just turned on + mHandler.removeCallbacks(mAutoBrightnessTask); + mLightSensorPendingDecrease = false; + mLightSensorPendingIncrease = false; + lightSensorChangedLocked(value, immediate); + } else { + if ((value > mLightSensorValue && mLightSensorPendingDecrease) || + (value < mLightSensorValue && mLightSensorPendingIncrease) || + (value == mLightSensorValue) || + (!mLightSensorPendingDecrease && !mLightSensorPendingIncrease)) { + // delay processing to debounce the sensor + mHandler.removeCallbacks(mAutoBrightnessTask); + mLightSensorPendingDecrease = (value < mLightSensorValue); + mLightSensorPendingIncrease = (value > mLightSensorValue); + if (mLightSensorPendingDecrease || mLightSensorPendingIncrease) { + mLightSensorPendingValue = value; + mHandler.postDelayed(mAutoBrightnessTask, LIGHT_SENSOR_DELAY); + } + } else { + mLightSensorPendingValue = value; + } + } + } + SensorEventListener mLightListener = new SensorEventListener() { public void onSensorChanged(SensorEvent event) { + if (mDebugLightSensor) { + Slog.d(TAG, "onSensorChanged: light value: " + event.values[0]); + } synchronized (mLocks) { // ignore light sensor while screen is turning off if (isScreenTurningOffLocked()) { return; } - - int value = (int)event.values[0]; - long milliseconds = SystemClock.elapsedRealtime(); - if (mDebugLightSensor) { - Slog.d(TAG, "onSensorChanged: light value: " + value); - } - if (mLightSensorValue == -1 || - milliseconds < mLastScreenOnTime + mLightSensorWarmupTime) { - // process the value immediately if screen has just turned on - mHandler.removeCallbacks(mAutoBrightnessTask); - mLightSensorPendingDecrease = false; - mLightSensorPendingIncrease = false; - lightSensorChangedLocked(value); - } else { - if ((value > mLightSensorValue && mLightSensorPendingDecrease) || - (value < mLightSensorValue && mLightSensorPendingIncrease) || - (value == mLightSensorValue) || - (!mLightSensorPendingDecrease && !mLightSensorPendingIncrease)) { - // delay processing to debounce the sensor - mHandler.removeCallbacks(mAutoBrightnessTask); - mLightSensorPendingDecrease = (value < mLightSensorValue); - mLightSensorPendingIncrease = (value > mLightSensorValue); - if (mLightSensorPendingDecrease || mLightSensorPendingIncrease) { - mLightSensorPendingValue = value; - mHandler.postDelayed(mAutoBrightnessTask, LIGHT_SENSOR_DELAY); - } - } else { - mLightSensorPendingValue = value; - } - } + handleLightSensorValue((int)event.values[0], false); } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3ae62ad..762acbb 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -133,8 +133,8 @@ class ServerThread extends Thread { // Critical services... try { - Slog.i(TAG, "Entropy Service"); - ServiceManager.addService("entropy", new EntropyService()); + Slog.i(TAG, "Entropy Mixer"); + ServiceManager.addService("entropy", new EntropyMixer()); Slog.i(TAG, "Power Manager"); power = new PowerManagerService(); diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java index c7fbc00..c5c2901 100644 --- a/services/java/com/android/server/UiModeManagerService.java +++ b/services/java/com/android/server/UiModeManagerService.java @@ -65,7 +65,7 @@ class UiModeManagerService extends IUiModeManager.Stub { // Enable launching of applications when entering the dock. private static final boolean ENABLE_LAUNCH_CAR_DOCK_APP = true; - private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = true; + private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = false; private static final int MSG_UPDATE_TWILIGHT = 0; private static final int MSG_ENABLE_LOCATION_UPDATES = 1; @@ -90,6 +90,7 @@ class UiModeManagerService extends IUiModeManager.Stub { private int mNightMode = UiModeManager.MODE_NIGHT_NO; private boolean mCarModeEnabled = false; private boolean mCharging = false; + private final int mDefaultUiModeType; private final boolean mCarModeKeepsScreenOn; private final boolean mDeskModeKeepsScreenOn; @@ -347,6 +348,8 @@ class UiModeManagerService extends IUiModeManager.Stub { mConfiguration.setToDefaults(); + mDefaultUiModeType = context.getResources().getInteger( + com.android.internal.R.integer.config_defaultUiModeType); mCarModeKeepsScreenOn = (context.getResources().getInteger( com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1); mDeskModeKeepsScreenOn = (context.getResources().getInteger( @@ -452,7 +455,7 @@ class UiModeManagerService extends IUiModeManager.Stub { } final void updateConfigurationLocked(boolean sendIt) { - int uiMode = Configuration.UI_MODE_TYPE_NORMAL; + int uiMode = mDefaultUiModeType; if (mCarModeEnabled) { uiMode = Configuration.UI_MODE_TYPE_CAR; } else if (isDeskDockState(mDockState)) { diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index aef3426..5208785 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -61,6 +61,7 @@ import android.text.TextUtils; import android.util.Slog; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; @@ -112,6 +113,10 @@ public class WifiService extends IWifiManager.Stub { private int mScanLocksAcquired; private int mScanLocksReleased; + /* A mapping from UID to scan count */ + private HashMap<Integer, Integer> mScanCount = + new HashMap<Integer, Integer>(); + private final List<Multicaster> mMulticasters = new ArrayList<Multicaster>(); private int mMulticastEnabled; @@ -527,6 +532,15 @@ public class WifiService extends IWifiManager.Stub { */ public void startScan(boolean forceActive) { enforceChangePermission(); + + int uid = Binder.getCallingUid(); + int count = 0; + synchronized (mScanCount) { + if (mScanCount.containsKey(uid)) { + count = mScanCount.get(uid); + } + mScanCount.put(uid, ++count); + } mWifiStateMachine.startScan(forceActive); } @@ -676,7 +690,12 @@ public class WifiService extends IWifiManager.Stub { */ public List<WifiConfiguration> getConfiguredNetworks() { enforceAccessPermission(); - return mWifiStateMachine.syncGetConfiguredNetworks(); + if (mWifiStateMachineChannel != null) { + return mWifiStateMachine.syncGetConfiguredNetworks(mWifiStateMachineChannel); + } else { + Slog.e(TAG, "mWifiStateMachineChannel is not initialized"); + return null; + } } /** @@ -985,6 +1004,13 @@ public class WifiService extends IWifiManager.Stub { } mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent); } + + //Start scan stats tracking when device unplugged + if (pluggedType == 0) { + synchronized (mScanCount) { + mScanCount.clear(); + } + } mPluggedType = pluggedType; } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, @@ -1175,6 +1201,13 @@ public class WifiService extends IWifiManager.Stub { pw.println("Locks held:"); mLocks.dump(pw); + pw.println("Scan count since last plugged in"); + synchronized (mScanCount) { + for(int sc : mScanCount.keySet()) { + pw.println("UID: " + sc + " Scan count: " + mScanCount.get(sc)); + } + } + pw.println(); pw.println("WifiWatchdogStateMachine dump"); mWifiWatchdogStateMachine.dump(pw); diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index b70ed96..8bda755 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -46,7 +46,6 @@ import android.text.TextUtils.SimpleStringSplitter; import android.util.Slog; import android.util.SparseArray; import android.view.IWindow; -import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -96,6 +95,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private static final int DO_SET_SERVICE_INFO = 10; + public static final int ACTIVE_WINDOW_ID = -1; + + public static final long ROOT_NODE_ID = -1; + private static int sNextWindowId; final HandlerCaller mCaller; @@ -467,7 +470,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - public void registerEventListener(IEventListener listener) { + public void registerUiTestAutomationService(IEventListener listener, + AccessibilityServiceInfo accessibilityServiceInfo) { mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT, FUNCTION_REGISTER_EVENT_LISTENER); ComponentName componentName = new ComponentName("foo.bar", @@ -490,13 +494,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } // Hook the automation service up. - AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo(); - accessibilityServiceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; - accessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; Service service = new Service(componentName, accessibilityServiceInfo, true); service.onServiceConnected(componentName, listener.asBinder()); } + public void unregisterUiTestAutomationService(IEventListener listener) { + synchronized (mLock) { + final int serviceCount = mServices.size(); + for (int i = 0; i < serviceCount; i++) { + Service service = mServices.get(i); + if (service.mServiceInterface == listener && service.mIsAutomation) { + // Automation service is not bound, so pretend it died to perform clean up. + service.binderDied(); + } + } + } + } + /** * Removes an AccessibilityInteractionConnection. * @@ -1070,10 +1084,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - public float findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId, - int interactionId, IAccessibilityInteractionConnectionCallback callback, - long interrogatingTid) + public float findAccessibilityNodeInfoByViewId(int accessibilityWindowId, + long accessibilityNodeId, int viewId, int interactionId, + IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); IAccessibilityInteractionConnection connection = null; synchronized (mLock) { mSecurityPolicy.enforceCanRetrieveWindowContent(this); @@ -1081,12 +1096,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (!permissionGranted) { return 0; } else { - connection = getConnectionToRetrievalAllowingWindowLocked(); + connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - if (DEBUG) { - Slog.e(LOG_TAG, "No interaction connection to a retrieve " - + "allowing window."); - } return 0; } } @@ -1094,44 +1105,33 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfoByViewId(viewId, interactionId, callback, - interrogatingPid, interrogatingTid); + connection.findAccessibilityNodeInfoByViewId(accessibilityNodeId, viewId, + interactionId, callback, interrogatingPid, interrogatingTid); } catch (RemoteException re) { if (DEBUG) { - Slog.e(LOG_TAG, "Error finding node."); + Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId()."); } } finally { Binder.restoreCallingIdentity(identityToken); } - return getCompatibilityScale(mSecurityPolicy.getRetrievalAllowingWindowLocked()); - } - - public float findAccessibilityNodeInfosByViewTextInActiveWindow( - String text, int interactionId, - IAccessibilityInteractionConnectionCallback callback, long threadId) - throws RemoteException { - return findAccessibilityNodeInfosByViewText(text, - mSecurityPolicy.mRetrievalAlowingWindowId, View.NO_ID, interactionId, callback, - threadId); + return getCompatibilityScale(resolvedWindowId); } - public float findAccessibilityNodeInfosByViewText(String text, - int accessibilityWindowId, int accessibilityViewId, int interactionId, + public float findAccessibilityNodeInfosByText(int accessibilityWindowId, + long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); IAccessibilityInteractionConnection connection = null; synchronized (mLock) { mSecurityPolicy.enforceCanRetrieveWindowContent(this); final boolean permissionGranted = - mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId); + mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { return 0; } else { - connection = getConnectionToRetrievalAllowingWindowLocked(); + connection = getConnectionLocked(resolvedWindowId); if (connection == null) { - if (DEBUG) { - Slog.e(LOG_TAG, "No interaction connection to focused window."); - } return 0; } } @@ -1139,89 +1139,77 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfosByViewText(text, accessibilityViewId, + connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text, interactionId, callback, interrogatingPid, interrogatingTid); } catch (RemoteException re) { if (DEBUG) { - Slog.e(LOG_TAG, "Error finding node."); + Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()"); } } finally { Binder.restoreCallingIdentity(identityToken); } - return getCompatibilityScale(accessibilityWindowId); + return getCompatibilityScale(resolvedWindowId); } public float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, - int accessibilityViewId, int interactionId, + long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException { + final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); IAccessibilityInteractionConnection connection = null; synchronized (mLock) { mSecurityPolicy.enforceCanRetrieveWindowContent(this); final boolean permissionGranted = - mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, accessibilityWindowId); + mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId); if (!permissionGranted) { return 0; } else { - AccessibilityConnectionWrapper wrapper = - mWindowIdToInteractionConnectionWrapperMap.get(accessibilityWindowId); - if (wrapper == null) { - if (DEBUG) { - Slog.e(LOG_TAG, "No interaction connection to window: " - + accessibilityWindowId); - } + connection = getConnectionLocked(resolvedWindowId); + if (connection == null) { return 0; } - connection = wrapper.mConnection; } } final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityViewId, + connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId, interactionId, callback, interrogatingPid, interrogatingTid); } catch (RemoteException re) { if (DEBUG) { - Slog.e(LOG_TAG, "Error requesting node with accessibilityViewId: " - + accessibilityViewId); + Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()"); } } finally { Binder.restoreCallingIdentity(identityToken); } - return getCompatibilityScale(accessibilityWindowId); + return getCompatibilityScale(resolvedWindowId); } public boolean performAccessibilityAction(int accessibilityWindowId, - int accessibilityViewId, int action, int interactionId, + long accessibilityNodeId, int action, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) { + final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId); IAccessibilityInteractionConnection connection = null; synchronized (mLock) { final boolean permissionGranted = mSecurityPolicy.canPerformActionLocked(this, - accessibilityWindowId, action); + resolvedWindowId, action); if (!permissionGranted) { return false; } else { - AccessibilityConnectionWrapper wrapper = - mWindowIdToInteractionConnectionWrapperMap.get(accessibilityWindowId); - if (wrapper == null) { - if (DEBUG) { - Slog.e(LOG_TAG, "No interaction connection to window: " - + accessibilityWindowId); - } + connection = getConnectionLocked(resolvedWindowId); + if (connection == null) { return false; } - connection = wrapper.mConnection; } } final int interrogatingPid = Binder.getCallingPid(); final long identityToken = Binder.clearCallingIdentity(); try { - connection.performAccessibilityAction(accessibilityViewId, action, interactionId, + connection.performAccessibilityAction(accessibilityNodeId, action, interactionId, callback, interrogatingPid, interrogatingTid); } catch (RemoteException re) { if (DEBUG) { - Slog.e(LOG_TAG, "Error requesting node with accessibilityViewId: " - + accessibilityViewId); + Slog.e(LOG_TAG, "Error calling performAccessibilityAction()"); } } finally { Binder.restoreCallingIdentity(identityToken); @@ -1265,14 +1253,26 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private IAccessibilityInteractionConnection getConnectionToRetrievalAllowingWindowLocked() { - final int windowId = mSecurityPolicy.getRetrievalAllowingWindowLocked(); + private IAccessibilityInteractionConnection getConnectionLocked(int windowId) { if (DEBUG) { Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId); } - AccessibilityConnectionWrapper wrapper = - mWindowIdToInteractionConnectionWrapperMap.get(windowId); - return (wrapper != null) ? wrapper.mConnection : null; + AccessibilityConnectionWrapper wrapper = mWindowIdToInteractionConnectionWrapperMap.get( + windowId); + if (wrapper != null && wrapper.mConnection != null) { + return wrapper.mConnection; + } + if (DEBUG) { + Slog.e(LOG_TAG, "No interaction connection to window: " + windowId); + } + return null; + } + + private int resolveAccessibilityWindowId(int accessibilityWindowId) { + if (accessibilityWindowId == ACTIVE_WINDOW_ID) { + return mSecurityPolicy.mRetrievalAlowingWindowId; + } + return accessibilityWindowId; } private float getCompatibilityScale(int windowId) { diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index df58e83..6fd5c07 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -75,6 +75,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.UserInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -104,6 +105,7 @@ import android.os.ServiceManager; import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserId; import android.provider.Settings; import android.util.EventLog; import android.util.Pair; @@ -111,6 +113,7 @@ import android.util.Slog; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.SparseArray; +import android.util.SparseIntArray; import android.util.TimeUtils; import android.view.Gravity; import android.view.LayoutInflater; @@ -135,6 +138,7 @@ import java.io.StringWriter; import java.lang.IllegalStateException; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -149,7 +153,9 @@ import java.util.concurrent.atomic.AtomicLong; public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback { + private static final String USER_DATA_DIR = "/data/user/"; static final String TAG = "ActivityManager"; + static final String TAG_MU = "ActivityManagerServiceMU"; static final boolean DEBUG = false; static final boolean localLOGV = DEBUG; static final boolean DEBUG_SWITCH = localLOGV || false; @@ -158,6 +164,7 @@ public final class ActivityManagerService extends ActivityManagerNative static final boolean DEBUG_OOM_ADJ = localLOGV || false; static final boolean DEBUG_TRANSITION = localLOGV || false; static final boolean DEBUG_BROADCAST = localLOGV || false; + static final boolean DEBUG_BACKGROUND_BROADCAST = DEBUG_BROADCAST || 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; @@ -171,6 +178,7 @@ public final class ActivityManagerService extends ActivityManagerNative 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 DEBUG_MU = localLOGV || false; static final boolean VALIDATE_TOKENS = false; static final boolean SHOW_ACTIVITY_START_TIME = true; @@ -221,7 +229,8 @@ public final class ActivityManagerService extends ActivityManagerNative 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; + static final int BROADCAST_FG_TIMEOUT = 10*1000; + static final int BROADCAST_BG_TIMEOUT = 60*1000; // How long we wait for a service to finish executing. static final int SERVICE_TIMEOUT = 20*1000; @@ -276,33 +285,832 @@ public final class ActivityManagerService extends ActivityManagerNative = new ArrayList<PendingActivityLaunch>(); /** - * 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 - * a bunch of processes to execute IntentReceiver components. + * BROADCASTS + * + * We keep two broadcast queues and associated bookkeeping, one for those at + * foreground priority, and one for normal (background-priority) broadcasts. */ - final ArrayList<BroadcastRecord> mParallelBroadcasts - = new ArrayList<BroadcastRecord>(); + public class BroadcastQueue { + static final String TAG = "BroadcastQueue"; - /** - * List of all active broadcasts that are to be executed one at a time. - * The object at the top of the list is the currently activity broadcasts; - * those after it are waiting for the top to finish.. - */ - final ArrayList<BroadcastRecord> mOrderedBroadcasts - = new ArrayList<BroadcastRecord>(); + static final int MAX_BROADCAST_HISTORY = 25; - /** - * Historical data of past broadcasts, for debugging. - */ - static final int MAX_BROADCAST_HISTORY = 25; - final BroadcastRecord[] mBroadcastHistory - = new BroadcastRecord[MAX_BROADCAST_HISTORY]; + /** + * Recognizable moniker for this queue + */ + String mQueueName; - /** - * Set when we current have a BROADCAST_INTENT_MSG in flight. - */ - boolean mBroadcastsScheduled = false; + /** + * Timeout period for this queue's broadcasts + */ + long mTimeoutPeriod; + + /** + * Lists 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 + * a bunch of processes to execute IntentReceiver components. Background- + * and foreground-priority broadcasts are queued separately. + */ + final ArrayList<BroadcastRecord> mParallelBroadcasts + = new ArrayList<BroadcastRecord>(); + /** + * List of all active broadcasts that are to be executed one at a time. + * The object at the top of the list is the currently activity broadcasts; + * those after it are waiting for the top to finish. As with parallel + * broadcasts, separate background- and foreground-priority queues are + * maintained. + */ + final ArrayList<BroadcastRecord> mOrderedBroadcasts + = new ArrayList<BroadcastRecord>(); + + /** + * Historical data of past broadcasts, for debugging. + */ + final BroadcastRecord[] mBroadcastHistory + = new BroadcastRecord[MAX_BROADCAST_HISTORY]; + + /** + * Set when we current have a BROADCAST_INTENT_MSG in flight. + */ + boolean mBroadcastsScheduled = false; + + /** + * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler. + */ + boolean mPendingBroadcastTimeoutMessage; + + /** + * Intent broadcasts that we have tried to start, but are + * waiting for the application's process to be created. We only + * need one per scheduling class (instead of a list) because we always + * process broadcasts one at a time, so no others can be started while + * waiting for this one. + */ + BroadcastRecord mPendingBroadcast = null; + + /** + * The receiver index that is pending, to restart the broadcast if needed. + */ + int mPendingBroadcastRecvIndex; + + BroadcastQueue(String name, long timeoutPeriod) { + mQueueName = name; + mTimeoutPeriod = timeoutPeriod; + } + + public boolean isPendingBroadcastProcessLocked(int pid) { + return mPendingBroadcast != null && mPendingBroadcast.curApp.pid == pid; + } + + public void enqueueParallelBroadcastLocked(BroadcastRecord r) { + mParallelBroadcasts.add(r); + } + + public void enqueueOrderedBroadcastLocked(BroadcastRecord r) { + mOrderedBroadcasts.add(r); + } + + public final boolean replaceParallelBroadcastLocked(BroadcastRecord r) { + for (int i=mParallelBroadcasts.size()-1; i>=0; i--) { + if (r.intent.filterEquals(mParallelBroadcasts.get(i).intent)) { + if (DEBUG_BROADCAST) Slog.v(TAG, + "***** DROPPING PARALLEL [" + + mQueueName + "]: " + r.intent); + mParallelBroadcasts.set(i, r); + return true; + } + } + return false; + } + + public final boolean replaceOrderedBroadcastLocked(BroadcastRecord r) { + for (int i=mOrderedBroadcasts.size()-1; i>0; i--) { + if (r.intent.filterEquals(mOrderedBroadcasts.get(i).intent)) { + if (DEBUG_BROADCAST) Slog.v(TAG, + "***** DROPPING ORDERED [" + + mQueueName + "]: " + r.intent); + mOrderedBroadcasts.set(i, r); + return true; + } + } + return false; + } + + public boolean sendPendingBroadcastsLocked(ProcessRecord app) { + boolean didSomething = false; + final BroadcastRecord br = mPendingBroadcast; + if (br != null && br.curApp.pid == app.pid) { + try { + mPendingBroadcast = null; + processCurBroadcastLocked(br, app); + didSomething = true; + } catch (Exception e) { + Slog.w(TAG, "Exception in new application when starting receiver " + + br.curComponent.flattenToShortString(), e); + logBroadcastReceiverDiscardLocked(br); + finishReceiverLocked(br, br.resultCode, br.resultData, + br.resultExtras, br.resultAbort, true); + scheduleBroadcastsLocked(); + // We need to reset the state if we fails to start the receiver. + br.state = BroadcastRecord.IDLE; + throw new RuntimeException(e.getMessage()); + } + } + return didSomething; + } + + public void skipPendingBroadcastLocked(int pid) { + final BroadcastRecord br = mPendingBroadcast; + if (br != null && br.curApp.pid == pid) { + br.state = BroadcastRecord.IDLE; + br.nextReceiver = mPendingBroadcastRecvIndex; + mPendingBroadcast = null; + scheduleBroadcastsLocked(); + } + } + + public void skipCurrentReceiverLocked(ProcessRecord app) { + boolean reschedule = false; + BroadcastRecord r = app.curReceiver; + if (r != null) { + // 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. + logBroadcastReceiverDiscardLocked(r); + finishReceiverLocked(r, r.resultCode, r.resultData, + r.resultExtras, r.resultAbort, true); + reschedule = true; + } + + r = mPendingBroadcast; + if (r != null && r.curApp == app) { + if (DEBUG_BROADCAST) Slog.v(TAG, + "[" + mQueueName + "] skip & discard pending app " + r); + logBroadcastReceiverDiscardLocked(r); + finishReceiverLocked(r, r.resultCode, r.resultData, + r.resultExtras, r.resultAbort, true); + reschedule = true; + } + if (reschedule) { + scheduleBroadcastsLocked(); + } + } + + public void scheduleBroadcastsLocked() { + if (DEBUG_BROADCAST) Slog.v(TAG, "Schedule broadcasts [" + + mQueueName + "]: current=" + + mBroadcastsScheduled); + + if (mBroadcastsScheduled) { + return; + } + mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this)); + mBroadcastsScheduled = true; + } + + public BroadcastRecord getMatchingOrderedReceiver(IBinder receiver) { + if (mOrderedBroadcasts.size() > 0) { + final BroadcastRecord r = mOrderedBroadcasts.get(0); + if (r != null && r.receiver == receiver) { + return r; + } + } + return null; + } + + public boolean finishReceiverLocked(BroadcastRecord r, int resultCode, + String resultData, Bundle resultExtras, boolean resultAbort, + boolean explicit) { + int state = r.state; + r.state = BroadcastRecord.IDLE; + if (state == BroadcastRecord.IDLE) { + if (explicit) { + Slog.w(TAG, "finishReceiver [" + mQueueName + "] called but state is IDLE"); + } + } + r.receiver = null; + r.intent.setComponent(null); + if (r.curApp != null) { + r.curApp.curReceiver = null; + } + if (r.curFilter != null) { + r.curFilter.receiverList.curBroadcast = null; + } + r.curFilter = null; + r.curApp = null; + r.curComponent = null; + r.curReceiver = null; + mPendingBroadcast = null; + + r.resultCode = resultCode; + r.resultData = resultData; + r.resultExtras = resultExtras; + r.resultAbort = resultAbort; + + // We will process the next receiver right now if this is finishing + // an app receiver (which is always asynchronous) or after we have + // come back from calling a receiver. + return state == BroadcastRecord.APP_RECEIVE + || state == BroadcastRecord.CALL_DONE_RECEIVE; + } + + private final void processNextBroadcast(boolean fromMsg) { + synchronized(ActivityManagerService.this) { + BroadcastRecord r; + + if (DEBUG_BROADCAST) Slog.v(TAG, "processNextBroadcast [" + + mQueueName + "]: " + + mParallelBroadcasts.size() + " broadcasts, " + + mOrderedBroadcasts.size() + " ordered broadcasts"); + + updateCpuStats(); + + if (fromMsg) { + mBroadcastsScheduled = false; + } + + // First, deliver any non-serialized broadcasts right away. + while (mParallelBroadcasts.size() > 0) { + r = mParallelBroadcasts.remove(0); + r.dispatchTime = SystemClock.uptimeMillis(); + r.dispatchClockTime = System.currentTimeMillis(); + final int N = r.receivers.size(); + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing parallel broadcast [" + + mQueueName + "] " + r); + for (int i=0; i<N; i++) { + Object target = r.receivers.get(i); + if (DEBUG_BROADCAST) Slog.v(TAG, + "Delivering non-ordered on [" + mQueueName + "] to registered " + + target + ": " + r); + deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false); + } + addBroadcastToHistoryLocked(r); + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Done with parallel broadcast [" + + mQueueName + "] " + r); + } + + // Now take care of the next serialized one... + + // If we are waiting for a process to come up to handle the next + // broadcast, then do nothing at this point. Just in case, we + // check that the process we're waiting for still exists. + if (mPendingBroadcast != null) { + if (DEBUG_BROADCAST_LIGHT) { + Slog.v(TAG, "processNextBroadcast [" + + mQueueName + "]: waiting for " + + mPendingBroadcast.curApp); + } + + boolean isDead; + synchronized (mPidsSelfLocked) { + isDead = (mPidsSelfLocked.get(mPendingBroadcast.curApp.pid) == null); + } + if (!isDead) { + // It's still alive, so keep waiting + return; + } else { + Slog.w(TAG, "pending app [" + + mQueueName + "]" + mPendingBroadcast.curApp + + " died before responding to broadcast"); + mPendingBroadcast.state = BroadcastRecord.IDLE; + mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex; + mPendingBroadcast = null; + } + } + + boolean looped = false; + + do { + if (mOrderedBroadcasts.size() == 0) { + // No more broadcasts pending, so all done! + scheduleAppGcsLocked(); + if (looped) { + // If we had finished the last ordered broadcast, then + // make sure all processes have correct oom and sched + // adjustments. + updateOomAdjLocked(); + } + return; + } + r = mOrderedBroadcasts.get(0); + boolean forceReceive = false; + + // Ensure that even if something goes awry with the timeout + // detection, we catch "hung" broadcasts here, discard them, + // 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 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 (mProcessesReady && r.dispatchTime > 0) { + long now = SystemClock.uptimeMillis(); + if ((numReceivers > 0) && + (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) { + Slog.w(TAG, "Hung broadcast [" + + mQueueName + "] discarded after timeout failure:" + + " now=" + now + + " dispatchTime=" + r.dispatchTime + + " startTime=" + r.receiverTime + + " intent=" + r.intent + + " numReceivers=" + numReceivers + + " nextReceiver=" + r.nextReceiver + + " state=" + r.state); + broadcastTimeoutLocked(false); // forcibly finish this broadcast + forceReceive = true; + r.state = BroadcastRecord.IDLE; + } + } + + if (r.state != BroadcastRecord.IDLE) { + if (DEBUG_BROADCAST) Slog.d(TAG, + "processNextBroadcast(" + + mQueueName + ") called when not idle (state=" + + r.state + ")"); + return; + } + + if (r.receivers == null || r.nextReceiver >= numReceivers + || r.resultAbort || forceReceive) { + // No more receivers for this broadcast! Send the final + // result if requested... + if (r.resultTo != null) { + try { + if (DEBUG_BROADCAST) { + int seq = r.intent.getIntExtra("seq", -1); + Slog.i(TAG, "Finishing broadcast [" + + mQueueName + "] " + r.intent.getAction() + + " seq=" + seq + " app=" + r.callerApp); + } + performReceiveLocked(r.callerApp, r.resultTo, + new Intent(r.intent), r.resultCode, + r.resultData, r.resultExtras, false, false); + // Set this to null so that the reference + // (local and remote) isnt kept in the mBroadcastHistory. + r.resultTo = null; + } catch (RemoteException e) { + Slog.w(TAG, "Failure [" + + mQueueName + "] sending broadcast result of " + + r.intent, e); + } + } + + if (DEBUG_BROADCAST) Slog.v(TAG, "Cancelling BROADCAST_TIMEOUT_MSG"); + cancelBroadcastTimeoutLocked(); + + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Finished with ordered broadcast " + + r); + + // ... and on to the next... + addBroadcastToHistoryLocked(r); + mOrderedBroadcasts.remove(0); + r = null; + looped = true; + continue; + } + } while (r == null); + + // Get the next receiver... + int recIdx = r.nextReceiver++; + + // Keep track of when this receiver started, and make sure there + // is a timeout message pending to kill it if need be. + r.receiverTime = SystemClock.uptimeMillis(); + if (recIdx == 0) { + r.dispatchTime = r.receiverTime; + r.dispatchClockTime = System.currentTimeMillis(); + if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing ordered broadcast [" + + mQueueName + "] " + r); + } + if (! mPendingBroadcastTimeoutMessage) { + long timeoutTime = r.receiverTime + mTimeoutPeriod; + if (DEBUG_BROADCAST) Slog.v(TAG, + "Submitting BROADCAST_TIMEOUT_MSG [" + + mQueueName + "] for " + r + " at " + timeoutTime); + setBroadcastTimeoutLocked(timeoutTime); + } + + Object nextReceiver = r.receivers.get(recIdx); + if (nextReceiver instanceof BroadcastFilter) { + // Simple case: this is a registered receiver who gets + // a direct call. + BroadcastFilter filter = (BroadcastFilter)nextReceiver; + if (DEBUG_BROADCAST) Slog.v(TAG, + "Delivering ordered [" + + mQueueName + "] to registered " + + filter + ": " + r); + deliverToRegisteredReceiverLocked(r, filter, r.ordered); + if (r.receiver == null || !r.ordered) { + // The receiver has already finished, so schedule to + // process the next one. + if (DEBUG_BROADCAST) Slog.v(TAG, "Quick finishing [" + + mQueueName + "]: ordered=" + + r.ordered + " receiver=" + r.receiver); + r.state = BroadcastRecord.IDLE; + scheduleBroadcastsLocked(); + } + return; + } + + // Hard case: need to instantiate the receiver, possibly + // starting its application process to host it. + + ResolveInfo info = + (ResolveInfo)nextReceiver; + + boolean skip = false; + int perm = checkComponentPermission(info.activityInfo.permission, + r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid, + info.activityInfo.exported); + if (perm != PackageManager.PERMISSION_GRANTED) { + if (!info.activityInfo.exported) { + Slog.w(TAG, "Permission Denial: broadcasting " + + r.intent.toString() + + " from " + r.callerPackage + " (pid=" + r.callingPid + + ", uid=" + r.callingUid + ")" + + " is not exported from uid " + info.activityInfo.applicationInfo.uid + + " due to receiver " + info.activityInfo.packageName + + "/" + info.activityInfo.name); + } else { + Slog.w(TAG, "Permission Denial: broadcasting " + + r.intent.toString() + + " from " + r.callerPackage + " (pid=" + r.callingPid + + ", uid=" + r.callingUid + ")" + + " requires " + info.activityInfo.permission + + " due to receiver " + info.activityInfo.packageName + + "/" + info.activityInfo.name); + } + skip = true; + } + if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID && + r.requiredPermission != null) { + try { + perm = AppGlobals.getPackageManager(). + checkPermission(r.requiredPermission, + info.activityInfo.applicationInfo.packageName); + } catch (RemoteException e) { + perm = PackageManager.PERMISSION_DENIED; + } + if (perm != PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "Permission Denial: receiving " + + r.intent + " to " + + info.activityInfo.applicationInfo.packageName + + " requires " + r.requiredPermission + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")"); + skip = true; + } + } + 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 [" + + mQueueName + "] " + r + " to " + r.curApp + + ": process crashing"); + skip = true; + } + + if (skip) { + if (DEBUG_BROADCAST) Slog.v(TAG, + "Skipping delivery of ordered [" + + mQueueName + "] " + r + " for whatever reason"); + r.receiver = null; + r.curFilter = null; + r.state = BroadcastRecord.IDLE; + scheduleBroadcastsLocked(); + return; + } + + r.state = BroadcastRecord.APP_RECEIVE; + String targetProcess = info.activityInfo.processName; + r.curComponent = new ComponentName( + info.activityInfo.applicationInfo.packageName, + info.activityInfo.name); + if (r.callingUid != Process.SYSTEM_UID) { + info.activityInfo = getActivityInfoForUser(info.activityInfo, UserId + .getUserId(r.callingUid)); + } + r.curReceiver = info.activityInfo; + if (DEBUG_MU && r.callingUid > UserId.PER_USER_RANGE) { + Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, " + + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = " + + info.activityInfo.applicationInfo.uid); + } + + // Broadcast is being executed, its package can't be stopped. + try { + AppGlobals.getPackageManager().setPackageStoppedState( + r.curComponent.getPackageName(), false); + } catch (RemoteException e) { + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Failed trying to unstop package " + + r.curComponent.getPackageName() + ": " + e); + } + + // Is this receiver's application already running? + ProcessRecord app = getProcessRecordLocked(targetProcess, + info.activityInfo.applicationInfo.uid); + if (app != null && app.thread != null) { + try { + app.addPackage(info.activityInfo.packageName); + processCurBroadcastLocked(r, app); + return; + } catch (RemoteException e) { + Slog.w(TAG, "Exception when sending broadcast to " + + r.curComponent, e); + } + + // If a dead object exception was thrown -- fall through to + // restart the application. + } + + // Not running -- get it started, to be executed when the app comes up. + if (DEBUG_BROADCAST) Slog.v(TAG, + "Need to start app [" + + mQueueName + "] " + targetProcess + " for broadcast " + r); + if ((r.curApp=startProcessLocked(targetProcess, + info.activityInfo.applicationInfo, true, + r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND, + "broadcast", r.curComponent, + (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0)) + == null) { + // Ah, this recipient is unavailable. Finish it if necessary, + // and mark the broadcast record as ready for the next. + Slog.w(TAG, "Unable to launch app " + + info.activityInfo.applicationInfo.packageName + "/" + + info.activityInfo.applicationInfo.uid + " for broadcast " + + r.intent + ": process is bad"); + logBroadcastReceiverDiscardLocked(r); + finishReceiverLocked(r, r.resultCode, r.resultData, + r.resultExtras, r.resultAbort, true); + scheduleBroadcastsLocked(); + r.state = BroadcastRecord.IDLE; + return; + } + + mPendingBroadcast = r; + mPendingBroadcastRecvIndex = recIdx; + } + } + + final void setBroadcastTimeoutLocked(long timeoutTime) { + if (! mPendingBroadcastTimeoutMessage) { + Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this); + mHandler.sendMessageAtTime(msg, timeoutTime); + mPendingBroadcastTimeoutMessage = true; + } + } + + final void cancelBroadcastTimeoutLocked() { + if (mPendingBroadcastTimeoutMessage) { + mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this); + mPendingBroadcastTimeoutMessage = false; + } + } + + 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() + mTimeoutPeriod; + setBroadcastTimeoutLocked(timeoutTime); + return; + } + 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 + mTimeoutPeriod; + 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 [" + + mQueueName + "] @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for " + + timeoutTime); + setBroadcastTimeoutLocked(timeoutTime); + return; + } + } + + 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; + } + + ProcessRecord app = null; + String anrMessage = 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 (ActivityManagerService.this.mPidsSelfLocked) { + app = ActivityManagerService.this.mPidsSelfLocked.get( + bf.receiverList.pid); + } + } + } 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, r.resultCode, r.resultData, + r.resultExtras, r.resultAbort, true); + scheduleBroadcastsLocked(); + + if (anrMessage != null) { + // 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 addBroadcastToHistoryLocked(BroadcastRecord r) { + if (r.callingUid < 0) { + // This was from a registerReceiver() call; ignore it. + return; + } + System.arraycopy(mBroadcastHistory, 0, mBroadcastHistory, 1, + MAX_BROADCAST_HISTORY-1); + r.finishTime = SystemClock.uptimeMillis(); + mBroadcastHistory[0] = r; + } + + final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) { + if (r.nextReceiver > 0) { + Object curReceiver = r.receivers.get(r.nextReceiver-1); + if (curReceiver instanceof BroadcastFilter) { + BroadcastFilter bf = (BroadcastFilter) curReceiver; + EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER, + System.identityHashCode(r), + r.intent.getAction(), + r.nextReceiver - 1, + System.identityHashCode(bf)); + } else { + EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP, + System.identityHashCode(r), + r.intent.getAction(), + r.nextReceiver - 1, + ((ResolveInfo)curReceiver).toString()); + } + } else { + Slog.w(TAG, "Discarding broadcast before first receiver is invoked: " + + r); + EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP, + System.identityHashCode(r), + r.intent.getAction(), + r.nextReceiver, + "NONE"); + } + } + + final boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args, + int opti, boolean dumpAll, String dumpPackage, boolean needSep) { + if (mParallelBroadcasts.size() > 0 || mOrderedBroadcasts.size() > 0 + || mPendingBroadcast != null) { + boolean printed = false; + for (int i=mParallelBroadcasts.size()-1; i>=0; i--) { + BroadcastRecord br = mParallelBroadcasts.get(i); + if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) { + continue; + } + if (!printed) { + if (needSep) { + pw.println(); + needSep = false; + } + printed = true; + pw.println(" Active broadcasts [" + mQueueName + "]:"); + } + pw.println(" Broadcast #" + i + ":"); + br.dump(pw, " "); + } + printed = false; + needSep = true; + for (int i=mOrderedBroadcasts.size()-1; i>=0; i--) { + BroadcastRecord br = mOrderedBroadcasts.get(i); + if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) { + continue; + } + if (!printed) { + if (needSep) { + pw.println(); + } + needSep = true; + pw.println(" Active ordered broadcasts [" + mQueueName + "]:"); + } + pw.println(" Ordered Broadcast #" + i + ":"); + mOrderedBroadcasts.get(i).dump(pw, " "); + } + if (dumpPackage == null || (mPendingBroadcast != null + && dumpPackage.equals(mPendingBroadcast.callerPackage))) { + if (needSep) { + pw.println(); + } + pw.println(" Pending broadcast [" + mQueueName + "]:"); + if (mPendingBroadcast != null) { + mPendingBroadcast.dump(pw, " "); + } else { + pw.println(" (null)"); + } + needSep = true; + } + } + + boolean printed = false; + for (int i=0; i<MAX_BROADCAST_HISTORY; i++) { + BroadcastRecord r = mBroadcastHistory[i]; + if (r == null) { + break; + } + if (dumpPackage != null && !dumpPackage.equals(r.callerPackage)) { + continue; + } + if (!printed) { + if (needSep) { + pw.println(); + } + needSep = true; + pw.println(" Historical broadcasts [" + mQueueName + "]:"); + printed = true; + } + if (dumpAll) { + pw.print(" Historical Broadcast #"); pw.print(i); pw.println(":"); + r.dump(pw, " "); + } else { + if (i >= 50) { + pw.println(" ..."); + break; + } + pw.print(" #"); pw.print(i); pw.print(": "); pw.println(r); + } + } + + return needSep; + } + } + + final BroadcastQueue mFgBroadcastQueue = new BroadcastQueue("foreground", BROADCAST_FG_TIMEOUT); + final BroadcastQueue mBgBroadcastQueue = new BroadcastQueue("background", BROADCAST_BG_TIMEOUT); + // Convenient for easy iteration over the queues. Foreground is first + // so that dispatch of foreground broadcasts gets precedence. + final BroadcastQueue[] mBroadcastQueues = { mFgBroadcastQueue, mBgBroadcastQueue }; + + BroadcastQueue broadcastQueueForIntent(Intent intent) { + final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0; + if (DEBUG_BACKGROUND_BROADCAST) { + Slog.i(TAG, "Broadcast intent " + intent + " on " + + (isFg ? "foreground" : "background") + + " queue"); + } + return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue; + } + + BroadcastRecord broadcastRecordForReceiverLocked(IBinder receiver) { + for (BroadcastQueue queue : mBroadcastQueues) { + BroadcastRecord r = queue.getMatchingOrderedReceiver(receiver); + if (r != null) { + return r; + } + } + return null; + } /** * Activity we have told the window manager to have key focus. @@ -456,25 +1264,6 @@ public final class ActivityManagerService extends ActivityManagerNative 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 - * one at a time, so no others can be started while waiting for this - * one. - */ - 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. @@ -513,17 +1302,7 @@ public final class ActivityManagerService extends ActivityManagerNative final HashMap<String, ArrayList<Intent>> mStickyBroadcasts = new HashMap<String, ArrayList<Intent>>(); - /** - * All currently running services. - */ - final HashMap<ComponentName, ServiceRecord> mServices = - new HashMap<ComponentName, ServiceRecord>(); - - /** - * All currently running services indexed by the Intent used to start them. - */ - final HashMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent = - new HashMap<Intent.FilterComparison, ServiceRecord>(); + final ServiceMap mServiceMap = new ServiceMap(); /** * All currently bound service connections. Keys are the IBinder of @@ -589,6 +1368,8 @@ public final class ActivityManagerService extends ActivityManagerNative final HashMap<ComponentName, ContentProviderRecord> mProvidersByClass = new HashMap<ComponentName, ContentProviderRecord>(); + final ProviderMap mProviderMap = new ProviderMap(); + /** * List of content providers who have clients waiting for them. The * application is currently being launched and the provider will be @@ -619,6 +1400,7 @@ public final class ActivityManagerService extends ActivityManagerNative uid = _uid; } } + private static ThreadLocal<Identity> sCallerIdentity = new ThreadLocal<Identity>(); /** @@ -910,11 +1692,12 @@ public final class ActivityManagerService extends ActivityManagerNative Intent intent = new Intent("android.intent.action.ANR"); if (!mProcessesReady) { - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY + | Intent.FLAG_RECEIVER_FOREGROUND); } broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, - false, false, MY_PID, Process.SYSTEM_UID); + false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */); Dialog d = new AppNotRespondingDialog(ActivityManagerService.this, mContext, proc, (ActivityRecord)data.get("activity")); @@ -987,11 +1770,13 @@ public final class ActivityManagerService extends ActivityManagerNative case BROADCAST_INTENT_MSG: { if (DEBUG_BROADCAST) Slog.v( TAG, "Received BROADCAST_INTENT_MSG"); - processNextBroadcast(true); + BroadcastQueue queue = (BroadcastQueue) msg.obj; + queue.processNextBroadcast(true); } break; case BROADCAST_TIMEOUT_MSG: { + final BroadcastQueue queue = (BroadcastQueue) msg.obj; synchronized (ActivityManagerService.this) { - broadcastTimeoutLocked(true); + queue.broadcastTimeoutLocked(true); } } break; case SERVICE_TIMEOUT_MSG: { @@ -1299,6 +2084,7 @@ public final class ActivityManagerService extends ActivityManagerNative ServiceManager.addService("activity", m); ServiceManager.addService("meminfo", new MemBinder(m)); ServiceManager.addService("gfxinfo", new GraphicsBinder(m)); + ServiceManager.addService("dbinfo", new DbBinder(m)); if (MONITOR_CPU_USAGE) { ServiceManager.addService("cpuinfo", new CpuBinder(m)); } @@ -1453,6 +2239,26 @@ public final class ActivityManagerService extends ActivityManagerNative } } + static class DbBinder extends Binder { + ActivityManagerService mActivityManagerService; + DbBinder(ActivityManagerService activityManagerService) { + mActivityManagerService = activityManagerService; + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mActivityManagerService.checkCallingPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump dbinfo from from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " without permission " + android.Manifest.permission.DUMP); + return; + } + + mActivityManagerService.dumpDbInfo(fd, pw, args); + } + } + static class CpuBinder extends Binder { ActivityManagerService mActivityManagerService; CpuBinder(ActivityManagerService activityManagerService) { @@ -1790,6 +2596,7 @@ public final class ActivityManagerService extends ActivityManagerNative processName); return procs != null ? procs.valueAt(0) : null; } + // uid = applyUserId(uid); ProcessRecord proc = mProcessNames.get(processName, uid); return proc; } @@ -1919,6 +2726,7 @@ public final class ActivityManagerService extends ActivityManagerNative try { int uid = app.info.uid; + int[] gids = null; try { gids = mContext.getPackageManager().getPackageGids( @@ -2030,7 +2838,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - boolean startHomeActivityLocked() { + boolean startHomeActivityLocked(int userId) { if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL && mTopAction == null) { // We are running in factory test mode, but unable to find @@ -2053,6 +2861,8 @@ public final class ActivityManagerService extends ActivityManagerNative aInfo.applicationInfo.packageName, aInfo.name)); // Don't do this if the home app is currently being // instrumented. + aInfo = new ActivityInfo(aInfo); + aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId); ProcessRecord app = getProcessRecordLocked(aInfo.processName, aInfo.applicationInfo.uid); if (app == null || app.instrumentationClass == null) { @@ -2061,11 +2871,10 @@ public final class ActivityManagerService extends ActivityManagerNative null, null, 0, 0, 0, false, false, null); } } - - + return true; } - + /** * Starts the "new version setup screen" if appropriate. */ @@ -2229,10 +3038,23 @@ public final class ActivityManagerService extends ActivityManagerNative int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, String profileFile, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { + int userId = 0; + if (intent.getCategories() != null && intent.getCategories().contains(Intent.CATEGORY_HOME)) { + // Requesting home, set the identity to the current user + // HACK! + userId = mCurrentUserId; + } else { + // TODO: Fix this in a better way - calls coming from SystemUI should probably carry + // the current user's userId + if (Binder.getCallingUid() < Process.FIRST_APPLICATION_UID) { + userId = 0; + } else { + userId = Binder.getOrigCallingUser(); + } + } return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, - grantedUriPermissions, grantedMode, resultTo, resultWho, - requestCode, onlyIfNeeded, debug, profileFile, profileFd, autoStopProfiler, - null, null); + grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, + debug, profileFile, profileFd, autoStopProfiler, null, null, userId); } public final WaitResult startActivityAndWait(IApplicationThread caller, @@ -2241,10 +3063,11 @@ public final class ActivityManagerService extends ActivityManagerNative String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, String profileFile, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { WaitResult res = new WaitResult(); + int userId = Binder.getOrigCallingUser(); mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, profileFile, profileFd, autoStopProfiler, - res, null); + res, null, userId); return res; } @@ -2253,9 +3076,11 @@ public final class ActivityManagerService extends ActivityManagerNative int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, Configuration config) { - return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, + int ret = mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, - requestCode, onlyIfNeeded, debug, null, null, false, null, config); + requestCode, onlyIfNeeded, + debug, null, null, false, null, config, Binder.getOrigCallingUser()); + return ret; } public int startActivityIntentSender(IApplicationThread caller, @@ -2283,9 +3108,9 @@ public final class ActivityManagerService extends ActivityManagerNative mAppSwitchesAllowedTime = 0; } } - - return pir.sendInner(0, fillInIntent, resolvedType, null, + int ret = pir.sendInner(0, fillInIntent, resolvedType, null, null, resultTo, resultWho, requestCode, flagsMask, flagsValues); + return ret; } public boolean startNextMatchingActivity(IBinder callingActivity, @@ -2388,20 +3213,24 @@ public final class ActivityManagerService extends ActivityManagerNative // This is so super not safe, that only the system (or okay root) // can do it. + int userId = Binder.getOrigCallingUser(); final int callingUid = Binder.getCallingUid(); if (callingUid != 0 && callingUid != Process.myUid()) { throw new SecurityException( "startActivityInPackage only available to the system"); } - return mMainStack.startActivityMayWait(null, uid, intent, resolvedType, + int ret = mMainStack.startActivityMayWait(null, uid, intent, resolvedType, null, 0, resultTo, resultWho, requestCode, onlyIfNeeded, false, - null, null, false, null, null); + null, null, false, null, null, userId); + return ret; } public final int startActivities(IApplicationThread caller, Intent[] intents, String[] resolvedTypes, IBinder resultTo) { - return mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo); + int ret = mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo, + Binder.getOrigCallingUser()); + return ret; } public final int startActivitiesInPackage(int uid, @@ -2414,8 +3243,9 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException( "startActivityInPackage only available to the system"); } - - return mMainStack.startActivities(null, uid, intents, resolvedTypes, resultTo); + int ret = mMainStack.startActivities(null, uid, intents, resolvedTypes, resultTo, + UserId.getUserId(uid)); + return ret; } final void addRecentTaskLocked(TaskRecord task) { @@ -2427,8 +3257,9 @@ public final class ActivityManagerService extends ActivityManagerNative // Remove any existing entries that are the same kind of task. for (int i=0; i<N; i++) { TaskRecord tr = mRecentTasks.get(i); - if ((task.affinity != null && task.affinity.equals(tr.affinity)) - || (task.intent != null && task.intent.filterEquals(tr.intent))) { + if (task.userId == tr.userId + && ((task.affinity != null && task.affinity.equals(tr.affinity)) + || (task.intent != null && task.intent.filterEquals(tr.intent)))) { mRecentTasks.remove(i); i--; N--; @@ -2787,7 +3618,6 @@ public final class ActivityManagerService extends ActivityManagerNative private final int getLRURecordIndexForAppLocked(IApplicationThread thread) { IBinder threadBinder = thread.asBinder(); - // Find the application record. for (int i=mLruProcesses.size()-1; i>=0; i--) { ProcessRecord rec = mLruProcesses.get(i); @@ -3164,7 +3994,7 @@ public final class ActivityManagerService extends ActivityManagerNative } public boolean clearApplicationUserData(final String packageName, - final IPackageDataObserver observer) { + final IPackageDataObserver observer, final int userId) { int uid = Binder.getCallingUid(); int pid = Binder.getCallingPid(); long callingId = Binder.clearCallingIdentity(); @@ -3199,7 +4029,7 @@ public final class ActivityManagerService extends ActivityManagerNative Uri.fromParts("package", packageName, null)); intent.putExtra(Intent.EXTRA_UID, pkgUid); broadcastIntentInPackage("android", Process.SYSTEM_UID, intent, - null, null, 0, null, null, null, false, false); + null, null, 0, null, null, null, false, false, userId); } catch (RemoteException e) { } } finally { @@ -3294,7 +4124,7 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, msg); throw new SecurityException(msg); } - + final int userId = Binder.getOrigCallingUser(); long callingId = Binder.clearCallingIdentity(); try { IPackageManager pm = AppGlobals.getPackageManager(); @@ -3302,6 +4132,8 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized(this) { try { pkgUid = pm.getPackageUid(packageName); + // Convert the uid to the one for the calling user + pkgUid = UserId.getUid(userId, pkgUid); } catch (RemoteException e) { } if (pkgUid == -1) { @@ -3384,7 +4216,7 @@ public final class ActivityManagerService extends ActivityManagerNative } broadcastIntentLocked(null, null, intent, null, - null, 0, null, null, null, false, false, -1, uid); + null, 0, null, null, null, false, false, -1, uid, 0 /* TODO: Verify */); } Binder.restoreCallingIdentity(origId); } @@ -3444,7 +4276,8 @@ public final class ActivityManagerService extends ActivityManagerNative intent.putExtra(Intent.EXTRA_UID, uid); broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, - false, false, MY_PID, Process.SYSTEM_UID); + false, false, + MY_PID, Process.SYSTEM_UID, UserId.getUserId(uid)); } private final boolean killPackageProcessesLocked(String packageName, int uid, @@ -3548,7 +4381,8 @@ public final class ActivityManagerService extends ActivityManagerNative } ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); - for (ServiceRecord service : mServices.values()) { + int userId = UserId.getUserId(uid); + for (ServiceRecord service : mServiceMap.getAllServices(userId)) { if (service.packageName.equals(name) && (service.app == null || evenPersistent || !service.app.persistent)) { if (!doit) { @@ -3687,12 +4521,9 @@ public final class ActivityManagerService extends ActivityManagerNative // Can't happen; the backup manager is local } } - if (mPendingBroadcast != null && mPendingBroadcast.curApp.pid == pid) { + if (isPendingBroadcastProcessLocked(pid)) { Slog.w(TAG, "Unattached app died before broadcast acknowledged, skipping"); - mPendingBroadcast.state = BroadcastRecord.IDLE; - mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex; - mPendingBroadcast = null; - scheduleBroadcastsLocked(); + skipPendingBroadcastLocked(pid); } } else { Slog.w(TAG, "Spurious process start timeout - pid not known for " + app); @@ -3891,23 +4722,13 @@ public final class ActivityManagerService extends ActivityManagerNative } } - // Check if the next broadcast receiver is in this process... - BroadcastRecord br = mPendingBroadcast; - if (!badApp && br != null && br.curApp == app) { + // Check if a next-broadcast receiver is in this process... + if (!badApp && isPendingBroadcastProcessLocked(pid)) { try { - mPendingBroadcast = null; - processCurBroadcastLocked(br, app); - didSomething = true; + didSomething = sendPendingBroadcastsLocked(app); } catch (Exception e) { - Slog.w(TAG, "Exception in new application when starting receiver " - + br.curComponent.flattenToShortString(), e); + // If the app died trying to launch the receiver we declare it 'bad' badApp = true; - logBroadcastReceiverDiscardLocked(br); - finishReceiverLocked(br.receiver, br.resultCode, br.resultData, - br.resultExtras, br.resultAbort, true); - scheduleBroadcastsLocked(); - // We need to reset the state if we fails to start the receiver. - br.state = BroadcastRecord.IDLE; } } @@ -4024,11 +4845,13 @@ public final class ActivityManagerService extends ActivityManagerNative mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY); // Tell anyone interested that we are done booting! SystemProperties.set("sys.boot_completed", "1"); + SystemProperties.set("dev.bootcomplete", "1"); + /* TODO: Send this to all users that are to be logged in on startup */ broadcastIntentLocked(null, null, new Intent(Intent.ACTION_BOOT_COMPLETED, null), null, null, 0, null, null, android.Manifest.permission.RECEIVE_BOOT_COMPLETED, - false, false, MY_PID, Process.SYSTEM_UID); + false, false, MY_PID, Process.SYSTEM_UID, Binder.getOrigCallingUser()); } } } @@ -4169,7 +4992,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { int uid = AppGlobals.getPackageManager() .getPackageUid(packageName); - if (uid != Binder.getCallingUid()) { + if (UserId.getAppId(callingUid) != uid) { String msg = "Permission Denial: getIntentSender() from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() @@ -4180,7 +5003,10 @@ public final class ActivityManagerService extends ActivityManagerNative } } - return getIntentSenderLocked(type, packageName, callingUid, + if (DEBUG_MU) + Slog.i(TAG_MU, "Getting intent sender for origCallingUid=" + + Binder.getOrigCallingUid()); + return getIntentSenderLocked(type, packageName, Binder.getOrigCallingUid(), token, resultWho, requestCode, intents, resolvedTypes, flags); } catch (RemoteException e) { @@ -4192,6 +5018,8 @@ public final class ActivityManagerService extends ActivityManagerNative IIntentSender getIntentSenderLocked(int type, String packageName, int callingUid, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags) { + if (DEBUG_MU) + Slog.v(TAG_MU, "getIntentSenderLocked(): uid=" + callingUid); ActivityRecord activity = null; if (type == INTENT_SENDER_ACTIVITY_RESULT) { activity = mMainStack.isInStackLocked(token); @@ -4435,7 +5263,7 @@ public final class ActivityManagerService extends ActivityManagerNative } // If there is a uid that owns whatever is being accessed, it has // blanket access to it regardless of the permissions it requires. - if (owningUid >= 0 && uid == owningUid) { + if (owningUid >= 0 && UserId.isSameApp(uid, owningUid)) { return PackageManager.PERMISSION_GRANTED; } // If the target is not exported, then nobody else can get to it. @@ -4469,7 +5297,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (permission == null) { return PackageManager.PERMISSION_DENIED; } - return checkComponentPermission(permission, pid, uid, -1, true); + return checkComponentPermission(permission, pid, UserId.getAppId(uid), -1, true); } /** @@ -4479,7 +5307,7 @@ public final class ActivityManagerService extends ActivityManagerNative int checkCallingPermission(String permission) { return checkPermission(permission, Binder.getCallingPid(), - Binder.getCallingUid()); + UserId.getAppId(Binder.getCallingUid())); } /** @@ -4593,6 +5421,7 @@ public final class ActivityManagerService extends ActivityManagerNative pid = tlsIdentity.pid; } + uid = UserId.getAppId(uid); // Our own process gets to do everything. if (pid == MY_PID) { return PackageManager.PERMISSION_GRANTED; @@ -4635,7 +5464,8 @@ public final class ActivityManagerService extends ActivityManagerNative String name = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr = mProvidersByName.get(name); + ContentProviderRecord cpr = mProviderMap.getProviderByName(name, + UserId.getUserId(callingUid)); if (cpr != null) { pi = cpr.info; } else { @@ -4891,7 +5721,8 @@ public final class ActivityManagerService extends ActivityManagerNative final String authority = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr = mProvidersByName.get(authority); + ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, + UserId.getUserId(callingUid)); if (cpr != null) { pi = cpr.info; } else { @@ -4985,7 +5816,8 @@ public final class ActivityManagerService extends ActivityManagerNative final String authority = uri.getAuthority(); ProviderInfo pi = null; - ContentProviderRecord cpr = mProvidersByName.get(authority); + ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, + UserId.getUserId(r.info.uid)); if (cpr != null) { pi = cpr.info; } else { @@ -5224,6 +6056,12 @@ public final class ActivityManagerService extends ActivityManagerNative public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags) { + final int callingUid = Binder.getCallingUid(); + // If it's the system uid asking, then use the current user id. + // TODO: Make sure that there aren't any other legitimate calls from the system uid that + // require the entire list. + final int callingUserId = callingUid == Process.SYSTEM_UID + ? mCurrentUserId : UserId.getUserId(callingUid); synchronized (this) { enforceCallingPermission(android.Manifest.permission.GET_TASKS, "getRecentTasks()"); @@ -5236,11 +6074,14 @@ public final class ActivityManagerService extends ActivityManagerNative maxNum < N ? maxNum : N); for (int i=0; i<N && maxNum > 0; i++) { TaskRecord tr = mRecentTasks.get(i); + // Only add calling user's recent tasks + if (tr.userId != callingUserId) continue; // Return the entry if desired by the caller. We always return // the first entry, because callers always expect this to be the - // forground app. We may filter others if the caller has + // foreground app. We may filter others if the caller has // not supplied RECENT_WITH_EXCLUDED and there is some reason // we should exclude the entry. + if (i == 0 || ((flags&ActivityManager.RECENT_WITH_EXCLUDED) != 0) || (tr.intent == null) @@ -5329,7 +6170,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Find any running services associated with this app. ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); - for (ServiceRecord sr : mServices.values()) { + for (ServiceRecord sr : mServiceMap.getAllServices(root.userId)) { if (sr.packageName.equals(component.getPackageName())) { services.add(sr); } @@ -5700,17 +6541,23 @@ public final class ActivityManagerService extends ActivityManagerNative STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS); } catch (RemoteException ex) { } + if (DEBUG_MU) + Slog.v(TAG_MU, "generateApplicationProvidersLocked, app.info.uid = " + app.info.uid); + int userId = UserId.getUserId(app.info.uid); if (providers != null) { final int N = providers.size(); for (int i=0; i<N; i++) { ProviderInfo cpi = (ProviderInfo)providers.get(i); + ComponentName comp = new ComponentName(cpi.packageName, cpi.name); - ContentProviderRecord cpr = mProvidersByClass.get(comp); + ContentProviderRecord cpr = mProviderMap.getProviderByClass(comp, userId); if (cpr == null) { cpr = new ContentProviderRecord(cpi, app.info, comp); - mProvidersByClass.put(comp, cpr); + mProviderMap.putProviderByClass(comp, cpr); } + if (DEBUG_MU) + Slog.v(TAG_MU, "generateApplicationProvidersLocked, cpi.uid = " + cpr.uid); app.pubProviders.put(cpi.name, cpr); app.addPackage(cpi.applicationInfo.packageName); ensurePackageDexOpt(cpi.applicationInfo.packageName); @@ -5820,8 +6667,8 @@ public final class ActivityManagerService extends ActivityManagerNative return false; } - private final ContentProviderHolder getContentProviderImpl( - IApplicationThread caller, String name) { + private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller, + String name) { ContentProviderRecord cpr; ProviderInfo cpi = null; @@ -5838,7 +6685,8 @@ public final class ActivityManagerService extends ActivityManagerNative } // First check if this content provider has been published... - cpr = mProvidersByName.get(name); + int userId = UserId.getUserId(r != null ? r.info.uid : Binder.getCallingUid()); + cpr = mProviderMap.getProviderByName(name, userId); boolean providerRunning = cpr != null; if (providerRunning) { cpi = cpr.info; @@ -5923,6 +6771,9 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } + cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, + Binder.getOrigCallingUser()); + String msg; if ((msg=checkContentProviderPermissionLocked(cpi, r)) != null) { throw new SecurityException(msg); @@ -5938,7 +6789,7 @@ public final class ActivityManagerService extends ActivityManagerNative } ComponentName comp = new ComponentName(cpi.packageName, cpi.name); - cpr = mProvidersByClass.get(comp); + cpr = mProviderMap.getProviderByClass(comp, Binder.getOrigCallingUser()); final boolean firstClass = cpr == null; if (firstClass) { try { @@ -5952,6 +6803,7 @@ public final class ActivityManagerService extends ActivityManagerNative + cpi.name); return null; } + ai = getAppInfoForUser(ai, Binder.getOrigCallingUser()); cpr = new ContentProviderRecord(cpi, ai, comp); } catch (RemoteException ex) { // pm is in same process, this will never happen. @@ -6020,9 +6872,9 @@ public final class ActivityManagerService extends ActivityManagerNative // Make sure the provider is published (the same provider class // may be published under multiple names). if (firstClass) { - mProvidersByClass.put(comp, cpr); + mProviderMap.putProviderByClass(comp, cpr); } - mProvidersByName.put(name, cpr); + mProviderMap.putProviderByName(name, cpr); incProviderCount(r, cpr); } } @@ -6041,6 +6893,10 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } try { + if (DEBUG_MU) { + Slog.v(TAG_MU, "Waiting to start provider " + cpr + " launchingApp=" + + cpr.launchingApp); + } cpr.wait(); } catch (InterruptedException ex) { } @@ -6058,7 +6914,8 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } - return getContentProviderImpl(caller, name); + ContentProviderHolder contentProvider = getContentProviderImpl(caller, name); + return contentProvider; } private ContentProviderHolder getContentProviderExternal(String name) { @@ -6071,7 +6928,8 @@ public final class ActivityManagerService extends ActivityManagerNative */ public void removeContentProvider(IApplicationThread caller, String name) { synchronized (this) { - ContentProviderRecord cpr = mProvidersByName.get(name); + int userId = UserId.getUserId(Binder.getCallingUid()); + ContentProviderRecord cpr = mProviderMap.getProviderByName(name, userId); if(cpr == null) { // remove from mProvidersByClass if (DEBUG_PROVIDER) Slog.v(TAG, name + @@ -6086,8 +6944,11 @@ public final class ActivityManagerService extends ActivityManagerNative } //update content provider record entry info ComponentName comp = new ComponentName(cpr.info.packageName, cpr.info.name); - ContentProviderRecord localCpr = mProvidersByClass.get(comp); - if (localCpr.proc == r) { + ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId); + if (DEBUG_PROVIDER) Slog.v(TAG, "Removing provider requested by " + + r.info.processName + " from process " + + localCpr.appInfo.processName); + if (localCpr.launchingApp == r) { //should not happen. taken care of as a local provider Slog.w(TAG, "removeContentProvider called on local provider: " + cpr.info.name + " in process " + r.processName); @@ -6102,7 +6963,8 @@ public final class ActivityManagerService extends ActivityManagerNative private void removeContentProviderExternal(String name) { synchronized (this) { - ContentProviderRecord cpr = mProvidersByName.get(name); + ContentProviderRecord cpr = mProviderMap.getProviderByName(name, + Binder.getOrigCallingUser()); if(cpr == null) { //remove from mProvidersByClass if(localLOGV) Slog.v(TAG, name+" content provider not found in providers list"); @@ -6111,7 +6973,8 @@ public final class ActivityManagerService extends ActivityManagerNative //update content provider record entry info ComponentName comp = new ComponentName(cpr.info.packageName, cpr.info.name); - ContentProviderRecord localCpr = mProvidersByClass.get(comp); + ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, + Binder.getOrigCallingUser()); localCpr.externals--; if (localCpr.externals < 0) { Slog.e(TAG, "Externals < 0 for content provider " + localCpr); @@ -6128,6 +6991,8 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized(this) { final ProcessRecord r = getRecordForAppLocked(caller); + if (DEBUG_MU) + Slog.v(TAG_MU, "ProcessRecord uid = " + r.info.uid); if (r == null) { throw new SecurityException( "Unable to find app for caller " + caller @@ -6144,12 +7009,14 @@ public final class ActivityManagerService extends ActivityManagerNative continue; } ContentProviderRecord dst = r.pubProviders.get(src.info.name); + if (DEBUG_MU) + Slog.v(TAG_MU, "ContentProviderRecord uid = " + dst.uid); if (dst != null) { ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name); - mProvidersByClass.put(comp, dst); + mProviderMap.putProviderByClass(comp, dst); String names[] = dst.info.authority.split(";"); for (int j = 0; j < names.length; j++) { - mProvidersByName.put(names[j], dst); + mProviderMap.putProviderByName(names[j], dst); } int NL = mLaunchingProviders.size(); @@ -6894,8 +7761,10 @@ public final class ActivityManagerService extends ActivityManagerNative }; } Slog.i(TAG, "Sending system update to: " + intent.getComponent()); + /* TODO: Send this to all users */ broadcastIntentLocked(null, null, intent, null, finisher, - 0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID); + 0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID, + Process.SYSTEM_UID); if (finisher != null) { mWaitingUpdate = true; } @@ -7205,28 +8074,8 @@ public final class ActivityManagerService extends ActivityManagerNative } void skipCurrentReceiverLocked(ProcessRecord app) { - boolean reschedule = false; - BroadcastRecord r = app.curReceiver; - if (r != null) { - // 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. - logBroadcastReceiverDiscardLocked(r); - finishReceiverLocked(r.receiver, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); - reschedule = true; - } - r = mPendingBroadcast; - if (r != null && r.curApp == app) { - if (DEBUG_BROADCAST) Slog.v(TAG, - "skip & discard pending app " + r); - logBroadcastReceiverDiscardLocked(r); - finishReceiverLocked(r.receiver, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); - reschedule = true; - } - if (reschedule) { - scheduleBroadcastsLocked(); + for (BroadcastQueue queue : mBroadcastQueues) { + queue.skipCurrentReceiverLocked(app); } } @@ -7945,6 +8794,7 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" p[rocesses] [PACKAGE_NAME]: process state"); pw.println(" o[om]: out of memory management"); pw.println(" prov[iders] [COMP_SPEC ...]: content provider state"); + pw.println(" provider [COMP_SPEC]: provider client-side state"); pw.println(" s[ervices] [COMP_SPEC ...]: service state"); pw.println(" service [COMP_SPEC]: service client-side state"); pw.println(" package [PACKAGE_NAME]: all state related to given package"); @@ -8027,6 +8877,23 @@ public final class ActivityManagerService extends ActivityManagerNative dumpOomLocked(fd, pw, args, opti, true); } return; + } else if ("provider".equals(cmd)) { + String[] newArgs; + String name; + if (opti >= args.length) { + name = null; + newArgs = EMPTY_STRING_ARRAY; + } else { + name = args[opti]; + opti++; + newArgs = new String[args.length - opti]; + if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti); + } + if (!dumpProvider(fd, pw, name, newArgs, 0, dumpAll)) { + pw.println("No providers match: " + name); + pw.println("Use -h for help."); + } + return; } else if ("providers".equals(cmd) || "prov".equals(cmd)) { synchronized (this) { dumpProvidersLocked(fd, pw, args, opti, true, null); @@ -8523,8 +9390,14 @@ public final class ActivityManagerService extends ActivityManagerNative if ("all".equals(name)) { synchronized (this) { - for (ServiceRecord r1 : mServices.values()) { - services.add(r1); + try { + List<UserInfo> users = AppGlobals.getPackageManager().getUsers(); + for (UserInfo user : users) { + for (ServiceRecord r1 : mServiceMap.getAllServices(user.id)) { + services.add(r1); + } + } + } catch (RemoteException re) { } } } else { @@ -8542,18 +9415,24 @@ public final class ActivityManagerService extends ActivityManagerNative } synchronized (this) { - for (ServiceRecord r1 : mServices.values()) { - if (componentName != null) { - if (r1.name.equals(componentName)) { - services.add(r1); - } - } else if (name != null) { - if (r1.name.flattenToString().contains(name)) { - services.add(r1); + try { + List<UserInfo> users = AppGlobals.getPackageManager().getUsers(); + for (UserInfo user : users) { + for (ServiceRecord r1 : mServiceMap.getAllServices(user.id)) { + if (componentName != null) { + if (r1.name.equals(componentName)) { + services.add(r1); + } + } else if (name != null) { + if (r1.name.flattenToString().contains(name)) { + services.add(r1); + } + } else if (System.identityHashCode(r1) == objectId) { + services.add(r1); + } } - } else if (System.identityHashCode(r1) == objectId) { - services.add(r1); } + } catch (RemoteException re) { } } } @@ -8611,6 +9490,110 @@ public final class ActivityManagerService extends ActivityManagerNative } } + /** + * There are three ways to call this: + * - no provider specified: dump all the providers + * - a flattened component name that matched an existing provider was specified as the + * first arg: dump that one provider + * - the first arg isn't the flattened component name of an existing provider: + * dump all providers whose component contains the first arg as a substring + */ + protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args, + int opti, boolean dumpAll) { + ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>(); + + if ("all".equals(name)) { + synchronized (this) { + for (ContentProviderRecord r1 : mProvidersByClass.values()) { + providers.add(r1); + } + } + } else { + ComponentName componentName = name != null + ? ComponentName.unflattenFromString(name) : null; + int objectId = 0; + if (componentName == null) { + // Not a '/' separated full component name; maybe an object ID? + try { + objectId = Integer.parseInt(name, 16); + name = null; + componentName = null; + } catch (RuntimeException e) { + } + } + + synchronized (this) { + for (ContentProviderRecord r1 : mProvidersByClass.values()) { + if (componentName != null) { + if (r1.name.equals(componentName)) { + providers.add(r1); + } + } else if (name != null) { + if (r1.name.flattenToString().contains(name)) { + providers.add(r1); + } + } else if (System.identityHashCode(r1) == objectId) { + providers.add(r1); + } + } + } + } + + if (providers.size() <= 0) { + return false; + } + + boolean needSep = false; + for (int i=0; i<providers.size(); i++) { + if (needSep) { + pw.println(); + } + needSep = true; + dumpProvider("", fd, pw, providers.get(i), args, dumpAll); + } + return true; + } + + /** + * Invokes IApplicationThread.dumpProvider() on the thread of the specified provider if + * there is a thread associated with the provider. + */ + private void dumpProvider(String prefix, FileDescriptor fd, PrintWriter pw, + final ContentProviderRecord r, String[] args, boolean dumpAll) { + String innerPrefix = prefix + " "; + synchronized (this) { + pw.print(prefix); pw.print("PROVIDER "); + pw.print(r); + pw.print(" pid="); + if (r.proc != null) pw.println(r.proc.pid); + else pw.println("(not running)"); + if (dumpAll) { + r.dump(pw, innerPrefix); + } + } + if (r.proc != null && r.proc.thread != null) { + pw.println(" Client:"); + pw.flush(); + try { + TransferPipe tp = new TransferPipe(); + try { + r.proc.thread.dumpProvider( + tp.getWriteFd().getFileDescriptor(), r.provider.asBinder(), args); + tp.setBufferPrefix(" "); + // Short timeout, since blocking here can + // deadlock with the application. + tp.go(fd, 2000); + } finally { + tp.kill(); + } + } catch (IOException ex) { + pw.println(" Failure while dumping the provider: " + ex); + } catch (RemoteException ex) { + pw.println(" Got a RemoteException while dumping the service"); + } + } + } + static class ItemMatcher { ArrayList<ComponentName> components; ArrayList<String> strings; @@ -8827,84 +9810,11 @@ public final class ActivityManagerService extends ActivityManagerNative needSep = true; } } - - if (mParallelBroadcasts.size() > 0 || mOrderedBroadcasts.size() > 0 - || mPendingBroadcast != null) { - boolean printed = false; - for (int i=mParallelBroadcasts.size()-1; i>=0; i--) { - BroadcastRecord br = mParallelBroadcasts.get(i); - if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) { - continue; - } - if (!printed) { - if (needSep) { - pw.println(); - } - needSep = true; - pw.println(" Active broadcasts:"); - } - pw.println(" Broadcast #" + i + ":"); - br.dump(pw, " "); - } - printed = false; - for (int i=mOrderedBroadcasts.size()-1; i>=0; i--) { - BroadcastRecord br = mOrderedBroadcasts.get(i); - if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) { - continue; - } - if (!printed) { - if (needSep) { - pw.println(); - } - needSep = true; - pw.println(" Active ordered broadcasts:"); - } - pw.println(" Ordered Broadcast #" + i + ":"); - mOrderedBroadcasts.get(i).dump(pw, " "); - } - if (dumpPackage == null || (mPendingBroadcast != null - && dumpPackage.equals(mPendingBroadcast.callerPackage))) { - if (needSep) { - pw.println(); - } - pw.println(" Pending broadcast:"); - if (mPendingBroadcast != null) { - mPendingBroadcast.dump(pw, " "); - } else { - pw.println(" (null)"); - } - needSep = true; - } - } - boolean printed = false; - for (int i=0; i<MAX_BROADCAST_HISTORY; i++) { - BroadcastRecord r = mBroadcastHistory[i]; - if (r == null) { - break; - } - if (dumpPackage != null && !dumpPackage.equals(r.callerPackage)) { - continue; - } - if (!printed) { - if (needSep) { - pw.println(); - } - needSep = true; - pw.println(" Historical broadcasts:"); - printed = true; - } - if (dumpAll) { - pw.print(" Historical Broadcast #"); pw.print(i); pw.println(":"); - r.dump(pw, " "); - } else { - if (i >= 50) { - pw.println(" ..."); - break; - } - pw.print(" #"); pw.print(i); pw.print(": "); pw.println(r); - } + for (BroadcastQueue q : mBroadcastQueues) { + needSep = q.dumpLocked(fd, pw, args, opti, dumpAll, dumpPackage, needSep); } + needSep = true; if (mStickyBroadcasts != null && dumpPackage == null) { @@ -8941,7 +9851,10 @@ public final class ActivityManagerService extends ActivityManagerNative if (dumpAll) { pw.println(); - pw.println(" mBroadcastsScheduled=" + mBroadcastsScheduled); + for (BroadcastQueue queue : mBroadcastQueues) { + pw.println(" mBroadcastsScheduled [" + queue.mQueueName + "]=" + + queue.mBroadcastsScheduled); + } pw.println(" mHandler:"); mHandler.dump(new PrintWriterPrinter(pw), " "); needSep = true; @@ -8950,6 +9863,9 @@ public final class ActivityManagerService extends ActivityManagerNative return needSep; } + /** + * Prints a list of ServiceRecords (dumpsys activity services) + */ boolean dumpServicesLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) { boolean needSep = false; @@ -8958,75 +9874,87 @@ public final class ActivityManagerService extends ActivityManagerNative matcher.build(args, opti); pw.println("ACTIVITY MANAGER SERVICES (dumpsys activity services)"); - if (mServices.size() > 0) { - boolean printed = false; - long nowReal = SystemClock.elapsedRealtime(); - Iterator<ServiceRecord> it = mServices.values().iterator(); - needSep = false; - while (it.hasNext()) { - ServiceRecord r = it.next(); - if (!matcher.match(r, r.name)) { - continue; - } - if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { - continue; - } - if (!printed) { - pw.println(" Active services:"); - printed = true; - } - if (needSep) { - pw.println(); - } - pw.print(" * "); pw.println(r); - if (dumpAll) { - r.dump(pw, " "); - needSep = true; - } else { - pw.print(" app="); pw.println(r.app); - pw.print(" created="); - TimeUtils.formatDuration(r.createTime, nowReal, pw); - pw.print(" started="); pw.print(r.startRequested); - pw.print(" connections="); pw.println(r.connections.size()); - if (r.connections.size() > 0) { - pw.println(" Connections:"); - for (ArrayList<ConnectionRecord> clist : r.connections.values()) { - for (int i=0; i<clist.size(); i++) { - ConnectionRecord conn = clist.get(i); - pw.print(" "); - pw.print(conn.binding.intent.intent.getIntent().toShortString( - false, false, false)); - pw.print(" -> "); - ProcessRecord proc = conn.binding.client; - pw.println(proc != null ? proc.toShortString() : "null"); + try { + List<UserInfo> users = AppGlobals.getPackageManager().getUsers(); + for (UserInfo user : users) { + if (mServiceMap.getAllServices(user.id).size() > 0) { + boolean printed = false; + long nowReal = SystemClock.elapsedRealtime(); + Iterator<ServiceRecord> it = mServiceMap.getAllServices( + user.id).iterator(); + needSep = false; + while (it.hasNext()) { + ServiceRecord r = it.next(); + if (!matcher.match(r, r.name)) { + continue; + } + if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { + continue; + } + if (!printed) { + pw.println(" Active services:"); + printed = true; + } + if (needSep) { + pw.println(); + } + pw.print(" * "); + pw.println(r); + if (dumpAll) { + r.dump(pw, " "); + needSep = true; + } else { + pw.print(" app="); + pw.println(r.app); + pw.print(" created="); + TimeUtils.formatDuration(r.createTime, nowReal, pw); + pw.print(" started="); + pw.print(r.startRequested); + pw.print(" connections="); + pw.println(r.connections.size()); + if (r.connections.size() > 0) { + pw.println(" Connections:"); + for (ArrayList<ConnectionRecord> clist : r.connections.values()) { + for (int i = 0; i < clist.size(); i++) { + ConnectionRecord conn = clist.get(i); + pw.print(" "); + pw.print(conn.binding.intent.intent.getIntent() + .toShortString(false, false, false)); + pw.print(" -> "); + ProcessRecord proc = conn.binding.client; + pw.println(proc != null ? proc.toShortString() : "null"); + } + } } } - } - } - if (dumpClient && r.app != null && r.app.thread != null) { - pw.println(" Client:"); - pw.flush(); - try { - TransferPipe tp = new TransferPipe(); - try { - r.app.thread.dumpService( - tp.getWriteFd().getFileDescriptor(), r, args); - tp.setBufferPrefix(" "); - // Short timeout, since blocking here can - // deadlock with the application. - tp.go(fd, 2000); - } finally { - tp.kill(); + if (dumpClient && r.app != null && r.app.thread != null) { + pw.println(" Client:"); + pw.flush(); + try { + TransferPipe tp = new TransferPipe(); + try { + r.app.thread.dumpService(tp.getWriteFd().getFileDescriptor(), + r, args); + tp.setBufferPrefix(" "); + // Short timeout, since blocking here can + // deadlock with the application. + tp.go(fd, 2000); + } finally { + tp.kill(); + } + } catch (IOException e) { + pw.println(" Failure while dumping the service: " + e); + } catch (RemoteException e) { + pw.println(" Got a RemoteException while dumping the service"); + } + needSep = true; } - } catch (IOException e) { - pw.println(" Failure while dumping the service: " + e); - } catch (RemoteException e) { - pw.println(" Got a RemoteException while dumping the service"); } - needSep = true; + needSep = printed; } } - needSep = printed; + } catch (RemoteException re) { + } if (mPendingServices.size() > 0) { @@ -9136,76 +10064,8 @@ public final class ActivityManagerService extends ActivityManagerNative matcher.build(args, opti); pw.println("ACTIVITY MANAGER CONTENT PROVIDERS (dumpsys activity providers)"); - if (mProvidersByClass.size() > 0) { - boolean printed = false; - Iterator<Map.Entry<ComponentName, ContentProviderRecord>> it - = mProvidersByClass.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<ComponentName, ContentProviderRecord> e = it.next(); - ContentProviderRecord r = e.getValue(); - ComponentName comp = e.getKey(); - String cls = comp.getClassName(); - int end = cls.lastIndexOf('.'); - if (end > 0 && end < (cls.length()-2)) { - cls = cls.substring(end+1); - } - if (!matcher.match(r, comp)) { - continue; - } - if (dumpPackage != null && !dumpPackage.equals(comp.getPackageName())) { - continue; - } - if (!printed) { - if (needSep) pw.println(" "); - needSep = true; - pw.println(" Published content providers (by class):"); - printed = true; - } - pw.print(" * "); pw.print(cls); pw.print(" ("); - pw.print(comp.flattenToShortString()); pw.println(")"); - if (dumpAll) { - r.dump(pw, " "); - } else { - if (r.proc != null) { - pw.print(" "); pw.println(r.proc); - } else { - pw.println(); - } - if (r.clients.size() > 0) { - pw.println(" Clients:"); - for (ProcessRecord cproc : r.clients) { - pw.print(" - "); pw.println(cproc); - } - } - } - } - } - - if (dumpAll) { - if (mProvidersByName.size() > 0) { - boolean printed = false; - Iterator<Map.Entry<String, ContentProviderRecord>> it - = mProvidersByName.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<String, ContentProviderRecord> e = it.next(); - ContentProviderRecord r = e.getValue(); - if (!matcher.match(r, r.name)) { - continue; - } - if (dumpPackage != null && !dumpPackage.equals(r.name.getPackageName())) { - continue; - } - if (!printed) { - if (needSep) pw.println(" "); - needSep = true; - pw.println(" Authority to provider mappings:"); - printed = true; - } - pw.print(" "); pw.print(e.getKey()); pw.println(":"); - pw.print(" "); pw.println(r); - } - } - } + + mProviderMap.dumpProvidersLocked(pw, dumpAll); if (mLaunchingProviders.size() > 0) { boolean printed = false; @@ -9616,6 +10476,38 @@ public final class ActivityManagerService extends ActivityManagerNative } } + final void dumpDbInfo(FileDescriptor fd, PrintWriter pw, String[] args) { + ArrayList<ProcessRecord> procs = collectProcesses(pw, 0, args); + if (procs == null) { + return; + } + + pw.println("Applications Database Info:"); + + for (int i = procs.size() - 1 ; i >= 0 ; i--) { + ProcessRecord r = procs.get(i); + if (r.thread != null) { + pw.println("\n** Database info for pid " + r.pid + " [" + r.processName + "] **"); + pw.flush(); + try { + TransferPipe tp = new TransferPipe(); + try { + r.thread.dumpDbInfo(tp.getWriteFd().getFileDescriptor(), args); + tp.go(fd); + } finally { + tp.kill(); + } + } catch (IOException e) { + pw.println("Failure while dumping the app: " + r); + pw.flush(); + } catch (RemoteException e) { + pw.println("Got a RemoteException while dumping the app " + r); + pw.flush(); + } + } + } + } + final static class MemItem { final String label; final String shortLabel; @@ -10059,10 +10951,10 @@ public final class ActivityManagerService extends ActivityManagerNative cpr.notifyAll(); } - mProvidersByClass.remove(cpr.name); + mProviderMap.removeProviderByClass(cpr.name, UserId.getUserId(cpr.uid)); String names[] = cpr.info.authority.split(";"); for (int j = 0; j < names.length; j++) { - mProvidersByName.remove(names[j]); + mProviderMap.removeProviderByName(names[j], UserId.getUserId(cpr.uid)); } Iterator<ProcessRecord> cit = cpr.clients.iterator(); @@ -10338,8 +11230,10 @@ public final class ActivityManagerService extends ActivityManagerNative ArrayList<ActivityManager.RunningServiceInfo> res = new ArrayList<ActivityManager.RunningServiceInfo>(); - if (mServices.size() > 0) { - Iterator<ServiceRecord> it = mServices.values().iterator(); + int userId = UserId.getUserId(Binder.getCallingUid()); + if (mServiceMap.getAllServices(userId).size() > 0) { + Iterator<ServiceRecord> it + = mServiceMap.getAllServices(userId).iterator(); while (it.hasNext() && res.size() < maxNum) { res.add(makeRunningServiceInfoLocked(it.next())); } @@ -10359,7 +11253,8 @@ public final class ActivityManagerService extends ActivityManagerNative public PendingIntent getRunningServiceControlPanel(ComponentName name) { synchronized (this) { - ServiceRecord r = mServices.get(name); + int userId = UserId.getUserId(Binder.getCallingUid()); + ServiceRecord r = mServiceMap.getServiceByName(name, userId); if (r != null) { for (ArrayList<ConnectionRecord> conn : r.connections.values()) { for (int i=0; i<conn.size(); i++) { @@ -10375,7 +11270,7 @@ public final class ActivityManagerService extends ActivityManagerNative private final ServiceRecord findServiceLocked(ComponentName name, IBinder token) { - ServiceRecord r = mServices.get(name); + ServiceRecord r = mServiceMap.getServiceByName(name, Binder.getOrigCallingUser()); return r == token ? r : null; } @@ -10393,11 +11288,11 @@ public final class ActivityManagerService extends ActivityManagerNative String resolvedType) { ServiceRecord r = null; if (service.getComponent() != null) { - r = mServices.get(service.getComponent()); + r = mServiceMap.getServiceByName(service.getComponent(), Binder.getOrigCallingUser()); } if (r == null) { Intent.FilterComparison filter = new Intent.FilterComparison(service); - r = mServicesByIntent.get(filter); + r = mServiceMap.getServiceByIntent(filter, Binder.getOrigCallingUser()); } if (r == null) { @@ -10413,7 +11308,7 @@ public final class ActivityManagerService extends ActivityManagerNative ComponentName name = new ComponentName( sInfo.applicationInfo.packageName, sInfo.name); - r = mServices.get(name); + r = mServiceMap.getServiceByName(name, Binder.getOrigCallingUser()); } catch (RemoteException ex) { // pm is in same process, this will never happen. } @@ -10460,11 +11355,17 @@ public final class ActivityManagerService extends ActivityManagerNative private ServiceLookupResult retrieveServiceLocked(Intent service, String resolvedType, int callingPid, int callingUid) { ServiceRecord r = null; + if (DEBUG_SERVICE) + Slog.v(TAG, "retrieveServiceLocked: " + service + " type=" + resolvedType + + " origCallingUid=" + callingUid); + if (service.getComponent() != null) { - r = mServices.get(service.getComponent()); + r = mServiceMap.getServiceByName(service.getComponent(), Binder.getOrigCallingUser()); + } + if (r == null) { + Intent.FilterComparison filter = new Intent.FilterComparison(service); + r = mServiceMap.getServiceByIntent(filter, Binder.getOrigCallingUser()); } - Intent.FilterComparison filter = new Intent.FilterComparison(service); - r = mServicesByIntent.get(filter); if (r == null) { try { ResolveInfo rInfo = @@ -10477,12 +11378,16 @@ public final class ActivityManagerService extends ActivityManagerNative ": not found"); return null; } - + if (Binder.getOrigCallingUser() > 0) { + sInfo.applicationInfo = getAppInfoForUser(sInfo.applicationInfo, + Binder.getOrigCallingUser()); + } ComponentName name = new ComponentName( sInfo.applicationInfo.packageName, sInfo.name); - r = mServices.get(name); + r = mServiceMap.getServiceByName(name, Binder.getOrigCallingUser()); if (r == null) { - filter = new Intent.FilterComparison(service.cloneFilter()); + Intent.FilterComparison filter = new Intent.FilterComparison( + service.cloneFilter()); ServiceRestarter res = new ServiceRestarter(); BatteryStatsImpl.Uid.Pkg.Serv ss = null; BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); @@ -10493,8 +11398,8 @@ public final class ActivityManagerService extends ActivityManagerNative } r = new ServiceRecord(this, ss, name, filter, sInfo, res); res.setService(r); - mServices.put(name, r); - mServicesByIntent.put(filter, r); + mServiceMap.putServiceByName(name, UserId.getUserId(r.appInfo.uid), r); + mServiceMap.putServiceByIntent(filter, UserId.getUserId(r.appInfo.uid), r); // Make sure this component isn't in the pending list. int N = mPendingServices.size(); @@ -10641,7 +11546,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.thread == null) { throw new RemoteException(); } - + if (DEBUG_MU) + Slog.v(TAG_MU, "realStartServiceLocked, ServiceRecord.uid = " + r.appInfo.uid + + ", ProcessRecord.uid = " + app.info.uid); r.app = app; r.restartTime = r.lastActivity = SystemClock.uptimeMillis(); @@ -10838,6 +11745,8 @@ public final class ActivityManagerService extends ActivityManagerNative final String appName = r.processName; ProcessRecord app = getProcessRecordLocked(appName, r.appInfo.uid); + if (DEBUG_MU) + Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid + " app=" + app); if (app != null && app.thread != null) { try { app.addPackage(r.appInfo.packageName); @@ -10942,8 +11851,8 @@ public final class ActivityManagerService extends ActivityManagerNative System.identityHashCode(r), r.shortName, (r.app != null) ? r.app.pid : -1); - mServices.remove(r.name); - mServicesByIntent.remove(r.intent); + mServiceMap.removeServiceByName(r.name, r.userId); + mServiceMap.removeServiceByIntent(r.intent, r.userId); r.totalRestartCount = 0; unscheduleServiceRestartLocked(r); @@ -11057,6 +11966,8 @@ public final class ActivityManagerService extends ActivityManagerNative throw new IllegalArgumentException("File descriptors passed in Intent"); } + if (DEBUG_SERVICE) + Slog.v(TAG, "startService: " + service + " type=" + resolvedType); synchronized(this) { final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); @@ -11071,6 +11982,8 @@ public final class ActivityManagerService extends ActivityManagerNative ComponentName startServiceInPackage(int uid, Intent service, String resolvedType) { synchronized(this) { + if (DEBUG_SERVICE) + Slog.v(TAG, "startServiceInPackage: " + service + " type=" + resolvedType); final long origId = Binder.clearCallingIdentity(); ComponentName res = startServiceLocked(null, service, resolvedType, -1, uid); @@ -11274,6 +12187,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (DEBUG_SERVICE) Slog.v(TAG, "bindService: " + service + " type=" + resolvedType + " conn=" + connection.asBinder() + " flags=0x" + Integer.toHexString(flags)); + if (DEBUG_MU) + Slog.i(TAG_MU, "bindService uid=" + Binder.getCallingUid() + " origUid=" + + Binder.getOrigCallingUid()); final ProcessRecord callerApp = getRecordForAppLocked(caller); if (callerApp == null) { throw new SecurityException( @@ -11316,7 +12232,7 @@ public final class ActivityManagerService extends ActivityManagerNative ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, - Binder.getCallingPid(), Binder.getCallingUid()); + Binder.getCallingPid(), Binder.getOrigCallingUid()); if (res == null) { return 0; } @@ -11662,7 +12578,9 @@ public final class ActivityManagerService extends ActivityManagerNative r.callStart = false; } } - + if (DEBUG_MU) + Slog.v(TAG_MU, "before serviceDontExecutingLocked, uid=" + + Binder.getOrigCallingUid()); final long origId = Binder.clearCallingIdentity(); serviceDoneExecutingLocked(r, inStopping); Binder.restoreCallingIdentity(origId); @@ -11893,15 +12811,25 @@ public final class ActivityManagerService extends ActivityManagerNative return cur; } - private final void scheduleBroadcastsLocked() { - if (DEBUG_BROADCAST) Slog.v(TAG, "Schedule broadcasts: current=" - + mBroadcastsScheduled); + boolean isPendingBroadcastProcessLocked(int pid) { + return mFgBroadcastQueue.isPendingBroadcastProcessLocked(pid) + || mBgBroadcastQueue.isPendingBroadcastProcessLocked(pid); + } - if (mBroadcastsScheduled) { - return; + void skipPendingBroadcastLocked(int pid) { + Slog.w(TAG, "Unattached app died before broadcast acknowledged, skipping"); + for (BroadcastQueue queue : mBroadcastQueues) { + queue.skipPendingBroadcastLocked(pid); + } + } + + // The app just attached; send any pending broadcasts that it should receive + boolean sendPendingBroadcastsLocked(ProcessRecord app) { + boolean didSomething = false; + for (BroadcastQueue queue : mBroadcastQueues) { + didSomething |= queue.sendPendingBroadcastsLocked(app); } - mHandler.sendEmptyMessage(BROADCAST_INTENT_MSG); - mBroadcastsScheduled = true; + return didSomething; } public Intent registerReceiver(IApplicationThread caller, String callerPackage, @@ -11983,13 +12911,12 @@ public final class ActivityManagerService extends ActivityManagerNative int N = allSticky.size(); for (int i=0; i<N; i++) { Intent intent = (Intent)allSticky.get(i); - BroadcastRecord r = new BroadcastRecord(intent, null, + BroadcastQueue queue = broadcastQueueForIntent(intent); + BroadcastRecord r = new BroadcastRecord(queue, intent, null, null, -1, -1, null, receivers, null, 0, null, null, false, true, true); - if (mParallelBroadcasts.size() == 0) { - scheduleBroadcastsLocked(); - } - mParallelBroadcasts.add(r); + queue.enqueueParallelBroadcastLocked(r); + queue.scheduleBroadcastsLocked(); } } @@ -12000,38 +12927,46 @@ public final class ActivityManagerService extends ActivityManagerNative public void unregisterReceiver(IIntentReceiver receiver) { if (DEBUG_BROADCAST) Slog.v(TAG, "Unregister receiver: " + receiver); - boolean doNext = false; + final long origId = Binder.clearCallingIdentity(); + try { + boolean doTrim = false; - synchronized(this) { - ReceiverList rl + synchronized(this) { + ReceiverList rl = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder()); - if (rl != null) { - if (rl.curBroadcast != null) { - BroadcastRecord r = rl.curBroadcast; - doNext = finishReceiverLocked( - receiver.asBinder(), r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); - } + if (rl != null) { + if (rl.curBroadcast != null) { + BroadcastRecord r = rl.curBroadcast; + final boolean doNext = finishReceiverLocked( + receiver.asBinder(), r.resultCode, r.resultData, + r.resultExtras, r.resultAbort, true); + if (doNext) { + doTrim = true; + r.queue.processNextBroadcast(false); + } + } - if (rl.app != null) { - rl.app.receivers.remove(rl); - } - removeReceiverLocked(rl); - if (rl.linkedToDeath) { - rl.linkedToDeath = false; - rl.receiver.asBinder().unlinkToDeath(rl, 0); + if (rl.app != null) { + rl.app.receivers.remove(rl); + } + removeReceiverLocked(rl); + if (rl.linkedToDeath) { + rl.linkedToDeath = false; + rl.receiver.asBinder().unlinkToDeath(rl, 0); + } } } - } - if (!doNext) { - return; + // If we actually concluded any broadcasts, we might now be able + // to trim the recipients' apps from our working set + if (doTrim) { + trimApplications(); + return; + } + + } finally { + Binder.restoreCallingIdentity(origId); } - - final long origId = Binder.clearCallingIdentity(); - processNextBroadcast(false); - trimApplications(); - Binder.restoreCallingIdentity(origId); } void removeReceiverLocked(ReceiverList rl) { @@ -12058,7 +12993,8 @@ public final class ActivityManagerService extends ActivityManagerNative String callerPackage, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, String requiredPermission, - boolean ordered, boolean sticky, int callingPid, int callingUid) { + boolean ordered, boolean sticky, int callingPid, int callingUid, + int userId) { intent = new Intent(intent); // By default broadcasts do not go to stopped apps. @@ -12233,10 +13169,11 @@ public final class ActivityManagerService extends ActivityManagerNative if (ai != null) { receivers = new ArrayList(); ResolveInfo ri = new ResolveInfo(); - ri.activityInfo = ai; + ri.activityInfo = getActivityInfoForUser(ai, userId); receivers.add(ri); } } else { + // TODO: Apply userId // Need to resolve the intent to interested receivers... if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) { @@ -12261,28 +13198,17 @@ public final class ActivityManagerService extends ActivityManagerNative // If we are not serializing this broadcast, then send the // registered receivers separately so they don't wait for the // components to be launched. - BroadcastRecord r = new BroadcastRecord(intent, callerApp, + final BroadcastQueue queue = broadcastQueueForIntent(intent); + BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, registeredReceivers, resultTo, resultCode, resultData, map, ordered, sticky, false); if (DEBUG_BROADCAST) Slog.v( - TAG, "Enqueueing parallel broadcast " + r - + ": prev had " + mParallelBroadcasts.size()); - boolean replaced = false; - if (replacePending) { - for (int i=mParallelBroadcasts.size()-1; i>=0; i--) { - if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) { - if (DEBUG_BROADCAST) Slog.v(TAG, - "***** DROPPING PARALLEL: " + intent); - mParallelBroadcasts.set(i, r); - replaced = true; - break; - } - } - } + TAG, "Enqueueing parallel broadcast " + r); + final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r); if (!replaced) { - mParallelBroadcasts.add(r); - scheduleBroadcastsLocked(); + queue.enqueueParallelBroadcastLocked(r); + queue.scheduleBroadcastsLocked(); } registeredReceivers = null; NR = 0; @@ -12362,32 +13288,22 @@ public final class ActivityManagerService extends ActivityManagerNative if ((receivers != null && receivers.size() > 0) || resultTo != null) { - BroadcastRecord r = new BroadcastRecord(intent, callerApp, + BroadcastQueue queue = broadcastQueueForIntent(intent); + BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, receivers, resultTo, resultCode, resultData, map, ordered, sticky, false); if (DEBUG_BROADCAST) Slog.v( TAG, "Enqueueing ordered broadcast " + r - + ": prev had " + mOrderedBroadcasts.size()); + + ": prev had " + queue.mOrderedBroadcasts.size()); if (DEBUG_BROADCAST) { int seq = r.intent.getIntExtra("seq", -1); Slog.i(TAG, "Enqueueing broadcast " + r.intent.getAction() + " seq=" + seq); } - boolean replaced = false; - if (replacePending) { - 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); - mOrderedBroadcasts.set(i, r); - replaced = true; - break; - } - } - } + boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r); if (!replaced) { - mOrderedBroadcasts.add(r); - scheduleBroadcastsLocked(); + queue.enqueueOrderedBroadcastLocked(r); + queue.scheduleBroadcastsLocked(); } } @@ -12426,7 +13342,7 @@ public final class ActivityManagerService extends ActivityManagerNative public final int broadcastIntent(IApplicationThread caller, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, - String requiredPermission, boolean serialized, boolean sticky) { + String requiredPermission, boolean serialized, boolean sticky, int userId) { synchronized(this) { intent = verifyBroadcastLocked(intent); @@ -12437,8 +13353,8 @@ public final class ActivityManagerService extends ActivityManagerNative int res = broadcastIntentLocked(callerApp, callerApp != null ? callerApp.info.packageName : null, intent, resolvedType, resultTo, - resultCode, resultData, map, requiredPermission, serialized, - sticky, callingPid, callingUid); + resultCode, resultData, map, requiredPermission, serialized, sticky, + callingPid, callingUid, userId); Binder.restoreCallingIdentity(origId); return res; } @@ -12447,21 +13363,21 @@ public final class ActivityManagerService extends ActivityManagerNative int broadcastIntentInPackage(String packageName, int uid, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, - String requiredPermission, boolean serialized, boolean sticky) { + String requiredPermission, boolean serialized, boolean sticky, int userId) { synchronized(this) { intent = verifyBroadcastLocked(intent); final long origId = Binder.clearCallingIdentity(); int res = broadcastIntentLocked(null, packageName, intent, resolvedType, resultTo, resultCode, resultData, map, requiredPermission, - serialized, sticky, -1, uid); + serialized, sticky, -1, uid, userId); Binder.restoreCallingIdentity(origId); return res; } } - public final void unbroadcastIntent(IApplicationThread caller, - Intent intent) { + // TODO: Use the userId; maybe mStickyBroadcasts need to be tied to the user. + public final void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) { // Refuse possible leaked file descriptors if (intent != null && intent.hasFileDescriptors() == true) { throw new IllegalArgumentException("File descriptors passed in Intent"); @@ -12494,54 +13410,14 @@ public final class ActivityManagerService extends ActivityManagerNative private final boolean finishReceiverLocked(IBinder receiver, int resultCode, String resultData, Bundle resultExtras, boolean resultAbort, boolean explicit) { - if (mOrderedBroadcasts.size() == 0) { - if (explicit) { - Slog.w(TAG, "finishReceiver called but no pending broadcasts"); - } - return false; - } - BroadcastRecord r = mOrderedBroadcasts.get(0); - if (r.receiver == null) { - if (explicit) { - Slog.w(TAG, "finishReceiver called but none active"); - } - return false; - } - if (r.receiver != receiver) { - Slog.w(TAG, "finishReceiver called but active receiver is different"); + final BroadcastRecord r = broadcastRecordForReceiverLocked(receiver); + if (r == null) { + Slog.w(TAG, "finishReceiver called but not found on queue"); return false; } - int state = r.state; - r.state = BroadcastRecord.IDLE; - if (state == BroadcastRecord.IDLE) { - if (explicit) { - Slog.w(TAG, "finishReceiver called but state is IDLE"); - } - } - r.receiver = null; - r.intent.setComponent(null); - if (r.curApp != null) { - r.curApp.curReceiver = null; - } - if (r.curFilter != null) { - r.curFilter.receiverList.curBroadcast = null; - } - r.curFilter = null; - r.curApp = null; - r.curComponent = null; - r.curReceiver = null; - mPendingBroadcast = null; - - r.resultCode = resultCode; - r.resultData = resultData; - r.resultExtras = resultExtras; - r.resultAbort = resultAbort; - // We will process the next receiver right now if this is finishing - // an app receiver (which is always asynchronous) or after we have - // come back from calling a receiver. - return state == BroadcastRecord.APP_RECEIVE - || state == BroadcastRecord.CALL_DONE_RECEIVE; + return r.queue.finishReceiverLocked(r, resultCode, resultData, resultExtras, resultAbort, + explicit); } public void finishReceiver(IBinder who, int resultCode, String resultData, @@ -12553,153 +13429,25 @@ public final class ActivityManagerService extends ActivityManagerNative throw new IllegalArgumentException("File descriptors passed in Bundle"); } - boolean doNext; - final long origId = Binder.clearCallingIdentity(); + try { + boolean doNext = false; + BroadcastRecord r = null; - synchronized(this) { - doNext = finishReceiverLocked( - who, resultCode, resultData, resultExtras, resultAbort, true); - } - - if (doNext) { - processNextBroadcast(false); - } - trimApplications(); - - Binder.restoreCallingIdentity(origId); - } - - private final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) { - if (r.nextReceiver > 0) { - Object curReceiver = r.receivers.get(r.nextReceiver-1); - if (curReceiver instanceof BroadcastFilter) { - BroadcastFilter bf = (BroadcastFilter) curReceiver; - EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER, - System.identityHashCode(r), - r.intent.getAction(), - r.nextReceiver - 1, - System.identityHashCode(bf)); - } else { - EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP, - System.identityHashCode(r), - r.intent.getAction(), - r.nextReceiver - 1, - ((ResolveInfo)curReceiver).toString()); - } - } else { - Slog.w(TAG, "Discarding broadcast before first receiver is invoked: " - + r); - EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP, - System.identityHashCode(r), - r.intent.getAction(), - r.nextReceiver, - "NONE"); - } - } - - private final void setBroadcastTimeoutLocked(long timeoutTime) { - if (! mPendingBroadcastTimeoutMessage) { - Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG); - mHandler.sendMessageAtTime(msg, timeoutTime); - mPendingBroadcastTimeoutMessage = true; - } - } - - 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; - } - 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 " - + timeoutTime); - setBroadcastTimeoutLocked(timeoutTime); - return; - } - } - - 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; - } - - ProcessRecord app = null; - String anrMessage = 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); + synchronized(this) { + r = broadcastRecordForReceiverLocked(who); + if (r != null) { + doNext = r.queue.finishReceiverLocked(r, resultCode, + resultData, resultExtras, resultAbort, true); } } - } 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) { - // 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)); + if (doNext) { + r.queue.processNextBroadcast(false); + } + trimApplications(); + } finally { + Binder.restoreCallingIdentity(origId); } } @@ -12834,334 +13582,6 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private final void addBroadcastToHistoryLocked(BroadcastRecord r) { - if (r.callingUid < 0) { - // This was from a registerReceiver() call; ignore it. - return; - } - System.arraycopy(mBroadcastHistory, 0, mBroadcastHistory, 1, - MAX_BROADCAST_HISTORY-1); - r.finishTime = SystemClock.uptimeMillis(); - mBroadcastHistory[0] = r; - } - - private final void processNextBroadcast(boolean fromMsg) { - synchronized(this) { - BroadcastRecord r; - - if (DEBUG_BROADCAST) Slog.v(TAG, "processNextBroadcast: " - + mParallelBroadcasts.size() + " broadcasts, " - + mOrderedBroadcasts.size() + " ordered broadcasts"); - - updateCpuStats(); - - if (fromMsg) { - mBroadcastsScheduled = false; - } - - // First, deliver any non-serialized broadcasts right away. - while (mParallelBroadcasts.size() > 0) { - r = mParallelBroadcasts.remove(0); - r.dispatchTime = SystemClock.uptimeMillis(); - r.dispatchClockTime = System.currentTimeMillis(); - final int N = r.receivers.size(); - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing parallel broadcast " - + r); - for (int i=0; i<N; i++) { - Object target = r.receivers.get(i); - if (DEBUG_BROADCAST) Slog.v(TAG, - "Delivering non-ordered to registered " - + target + ": " + r); - deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false); - } - addBroadcastToHistoryLocked(r); - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Done with parallel broadcast " - + r); - } - - // Now take care of the next serialized one... - - // If we are waiting for a process to come up to handle the next - // broadcast, then do nothing at this point. Just in case, we - // check that the process we're waiting for still exists. - if (mPendingBroadcast != null) { - if (DEBUG_BROADCAST_LIGHT) { - Slog.v(TAG, "processNextBroadcast: waiting for " - + mPendingBroadcast.curApp); - } - - boolean isDead; - synchronized (mPidsSelfLocked) { - isDead = (mPidsSelfLocked.get(mPendingBroadcast.curApp.pid) == null); - } - if (!isDead) { - // It's still alive, so keep waiting - return; - } else { - Slog.w(TAG, "pending app " + mPendingBroadcast.curApp - + " died before responding to broadcast"); - mPendingBroadcast.state = BroadcastRecord.IDLE; - mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex; - mPendingBroadcast = null; - } - } - - boolean looped = false; - - do { - if (mOrderedBroadcasts.size() == 0) { - // No more broadcasts pending, so all done! - scheduleAppGcsLocked(); - if (looped) { - // If we had finished the last ordered broadcast, then - // make sure all processes have correct oom and sched - // adjustments. - updateOomAdjLocked(); - } - return; - } - r = mOrderedBroadcasts.get(0); - boolean forceReceive = false; - - // Ensure that even if something goes awry with the timeout - // detection, we catch "hung" broadcasts here, discard them, - // 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 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 (mProcessesReady && r.dispatchTime > 0) { - long now = SystemClock.uptimeMillis(); - if ((numReceivers > 0) && - (now > r.dispatchTime + (2*BROADCAST_TIMEOUT*numReceivers))) { - Slog.w(TAG, "Hung broadcast discarded after timeout failure:" - + " now=" + now - + " dispatchTime=" + r.dispatchTime - + " startTime=" + r.receiverTime - + " intent=" + r.intent - + " numReceivers=" + numReceivers - + " nextReceiver=" + r.nextReceiver - + " state=" + r.state); - broadcastTimeoutLocked(false); // forcibly finish this broadcast - forceReceive = true; - r.state = BroadcastRecord.IDLE; - } - } - - if (r.state != BroadcastRecord.IDLE) { - if (DEBUG_BROADCAST) Slog.d(TAG, - "processNextBroadcast() called when not idle (state=" - + r.state + ")"); - return; - } - - if (r.receivers == null || r.nextReceiver >= numReceivers - || r.resultAbort || forceReceive) { - // No more receivers for this broadcast! Send the final - // result if requested... - if (r.resultTo != null) { - try { - if (DEBUG_BROADCAST) { - int seq = r.intent.getIntExtra("seq", -1); - Slog.i(TAG, "Finishing broadcast " + r.intent.getAction() - + " seq=" + seq + " app=" + r.callerApp); - } - performReceiveLocked(r.callerApp, r.resultTo, - new Intent(r.intent), r.resultCode, - r.resultData, r.resultExtras, false, false); - // Set this to null so that the reference - // (local and remote) isnt kept in the mBroadcastHistory. - r.resultTo = null; - } catch (RemoteException e) { - Slog.w(TAG, "Failure sending broadcast result of " + r.intent, e); - } - } - - if (DEBUG_BROADCAST) Slog.v(TAG, "Cancelling BROADCAST_TIMEOUT_MSG"); - cancelBroadcastTimeoutLocked(); - - if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Finished with ordered broadcast " - + r); - - // ... and on to the next... - addBroadcastToHistoryLocked(r); - mOrderedBroadcasts.remove(0); - r = null; - looped = true; - continue; - } - } while (r == null); - - // Get the next receiver... - int recIdx = r.nextReceiver++; - - // Keep track of when this receiver started, and make sure there - // is a timeout message pending to kill it if need be. - r.receiverTime = SystemClock.uptimeMillis(); - if (recIdx == 0) { - r.dispatchTime = r.receiverTime; - r.dispatchClockTime = System.currentTimeMillis(); - 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 + " at " + timeoutTime); - setBroadcastTimeoutLocked(timeoutTime); - } - - Object nextReceiver = r.receivers.get(recIdx); - if (nextReceiver instanceof BroadcastFilter) { - // Simple case: this is a registered receiver who gets - // a direct call. - BroadcastFilter filter = (BroadcastFilter)nextReceiver; - if (DEBUG_BROADCAST) Slog.v(TAG, - "Delivering ordered to registered " - + filter + ": " + r); - deliverToRegisteredReceiverLocked(r, filter, r.ordered); - if (r.receiver == null || !r.ordered) { - // The receiver has already finished, so schedule to - // process the next one. - if (DEBUG_BROADCAST) Slog.v(TAG, "Quick finishing: ordered=" - + r.ordered + " receiver=" + r.receiver); - r.state = BroadcastRecord.IDLE; - scheduleBroadcastsLocked(); - } - return; - } - - // Hard case: need to instantiate the receiver, possibly - // starting its application process to host it. - - ResolveInfo info = - (ResolveInfo)nextReceiver; - - boolean skip = false; - int perm = checkComponentPermission(info.activityInfo.permission, - r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid, - info.activityInfo.exported); - if (perm != PackageManager.PERMISSION_GRANTED) { - if (!info.activityInfo.exported) { - Slog.w(TAG, "Permission Denial: broadcasting " - + r.intent.toString() - + " from " + r.callerPackage + " (pid=" + r.callingPid - + ", uid=" + r.callingUid + ")" - + " is not exported from uid " + info.activityInfo.applicationInfo.uid - + " due to receiver " + info.activityInfo.packageName - + "/" + info.activityInfo.name); - } else { - Slog.w(TAG, "Permission Denial: broadcasting " - + r.intent.toString() - + " from " + r.callerPackage + " (pid=" + r.callingPid - + ", uid=" + r.callingUid + ")" - + " requires " + info.activityInfo.permission - + " due to receiver " + info.activityInfo.packageName - + "/" + info.activityInfo.name); - } - skip = true; - } - if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID && - r.requiredPermission != null) { - try { - perm = AppGlobals.getPackageManager(). - checkPermission(r.requiredPermission, - info.activityInfo.applicationInfo.packageName); - } catch (RemoteException e) { - perm = PackageManager.PERMISSION_DENIED; - } - if (perm != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission Denial: receiving " - + r.intent + " to " - + info.activityInfo.applicationInfo.packageName - + " requires " + r.requiredPermission - + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - skip = true; - } - } - 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; - scheduleBroadcastsLocked(); - return; - } - - r.state = BroadcastRecord.APP_RECEIVE; - String targetProcess = info.activityInfo.processName; - r.curComponent = new ComponentName( - info.activityInfo.applicationInfo.packageName, - info.activityInfo.name); - r.curReceiver = info.activityInfo; - - // Broadcast is being executed, its package can't be stopped. - try { - AppGlobals.getPackageManager().setPackageStoppedState( - r.curComponent.getPackageName(), false); - } catch (RemoteException e) { - } catch (IllegalArgumentException e) { - Slog.w(TAG, "Failed trying to unstop package " - + r.curComponent.getPackageName() + ": " + e); - } - - // Is this receiver's application already running? - ProcessRecord app = getProcessRecordLocked(targetProcess, - info.activityInfo.applicationInfo.uid); - if (app != null && app.thread != null) { - try { - app.addPackage(info.activityInfo.packageName); - processCurBroadcastLocked(r, app); - return; - } catch (RemoteException e) { - Slog.w(TAG, "Exception when sending broadcast to " - + r.curComponent, e); - } - - // If a dead object exception was thrown -- fall through to - // restart the application. - } - - // 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, - "broadcast", r.curComponent, - (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0)) - == null) { - // Ah, this recipient is unavailable. Finish it if necessary, - // and mark the broadcast record as ready for the next. - Slog.w(TAG, "Unable to launch app " - + info.activityInfo.applicationInfo.packageName + "/" - + info.activityInfo.applicationInfo.uid + " for broadcast " - + r.intent + ": process is bad"); - logBroadcastReceiverDiscardLocked(r); - finishReceiverLocked(r.receiver, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); - scheduleBroadcastsLocked(); - r.state = BroadcastRecord.IDLE; - return; - } - - mPendingBroadcast = r; - mPendingBroadcastRecvIndex = recIdx; - } - } - // ========================================================= // INSTRUMENTATION // ========================================================= @@ -13433,12 +13853,12 @@ public final class ActivityManagerService extends ActivityManagerNative intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_REPLACE_PENDING); broadcastIntentLocked(null, null, intent, null, null, 0, null, null, - null, false, false, MY_PID, Process.SYSTEM_UID); + null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */); if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) { broadcastIntentLocked(null, null, new Intent(Intent.ACTION_LOCALE_CHANGED), null, null, 0, null, null, - null, false, false, MY_PID, Process.SYSTEM_UID); + null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */); } } } @@ -13484,6 +13904,28 @@ public final class ActivityManagerService extends ActivityManagerNative // LIFETIME MANAGEMENT // ========================================================= + // Returns which broadcast queue the app is the current [or imminent] receiver + // on, or 'null' if the app is not an active broadcast recipient. + private BroadcastQueue isReceivingBroadcast(ProcessRecord app) { + BroadcastRecord r = app.curReceiver; + if (r != null) { + return r.queue; + } + + // It's not the current receiver, but it might be starting up to become one + synchronized (this) { + for (BroadcastQueue queue : mBroadcastQueues) { + r = queue.mPendingBroadcast; + if (r != null && r.curApp == app) { + // found it; report which queue it's in + return queue; + } + } + } + + return null; + } + private final int computeOomAdjLocked(ProcessRecord app, int hiddenAdj, ProcessRecord TOP_APP, boolean recursed, boolean doingAll) { if (mAdjSeq == app.adjSeq) { @@ -13549,6 +13991,7 @@ public final class ActivityManagerService extends ActivityManagerNative // important to least, and assign an appropriate OOM adjustment. int adj; int schedGroup; + BroadcastQueue queue; if (app == TOP_APP) { // The last app on the list is the foreground app. adj = ProcessList.FOREGROUND_APP_ADJ; @@ -13560,12 +14003,14 @@ public final class ActivityManagerService extends ActivityManagerNative adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "instrumentation"; - } else if (app.curReceiver != null || - (mPendingBroadcast != null && mPendingBroadcast.curApp == app)) { + } else if ((queue = isReceivingBroadcast(app)) != null) { // An app that is currently receiving a broadcast also - // counts as being in the foreground. + // counts as being in the foreground for OOM killer purposes. + // It's placed in a sched group based on the nature of the + // broadcast as reflected by which queue it's active in. adj = ProcessList.FOREGROUND_APP_ADJ; - schedGroup = Process.THREAD_GROUP_DEFAULT; + schedGroup = (queue == mFgBroadcastQueue) + ? Process.THREAD_GROUP_DEFAULT : Process.THREAD_GROUP_BG_NONINTERACTIVE; app.adjType = "broadcast"; } else if (app.executingServices.size() > 0) { // An app that is currently executing a service callback also @@ -14006,8 +14451,13 @@ public final class ActivityManagerService extends ActivityManagerNative * Returns true if things are idle enough to perform GCs. */ private final boolean canGcNowLocked() { - return mParallelBroadcasts.size() == 0 - && mOrderedBroadcasts.size() == 0 + boolean processingBroadcasts = false; + for (BroadcastQueue q : mBroadcastQueues) { + if (q.mParallelBroadcasts.size() != 0 || q.mOrderedBroadcasts.size() != 0) { + processingBroadcasts = true; + } + } + return !processingBroadcasts && (mSleeping || (mMainStack.mResumedActivity != null && mMainStack.mResumedActivity.idle)); } @@ -14751,8 +15201,157 @@ public final class ActivityManagerService extends ActivityManagerNative // Multi-user methods - public boolean switchUser(int userid) { - // TODO + private int mCurrentUserId; + private SparseIntArray mLoggedInUsers = new SparseIntArray(5); + + public boolean switchUser(int userId) { + final int callingUid = Binder.getCallingUid(); + if (callingUid != 0 && callingUid != Process.myUid()) { + Slog.e(TAG, "Trying to switch user from unauthorized app"); + return false; + } + if (mCurrentUserId == userId) + return true; + + synchronized (this) { + // Check if user is already logged in, otherwise check if user exists first before + // adding to the list of logged in users. + if (mLoggedInUsers.indexOfKey(userId) < 0) { + if (!userExists(userId)) { + return false; + } + mLoggedInUsers.append(userId, userId); + } + + mCurrentUserId = userId; + boolean haveActivities = mMainStack.switchUser(userId); + if (!haveActivities) { + startHomeActivityLocked(userId); + } + } return true; } + + private boolean userExists(int userId) { + try { + List<UserInfo> users = AppGlobals.getPackageManager().getUsers(); + for (UserInfo user : users) { + if (user.id == userId) { + return true; + } + } + } catch (RemoteException re) { + // Won't happen, in same process + } + + return false; + } + + + private int applyUserId(int uid, int userId) { + return UserId.getUid(userId, uid); + } + + private ApplicationInfo getAppInfoForUser(ApplicationInfo info, int userId) { + ApplicationInfo newInfo = new ApplicationInfo(info); + newInfo.uid = applyUserId(info.uid, userId); + if (newInfo.uid >= Process.FIRST_APPLICATION_UID) { + newInfo.dataDir = USER_DATA_DIR + userId + "/" + + info.packageName; + } + return newInfo; + } + + ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId) { + if (aInfo.applicationInfo.uid < Process.FIRST_APPLICATION_UID + || userId < 1) { + return aInfo; + } + + ActivityInfo info = new ActivityInfo(aInfo); + info.applicationInfo = getAppInfoForUser(info.applicationInfo, userId); + return info; + } + + static class ServiceMap { + + private final SparseArray<HashMap<ComponentName, ServiceRecord>> mServicesByNamePerUser + = new SparseArray<HashMap<ComponentName, ServiceRecord>>(); + private final SparseArray<HashMap<Intent.FilterComparison, ServiceRecord>> + mServicesByIntentPerUser = new SparseArray< + HashMap<Intent.FilterComparison, ServiceRecord>>(); + + ServiceRecord getServiceByName(ComponentName name, int callingUser) { + // TODO: Deal with global services + if (DEBUG_MU) + Slog.v(TAG_MU, "getServiceByName(" + name + "), callingUser = " + callingUser); + return getServices(callingUser).get(name); + } + + ServiceRecord getServiceByName(ComponentName name) { + return getServiceByName(name, -1); + } + + ServiceRecord getServiceByIntent(Intent.FilterComparison filter, int callingUser) { + // TODO: Deal with global services + if (DEBUG_MU) + Slog.v(TAG_MU, "getServiceByIntent(" + filter + "), callingUser = " + callingUser); + return getServicesByIntent(callingUser).get(filter); + } + + ServiceRecord getServiceByIntent(Intent.FilterComparison filter) { + return getServiceByIntent(filter, -1); + } + + void putServiceByName(ComponentName name, int callingUser, ServiceRecord value) { + // TODO: Deal with global services + getServices(callingUser).put(name, value); + } + + void putServiceByIntent(Intent.FilterComparison filter, int callingUser, + ServiceRecord value) { + // TODO: Deal with global services + getServicesByIntent(callingUser).put(filter, value); + } + + void removeServiceByName(ComponentName name, int callingUser) { + // TODO: Deal with global services + ServiceRecord removed = getServices(callingUser).remove(name); + if (DEBUG_MU) + Slog.v(TAG, "removeServiceByName user=" + callingUser + " name=" + name + + " removed=" + removed); + } + + void removeServiceByIntent(Intent.FilterComparison filter, int callingUser) { + // TODO: Deal with global services + ServiceRecord removed = getServicesByIntent(callingUser).remove(filter); + if (DEBUG_MU) + Slog.v(TAG_MU, "removeServiceByIntent user=" + callingUser + " intent=" + filter + + " removed=" + removed); + } + + Collection<ServiceRecord> getAllServices(int callingUser) { + // TODO: Deal with global services + return getServices(callingUser).values(); + } + + private HashMap<ComponentName, ServiceRecord> getServices(int callingUser) { + HashMap map = mServicesByNamePerUser.get(callingUser); + if (map == null) { + map = new HashMap<ComponentName, ServiceRecord>(); + mServicesByNamePerUser.put(callingUser, map); + } + return map; + } + + private HashMap<Intent.FilterComparison, ServiceRecord> getServicesByIntent( + int callingUser) { + HashMap map = mServicesByIntentPerUser.get(callingUser); + if (map == null) { + map = new HashMap<Intent.FilterComparison, ServiceRecord>(); + mServicesByIntentPerUser.put(callingUser, map); + } + return map; + } + } } diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index c819114..cdab6c6 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Bitmap; import android.os.Build; @@ -34,6 +35,7 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserId; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -55,6 +57,7 @@ final class ActivityRecord { final IApplicationToken.Stub appToken; // window manager token final ActivityInfo info; // all about me final int launchedFromUid; // always the uid who started the activity. + final int userId; // Which user is this running for? final Intent intent; // the original intent that generated us final ComponentName realActivity; // the intent component, or target of an alias. final String shortComponentName; // the short component name of the intent @@ -124,6 +127,7 @@ final class ActivityRecord { pw.print(prefix); pw.print("packageName="); pw.print(packageName); pw.print(" processName="); pw.println(processName); pw.print(prefix); pw.print("launchedFromUid="); pw.print(launchedFromUid); + pw.print(prefix); pw.print("userId="); pw.print(userId); pw.print(" app="); pw.println(app); pw.print(prefix); pw.println(intent.toInsecureString()); pw.print(prefix); pw.print("frontOfTask="); pw.print(frontOfTask); @@ -281,6 +285,7 @@ final class ActivityRecord { appToken = new Token(this); info = aInfo; launchedFromUid = _launchedFromUid; + userId = UserId.getUserId(aInfo.applicationInfo.uid); intent = _intent; shortComponentName = _intent.getComponent().flattenToShortString(); resolvedType = _resolvedType; diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 6c11953..c3ae6a1 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -60,6 +60,7 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserId; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -274,6 +275,8 @@ final class ActivityStack { int mThumbnailWidth = -1; int mThumbnailHeight = -1; + private int mCurrentUser; + static final int SLEEP_TIMEOUT_MSG = 8; static final int PAUSE_TIMEOUT_MSG = 9; static final int IDLE_TIMEOUT_MSG = 10; @@ -365,6 +368,7 @@ final class ActivityStack { } final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) { + // TODO: Don't look for any tasks from other users int i = mHistory.size()-1; while (i >= 0) { ActivityRecord r = mHistory.get(i); @@ -377,6 +381,7 @@ final class ActivityStack { } final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) { + // TODO: Don't look for any tasks from other users int i = mHistory.size()-1; while (i >= 0) { ActivityRecord r = mHistory.get(i); @@ -398,6 +403,7 @@ final class ActivityStack { * @return Returns the HistoryRecord of the next activity on the stack. */ final ActivityRecord topRunningActivityLocked(IBinder token, int taskId) { + // TODO: Don't look for any tasks from other users int i = mHistory.size()-1; while (i >= 0) { ActivityRecord r = mHistory.get(i); @@ -444,10 +450,11 @@ final class ActivityStack { TaskRecord cp = null; + final int userId = UserId.getUserId(info.applicationInfo.uid); final int N = mHistory.size(); for (int i=(N-1); i>=0; i--) { ActivityRecord r = mHistory.get(i); - if (!r.finishing && r.task != cp + if (!r.finishing && r.task != cp && r.userId == userId && r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE) { cp = r.task; //Slog.i(TAG, "Comparing existing cls=" + r.task.intent.getComponent().flattenToShortString() @@ -487,12 +494,13 @@ final class ActivityStack { if (info.targetActivity != null) { cls = new ComponentName(info.packageName, info.targetActivity); } + final int userId = UserId.getUserId(info.applicationInfo.uid); final int N = mHistory.size(); for (int i=(N-1); i>=0; i--) { ActivityRecord r = mHistory.get(i); if (!r.finishing) { - if (r.intent.getComponent().equals(cls)) { + if (r.intent.getComponent().equals(cls) && r.userId == userId) { //Slog.i(TAG, "Found matching class!"); //dump(); //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); @@ -511,6 +519,43 @@ final class ActivityStack { mService.mHandler.sendMessage(msg); } + /* + * Move the activities around in the stack to bring a user to the foreground. + * @return whether there are any activities for the specified user. + */ + final boolean switchUser(int userId) { + synchronized (mService) { + mCurrentUser = userId; + + // Only one activity? Nothing to do... + if (mHistory.size() < 2) + return false; + + boolean haveActivities = false; + // Check if the top activity is from the new user. + ActivityRecord top = mHistory.get(mHistory.size() - 1); + if (top.userId == userId) return true; + // Otherwise, move the user's activities to the top. + int N = mHistory.size(); + int i = 0; + while (i < N) { + ActivityRecord r = mHistory.get(i); + if (r.userId == userId) { + ActivityRecord moveToTop = mHistory.remove(i); + mHistory.add(moveToTop); + // No need to check the top one now + N--; + haveActivities = true; + } else { + i++; + } + } + // Transition from the old top to the new top + resumeTopActivityLocked(top); + return haveActivities; + } + } + final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app, boolean andResume, boolean checkConfig) throws RemoteException { @@ -1272,7 +1317,7 @@ final class ActivityStack { // There are no more activities! Let's just start up the // Launcher... if (mMainStack) { - return mService.startHomeActivityLocked(); + return mService.startHomeActivityLocked(0); } } @@ -1384,6 +1429,7 @@ final class ActivityStack { // Launching this app's activity, make sure the app is no longer // considered stopped. try { + // TODO: Apply to the correct userId AppGlobals.getPackageManager().setPackageStoppedState( next.packageName, false); } catch (RemoteException e1) { @@ -2354,7 +2400,7 @@ final class ActivityStack { } } } - + ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid, intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho, requestCode, componentSpecified); @@ -2420,7 +2466,8 @@ final class ActivityStack { int grantedMode, boolean onlyIfNeeded, boolean doResume) { final Intent intent = r.intent; final int callingUid = r.launchedFromUid; - + final int userId = r.userId; + int launchFlags = intent.getFlags(); // We'll invoke onUserLeaving before onPause only if the launching @@ -2648,7 +2695,7 @@ final class ActivityStack { // once. ActivityRecord top = topRunningNonDelayedActivityLocked(notTop); if (top != null && r.resultTo == null) { - if (top.realActivity.equals(r.realActivity)) { + if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) { if (top.app != null && top.app.thread != null) { if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP @@ -2821,12 +2868,12 @@ final class ActivityStack { int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, String profileFile, ParcelFileDescriptor profileFd, - boolean autoStopProfiler, WaitResult outResult, Configuration config) { + boolean autoStopProfiler, + WaitResult outResult, Configuration config, int userId) { // 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! @@ -2835,6 +2882,7 @@ final class ActivityStack { // Collect information about the target of the Intent. ActivityInfo aInfo = resolveActivity(intent, resolvedType, debug, profileFile, profileFd, autoStopProfiler); + aInfo = mService.getActivityInfoForUser(aInfo, userId); synchronized (mService) { int callingPid; @@ -2915,6 +2963,7 @@ final class ActivityStack { PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS); aInfo = rInfo != null ? rInfo.activityInfo : null; + aInfo = mService.getActivityInfoForUser(aInfo, userId); } catch (RemoteException e) { aInfo = null; } @@ -2977,7 +3026,8 @@ final class ActivityStack { } final int startActivities(IApplicationThread caller, int callingUid, - Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + Intent[] intents, + String[] resolvedTypes, IBinder resultTo, int userId) { if (intents == null) { throw new NullPointerException("intents is null"); } @@ -3022,6 +3072,8 @@ final class ActivityStack { // Collect information about the target of the Intent. ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], false, null, null, false); + // TODO: New, check if this is correct + aInfo = mService.getActivityInfoForUser(aInfo, userId); if (mMainStack && aInfo != null && (aInfo.applicationInfo.flags & ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java index bcb0134..6738e4f 100644 --- a/services/java/com/android/server/am/BroadcastRecord.java +++ b/services/java/com/android/server/am/BroadcastRecord.java @@ -59,6 +59,7 @@ class BroadcastRecord extends Binder { IBinder receiver; // who is currently running, null if none. int state; int anrCount; // has this broadcast record hit any ANRs? + ActivityManagerService.BroadcastQueue queue; // the outbound queue handling this broadcast static final int IDLE = 0; static final int APP_RECEIVE = 1; @@ -161,11 +162,13 @@ class BroadcastRecord extends Binder { } } - BroadcastRecord(Intent _intent, ProcessRecord _callerApp, String _callerPackage, + BroadcastRecord(ActivityManagerService.BroadcastQueue _queue, + Intent _intent, ProcessRecord _callerApp, String _callerPackage, int _callingPid, int _callingUid, String _requiredPermission, List _receivers, IIntentReceiver _resultTo, int _resultCode, String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky, boolean _initialSticky) { + queue = _queue; intent = _intent; callerApp = _callerApp; callerPackage = _callerPackage; diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index abd2a1f..3b6a97c 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserId; import android.util.Slog; import java.io.PrintWriter; @@ -248,7 +249,8 @@ class PendingIntentRecord extends IIntentSender.Stub { owner.broadcastIntentInPackage(key.packageName, uid, finalIntent, resolvedType, finishedReceiver, code, null, null, - requiredPermission, (finishedReceiver != null), false); + requiredPermission, (finishedReceiver != null), false, UserId + .getUserId(uid)); sendFinish = false; } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, diff --git a/services/java/com/android/server/am/ProviderMap.java b/services/java/com/android/server/am/ProviderMap.java new file mode 100644 index 0000000..44e7ecc --- /dev/null +++ b/services/java/com/android/server/am/ProviderMap.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.Process; +import android.os.UserId; +import android.util.Slog; +import android.util.SparseArray; + +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Keeps track of content providers by authority (name) and class. It separates the mapping by + * user and ones that are not user-specific (system providers). + */ +public class ProviderMap { + + private static final String TAG = "ProviderMap"; + + private static final boolean DBG = false; + + private final HashMap<String, ContentProviderRecord> mGlobalByName + = new HashMap<String, ContentProviderRecord>(); + private final HashMap<ComponentName, ContentProviderRecord> mGlobalByClass + = new HashMap<ComponentName, ContentProviderRecord>(); + + private final SparseArray<HashMap<String, ContentProviderRecord>> mProvidersByNamePerUser + = new SparseArray<HashMap<String, ContentProviderRecord>>(); + private final SparseArray<HashMap<ComponentName, ContentProviderRecord>> mProvidersByClassPerUser + = new SparseArray<HashMap<ComponentName, ContentProviderRecord>>(); + + ContentProviderRecord getProviderByName(String name) { + return getProviderByName(name, -1); + } + + ContentProviderRecord getProviderByName(String name, int userId) { + if (DBG) { + Slog.i(TAG, "getProviderByName: " + name + " , callingUid = " + Binder.getCallingUid()); + } + // Try to find it in the global list + ContentProviderRecord record = mGlobalByName.get(name); + if (record != null) { + return record; + } + + // Check the current user's list + return getProvidersByName(userId).get(name); + } + + ContentProviderRecord getProviderByClass(ComponentName name) { + return getProviderByClass(name, -1); + } + + ContentProviderRecord getProviderByClass(ComponentName name, int userId) { + if (DBG) { + Slog.i(TAG, "getProviderByClass: " + name + ", callingUid = " + Binder.getCallingUid()); + } + // Try to find it in the global list + ContentProviderRecord record = mGlobalByClass.get(name); + if (record != null) { + return record; + } + + // Check the current user's list + return getProvidersByClass(userId).get(name); + } + + void putProviderByName(String name, ContentProviderRecord record) { + if (DBG) { + Slog.i(TAG, "putProviderByName: " + name + " , callingUid = " + Binder.getCallingUid() + + ", record uid = " + record.appInfo.uid); + } + if (record.appInfo.uid < Process.FIRST_APPLICATION_UID) { + mGlobalByName.put(name, record); + } else { + final int userId = UserId.getUserId(record.appInfo.uid); + getProvidersByName(userId).put(name, record); + } + } + + void putProviderByClass(ComponentName name, ContentProviderRecord record) { + if (DBG) { + Slog.i(TAG, "putProviderByClass: " + name + " , callingUid = " + Binder.getCallingUid() + + ", record uid = " + record.appInfo.uid); + } + if (record.appInfo.uid < Process.FIRST_APPLICATION_UID) { + mGlobalByClass.put(name, record); + } else { + final int userId = UserId.getUserId(record.appInfo.uid); + getProvidersByClass(userId).put(name, record); + } + } + + void removeProviderByName(String name, int optionalUserId) { + if (mGlobalByName.containsKey(name)) { + if (DBG) + Slog.i(TAG, "Removing from globalByName name=" + name); + mGlobalByName.remove(name); + } else { + // TODO: Verify this works, i.e., the caller happens to be from the correct user + if (DBG) + Slog.i(TAG, + "Removing from providersByName name=" + name + " user=" + + (optionalUserId == -1 ? Binder.getOrigCallingUser() : optionalUserId)); + getProvidersByName(optionalUserId).remove(name); + } + } + + void removeProviderByClass(ComponentName name, int optionalUserId) { + if (mGlobalByClass.containsKey(name)) { + if (DBG) + Slog.i(TAG, "Removing from globalByClass name=" + name); + mGlobalByClass.remove(name); + } else { + if (DBG) + Slog.i(TAG, + "Removing from providersByClass name=" + name + " user=" + + (optionalUserId == -1 ? Binder.getOrigCallingUser() : optionalUserId)); + getProvidersByClass(optionalUserId).remove(name); + } + } + + private HashMap<String, ContentProviderRecord> getProvidersByName(int optionalUserId) { + final int userId = optionalUserId >= 0 + ? optionalUserId : Binder.getOrigCallingUser(); + final HashMap<String, ContentProviderRecord> map = mProvidersByNamePerUser.get(userId); + if (map == null) { + HashMap<String, ContentProviderRecord> newMap = new HashMap<String, ContentProviderRecord>(); + mProvidersByNamePerUser.put(userId, newMap); + return newMap; + } else { + return map; + } + } + + private HashMap<ComponentName, ContentProviderRecord> getProvidersByClass(int optionalUserId) { + final int userId = optionalUserId >= 0 + ? optionalUserId : Binder.getOrigCallingUser(); + final HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.get(userId); + if (map == null) { + HashMap<ComponentName, ContentProviderRecord> newMap = new HashMap<ComponentName, ContentProviderRecord>(); + mProvidersByClassPerUser.put(userId, newMap); + return newMap; + } else { + return map; + } + } + + private void dumpProvidersByClassLocked(PrintWriter pw, boolean dumpAll, + HashMap<ComponentName, ContentProviderRecord> map) { + Iterator<Map.Entry<ComponentName, ContentProviderRecord>> it = map.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<ComponentName, ContentProviderRecord> e = it.next(); + ContentProviderRecord r = e.getValue(); + if (dumpAll) { + pw.print(" * "); + pw.println(r); + r.dump(pw, " "); + } else { + pw.print(" * "); + pw.print(r.name.toShortString()); + /* + if (r.app != null) { + pw.println(":"); + pw.print(" "); + pw.println(r.app); + } else { + pw.println(); + } + */ + } + } + } + + private void dumpProvidersByNameLocked(PrintWriter pw, + HashMap<String, ContentProviderRecord> map) { + Iterator<Map.Entry<String, ContentProviderRecord>> it = map.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<String, ContentProviderRecord> e = it.next(); + ContentProviderRecord r = e.getValue(); + pw.print(" "); + pw.print(e.getKey()); + pw.print(": "); + pw.println(r); + } + } + + void dumpProvidersLocked(PrintWriter pw, boolean dumpAll) { + boolean needSep = false; + if (mGlobalByClass.size() > 0) { + if (needSep) + pw.println(" "); + pw.println(" Published content providers (by class):"); + dumpProvidersByClassLocked(pw, dumpAll, mGlobalByClass); + pw.println(" "); + } + + if (mProvidersByClassPerUser.size() > 1) { + for (int i = 0; i < mProvidersByClassPerUser.size(); i++) { + HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(i); + pw.println(" User " + mProvidersByClassPerUser.keyAt(i) + ":"); + dumpProvidersByClassLocked(pw, dumpAll, map); + pw.println(" "); + } + } else if (mProvidersByClassPerUser.size() == 1) { + HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(0); + dumpProvidersByClassLocked(pw, dumpAll, map); + } + needSep = true; + + if (dumpAll) { + pw.println(" "); + pw.println(" Authority to provider mappings:"); + dumpProvidersByNameLocked(pw, mGlobalByName); + + for (int i = 0; i < mProvidersByNamePerUser.size(); i++) { + if (i > 0) { + pw.println(" User " + mProvidersByNamePerUser.keyAt(i) + ":"); + } + dumpProvidersByNameLocked(pw, mProvidersByNamePerUser.valueAt(i)); + } + } + } +} diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index 257113b..75ba947 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -25,11 +25,13 @@ import android.app.NotificationManager; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserId; import android.util.Slog; import android.util.TimeUtils; @@ -60,6 +62,7 @@ class ServiceRecord extends Binder { // all information about the service. final ApplicationInfo appInfo; // information about service's app. + final int userId; // user that this service is running as final String packageName; // the package implementing intent's component final String processName; // process where this component wants to run final String permission;// permission needed to access service @@ -289,6 +292,7 @@ class ServiceRecord extends Binder { this.restarter = restarter; createTime = SystemClock.elapsedRealtime(); lastActivity = SystemClock.uptimeMillis(); + userId = UserId.getUserId(appInfo.uid); } public AppBindRecord retrieveAppBindingLocked(Intent intent, diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java index de3129b..47ec218 100644 --- a/services/java/com/android/server/am/TaskRecord.java +++ b/services/java/com/android/server/am/TaskRecord.java @@ -19,7 +19,9 @@ package com.android.server.am; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.graphics.Bitmap; +import android.os.UserId; import java.io.PrintWriter; @@ -37,6 +39,7 @@ class TaskRecord extends ThumbnailHolder { boolean askedCompatMode;// Have asked the user about compat mode for this task. String stringName; // caching of toString() result. + int userId; // user for which this task was created TaskRecord(int _taskId, ActivityInfo info, Intent _intent) { taskId = _taskId; @@ -84,13 +87,17 @@ class TaskRecord extends ThumbnailHolder { origActivity = new ComponentName(info.packageName, info.name); } } - + if (intent != null && (intent.getFlags()&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { // Once we are set to an Intent with this flag, we count this // task as having a true root activity. rootWasReset = true; } + + if (info.applicationInfo != null) { + userId = UserId.getUserId(info.applicationInfo.uid); + } } void dump(PrintWriter pw, String prefix) { @@ -154,6 +161,8 @@ class TaskRecord extends ThumbnailHolder { } else { sb.append(" ??"); } + sb.append(" U "); + sb.append(userId); sb.append('}'); return stringName = sb.toString(); } diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index cc1df4f..9573fda 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -546,14 +546,13 @@ public class Tethering extends INetworkManagementEventObserver.Stub { ifcg = mNMService.getInterfaceConfig(iface); if (ifcg != null) { InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR); - ifcg.addr = new LinkAddress(addr, USB_PREFIX_LENGTH); + ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH)); if (enabled) { - ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up"); + ifcg.setInterfaceUp(); } else { - ifcg.interfaceFlags = ifcg.interfaceFlags.replace("up", "down"); + ifcg.setInterfaceDown(); } - ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running", ""); - ifcg.interfaceFlags = ifcg.interfaceFlags.replace(" "," "); + ifcg.clearFlag("running"); mNMService.setInterfaceConfig(iface, ifcg); } } catch (Exception e) { diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java index a76e70f..c4f9ce1 100644 --- a/services/java/com/android/server/connectivity/Vpn.java +++ b/services/java/com/android/server/connectivity/Vpn.java @@ -33,6 +33,7 @@ import android.net.INetworkManagementEventObserver; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.os.Binder; +import android.os.FileUtils; import android.os.IBinder; import android.os.Parcel; import android.os.ParcelFileDescriptor; @@ -47,7 +48,6 @@ import com.android.internal.net.VpnConfig; import com.android.server.ConnectivityService.VpnCallback; import java.io.File; -import java.io.FileInputStream; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charsets; @@ -573,11 +573,7 @@ public class Vpn extends INetworkManagementEventObserver.Stub { } // Now we are connected. Read and parse the new state. - byte[] buffer = new byte[(int) state.length()]; - if (new FileInputStream(state).read(buffer) != buffer.length) { - throw new IllegalStateException("Cannot read the state"); - } - String[] parameters = new String(buffer, Charsets.UTF_8).split("\n", -1); + String[] parameters = FileUtils.readTextFile(state, 0, null).split("\n", -1); if (parameters.length != 6) { throw new IllegalStateException("Cannot parse the state"); } diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java index 00788ba..0ce5499 100755 --- a/services/java/com/android/server/location/GpsLocationProvider.java +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -554,8 +554,13 @@ public class GpsLocationProvider implements LocationProviderInterface { long delay; - // GPS requires fresh NTP time - if (mNtpTime.forceRefresh()) { + // force refresh NTP cache when outdated + if (mNtpTime.getCacheAge() >= NTP_INTERVAL) { + mNtpTime.forceRefresh(); + } + + // only update when NTP time is fresh + if (mNtpTime.getCacheAge() < NTP_INTERVAL) { long time = mNtpTime.getCachedNtpTime(); long timeReference = mNtpTime.getCachedNtpTimeReference(); long certainty = mNtpTime.getCacheCertainty(); diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java index 8c0f1e0..9772d6a 100644 --- a/services/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java @@ -48,6 +48,7 @@ import static android.net.NetworkTemplate.MATCH_MOBILE_4G; import static android.net.NetworkTemplate.MATCH_MOBILE_ALL; import static android.net.NetworkTemplate.MATCH_WIFI; import static android.net.NetworkTemplate.buildTemplateMobileAll; +import static android.net.TrafficStats.MB_IN_BYTES; import static android.text.format.DateUtils.DAY_IN_MILLIS; import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT; @@ -153,10 +154,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int VERSION_INIT = 1; private static final int VERSION_ADDED_SNOOZE = 2; private static final int VERSION_ADDED_RESTRICT_BACKGROUND = 3; - - private static final long KB_IN_BYTES = 1024; - private static final long MB_IN_BYTES = KB_IN_BYTES * 1024; - private static final long GB_IN_BYTES = MB_IN_BYTES * 1024; + private static final int VERSION_ADDED_METERED = 4; + private static final int VERSION_SPLIT_SNOOZE = 5; // @VisibleForTesting public static final int TYPE_WARNING = 0x1; @@ -175,6 +174,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final String ATTR_WARNING_BYTES = "warningBytes"; private static final String ATTR_LIMIT_BYTES = "limitBytes"; private static final String ATTR_LAST_SNOOZE = "lastSnooze"; + private static final String ATTR_LAST_WARNING_SNOOZE = "lastWarningSnooze"; + private static final String ATTR_LAST_LIMIT_SNOOZE = "lastLimitSnooze"; + private static final String ATTR_METERED = "metered"; private static final String ATTR_UID = "uid"; private static final String ATTR_POLICY = "policy"; @@ -182,7 +184,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // @VisibleForTesting public static final String ACTION_ALLOW_BACKGROUND = - "com.android.server.action.ACTION_ALLOW_BACKGROUND"; + "com.android.server.net.action.ALLOW_BACKGROUND"; + public static final String ACTION_SNOOZE_WARNING = + "com.android.server.net.action.SNOOZE_WARNING"; private static final long TIME_CACHE_MAX_AGE = DAY_IN_MILLIS; @@ -331,6 +335,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final IntentFilter allowFilter = new IntentFilter(ACTION_ALLOW_BACKGROUND); mContext.registerReceiver(mAllowReceiver, allowFilter, MANAGE_NETWORK_POLICY, mHandler); + // listen for snooze warning from notifications + final IntentFilter snoozeWarningFilter = new IntentFilter(ACTION_SNOOZE_WARNING); + mContext.registerReceiver(mSnoozeWarningReceiver, snoozeWarningFilter, + MANAGE_NETWORK_POLICY, mHandler); + } private IProcessObserver mProcessObserver = new IProcessObserver.Stub() { @@ -416,6 +425,21 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { }; /** + * Receiver that watches for {@link Notification} control of + * {@link NetworkPolicy#lastWarningSnooze}. + */ + private BroadcastReceiver mSnoozeWarningReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // on background handler thread, and verified MANAGE_NETWORK_POLICY + // permission above. + + final NetworkTemplate template = intent.getParcelableExtra(EXTRA_NETWORK_TEMPLATE); + performSnooze(template, TYPE_WARNING); + } + }; + + /** * Observer that watches for {@link INetworkManagementService} alerts. */ private INetworkManagementEventObserver mAlertObserver = new NetworkAlertObserver() { @@ -456,7 +480,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final long totalBytes = getTotalBytes(policy.template, start, end); if (policy.isOverLimit(totalBytes)) { - if (policy.lastSnooze >= start) { + if (policy.lastLimitSnooze >= start) { enqueueNotification(policy, TYPE_LIMIT_SNOOZED, totalBytes); } else { enqueueNotification(policy, TYPE_LIMIT, totalBytes); @@ -466,7 +490,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } else { notifyUnderLimitLocked(policy.template); - if (policy.warningBytes != WARNING_DISABLED && totalBytes >= policy.warningBytes) { + if (policy.isOverWarning(totalBytes) && policy.lastWarningSnooze < start) { enqueueNotification(policy, TYPE_WARNING, totalBytes); } } @@ -532,7 +556,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final String tag = buildNotificationTag(policy, type); final Notification.Builder builder = new Notification.Builder(mContext); builder.setOnlyAlertOnce(true); - builder.setOngoing(true); + builder.setWhen(0L); final Resources res = mContext.getResources(); switch (type) { @@ -545,9 +569,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { builder.setContentTitle(title); builder.setContentText(body); - final Intent intent = buildViewDataUsageIntent(policy.template); + final Intent snoozeIntent = buildSnoozeWarningIntent(policy.template); + builder.setDeleteIntent(PendingIntent.getBroadcast( + mContext, 0, snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + + final Intent viewIntent = buildViewDataUsageIntent(policy.template); builder.setContentIntent(PendingIntent.getActivity( - mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)); + mContext, 0, viewIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + break; } case TYPE_LIMIT: { @@ -572,6 +601,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { break; } + builder.setOngoing(true); builder.setSmallIcon(R.drawable.stat_notify_disabled); builder.setTicker(title); builder.setContentTitle(title); @@ -606,6 +636,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { break; } + builder.setOngoing(true); builder.setSmallIcon(R.drawable.stat_notify_error); builder.setTicker(title); builder.setContentTitle(title); @@ -718,10 +749,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final long totalBytes = getTotalBytes(policy.template, start, end); // disable data connection when over limit and not snoozed - final boolean overLimit = policy.isOverLimit(totalBytes) && policy.lastSnooze < start; - final boolean enabled = !overLimit; + final boolean overLimitWithoutSnooze = policy.isOverLimit(totalBytes) + && policy.lastLimitSnooze < start; + final boolean networkEnabled = !overLimitWithoutSnooze; - setNetworkTemplateEnabled(policy.template, enabled); + setNetworkTemplateEnabled(policy.template, networkEnabled); } } @@ -819,9 +851,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } final boolean hasLimit = policy.limitBytes != LIMIT_DISABLED; - if (hasLimit) { + if (hasLimit || policy.metered) { final long quotaBytes; - if (policy.lastSnooze >= start) { + if (!hasLimit) { + // metered network, but no policy limit; we still need to + // restrict apps, so push really high quota. + quotaBytes = Long.MAX_VALUE; + } else if (policy.lastLimitSnooze >= start) { // snoozing past quota, but we still need to restrict apps, // so push really high quota. quotaBytes = Long.MAX_VALUE; @@ -890,8 +926,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final int cycleDay = time.monthDay; final NetworkTemplate template = buildTemplateMobileAll(subscriberId); - mNetworkPolicy.put(template, new NetworkPolicy( - template, cycleDay, warningBytes, LIMIT_DISABLED, SNOOZE_NEVER)); + mNetworkPolicy.put(template, new NetworkPolicy(template, cycleDay, warningBytes, + LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, true)); writePolicyLocked(); } } @@ -929,17 +965,40 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final int cycleDay = readIntAttribute(in, ATTR_CYCLE_DAY); final long warningBytes = readLongAttribute(in, ATTR_WARNING_BYTES); final long limitBytes = readLongAttribute(in, ATTR_LIMIT_BYTES); - final long lastSnooze; - if (version >= VERSION_ADDED_SNOOZE) { - lastSnooze = readLongAttribute(in, ATTR_LAST_SNOOZE); + final long lastLimitSnooze; + if (version >= VERSION_SPLIT_SNOOZE) { + lastLimitSnooze = readLongAttribute(in, ATTR_LAST_LIMIT_SNOOZE); + } else if (version >= VERSION_ADDED_SNOOZE) { + lastLimitSnooze = readLongAttribute(in, ATTR_LAST_SNOOZE); } else { - lastSnooze = SNOOZE_NEVER; + lastLimitSnooze = SNOOZE_NEVER; + } + final boolean metered; + if (version >= VERSION_ADDED_METERED) { + metered = readBooleanAttribute(in, ATTR_METERED); + } else { + switch (networkTemplate) { + case MATCH_MOBILE_3G_LOWER: + case MATCH_MOBILE_4G: + case MATCH_MOBILE_ALL: + metered = true; + break; + default: + metered = false; + } + } + final long lastWarningSnooze; + if (version >= VERSION_SPLIT_SNOOZE) { + lastWarningSnooze = readLongAttribute(in, ATTR_LAST_WARNING_SNOOZE); + } else { + lastWarningSnooze = SNOOZE_NEVER; } final NetworkTemplate template = new NetworkTemplate( networkTemplate, subscriberId); - mNetworkPolicy.put(template, new NetworkPolicy( - template, cycleDay, warningBytes, limitBytes, lastSnooze)); + mNetworkPolicy.put(template, new NetworkPolicy(template, cycleDay, + warningBytes, limitBytes, lastWarningSnooze, lastLimitSnooze, + metered)); } else if (TAG_UID_POLICY.equals(tag)) { final int uid = readIntAttribute(in, ATTR_UID); @@ -994,7 +1053,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { out.startDocument(null, true); out.startTag(null, TAG_POLICY_LIST); - writeIntAttribute(out, ATTR_VERSION, VERSION_ADDED_RESTRICT_BACKGROUND); + writeIntAttribute(out, ATTR_VERSION, VERSION_SPLIT_SNOOZE); writeBooleanAttribute(out, ATTR_RESTRICT_BACKGROUND, mRestrictBackground); // write all known network policies @@ -1010,7 +1069,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { writeIntAttribute(out, ATTR_CYCLE_DAY, policy.cycleDay); writeLongAttribute(out, ATTR_WARNING_BYTES, policy.warningBytes); writeLongAttribute(out, ATTR_LIMIT_BYTES, policy.limitBytes); - writeLongAttribute(out, ATTR_LAST_SNOOZE, policy.lastSnooze); + writeLongAttribute(out, ATTR_LAST_WARNING_SNOOZE, policy.lastWarningSnooze); + writeLongAttribute(out, ATTR_LAST_LIMIT_SNOOZE, policy.lastLimitSnooze); + writeBooleanAttribute(out, ATTR_METERED, policy.metered); out.endTag(null, TAG_NETWORK_POLICY); } @@ -1120,9 +1181,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override - public void snoozePolicy(NetworkTemplate template) { + public void snoozeLimit(NetworkTemplate template) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + performSnooze(template, TYPE_LIMIT); + } + private void performSnooze(NetworkTemplate template, int type) { maybeRefreshTrustedTime(); final long currentTime = currentTimeMillis(); synchronized (mRulesLock) { @@ -1132,7 +1196,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { throw new IllegalArgumentException("unable to find policy for " + template); } - policy.lastSnooze = currentTime; + switch (type) { + case TYPE_WARNING: + policy.lastWarningSnooze = currentTime; + break; + case TYPE_LIMIT: + policy.lastLimitSnooze = currentTime; + break; + default: + throw new IllegalArgumentException("unexpected type"); + } updateNetworkEnabledLocked(); updateNetworkRulesLocked(); @@ -1225,12 +1298,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } synchronized (mRulesLock) { - if (argSet.contains("unsnooze")) { + if (argSet.contains("--unsnooze")) { for (NetworkPolicy policy : mNetworkPolicy.values()) { - policy.lastSnooze = SNOOZE_NEVER; + policy.clearSnooze(); } + + updateNetworkEnabledLocked(); + updateNetworkRulesLocked(); + updateNotificationsLocked(); writePolicyLocked(); - fout.println("Wiped snooze timestamps"); + + fout.println("Cleared snooze timestamps"); return; } @@ -1552,6 +1630,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private long getTotalBytes(NetworkTemplate template, long start, long end) { try { return mNetworkStats.getSummaryForNetwork(template, start, end).getTotalBytes(); + } catch (RuntimeException e) { + Slog.w(TAG, "problem reading network stats: " + e); + return 0; } catch (RemoteException e) { // ignored; service lives in system_server return 0; @@ -1575,6 +1656,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return new Intent(ACTION_ALLOW_BACKGROUND); } + private static Intent buildSnoozeWarningIntent(NetworkTemplate template) { + final Intent intent = new Intent(ACTION_SNOOZE_WARNING); + intent.putExtra(EXTRA_NETWORK_TEMPLATE, template); + return intent; + } + private static Intent buildNetworkOverLimitIntent(NetworkTemplate template) { final Intent intent = new Intent(); intent.setComponent(new ComponentName( diff --git a/services/java/com/android/server/net/NetworkStatsCollection.java b/services/java/com/android/server/net/NetworkStatsCollection.java new file mode 100644 index 0000000..70038d9 --- /dev/null +++ b/services/java/com/android/server/net/NetworkStatsCollection.java @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2012 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.net; + +import static android.net.NetworkStats.IFACE_ALL; +import static android.net.NetworkStats.SET_ALL; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.NetworkStats.UID_ALL; +import static android.net.TrafficStats.UID_REMOVED; + +import android.net.NetworkIdentity; +import android.net.NetworkStats; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; +import android.net.TrafficStats; +import android.text.format.DateUtils; + +import com.android.internal.os.AtomicFile; +import com.android.internal.util.FileRotator; +import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.Objects; +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.ProtocolException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import libcore.io.IoUtils; + +/** + * Collection of {@link NetworkStatsHistory}, stored based on combined key of + * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself. + */ +public class NetworkStatsCollection implements FileRotator.Reader { + private static final String TAG = "NetworkStatsCollection"; + + /** File header magic number: "ANET" */ + private static final int FILE_MAGIC = 0x414E4554; + + private static final int VERSION_NETWORK_INIT = 1; + + private static final int VERSION_UID_INIT = 1; + private static final int VERSION_UID_WITH_IDENT = 2; + private static final int VERSION_UID_WITH_TAG = 3; + private static final int VERSION_UID_WITH_SET = 4; + + private static final int VERSION_UNIFIED_INIT = 16; + + private HashMap<Key, NetworkStatsHistory> mStats = Maps.newHashMap(); + + private long mBucketDuration; + + private long mStartMillis; + private long mEndMillis; + private long mTotalBytes; + private boolean mDirty; + + public NetworkStatsCollection(long bucketDuration) { + mBucketDuration = bucketDuration; + reset(); + } + + public void reset() { + mStats.clear(); + mStartMillis = Long.MAX_VALUE; + mEndMillis = Long.MIN_VALUE; + mTotalBytes = 0; + mDirty = false; + } + + public long getStartMillis() { + return mStartMillis; + } + + public long getEndMillis() { + return mEndMillis; + } + + public long getTotalBytes() { + return mTotalBytes; + } + + public boolean isDirty() { + return mDirty; + } + + public void clearDirty() { + mDirty = false; + } + + public boolean isEmpty() { + return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE; + } + + /** + * Combine all {@link NetworkStatsHistory} in this collection which match + * the requested parameters. + */ + public NetworkStatsHistory getHistory( + NetworkTemplate template, int uid, int set, int tag, int fields) { + final NetworkStatsHistory combined = new NetworkStatsHistory( + mBucketDuration, estimateBuckets(), fields); + for (Map.Entry<Key, NetworkStatsHistory> entry : mStats.entrySet()) { + final Key key = entry.getKey(); + final boolean setMatches = set == SET_ALL || key.set == set; + if (key.uid == uid && setMatches && key.tag == tag + && templateMatches(template, key.ident)) { + combined.recordEntireHistory(entry.getValue()); + } + } + return combined; + } + + /** + * Summarize all {@link NetworkStatsHistory} in this collection which match + * the requested parameters. + */ + public NetworkStats getSummary(NetworkTemplate template, long start, long end) { + final long now = System.currentTimeMillis(); + + final NetworkStats stats = new NetworkStats(end - start, 24); + final NetworkStats.Entry entry = new NetworkStats.Entry(); + NetworkStatsHistory.Entry historyEntry = null; + + for (Map.Entry<Key, NetworkStatsHistory> mapEntry : mStats.entrySet()) { + final Key key = mapEntry.getKey(); + if (templateMatches(template, key.ident)) { + final NetworkStatsHistory history = mapEntry.getValue(); + historyEntry = history.getValues(start, end, now, historyEntry); + + entry.iface = IFACE_ALL; + entry.uid = key.uid; + entry.set = key.set; + entry.tag = key.tag; + entry.rxBytes = historyEntry.rxBytes; + entry.rxPackets = historyEntry.rxPackets; + entry.txBytes = historyEntry.txBytes; + entry.txPackets = historyEntry.txPackets; + entry.operations = historyEntry.operations; + + if (!entry.isEmpty()) { + stats.combineValues(entry); + } + } + } + + return stats; + } + + /** + * Record given {@link NetworkStats.Entry} into this collection. + */ + public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start, + long end, NetworkStats.Entry entry) { + noteRecordedHistory(start, end, entry.rxBytes + entry.txBytes); + findOrCreateHistory(ident, uid, set, tag).recordData(start, end, entry); + } + + /** + * Record given {@link NetworkStatsHistory} into this collection. + */ + private void recordHistory(Key key, NetworkStatsHistory history) { + if (history.size() == 0) return; + noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes()); + + final NetworkStatsHistory existing = mStats.get(key); + if (existing != null) { + existing.recordEntireHistory(history); + } else { + mStats.put(key, history); + } + } + + /** + * Record all {@link NetworkStatsHistory} contained in the given collection + * into this collection. + */ + public void recordCollection(NetworkStatsCollection another) { + for (Map.Entry<Key, NetworkStatsHistory> entry : another.mStats.entrySet()) { + recordHistory(entry.getKey(), entry.getValue()); + } + } + + private NetworkStatsHistory findOrCreateHistory( + NetworkIdentitySet ident, int uid, int set, int tag) { + final Key key = new Key(ident, uid, set, tag); + final NetworkStatsHistory existing = mStats.get(key); + + // update when no existing, or when bucket duration changed + NetworkStatsHistory updated = null; + if (existing == null) { + updated = new NetworkStatsHistory(mBucketDuration, 10); + } else if (existing.getBucketDuration() != mBucketDuration) { + updated = new NetworkStatsHistory(existing, mBucketDuration); + } + + if (updated != null) { + mStats.put(key, updated); + return updated; + } else { + return existing; + } + } + + /** {@inheritDoc} */ + public void read(InputStream in) throws IOException { + read(new DataInputStream(in)); + } + + public void read(DataInputStream in) throws IOException { + // verify file magic header intact + final int magic = in.readInt(); + if (magic != FILE_MAGIC) { + throw new ProtocolException("unexpected magic: " + magic); + } + + final int version = in.readInt(); + switch (version) { + case VERSION_UNIFIED_INIT: { + // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) + final int identSize = in.readInt(); + for (int i = 0; i < identSize; i++) { + final NetworkIdentitySet ident = new NetworkIdentitySet(in); + + final int size = in.readInt(); + for (int j = 0; j < size; j++) { + final int uid = in.readInt(); + final int set = in.readInt(); + final int tag = in.readInt(); + + final Key key = new Key(ident, uid, set, tag); + final NetworkStatsHistory history = new NetworkStatsHistory(in); + recordHistory(key, history); + } + } + break; + } + default: { + throw new ProtocolException("unexpected version: " + version); + } + } + } + + public void write(DataOutputStream out) throws IOException { + // cluster key lists grouped by ident + final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap(); + for (Key key : mStats.keySet()) { + ArrayList<Key> keys = keysByIdent.get(key.ident); + if (keys == null) { + keys = Lists.newArrayList(); + keysByIdent.put(key.ident, keys); + } + keys.add(key); + } + + out.writeInt(FILE_MAGIC); + out.writeInt(VERSION_UNIFIED_INIT); + + out.writeInt(keysByIdent.size()); + for (NetworkIdentitySet ident : keysByIdent.keySet()) { + final ArrayList<Key> keys = keysByIdent.get(ident); + ident.writeToStream(out); + + out.writeInt(keys.size()); + for (Key key : keys) { + final NetworkStatsHistory history = mStats.get(key); + out.writeInt(key.uid); + out.writeInt(key.set); + out.writeInt(key.tag); + history.writeToStream(out); + } + } + + out.flush(); + } + + @Deprecated + public void readLegacyNetwork(File file) throws IOException { + final AtomicFile inputFile = new AtomicFile(file); + + DataInputStream in = null; + try { + in = new DataInputStream(new BufferedInputStream(inputFile.openRead())); + + // verify file magic header intact + final int magic = in.readInt(); + if (magic != FILE_MAGIC) { + throw new ProtocolException("unexpected magic: " + magic); + } + + final int version = in.readInt(); + switch (version) { + case VERSION_NETWORK_INIT: { + // network := size *(NetworkIdentitySet NetworkStatsHistory) + final int size = in.readInt(); + for (int i = 0; i < size; i++) { + final NetworkIdentitySet ident = new NetworkIdentitySet(in); + final NetworkStatsHistory history = new NetworkStatsHistory(in); + + final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE); + recordHistory(key, history); + } + break; + } + default: { + throw new ProtocolException("unexpected version: " + version); + } + } + } catch (FileNotFoundException e) { + // missing stats is okay, probably first boot + } finally { + IoUtils.closeQuietly(in); + } + } + + @Deprecated + public void readLegacyUid(File file, boolean onlyTags) throws IOException { + final AtomicFile inputFile = new AtomicFile(file); + + DataInputStream in = null; + try { + in = new DataInputStream(new BufferedInputStream(inputFile.openRead())); + + // verify file magic header intact + final int magic = in.readInt(); + if (magic != FILE_MAGIC) { + throw new ProtocolException("unexpected magic: " + magic); + } + + final int version = in.readInt(); + switch (version) { + case VERSION_UID_INIT: { + // uid := size *(UID NetworkStatsHistory) + + // drop this data version, since we don't have a good + // mapping into NetworkIdentitySet. + break; + } + case VERSION_UID_WITH_IDENT: { + // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory)) + + // drop this data version, since this version only existed + // for a short time. + break; + } + case VERSION_UID_WITH_TAG: + case VERSION_UID_WITH_SET: { + // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) + final int identSize = in.readInt(); + for (int i = 0; i < identSize; i++) { + final NetworkIdentitySet ident = new NetworkIdentitySet(in); + + final int size = in.readInt(); + for (int j = 0; j < size; j++) { + final int uid = in.readInt(); + final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt() + : SET_DEFAULT; + final int tag = in.readInt(); + + final Key key = new Key(ident, uid, set, tag); + final NetworkStatsHistory history = new NetworkStatsHistory(in); + + if ((tag == TAG_NONE) != onlyTags) { + recordHistory(key, history); + } + } + } + break; + } + default: { + throw new ProtocolException("unexpected version: " + version); + } + } + } catch (FileNotFoundException e) { + // missing stats is okay, probably first boot + } finally { + IoUtils.closeQuietly(in); + } + } + + /** + * Remove any {@link NetworkStatsHistory} attributed to the requested UID, + * moving any {@link NetworkStats#TAG_NONE} series to + * {@link TrafficStats#UID_REMOVED}. + */ + public void removeUid(int uid) { + final ArrayList<Key> knownKeys = Lists.newArrayList(); + knownKeys.addAll(mStats.keySet()); + + // migrate all UID stats into special "removed" bucket + for (Key key : knownKeys) { + if (key.uid == uid) { + // only migrate combined TAG_NONE history + if (key.tag == TAG_NONE) { + final NetworkStatsHistory uidHistory = mStats.get(key); + final NetworkStatsHistory removedHistory = findOrCreateHistory( + key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE); + removedHistory.recordEntireHistory(uidHistory); + } + mStats.remove(key); + mDirty = true; + } + } + } + + private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) { + if (startMillis < mStartMillis) mStartMillis = startMillis; + if (endMillis > mEndMillis) mEndMillis = endMillis; + mTotalBytes += totalBytes; + mDirty = true; + } + + private int estimateBuckets() { + return (int) (Math.min(mEndMillis - mStartMillis, DateUtils.WEEK_IN_MILLIS * 5) + / mBucketDuration); + } + + public void dump(IndentingPrintWriter pw) { + final ArrayList<Key> keys = Lists.newArrayList(); + keys.addAll(mStats.keySet()); + Collections.sort(keys); + + for (Key key : keys) { + pw.print("ident="); pw.print(key.ident.toString()); + pw.print(" uid="); pw.print(key.uid); + pw.print(" set="); pw.print(NetworkStats.setToString(key.set)); + pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag)); + + final NetworkStatsHistory history = mStats.get(key); + pw.increaseIndent(); + history.dump(pw, true); + pw.decreaseIndent(); + } + } + + /** + * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity} + * in the given {@link NetworkIdentitySet}. + */ + private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) { + for (NetworkIdentity ident : identSet) { + if (template.matches(ident)) { + return true; + } + } + return false; + } + + private static class Key implements Comparable<Key> { + public final NetworkIdentitySet ident; + public final int uid; + public final int set; + public final int tag; + + private final int hashCode; + + public Key(NetworkIdentitySet ident, int uid, int set, int tag) { + this.ident = ident; + this.uid = uid; + this.set = set; + this.tag = tag; + hashCode = Objects.hashCode(ident, uid, set, tag); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Key) { + final Key key = (Key) obj; + return uid == key.uid && set == key.set && tag == key.tag + && Objects.equal(ident, key.ident); + } + return false; + } + + /** {@inheritDoc} */ + public int compareTo(Key another) { + return Integer.compare(uid, another.uid); + } + } +} diff --git a/services/java/com/android/server/net/NetworkStatsRecorder.java b/services/java/com/android/server/net/NetworkStatsRecorder.java new file mode 100644 index 0000000..e7ba358 --- /dev/null +++ b/services/java/com/android/server/net/NetworkStatsRecorder.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2012 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.net; + +import static android.net.NetworkStats.TAG_NONE; +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.net.NetworkStats; +import android.net.NetworkStats.NonMonotonicObserver; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; +import android.net.TrafficStats; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.util.FileRotator; +import com.android.internal.util.IndentingPrintWriter; +import com.google.android.collect.Sets; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.ref.WeakReference; +import java.util.HashSet; +import java.util.Map; + +/** + * Logic to record deltas between periodic {@link NetworkStats} snapshots into + * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}. + * Keeps pending changes in memory until they pass a specific threshold, in + * bytes. Uses {@link FileRotator} for persistence logic. + * <p> + * Not inherently thread safe. + */ +public class NetworkStatsRecorder { + private static final String TAG = "NetworkStatsRecorder"; + private static final boolean LOGD = true; + + private final FileRotator mRotator; + private final NonMonotonicObserver<String> mObserver; + private final String mCookie; + + private final long mBucketDuration; + private final long mPersistThresholdBytes; + private final boolean mOnlyTags; + + private NetworkStats mLastSnapshot; + + private final NetworkStatsCollection mPending; + private final NetworkStatsCollection mSinceBoot; + + private final CombiningRewriter mPendingRewriter; + + private WeakReference<NetworkStatsCollection> mComplete; + + public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer, + String cookie, long bucketDuration, long persistThresholdBytes, boolean onlyTags) { + mRotator = checkNotNull(rotator, "missing FileRotator"); + mObserver = checkNotNull(observer, "missing NonMonotonicObserver"); + mCookie = cookie; + + mBucketDuration = bucketDuration; + mPersistThresholdBytes = persistThresholdBytes; + mOnlyTags = onlyTags; + + mPending = new NetworkStatsCollection(bucketDuration); + mSinceBoot = new NetworkStatsCollection(bucketDuration); + + mPendingRewriter = new CombiningRewriter(mPending); + } + + public void resetLocked() { + mLastSnapshot = null; + mPending.reset(); + mSinceBoot.reset(); + mComplete.clear(); + } + + public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) { + return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null); + } + + /** + * Load complete history represented by {@link FileRotator}. Caches + * internally as a {@link WeakReference}, and updated with future + * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long + * as reference is valid. + */ + public NetworkStatsCollection getOrLoadCompleteLocked() { + NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; + if (complete == null) { + if (LOGD) Slog.d(TAG, "getOrLoadCompleteLocked() reading from disk for " + mCookie); + try { + complete = new NetworkStatsCollection(mBucketDuration); + mRotator.readMatching(complete, Long.MIN_VALUE, Long.MAX_VALUE); + complete.recordCollection(mPending); + mComplete = new WeakReference<NetworkStatsCollection>(complete); + } catch (IOException e) { + Log.wtf(TAG, "problem completely reading network stats", e); + } + } + return complete; + } + + /** + * Record any delta that occurred since last {@link NetworkStats} snapshot, + * using the given {@link Map} to identify network interfaces. First + * snapshot is considered bootstrap, and is not counted as delta. + */ + public void recordSnapshotLocked(NetworkStats snapshot, + Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) { + final HashSet<String> unknownIfaces = Sets.newHashSet(); + + // assume first snapshot is bootstrap and don't record + if (mLastSnapshot == null) { + mLastSnapshot = snapshot; + return; + } + + final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; + + final NetworkStats delta = NetworkStats.subtract( + snapshot, mLastSnapshot, mObserver, mCookie); + final long end = currentTimeMillis; + final long start = end - delta.getElapsedRealtime(); + + NetworkStats.Entry entry = null; + for (int i = 0; i < delta.size(); i++) { + entry = delta.getValues(i, entry); + final NetworkIdentitySet ident = ifaceIdent.get(entry.iface); + if (ident == null) { + unknownIfaces.add(entry.iface); + continue; + } + + // skip when no delta occured + if (entry.isEmpty()) continue; + + // only record tag data when requested + if ((entry.tag == TAG_NONE) != mOnlyTags) { + mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry); + + // also record against boot stats when present + if (mSinceBoot != null) { + mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry); + } + + // also record against complete dataset when present + if (complete != null) { + complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry); + } + } + } + + mLastSnapshot = snapshot; + + if (LOGD && unknownIfaces.size() > 0) { + Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats"); + } + } + + /** + * Consider persisting any pending deltas, if they are beyond + * {@link #mPersistThresholdBytes}. + */ + public void maybePersistLocked(long currentTimeMillis) { + final long pendingBytes = mPending.getTotalBytes(); + if (pendingBytes >= mPersistThresholdBytes) { + forcePersistLocked(currentTimeMillis); + } else { + mRotator.maybeRotate(currentTimeMillis); + } + } + + /** + * Force persisting any pending deltas. + */ + public void forcePersistLocked(long currentTimeMillis) { + if (mPending.isDirty()) { + if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie); + try { + mRotator.rewriteActive(mPendingRewriter, currentTimeMillis); + mRotator.maybeRotate(currentTimeMillis); + mPending.reset(); + } catch (IOException e) { + Log.wtf(TAG, "problem persisting pending stats", e); + } + } + } + + /** + * Remove the given UID from all {@link FileRotator} history, migrating it + * to {@link TrafficStats#UID_REMOVED}. + */ + public void removeUidLocked(int uid) { + try { + // process all existing data to migrate uid + mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uid)); + } catch (IOException e) { + Log.wtf(TAG, "problem removing UID " + uid, e); + } + + // clear UID from current stats snapshot + if (mLastSnapshot != null) { + mLastSnapshot = mLastSnapshot.withoutUid(uid); + } + } + + /** + * Rewriter that will combine current {@link NetworkStatsCollection} values + * with anything read from disk, and write combined set to disk. Clears the + * original {@link NetworkStatsCollection} when finished writing. + */ + private static class CombiningRewriter implements FileRotator.Rewriter { + private final NetworkStatsCollection mCollection; + + public CombiningRewriter(NetworkStatsCollection collection) { + mCollection = checkNotNull(collection, "missing NetworkStatsCollection"); + } + + /** {@inheritDoc} */ + public void reset() { + // ignored + } + + /** {@inheritDoc} */ + public void read(InputStream in) throws IOException { + mCollection.read(in); + } + + /** {@inheritDoc} */ + public boolean shouldWrite() { + return true; + } + + /** {@inheritDoc} */ + public void write(OutputStream out) throws IOException { + mCollection.write(new DataOutputStream(out)); + mCollection.reset(); + } + } + + /** + * Rewriter that will remove any {@link NetworkStatsHistory} attributed to + * the requested UID, only writing data back when modified. + */ + public static class RemoveUidRewriter implements FileRotator.Rewriter { + private final NetworkStatsCollection mTemp; + private final int mUid; + + public RemoveUidRewriter(long bucketDuration, int uid) { + mTemp = new NetworkStatsCollection(bucketDuration); + mUid = uid; + } + + /** {@inheritDoc} */ + public void reset() { + mTemp.reset(); + } + + /** {@inheritDoc} */ + public void read(InputStream in) throws IOException { + mTemp.read(in); + mTemp.clearDirty(); + mTemp.removeUid(mUid); + } + + /** {@inheritDoc} */ + public boolean shouldWrite() { + return mTemp.isDirty(); + } + + /** {@inheritDoc} */ + public void write(OutputStream out) throws IOException { + mTemp.write(new DataOutputStream(out)); + } + } + + public void importLegacyNetworkLocked(File file) throws IOException { + // legacy file still exists; start empty to avoid double importing + mRotator.deleteAll(); + + final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration); + collection.readLegacyNetwork(file); + + final long startMillis = collection.getStartMillis(); + final long endMillis = collection.getEndMillis(); + + if (!collection.isEmpty()) { + // process legacy data, creating active file at starting time, then + // using end time to possibly trigger rotation. + mRotator.rewriteActive(new CombiningRewriter(collection), startMillis); + mRotator.maybeRotate(endMillis); + } + } + + public void importLegacyUidLocked(File file) throws IOException { + // legacy file still exists; start empty to avoid double importing + mRotator.deleteAll(); + + final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration); + collection.readLegacyUid(file, mOnlyTags); + + final long startMillis = collection.getStartMillis(); + final long endMillis = collection.getEndMillis(); + + if (!collection.isEmpty()) { + // process legacy data, creating active file at starting time, then + // using end time to possibly trigger rotation. + mRotator.rewriteActive(new CombiningRewriter(collection), startMillis); + mRotator.maybeRotate(endMillis); + } + } + + public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) { + pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes()); + if (fullHistory) { + pw.println("Complete history:"); + getOrLoadCompleteLocked().dump(pw); + } else { + pw.println("History since boot:"); + mSinceBoot.dump(pw); + } + } +} diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index f660520..c9b67fc 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -34,14 +34,19 @@ import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.NetworkTemplate.buildTemplateWifi; -import static android.net.TrafficStats.UID_REMOVED; -import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION; -import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY; -import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD; +import static android.net.TrafficStats.MB_IN_BYTES; +import static android.provider.Settings.Secure.NETSTATS_DEV_BUCKET_DURATION; +import static android.provider.Settings.Secure.NETSTATS_DEV_DELETE_AGE; +import static android.provider.Settings.Secure.NETSTATS_DEV_PERSIST_BYTES; +import static android.provider.Settings.Secure.NETSTATS_DEV_ROTATE_AGE; +import static android.provider.Settings.Secure.NETSTATS_GLOBAL_ALERT_BYTES; import static android.provider.Settings.Secure.NETSTATS_POLL_INTERVAL; -import static android.provider.Settings.Secure.NETSTATS_TAG_MAX_HISTORY; +import static android.provider.Settings.Secure.NETSTATS_SAMPLE_ENABLED; +import static android.provider.Settings.Secure.NETSTATS_TIME_CACHE_MAX_AGE; import static android.provider.Settings.Secure.NETSTATS_UID_BUCKET_DURATION; -import static android.provider.Settings.Secure.NETSTATS_UID_MAX_HISTORY; +import static android.provider.Settings.Secure.NETSTATS_UID_DELETE_AGE; +import static android.provider.Settings.Secure.NETSTATS_UID_PERSIST_BYTES; +import static android.provider.Settings.Secure.NETSTATS_UID_ROTATE_AGE; import static android.telephony.PhoneStateListener.LISTEN_DATA_CONNECTION_STATE; import static android.telephony.PhoneStateListener.LISTEN_NONE; import static android.text.format.DateUtils.DAY_IN_MILLIS; @@ -61,19 +66,18 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; import android.net.INetworkStatsService; +import android.net.LinkProperties; import android.net.NetworkIdentity; import android.net.NetworkInfo; import android.net.NetworkState; import android.net.NetworkStats; -import android.net.NetworkStats.NonMonotonicException; +import android.net.NetworkStats.NonMonotonicObserver; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; +import android.net.TrafficStats; import android.os.Binder; import android.os.DropBoxManager; import android.os.Environment; @@ -94,32 +98,18 @@ import android.util.Slog; import android.util.SparseIntArray; import android.util.TrustedTime; -import com.android.internal.os.AtomicFile; -import com.android.internal.util.Objects; +import com.android.internal.util.FileRotator; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.EventLogTags; import com.android.server.connectivity.Tethering; -import com.google.android.collect.Lists; import com.google.android.collect.Maps; -import com.google.android.collect.Sets; -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.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; -import java.net.ProtocolException; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Random; - -import libcore.io.IoUtils; /** * Collect and persist detailed network statistics, and provide this data to @@ -127,19 +117,12 @@ import libcore.io.IoUtils; */ public class NetworkStatsService extends INetworkStatsService.Stub { private static final String TAG = "NetworkStats"; - private static final boolean LOGD = false; - private static final boolean LOGV = false; - - /** File header magic number: "ANET" */ - private static final int FILE_MAGIC = 0x414E4554; - private static final int VERSION_NETWORK_INIT = 1; - private static final int VERSION_UID_INIT = 1; - private static final int VERSION_UID_WITH_IDENT = 2; - private static final int VERSION_UID_WITH_TAG = 3; - private static final int VERSION_UID_WITH_SET = 4; + private static final boolean LOGD = true; + private static final boolean LOGV = true; private static final int MSG_PERFORM_POLL = 1; private static final int MSG_UPDATE_IFACES = 2; + private static final int MSG_REGISTER_GLOBAL_ALERT = 3; /** Flags to control detail level of poll event. */ private static final int FLAG_PERSIST_NETWORK = 0x1; @@ -147,9 +130,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID; private static final int FLAG_PERSIST_FORCE = 0x100; - /** Sample recent usage after each poll event. */ - private static final boolean ENABLE_SAMPLE_AFTER_POLL = true; - private static final String TAG_NETSTATS_ERROR = "netstats_error"; private final Context mContext; @@ -159,10 +139,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private final TelephonyManager mTeleManager; private final NetworkStatsSettings mSettings; + private final File mSystemDir; + private final File mBaseDir; + private final PowerManager.WakeLock mWakeLock; private IConnectivityManager mConnManager; - private DropBoxManager mDropBox; // @VisibleForTesting public static final String ACTION_NETWORK_STATS_POLL = @@ -172,71 +154,72 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private PendingIntent mPollIntent; - // TODO: trim empty history objects entirely - - private static final long KB_IN_BYTES = 1024; - private static final long MB_IN_BYTES = 1024 * KB_IN_BYTES; - private static final long GB_IN_BYTES = 1024 * MB_IN_BYTES; + private static final String PREFIX_DEV = "dev"; + private static final String PREFIX_UID = "uid"; + private static final String PREFIX_UID_TAG = "uid_tag"; /** * Settings that can be changed externally. */ public interface NetworkStatsSettings { public long getPollInterval(); - public long getPersistThreshold(); - public long getNetworkBucketDuration(); - public long getNetworkMaxHistory(); - public long getUidBucketDuration(); - public long getUidMaxHistory(); - public long getTagMaxHistory(); public long getTimeCacheMaxAge(); + public long getGlobalAlertBytes(); + public boolean getSampleEnabled(); + + public static class Config { + public final long bucketDuration; + public final long persistBytes; + public final long rotateAgeMillis; + public final long deleteAgeMillis; + + public Config(long bucketDuration, long persistBytes, long rotateAgeMillis, + long deleteAgeMillis) { + this.bucketDuration = bucketDuration; + this.persistBytes = persistBytes; + this.rotateAgeMillis = rotateAgeMillis; + this.deleteAgeMillis = deleteAgeMillis; + } + } + + public Config getDevConfig(); + public Config getUidConfig(); + public Config getUidTagConfig(); } private final Object mStatsLock = new Object(); /** Set of currently active ifaces. */ private HashMap<String, NetworkIdentitySet> mActiveIfaces = Maps.newHashMap(); - /** Set of historical {@code dev} stats for known networks. */ - private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkDevStats = Maps.newHashMap(); - /** Set of historical {@code xtables} stats for known networks. */ - private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkXtStats = Maps.newHashMap(); - /** Set of historical {@code xtables} stats for known UIDs. */ - private HashMap<UidStatsKey, NetworkStatsHistory> mUidStats = Maps.newHashMap(); - - /** Flag if {@link #mNetworkDevStats} have been loaded from disk. */ - private boolean mNetworkStatsLoaded = false; - /** Flag if {@link #mUidStats} have been loaded from disk. */ - private boolean mUidStatsLoaded = false; - - private NetworkStats mLastPollNetworkDevSnapshot; - private NetworkStats mLastPollNetworkXtSnapshot; - private NetworkStats mLastPollUidSnapshot; - private NetworkStats mLastPollOperationsSnapshot; - - private NetworkStats mLastPersistNetworkDevSnapshot; - private NetworkStats mLastPersistNetworkXtSnapshot; - private NetworkStats mLastPersistUidSnapshot; + /** Current default active iface. */ + private String mActiveIface; + + private final DropBoxNonMonotonicObserver mNonMonotonicObserver = + new DropBoxNonMonotonicObserver(); + + private NetworkStatsRecorder mDevRecorder; + private NetworkStatsRecorder mUidRecorder; + private NetworkStatsRecorder mUidTagRecorder; + + /** Cached {@link #mDevRecorder} stats. */ + private NetworkStatsCollection mDevStatsCached; /** Current counter sets for each UID. */ private SparseIntArray mActiveUidCounterSet = new SparseIntArray(); /** Data layer operation counters for splicing into other structures. */ - private NetworkStats mOperations = new NetworkStats(0L, 10); + private NetworkStats mUidOperations = new NetworkStats(0L, 10); private final HandlerThread mHandlerThread; private final Handler mHandler; - private final AtomicFile mNetworkDevFile; - private final AtomicFile mNetworkXtFile; - private final AtomicFile mUidFile; - public NetworkStatsService( Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) { this(context, networkManager, alarmManager, NtpTrustedTime.getInstance(context), - getSystemDir(), new DefaultNetworkStatsSettings(context)); + getDefaultSystemDir(), new DefaultNetworkStatsSettings(context)); } - private static File getSystemDir() { + private static File getDefaultSystemDir() { return new File(Environment.getDataDirectory(), "system"); } @@ -258,9 +241,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper(), mHandlerCallback); - mNetworkDevFile = new AtomicFile(new File(systemDir, "netstats.bin")); - mNetworkXtFile = new AtomicFile(new File(systemDir, "netstats_xt.bin")); - mUidFile = new AtomicFile(new File(systemDir, "netstats_uid.bin")); + mSystemDir = checkNotNull(systemDir); + mBaseDir = new File(systemDir, "netstats"); + mBaseDir.mkdirs(); } public void bindConnectivityManager(IConnectivityManager connManager) { @@ -268,17 +251,27 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } public void systemReady() { + if (!isBandwidthControlEnabled()) { + Slog.w(TAG, "bandwidth controls disabled, unable to track stats"); + return; + } + + // create data recorders along with historical rotators + mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false); + mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false); + mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true); + synchronized (mStatsLock) { + // upgrade any legacy stats, migrating them to rotated files + maybeUpgradeLegacyStatsLocked(); + // read historical network stats from disk, since policy service - // might need them right away. we delay loading detailed UID stats - // until actually needed. - readNetworkDevStatsLocked(); - readNetworkXtStatsLocked(); - mNetworkStatsLoaded = true; - } + // might need them right away. + mDevStatsCached = mDevRecorder.getOrLoadCompleteLocked(); - // bootstrap initial stats to prevent double-counting later - bootstrapStats(); + // bootstrap initial stats to prevent double-counting later + bootstrapStatsLocked(); + } // watch for network interfaces to be claimed final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION_IMMEDIATE); @@ -312,8 +305,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { registerPollAlarmLocked(); registerGlobalAlert(); + } - mDropBox = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE); + private NetworkStatsRecorder buildRecorder( + String prefix, NetworkStatsSettings.Config config, boolean includeTags) { + return new NetworkStatsRecorder( + new FileRotator(mBaseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis), + mNonMonotonicObserver, prefix, config.bucketDuration, config.persistBytes, + includeTags); } private void shutdownLocked() { @@ -325,18 +324,44 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mTeleManager.listen(mPhoneListener, LISTEN_NONE); - if (mNetworkStatsLoaded) { - writeNetworkDevStatsLocked(); - writeNetworkXtStatsLocked(); - } - if (mUidStatsLoaded) { - writeUidStatsLocked(); + final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis() + : System.currentTimeMillis(); + + // persist any pending stats + mDevRecorder.forcePersistLocked(currentTime); + mUidRecorder.forcePersistLocked(currentTime); + mUidTagRecorder.forcePersistLocked(currentTime); + + mDevRecorder = null; + mUidRecorder = null; + mUidTagRecorder = null; + + mDevStatsCached = null; + } + + private void maybeUpgradeLegacyStatsLocked() { + File file; + try { + file = new File(mSystemDir, "netstats.bin"); + if (file.exists()) { + mDevRecorder.importLegacyNetworkLocked(file); + file.delete(); + } + + file = new File(mSystemDir, "netstats_xt.bin"); + if (file.exists()) { + file.delete(); + } + + file = new File(mSystemDir, "netstats_uid.bin"); + if (file.exists()) { + mUidRecorder.importLegacyUidLocked(file); + mUidTagRecorder.importLegacyUidLocked(file); + file.delete(); + } + } catch (IOException e) { + Log.wtf(TAG, "problem during legacy upgrade", e); } - mNetworkDevStats.clear(); - mNetworkXtStats.clear(); - mUidStats.clear(); - mNetworkStatsLoaded = false; - mUidStatsLoaded = false; } /** @@ -367,7 +392,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { */ private void registerGlobalAlert() { try { - final long alertBytes = mSettings.getPersistThreshold(); + final long alertBytes = mSettings.getGlobalAlertBytes(); mNetworkManager.setGlobalAlert(alertBytes); } catch (IllegalStateException e) { Slog.w(TAG, "problem registering for global alert: " + e); @@ -378,161 +403,39 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) { - mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - return getHistoryForNetworkDev(template, fields); + return mDevStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields); } - private NetworkStatsHistory getHistoryForNetworkDev(NetworkTemplate template, int fields) { - return getHistoryForNetwork(template, fields, mNetworkDevStats); - } - - private NetworkStatsHistory getHistoryForNetworkXt(NetworkTemplate template, int fields) { - return getHistoryForNetwork(template, fields, mNetworkXtStats); - } - - private NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields, - HashMap<NetworkIdentitySet, NetworkStatsHistory> source) { - synchronized (mStatsLock) { - // combine all interfaces that match template - final NetworkStatsHistory combined = new NetworkStatsHistory( - mSettings.getNetworkBucketDuration(), estimateNetworkBuckets(), fields); - for (NetworkIdentitySet ident : source.keySet()) { - if (templateMatches(template, ident)) { - final NetworkStatsHistory history = source.get(ident); - if (history != null) { - combined.recordEntireHistory(history); - } - } - } - return combined; - } + @Override + public NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end) { + return mDevStatsCached.getSummary(template, start, end); } @Override public NetworkStatsHistory getHistoryForUid( NetworkTemplate template, int uid, int set, int tag, int fields) { - mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - - synchronized (mStatsLock) { - ensureUidStatsLoadedLocked(); - - // combine all interfaces that match template - final NetworkStatsHistory combined = new NetworkStatsHistory( - mSettings.getUidBucketDuration(), estimateUidBuckets(), fields); - for (UidStatsKey key : mUidStats.keySet()) { - final boolean setMatches = set == SET_ALL || key.set == set; - if (templateMatches(template, key.ident) && key.uid == uid && setMatches - && key.tag == tag) { - final NetworkStatsHistory history = mUidStats.get(key); - combined.recordEntireHistory(history); - } - } - - return combined; - } - } - - @Override - public NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end) { - mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - return getSummaryForNetworkDev(template, start, end); - } - - private NetworkStats getSummaryForNetworkDev(NetworkTemplate template, long start, long end) { - return getSummaryForNetwork(template, start, end, mNetworkDevStats); - } - - private NetworkStats getSummaryForNetworkXt(NetworkTemplate template, long start, long end) { - return getSummaryForNetwork(template, start, end, mNetworkXtStats); - } - - private NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end, - HashMap<NetworkIdentitySet, NetworkStatsHistory> source) { - synchronized (mStatsLock) { - // use system clock to be externally consistent - final long now = System.currentTimeMillis(); - - final NetworkStats stats = new NetworkStats(end - start, 1); - final NetworkStats.Entry entry = new NetworkStats.Entry(); - NetworkStatsHistory.Entry historyEntry = null; - - // combine total from all interfaces that match template - for (NetworkIdentitySet ident : source.keySet()) { - if (templateMatches(template, ident)) { - final NetworkStatsHistory history = source.get(ident); - historyEntry = history.getValues(start, end, now, historyEntry); - - entry.iface = IFACE_ALL; - entry.uid = UID_ALL; - entry.tag = TAG_NONE; - entry.rxBytes = historyEntry.rxBytes; - entry.rxPackets = historyEntry.rxPackets; - entry.txBytes = historyEntry.txBytes; - entry.txPackets = historyEntry.txPackets; - - stats.combineValues(entry); - } - } - - return stats; - } - } - - private long getHistoryStartLocked( - NetworkTemplate template, HashMap<NetworkIdentitySet, NetworkStatsHistory> source) { - long start = Long.MAX_VALUE; - for (NetworkIdentitySet ident : source.keySet()) { - if (templateMatches(template, ident)) { - final NetworkStatsHistory history = source.get(ident); - start = Math.min(start, history.getStart()); - } + // TODO: transition to stats sessions to avoid WeakReferences + if (tag == TAG_NONE) { + return mUidRecorder.getOrLoadCompleteLocked().getHistory( + template, uid, set, tag, fields); + } else { + return mUidTagRecorder.getOrLoadCompleteLocked().getHistory( + template, uid, set, tag, fields); } - return start; } @Override public NetworkStats getSummaryForAllUid( NetworkTemplate template, long start, long end, boolean includeTags) { - mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - - synchronized (mStatsLock) { - ensureUidStatsLoadedLocked(); - - // use system clock to be externally consistent - final long now = System.currentTimeMillis(); - - final NetworkStats stats = new NetworkStats(end - start, 24); - final NetworkStats.Entry entry = new NetworkStats.Entry(); - NetworkStatsHistory.Entry historyEntry = null; - - for (UidStatsKey key : mUidStats.keySet()) { - if (templateMatches(template, key.ident)) { - // always include summary under TAG_NONE, and include - // other tags when requested. - if (key.tag == TAG_NONE || includeTags) { - final NetworkStatsHistory history = mUidStats.get(key); - historyEntry = history.getValues(start, end, now, historyEntry); - - entry.iface = IFACE_ALL; - entry.uid = key.uid; - entry.set = key.set; - entry.tag = key.tag; - entry.rxBytes = historyEntry.rxBytes; - entry.rxPackets = historyEntry.rxPackets; - entry.txBytes = historyEntry.txBytes; - entry.txPackets = historyEntry.txPackets; - entry.operations = historyEntry.operations; - - if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0 - || entry.txPackets > 0 || entry.operations > 0) { - stats.combineValues(entry); - } - } - } - } - - return stats; - } + // TODO: transition to stats sessions to avoid WeakReferences + final NetworkStats stats = mUidRecorder.getOrLoadCompleteLocked().getSummary( + template, start, end); + if (includeTags) { + final NetworkStats tagStats = mUidTagRecorder.getOrLoadCompleteLocked().getSummary( + template, start, end); + stats.combineAllValues(tagStats); + } + return stats; } @Override @@ -543,7 +446,14 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // TODO: switch to data layer stats once kernel exports // for now, read network layer stats and flatten across all ifaces - final NetworkStats networkLayer = mNetworkManager.getNetworkStatsUidDetail(uid); + final long token = Binder.clearCallingIdentity(); + final NetworkStats networkLayer; + try { + networkLayer = mNetworkManager.getNetworkStatsUidDetail(uid); + } finally { + Binder.restoreCallingIdentity(token); + } + final NetworkStats dataLayer = new NetworkStats( networkLayer.getElapsedRealtime(), networkLayer.size()); @@ -555,7 +465,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } // splice in operation counts - dataLayer.spliceOperationsFrom(mOperations); + dataLayer.spliceOperationsFrom(mUidOperations); return dataLayer; } @@ -574,8 +484,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { synchronized (mStatsLock) { final int set = mActiveUidCounterSet.get(uid, SET_DEFAULT); - mOperations.combineValues(IFACE_ALL, uid, set, tag, 0L, 0L, 0L, 0L, operationCount); - mOperations.combineValues(IFACE_ALL, uid, set, TAG_NONE, 0L, 0L, 0L, 0L, operationCount); + mUidOperations.combineValues( + mActiveIface, uid, set, tag, 0L, 0L, 0L, 0L, operationCount); + mUidOperations.combineValues( + mActiveIface, uid, set, TAG_NONE, 0L, 0L, 0L, 0L, operationCount); } } @@ -596,7 +508,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public void forceUpdate() { mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - performPoll(FLAG_PERSIST_ALL); + + final long token = Binder.clearCallingIdentity(); + try { + performPoll(FLAG_PERSIST_ALL); + } finally { + Binder.restoreCallingIdentity(token); + } } /** @@ -680,7 +598,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mHandler.obtainMessage(MSG_PERFORM_POLL, flags, 0).sendToTarget(); // re-arm global alert for next update - registerGlobalAlert(); + mHandler.obtainMessage(MSG_REGISTER_GLOBAL_ALERT).sendToTarget(); } } }; @@ -743,13 +661,17 @@ public class NetworkStatsService extends INetworkStatsService.Stub { performPollLocked(FLAG_PERSIST_NETWORK); final NetworkState[] states; + final LinkProperties activeLink; try { states = mConnManager.getAllNetworkState(); + activeLink = mConnManager.getActiveLinkProperties(); } catch (RemoteException e) { // ignored; service lives in system_server return; } + mActiveIface = activeLink != null ? activeLink.getInterfaceName() : null; + // rebuild active interfaces based on connected networks mActiveIfaces.clear(); @@ -773,12 +695,20 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * Bootstrap initial stats snapshot, usually during {@link #systemReady()} * so we have baseline values without double-counting. */ - private void bootstrapStats() { + private void bootstrapStatsLocked() { + final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis() + : System.currentTimeMillis(); + try { - mLastPollUidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL); - mLastPollNetworkDevSnapshot = mNetworkManager.getNetworkStatsSummary(); - mLastPollNetworkXtSnapshot = computeNetworkXtSnapshotFromUid(mLastPollUidSnapshot); - mLastPollOperationsSnapshot = new NetworkStats(0L, 0); + // snapshot and record current counters; read UID stats first to + // avoid overcounting dev stats. + final NetworkStats uidSnapshot = getNetworkStatsUidDetail(); + final NetworkStats devSnapshot = getNetworkStatsSummary(); + + mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime); + mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime); + mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime); + } catch (IllegalStateException e) { Slog.w(TAG, "problem reading network stats: " + e); } catch (RemoteException e) { @@ -818,27 +748,16 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // TODO: consider marking "untrusted" times in historical stats final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis() : System.currentTimeMillis(); - final long threshold = mSettings.getPersistThreshold(); - final NetworkStats uidSnapshot; - final NetworkStats networkXtSnapshot; - final NetworkStats networkDevSnapshot; try { - // collect any tethering stats - final NetworkStats tetherSnapshot = getNetworkStatsTethering(); - - // record uid stats, folding in tethering stats - uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL); - uidSnapshot.combineAllValues(tetherSnapshot); - performUidPollLocked(uidSnapshot, currentTime); - - // record dev network stats - networkDevSnapshot = mNetworkManager.getNetworkStatsSummary(); - performNetworkDevPollLocked(networkDevSnapshot, currentTime); + // snapshot and record current counters; read UID stats first to + // avoid overcounting dev stats. + final NetworkStats uidSnapshot = getNetworkStatsUidDetail(); + final NetworkStats devSnapshot = getNetworkStatsSummary(); - // record xt network stats - networkXtSnapshot = computeNetworkXtSnapshotFromUid(uidSnapshot); - performNetworkXtPollLocked(networkXtSnapshot, currentTime); + mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime); + mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime); + mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime); } catch (IllegalStateException e) { Log.wtf(TAG, "problem reading network stats", e); @@ -848,26 +767,19 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return; } - // persist when enough network data has occurred - final long persistNetworkDevDelta = computeStatsDelta( - mLastPersistNetworkDevSnapshot, networkDevSnapshot, true, "devp").getTotalBytes(); - final long persistNetworkXtDelta = computeStatsDelta( - mLastPersistNetworkXtSnapshot, networkXtSnapshot, true, "xtp").getTotalBytes(); - final boolean networkOverThreshold = persistNetworkDevDelta > threshold - || persistNetworkXtDelta > threshold; - if (persistForce || (persistNetwork && networkOverThreshold)) { - writeNetworkDevStatsLocked(); - writeNetworkXtStatsLocked(); - mLastPersistNetworkDevSnapshot = networkDevSnapshot; - mLastPersistNetworkXtSnapshot = networkXtSnapshot; - } - - // persist when enough uid data has occurred - final long persistUidDelta = computeStatsDelta( - mLastPersistUidSnapshot, uidSnapshot, true, "uidp").getTotalBytes(); - if (persistForce || (persistUid && persistUidDelta > threshold)) { - writeUidStatsLocked(); - mLastPersistUidSnapshot = uidSnapshot; + // persist any pending data depending on requested flags + if (persistForce) { + mDevRecorder.forcePersistLocked(currentTime); + mUidRecorder.forcePersistLocked(currentTime); + mUidTagRecorder.forcePersistLocked(currentTime); + } else { + if (persistNetwork) { + mDevRecorder.maybePersistLocked(currentTime); + } + if (persistUid) { + mUidRecorder.maybePersistLocked(currentTime); + mUidTagRecorder.maybePersistLocked(currentTime); + } } if (LOGV) { @@ -875,9 +787,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { Slog.v(TAG, "performPollLocked() took " + duration + "ms"); } - if (ENABLE_SAMPLE_AFTER_POLL) { + if (mSettings.getSampleEnabled()) { // sample stats after each full poll - performSample(); + performSampleLocked(); } // finally, dispatch updated event to any listeners @@ -887,511 +799,58 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** - * Update {@link #mNetworkDevStats} historical usage. - */ - private void performNetworkDevPollLocked(NetworkStats networkDevSnapshot, long currentTime) { - final HashSet<String> unknownIface = Sets.newHashSet(); - - final NetworkStats delta = computeStatsDelta( - mLastPollNetworkDevSnapshot, networkDevSnapshot, false, "dev"); - final long timeStart = currentTime - delta.getElapsedRealtime(); - - NetworkStats.Entry entry = null; - for (int i = 0; i < delta.size(); i++) { - entry = delta.getValues(i, entry); - final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface); - if (ident == null) { - unknownIface.add(entry.iface); - continue; - } - - final NetworkStatsHistory history = findOrCreateNetworkDevStatsLocked(ident); - history.recordData(timeStart, currentTime, entry); - } - - mLastPollNetworkDevSnapshot = networkDevSnapshot; - - if (LOGD && unknownIface.size() > 0) { - Slog.w(TAG, "unknown dev interfaces " + unknownIface + ", ignoring those stats"); - } - } - - /** - * Update {@link #mNetworkXtStats} historical usage. - */ - private void performNetworkXtPollLocked(NetworkStats networkXtSnapshot, long currentTime) { - final HashSet<String> unknownIface = Sets.newHashSet(); - - final NetworkStats delta = computeStatsDelta( - mLastPollNetworkXtSnapshot, networkXtSnapshot, false, "xt"); - final long timeStart = currentTime - delta.getElapsedRealtime(); - - NetworkStats.Entry entry = null; - for (int i = 0; i < delta.size(); i++) { - entry = delta.getValues(i, entry); - final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface); - if (ident == null) { - unknownIface.add(entry.iface); - continue; - } - - final NetworkStatsHistory history = findOrCreateNetworkXtStatsLocked(ident); - history.recordData(timeStart, currentTime, entry); - } - - mLastPollNetworkXtSnapshot = networkXtSnapshot; - - if (LOGD && unknownIface.size() > 0) { - Slog.w(TAG, "unknown xt interfaces " + unknownIface + ", ignoring those stats"); - } - } - - /** - * Update {@link #mUidStats} historical usage. - */ - private void performUidPollLocked(NetworkStats uidSnapshot, long currentTime) { - ensureUidStatsLoadedLocked(); - - final NetworkStats delta = computeStatsDelta( - mLastPollUidSnapshot, uidSnapshot, false, "uid"); - final NetworkStats operationsDelta = computeStatsDelta( - mLastPollOperationsSnapshot, mOperations, false, "uidop"); - final long timeStart = currentTime - delta.getElapsedRealtime(); - - NetworkStats.Entry entry = null; - NetworkStats.Entry operationsEntry = null; - for (int i = 0; i < delta.size(); i++) { - entry = delta.getValues(i, entry); - final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface); - if (ident == null) { - if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0 - || entry.txPackets > 0) { - Log.w(TAG, "dropping UID delta from unknown iface: " + entry); - } - continue; - } - - // splice in operation counts since last poll - final int j = operationsDelta.findIndex(IFACE_ALL, entry.uid, entry.set, entry.tag); - if (j != -1) { - operationsEntry = operationsDelta.getValues(j, operationsEntry); - entry.operations = operationsEntry.operations; - } - - final NetworkStatsHistory history = findOrCreateUidStatsLocked( - ident, entry.uid, entry.set, entry.tag); - history.recordData(timeStart, currentTime, entry); - } - - mLastPollUidSnapshot = uidSnapshot; - mLastPollOperationsSnapshot = mOperations.clone(); - } - - /** * Sample recent statistics summary into {@link EventLog}. */ - private void performSample() { - final long largestBucketSize = Math.max( - mSettings.getNetworkBucketDuration(), mSettings.getUidBucketDuration()); - - // take sample as atomic buckets - final long now = mTime.hasCache() ? mTime.currentTimeMillis() : System.currentTimeMillis(); - final long end = now - (now % largestBucketSize) + largestBucketSize; - final long start = end - largestBucketSize; - + private void performSampleLocked() { + // TODO: migrate trustedtime fixes to separate binary log events final long trustedTime = mTime.hasCache() ? mTime.currentTimeMillis() : -1; - long devHistoryStart = Long.MAX_VALUE; - NetworkTemplate template = null; - NetworkStats.Entry devTotal = null; - NetworkStats.Entry xtTotal = null; - NetworkStats.Entry uidTotal = null; + NetworkTemplate template; + NetworkStats.Entry devTotal; + NetworkStats.Entry xtTotal; + NetworkStats.Entry uidTotal; // collect mobile sample template = buildTemplateMobileAll(getActiveSubscriberId(mContext)); - devTotal = getSummaryForNetworkDev(template, start, end).getTotal(devTotal); - devHistoryStart = getHistoryStartLocked(template, mNetworkDevStats); - xtTotal = getSummaryForNetworkXt(template, start, end).getTotal(xtTotal); - uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal); + devTotal = mDevRecorder.getTotalSinceBootLocked(template); + xtTotal = new NetworkStats.Entry(); + uidTotal = mUidRecorder.getTotalSinceBootLocked(template); EventLogTags.writeNetstatsMobileSample( devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets, xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets, uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets, - trustedTime, devHistoryStart); + trustedTime); // collect wifi sample template = buildTemplateWifi(); - devTotal = getSummaryForNetworkDev(template, start, end).getTotal(devTotal); - devHistoryStart = getHistoryStartLocked(template, mNetworkDevStats); - xtTotal = getSummaryForNetworkXt(template, start, end).getTotal(xtTotal); - uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal); + devTotal = mDevRecorder.getTotalSinceBootLocked(template); + xtTotal = new NetworkStats.Entry(); + uidTotal = mUidRecorder.getTotalSinceBootLocked(template); + EventLogTags.writeNetstatsWifiSample( devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets, xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets, uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets, - trustedTime, devHistoryStart); + trustedTime); } /** - * Clean up {@link #mUidStats} after UID is removed. + * Clean up {@link #mUidRecorder} after UID is removed. */ private void removeUidLocked(int uid) { - ensureUidStatsLoadedLocked(); - // perform one last poll before removing performPollLocked(FLAG_PERSIST_ALL); - final ArrayList<UidStatsKey> knownKeys = Lists.newArrayList(); - knownKeys.addAll(mUidStats.keySet()); - - // migrate all UID stats into special "removed" bucket - for (UidStatsKey key : knownKeys) { - if (key.uid == uid) { - // only migrate combined TAG_NONE history - if (key.tag == TAG_NONE) { - final NetworkStatsHistory uidHistory = mUidStats.get(key); - final NetworkStatsHistory removedHistory = findOrCreateUidStatsLocked( - key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE); - removedHistory.recordEntireHistory(uidHistory); - } - mUidStats.remove(key); - } - } - - // clear UID from current stats snapshot - if (mLastPollUidSnapshot != null) { - mLastPollUidSnapshot = mLastPollUidSnapshot.withoutUid(uid); - mLastPollNetworkXtSnapshot = computeNetworkXtSnapshotFromUid(mLastPollUidSnapshot); - } + mUidRecorder.removeUidLocked(uid); + mUidTagRecorder.removeUidLocked(uid); // clear kernel stats associated with UID resetKernelUidStats(uid); - - // since this was radical rewrite, push to disk - writeUidStatsLocked(); - } - - private NetworkStatsHistory findOrCreateNetworkXtStatsLocked(NetworkIdentitySet ident) { - return findOrCreateNetworkStatsLocked(ident, mNetworkXtStats); - } - - private NetworkStatsHistory findOrCreateNetworkDevStatsLocked(NetworkIdentitySet ident) { - return findOrCreateNetworkStatsLocked(ident, mNetworkDevStats); - } - - private NetworkStatsHistory findOrCreateNetworkStatsLocked( - NetworkIdentitySet ident, HashMap<NetworkIdentitySet, NetworkStatsHistory> source) { - final NetworkStatsHistory existing = source.get(ident); - - // update when no existing, or when bucket duration changed - final long bucketDuration = mSettings.getNetworkBucketDuration(); - NetworkStatsHistory updated = null; - if (existing == null) { - updated = new NetworkStatsHistory(bucketDuration, 10); - } else if (existing.getBucketDuration() != bucketDuration) { - updated = new NetworkStatsHistory( - bucketDuration, estimateResizeBuckets(existing, bucketDuration)); - updated.recordEntireHistory(existing); - } - - if (updated != null) { - source.put(ident, updated); - return updated; - } else { - return existing; - } - } - - private NetworkStatsHistory findOrCreateUidStatsLocked( - NetworkIdentitySet ident, int uid, int set, int tag) { - ensureUidStatsLoadedLocked(); - - final UidStatsKey key = new UidStatsKey(ident, uid, set, tag); - final NetworkStatsHistory existing = mUidStats.get(key); - - // update when no existing, or when bucket duration changed - final long bucketDuration = mSettings.getUidBucketDuration(); - NetworkStatsHistory updated = null; - if (existing == null) { - updated = new NetworkStatsHistory(bucketDuration, 10); - } else if (existing.getBucketDuration() != bucketDuration) { - updated = new NetworkStatsHistory( - bucketDuration, estimateResizeBuckets(existing, bucketDuration)); - updated.recordEntireHistory(existing); - } - - if (updated != null) { - mUidStats.put(key, updated); - return updated; - } else { - return existing; - } - } - - private void readNetworkDevStatsLocked() { - if (LOGV) Slog.v(TAG, "readNetworkDevStatsLocked()"); - readNetworkStats(mNetworkDevFile, mNetworkDevStats); - } - - private void readNetworkXtStatsLocked() { - if (LOGV) Slog.v(TAG, "readNetworkXtStatsLocked()"); - readNetworkStats(mNetworkXtFile, mNetworkXtStats); - } - - private static void readNetworkStats( - AtomicFile inputFile, HashMap<NetworkIdentitySet, NetworkStatsHistory> output) { - // clear any existing stats and read from disk - output.clear(); - - DataInputStream in = null; - try { - in = new DataInputStream(new BufferedInputStream(inputFile.openRead())); - - // verify file magic header intact - final int magic = in.readInt(); - if (magic != FILE_MAGIC) { - throw new ProtocolException("unexpected magic: " + magic); - } - - final int version = in.readInt(); - switch (version) { - case VERSION_NETWORK_INIT: { - // network := size *(NetworkIdentitySet NetworkStatsHistory) - final int size = in.readInt(); - for (int i = 0; i < size; i++) { - final NetworkIdentitySet ident = new NetworkIdentitySet(in); - final NetworkStatsHistory history = new NetworkStatsHistory(in); - output.put(ident, history); - } - break; - } - default: { - throw new ProtocolException("unexpected version: " + version); - } - } - } catch (FileNotFoundException e) { - // missing stats is okay, probably first boot - } catch (IOException e) { - Log.wtf(TAG, "problem reading network stats", e); - } finally { - IoUtils.closeQuietly(in); - } - } - - private void ensureUidStatsLoadedLocked() { - if (!mUidStatsLoaded) { - readUidStatsLocked(); - mUidStatsLoaded = true; - } - } - - private void readUidStatsLocked() { - if (LOGV) Slog.v(TAG, "readUidStatsLocked()"); - - // clear any existing stats and read from disk - mUidStats.clear(); - - DataInputStream in = null; - try { - in = new DataInputStream(new BufferedInputStream(mUidFile.openRead())); - - // verify file magic header intact - final int magic = in.readInt(); - if (magic != FILE_MAGIC) { - throw new ProtocolException("unexpected magic: " + magic); - } - - final int version = in.readInt(); - switch (version) { - case VERSION_UID_INIT: { - // uid := size *(UID NetworkStatsHistory) - - // drop this data version, since we don't have a good - // mapping into NetworkIdentitySet. - break; - } - case VERSION_UID_WITH_IDENT: { - // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory)) - - // drop this data version, since this version only existed - // for a short time. - break; - } - case VERSION_UID_WITH_TAG: - case VERSION_UID_WITH_SET: { - // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) - final int identSize = in.readInt(); - for (int i = 0; i < identSize; i++) { - final NetworkIdentitySet ident = new NetworkIdentitySet(in); - - final int size = in.readInt(); - for (int j = 0; j < size; j++) { - final int uid = in.readInt(); - final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt() - : SET_DEFAULT; - final int tag = in.readInt(); - - final UidStatsKey key = new UidStatsKey(ident, uid, set, tag); - final NetworkStatsHistory history = new NetworkStatsHistory(in); - mUidStats.put(key, history); - } - } - break; - } - default: { - throw new ProtocolException("unexpected version: " + version); - } - } - } catch (FileNotFoundException e) { - // missing stats is okay, probably first boot - } catch (IOException e) { - Log.wtf(TAG, "problem reading uid stats", e); - } finally { - IoUtils.closeQuietly(in); - } - } - - private void writeNetworkDevStatsLocked() { - if (LOGV) Slog.v(TAG, "writeNetworkDevStatsLocked()"); - writeNetworkStats(mNetworkDevStats, mNetworkDevFile); - } - - private void writeNetworkXtStatsLocked() { - if (LOGV) Slog.v(TAG, "writeNetworkXtStatsLocked()"); - writeNetworkStats(mNetworkXtStats, mNetworkXtFile); - } - - private void writeNetworkStats( - HashMap<NetworkIdentitySet, NetworkStatsHistory> input, AtomicFile outputFile) { - // TODO: consider duplicating stats and releasing lock while writing - - // trim any history beyond max - if (mTime.hasCache()) { - final long systemCurrentTime = System.currentTimeMillis(); - final long trustedCurrentTime = mTime.currentTimeMillis(); - - final long currentTime = Math.min(systemCurrentTime, trustedCurrentTime); - final long maxHistory = mSettings.getNetworkMaxHistory(); - - for (NetworkStatsHistory history : input.values()) { - final int beforeSize = history.size(); - history.removeBucketsBefore(currentTime - maxHistory); - final int afterSize = history.size(); - - if (beforeSize > 24 && afterSize < beforeSize / 2) { - // yikes, dropping more than half of significant history - final StringBuilder builder = new StringBuilder(); - builder.append("yikes, dropping more than half of history").append('\n'); - builder.append("systemCurrentTime=").append(systemCurrentTime).append('\n'); - builder.append("trustedCurrentTime=").append(trustedCurrentTime).append('\n'); - builder.append("maxHistory=").append(maxHistory).append('\n'); - builder.append("beforeSize=").append(beforeSize).append('\n'); - builder.append("afterSize=").append(afterSize).append('\n'); - mDropBox.addText(TAG_NETSTATS_ERROR, builder.toString()); - } - } - } - - FileOutputStream fos = null; - try { - fos = outputFile.startWrite(); - final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos)); - - out.writeInt(FILE_MAGIC); - out.writeInt(VERSION_NETWORK_INIT); - - out.writeInt(input.size()); - for (NetworkIdentitySet ident : input.keySet()) { - final NetworkStatsHistory history = input.get(ident); - ident.writeToStream(out); - history.writeToStream(out); - } - - out.flush(); - outputFile.finishWrite(fos); - } catch (IOException e) { - Log.wtf(TAG, "problem writing stats", e); - if (fos != null) { - outputFile.failWrite(fos); - } - } - } - - private void writeUidStatsLocked() { - if (LOGV) Slog.v(TAG, "writeUidStatsLocked()"); - - if (!mUidStatsLoaded) { - Slog.w(TAG, "asked to write UID stats when not loaded; skipping"); - return; - } - - // TODO: consider duplicating stats and releasing lock while writing - - // trim any history beyond max - if (mTime.hasCache()) { - final long currentTime = Math.min( - System.currentTimeMillis(), mTime.currentTimeMillis()); - final long maxUidHistory = mSettings.getUidMaxHistory(); - final long maxTagHistory = mSettings.getTagMaxHistory(); - for (UidStatsKey key : mUidStats.keySet()) { - final NetworkStatsHistory history = mUidStats.get(key); - - // detailed tags are trimmed sooner than summary in TAG_NONE - if (key.tag == TAG_NONE) { - history.removeBucketsBefore(currentTime - maxUidHistory); - } else { - history.removeBucketsBefore(currentTime - maxTagHistory); - } - } - } - - // build UidStatsKey lists grouped by ident - final HashMap<NetworkIdentitySet, ArrayList<UidStatsKey>> keysByIdent = Maps.newHashMap(); - for (UidStatsKey key : mUidStats.keySet()) { - ArrayList<UidStatsKey> keys = keysByIdent.get(key.ident); - if (keys == null) { - keys = Lists.newArrayList(); - keysByIdent.put(key.ident, keys); - } - keys.add(key); - } - - FileOutputStream fos = null; - try { - fos = mUidFile.startWrite(); - final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos)); - - out.writeInt(FILE_MAGIC); - out.writeInt(VERSION_UID_WITH_SET); - - out.writeInt(keysByIdent.size()); - for (NetworkIdentitySet ident : keysByIdent.keySet()) { - final ArrayList<UidStatsKey> keys = keysByIdent.get(ident); - ident.writeToStream(out); - - out.writeInt(keys.size()); - for (UidStatsKey key : keys) { - final NetworkStatsHistory history = mUidStats.get(key); - out.writeInt(key.uid); - out.writeInt(key.set); - out.writeInt(key.tag); - history.writeToStream(out); - } - } - - out.flush(); - mUidFile.finishWrite(fos); - } catch (IOException e) { - Log.wtf(TAG, "problem writing stats", e); - if (fos != null) { - mUidFile.failWrite(fos); - } - } } @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { mContext.enforceCallingOrSelfPermission(DUMP, TAG); final HashSet<String> argSet = new HashSet<String>(); @@ -1399,182 +858,68 @@ public class NetworkStatsService extends INetworkStatsService.Stub { argSet.add(arg); } - final boolean fullHistory = argSet.contains("full"); + // usage: dumpsys netstats --full --uid --tag + final boolean poll = argSet.contains("--poll") || argSet.contains("poll"); + final boolean fullHistory = argSet.contains("--full") || argSet.contains("full"); + final boolean includeUid = argSet.contains("--uid") || argSet.contains("detail"); + final boolean includeTag = argSet.contains("--tag") || argSet.contains("detail"); - synchronized (mStatsLock) { - // TODO: remove this testing code, since it corrupts stats - if (argSet.contains("generate")) { - generateRandomLocked(args); - pw.println("Generated stub stats"); - return; - } + final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - if (argSet.contains("poll")) { + synchronized (mStatsLock) { + if (poll) { performPollLocked(FLAG_PERSIST_ALL | FLAG_PERSIST_FORCE); pw.println("Forced poll"); return; } pw.println("Active interfaces:"); + pw.increaseIndent(); for (String iface : mActiveIfaces.keySet()) { final NetworkIdentitySet ident = mActiveIfaces.get(iface); - pw.print(" iface="); pw.print(iface); + pw.print("iface="); pw.print(iface); pw.print(" ident="); pw.println(ident.toString()); } - - pw.println("Known historical dev stats:"); - for (NetworkIdentitySet ident : mNetworkDevStats.keySet()) { - final NetworkStatsHistory history = mNetworkDevStats.get(ident); - pw.print(" ident="); pw.println(ident.toString()); - history.dump(" ", pw, fullHistory); + pw.decreaseIndent(); + + pw.println("Dev stats:"); + pw.increaseIndent(); + mDevRecorder.dumpLocked(pw, fullHistory); + pw.decreaseIndent(); + + if (includeUid) { + pw.println("UID stats:"); + pw.increaseIndent(); + mUidRecorder.dumpLocked(pw, fullHistory); + pw.decreaseIndent(); } - pw.println("Known historical xt stats:"); - for (NetworkIdentitySet ident : mNetworkXtStats.keySet()) { - final NetworkStatsHistory history = mNetworkXtStats.get(ident); - pw.print(" ident="); pw.println(ident.toString()); - history.dump(" ", pw, fullHistory); - } - - if (argSet.contains("detail")) { - // since explicitly requested with argument, we're okay to load - // from disk if not already in memory. - ensureUidStatsLoadedLocked(); - - final ArrayList<UidStatsKey> keys = Lists.newArrayList(); - keys.addAll(mUidStats.keySet()); - Collections.sort(keys); - - pw.println("Detailed UID stats:"); - for (UidStatsKey key : keys) { - pw.print(" ident="); pw.print(key.ident.toString()); - pw.print(" uid="); pw.print(key.uid); - pw.print(" set="); pw.print(NetworkStats.setToString(key.set)); - pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag)); - - final NetworkStatsHistory history = mUidStats.get(key); - history.dump(" ", pw, fullHistory); - } + if (includeTag) { + pw.println("UID tag stats:"); + pw.increaseIndent(); + mUidTagRecorder.dumpLocked(pw, fullHistory); + pw.decreaseIndent(); } } } - /** - * @deprecated only for temporary testing - */ - @Deprecated - private void generateRandomLocked(String[] args) { - final long totalBytes = Long.parseLong(args[1]); - final long totalTime = Long.parseLong(args[2]); - - final PackageManager pm = mContext.getPackageManager(); - final ArrayList<Integer> specialUidList = Lists.newArrayList(); - for (int i = 3; i < args.length; i++) { - try { - specialUidList.add(pm.getApplicationInfo(args[i], 0).uid); - } catch (NameNotFoundException e) { - throw new RuntimeException(e); - } - } - - final HashSet<Integer> otherUidSet = Sets.newHashSet(); - for (ApplicationInfo info : pm.getInstalledApplications(0)) { - if (pm.checkPermission(android.Manifest.permission.INTERNET, info.packageName) - == PackageManager.PERMISSION_GRANTED && !specialUidList.contains(info.uid)) { - otherUidSet.add(info.uid); - } - } - - final ArrayList<Integer> otherUidList = new ArrayList<Integer>(otherUidSet); - - final long end = System.currentTimeMillis(); - final long start = end - totalTime; - - mNetworkDevStats.clear(); - mNetworkXtStats.clear(); - mUidStats.clear(); - - final Random r = new Random(); - for (NetworkIdentitySet ident : mActiveIfaces.values()) { - final NetworkStatsHistory devHistory = findOrCreateNetworkDevStatsLocked(ident); - final NetworkStatsHistory xtHistory = findOrCreateNetworkXtStatsLocked(ident); - - final ArrayList<Integer> uidList = new ArrayList<Integer>(); - uidList.addAll(specialUidList); - - if (uidList.size() == 0) { - Collections.shuffle(otherUidList); - uidList.addAll(otherUidList); - } - - boolean first = true; - long remainingBytes = totalBytes; - for (int uid : uidList) { - final NetworkStatsHistory defaultHistory = findOrCreateUidStatsLocked( - ident, uid, SET_DEFAULT, TAG_NONE); - final NetworkStatsHistory foregroundHistory = findOrCreateUidStatsLocked( - ident, uid, SET_FOREGROUND, TAG_NONE); - - final long uidBytes = totalBytes / uidList.size(); - - final float fractionDefault = r.nextFloat(); - final long defaultBytes = (long) (uidBytes * fractionDefault); - final long foregroundBytes = (long) (uidBytes * (1 - fractionDefault)); - - defaultHistory.generateRandom(start, end, defaultBytes); - foregroundHistory.generateRandom(start, end, foregroundBytes); - - if (first) { - final long bumpTime = (start + end) / 2; - defaultHistory.recordData( - bumpTime, bumpTime + DAY_IN_MILLIS, 200 * MB_IN_BYTES, 0); - first = false; - } - - devHistory.recordEntireHistory(defaultHistory); - devHistory.recordEntireHistory(foregroundHistory); - xtHistory.recordEntireHistory(defaultHistory); - xtHistory.recordEntireHistory(foregroundHistory); - } - } + private NetworkStats getNetworkStatsSummary() throws RemoteException { + return mNetworkManager.getNetworkStatsSummary(); } /** - * Return the delta between two {@link NetworkStats} snapshots, where {@code - * before} can be {@code null}. + * Return snapshot of current UID statistics, including any + * {@link TrafficStats#UID_TETHERING} and {@link #mUidOperations} values. */ - private NetworkStats computeStatsDelta( - NetworkStats before, NetworkStats current, boolean collectStale, String type) { - if (before != null) { - try { - return current.subtract(before, false); - } catch (NonMonotonicException e) { - Log.w(TAG, "found non-monotonic values; saving to dropbox"); - - // record error for debugging - final StringBuilder builder = new StringBuilder(); - builder.append("found non-monotonic " + type + " values at left[" + e.leftIndex - + "] - right[" + e.rightIndex + "]\n"); - builder.append("left=").append(e.left).append('\n'); - builder.append("right=").append(e.right).append('\n'); - mDropBox.addText(TAG_NETSTATS_ERROR, builder.toString()); + private NetworkStats getNetworkStatsUidDetail() throws RemoteException { + final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL); - try { - // return clamped delta to help recover - return current.subtract(before, true); - } catch (NonMonotonicException e1) { - Log.wtf(TAG, "found non-monotonic values; returning empty delta", e1); - return new NetworkStats(0L, 10); - } - } - } else if (collectStale) { - // caller is okay collecting stale stats for first call. - return current; - } else { - // this is first snapshot; to prevent from double-counting we only - // observe traffic occuring between known snapshots. - return new NetworkStats(0L, 10); - } + // fold tethering stats and operations into uid snapshot + final NetworkStats tetherSnapshot = getNetworkStatsTethering(); + uidSnapshot.combineAllValues(tetherSnapshot); + uidSnapshot.combineAllValues(mUidOperations); + + return uidSnapshot; } /** @@ -1591,35 +936,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - private static NetworkStats computeNetworkXtSnapshotFromUid(NetworkStats uidSnapshot) { - return uidSnapshot.groupedByIface(); - } - - private int estimateNetworkBuckets() { - return (int) (mSettings.getNetworkMaxHistory() / mSettings.getNetworkBucketDuration()); - } - - private int estimateUidBuckets() { - return (int) (mSettings.getUidMaxHistory() / mSettings.getUidBucketDuration()); - } - - private static int estimateResizeBuckets(NetworkStatsHistory existing, long newBucketDuration) { - return (int) (existing.size() * existing.getBucketDuration() / newBucketDuration); - } - - /** - * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity} - * in the given {@link NetworkIdentitySet}. - */ - private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) { - for (NetworkIdentity ident : identSet) { - if (template.matches(ident)) { - return true; - } - } - return false; - } - private Handler.Callback mHandlerCallback = new Handler.Callback() { /** {@inheritDoc} */ public boolean handleMessage(Message msg) { @@ -1633,6 +949,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { updateIfaces(); return true; } + case MSG_REGISTER_GLOBAL_ALERT: { + registerGlobalAlert(); + return true; + } default: { return false; } @@ -1646,40 +966,31 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return telephony.getSubscriberId(); } - /** - * Key uniquely identifying a {@link NetworkStatsHistory} for a UID. - */ - private static class UidStatsKey implements Comparable<UidStatsKey> { - public final NetworkIdentitySet ident; - public final int uid; - public final int set; - public final int tag; - - public UidStatsKey(NetworkIdentitySet ident, int uid, int set, int tag) { - this.ident = ident; - this.uid = uid; - this.set = set; - this.tag = tag; - } - - @Override - public int hashCode() { - return Objects.hashCode(ident, uid, set, tag); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof UidStatsKey) { - final UidStatsKey key = (UidStatsKey) obj; - return Objects.equal(ident, key.ident) && uid == key.uid && set == key.set - && tag == key.tag; - } + private boolean isBandwidthControlEnabled() { + try { + return mNetworkManager.isBandwidthControlEnabled(); + } catch (RemoteException e) { + // ignored; service lives in system_server return false; } + } + private class DropBoxNonMonotonicObserver implements NonMonotonicObserver<String> { /** {@inheritDoc} */ - public int compareTo(UidStatsKey another) { - return Integer.compare(uid, another.uid); + public void foundNonMonotonic(NetworkStats left, int leftIndex, NetworkStats right, + int rightIndex, String cookie) { + Log.w(TAG, "found non-monotonic values; saving to dropbox"); + + // record error for debugging + final StringBuilder builder = new StringBuilder(); + builder.append("found non-monotonic " + cookie + " values at left[" + leftIndex + + "] - right[" + rightIndex + "]\n"); + builder.append("left=").append(left).append('\n'); + builder.append("right=").append(right).append('\n'); + + final DropBoxManager dropBox = (DropBoxManager) mContext.getSystemService( + Context.DROPBOX_SERVICE); + dropBox.addText(TAG_NETSTATS_ERROR, builder.toString()); } } @@ -1705,26 +1016,35 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public long getPollInterval() { return getSecureLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS); } - public long getPersistThreshold() { - return getSecureLong(NETSTATS_PERSIST_THRESHOLD, 2 * MB_IN_BYTES); - } - public long getNetworkBucketDuration() { - return getSecureLong(NETSTATS_NETWORK_BUCKET_DURATION, HOUR_IN_MILLIS); + public long getTimeCacheMaxAge() { + return getSecureLong(NETSTATS_TIME_CACHE_MAX_AGE, DAY_IN_MILLIS); } - public long getNetworkMaxHistory() { - return getSecureLong(NETSTATS_NETWORK_MAX_HISTORY, 90 * DAY_IN_MILLIS); + public long getGlobalAlertBytes() { + return getSecureLong(NETSTATS_GLOBAL_ALERT_BYTES, 2 * MB_IN_BYTES); } - public long getUidBucketDuration() { - return getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS); + public boolean getSampleEnabled() { + return getSecureBoolean(NETSTATS_SAMPLE_ENABLED, true); } - public long getUidMaxHistory() { - return getSecureLong(NETSTATS_UID_MAX_HISTORY, 90 * DAY_IN_MILLIS); + + public Config getDevConfig() { + return new Config(getSecureLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS), + getSecureLong(NETSTATS_DEV_PERSIST_BYTES, 2 * MB_IN_BYTES), + getSecureLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS), + getSecureLong(NETSTATS_DEV_DELETE_AGE, 90 * DAY_IN_MILLIS)); } - public long getTagMaxHistory() { - return getSecureLong(NETSTATS_TAG_MAX_HISTORY, 30 * DAY_IN_MILLIS); + + public Config getUidConfig() { + return new Config(getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS), + getSecureLong(NETSTATS_UID_PERSIST_BYTES, 2 * MB_IN_BYTES), + getSecureLong(NETSTATS_UID_ROTATE_AGE, 15 * DAY_IN_MILLIS), + getSecureLong(NETSTATS_UID_DELETE_AGE, 90 * DAY_IN_MILLIS)); } - public long getTimeCacheMaxAge() { - return DAY_IN_MILLIS; + + public Config getUidTagConfig() { + return new Config(getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS), + getSecureLong(NETSTATS_UID_PERSIST_BYTES, 2 * MB_IN_BYTES), + getSecureLong(NETSTATS_UID_ROTATE_AGE, 5 * DAY_IN_MILLIS), + getSecureLong(NETSTATS_UID_DELETE_AGE, 15 * DAY_IN_MILLIS)); } } } diff --git a/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java index 11ccd60..9b1973e 100644 --- a/services/java/com/android/server/pm/Installer.java +++ b/services/java/com/android/server/pm/Installer.java @@ -277,6 +277,27 @@ class Installer { return execute(builder.toString()); } + /** + * Clone all the package data directories from srcUserId to targetUserId. If copyData is true, + * some of the data is also copied, otherwise just empty directories are created with the + * correct access rights. + * @param srcUserId user to copy the data directories from + * @param targetUserId user to copy the data directories to + * @param copyData whether the data itself is to be copied. If false, empty directories are + * created. + * @return success/error code + */ + public int cloneUserData(int srcUserId, int targetUserId, boolean copyData) { + StringBuilder builder = new StringBuilder("cloneuserdata"); + builder.append(' '); + builder.append(srcUserId); + builder.append(' '); + builder.append(targetUserId); + builder.append(' '); + builder.append(copyData ? '1' : '0'); + return execute(builder.toString()); + } + public boolean ping() { if (execute("ping") < 0) { return false; diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index 6b61c47..38c128c 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -92,6 +92,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.UserId; import android.security.SystemKeyStore; import android.util.DisplayMetrics; import android.util.EventLog; @@ -620,11 +621,11 @@ public class PackageManagerService extends IPackageManager.Stub { packages = new String[size]; components = new ArrayList[size]; uids = new int[size]; - Iterator<HashMap.Entry<String, ArrayList<String>>> + Iterator<Map.Entry<String, ArrayList<String>>> it = mPendingBroadcasts.entrySet().iterator(); int i = 0; while (it.hasNext() && i < size) { - HashMap.Entry<String, ArrayList<String>> ent = it.next(); + Map.Entry<String, ArrayList<String>> ent = it.next(); packages[i] = ent.getKey(); components[i] = ent.getValue(); PackageSetting ps = mSettings.mPackages.get(ent.getKey()); @@ -1743,12 +1744,16 @@ public class PackageManagerService extends IPackageManager.Stub { } public ActivityInfo getActivityInfo(ComponentName component, int flags) { + return getActivityInfo(component, flags, Binder.getOrigCallingUser()); + } + + ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) { synchronized (mPackages) { PackageParser.Activity a = mActivities.mActivities.get(component); if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a); if (a != null && mSettings.isEnabledLPr(a.info, flags)) { - return PackageParser.generateActivityInfo(a, flags); + return PackageParser.generateActivityInfo(a, flags, userId); } if (mResolveComponentName.equals(component)) { return mResolveActivity; @@ -1758,36 +1763,48 @@ public class PackageManagerService extends IPackageManager.Stub { } public ActivityInfo getReceiverInfo(ComponentName component, int flags) { + return getReceiverInfo(component, flags, Binder.getOrigCallingUser()); + } + + ActivityInfo getReceiverInfo(ComponentName component, int flags, int userId) { synchronized (mPackages) { PackageParser.Activity a = mReceivers.mActivities.get(component); if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getReceiverInfo " + component + ": " + a); if (a != null && mSettings.isEnabledLPr(a.info, flags)) { - return PackageParser.generateActivityInfo(a, flags); + return PackageParser.generateActivityInfo(a, flags, userId); } } return null; } public ServiceInfo getServiceInfo(ComponentName component, int flags) { + return getServiceInfo(component, flags, Binder.getOrigCallingUser()); + } + + ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) { synchronized (mPackages) { PackageParser.Service s = mServices.mServices.get(component); if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getServiceInfo " + component + ": " + s); if (s != null && mSettings.isEnabledLPr(s.info, flags)) { - return PackageParser.generateServiceInfo(s, flags); + return PackageParser.generateServiceInfo(s, flags, userId); } } return null; } public ProviderInfo getProviderInfo(ComponentName component, int flags) { + return getProviderInfo(component, flags, UserId.getUserId(Binder.getCallingUid())); + } + + ProviderInfo getProviderInfo(ComponentName component, int flags, int userId) { synchronized (mPackages) { PackageParser.Provider p = mProvidersByComponent.get(component); if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getProviderInfo " + component + ": " + p); if (p != null && mSettings.isEnabledLPr(p.info, flags)) { - return PackageParser.generateProviderInfo(p, flags); + return PackageParser.generateProviderInfo(p, flags, userId); } } return null; @@ -1850,7 +1867,7 @@ public class PackageManagerService extends IPackageManager.Stub { public int checkUidPermission(String permName, int uid) { synchronized (mPackages) { - Object obj = mSettings.getUserIdLPr(uid); + Object obj = mSettings.getUserIdLPr(UserId.getAppId(uid)); if (obj != null) { GrantedPermissions gp = (GrantedPermissions)obj; if (gp.grantedPermissions.contains(permName)) { @@ -1881,7 +1898,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (permName != null) { BasePermission bp = findPermissionTreeLP(permName); if (bp != null) { - if (bp.uid == Binder.getCallingUid()) { + if (bp.uid == UserId.getAppId(Binder.getCallingUid())) { return bp; } throw new SecurityException("Calling uid " @@ -2010,6 +2027,9 @@ public class PackageManagerService extends IPackageManager.Stub { } public int checkUidSignatures(int uid1, int uid2) { + // Map to base uids. + uid1 = UserId.getAppId(uid1); + uid2 = UserId.getAppId(uid2); // reader synchronized (mPackages) { Signature[] s1; @@ -2067,6 +2087,7 @@ public class PackageManagerService extends IPackageManager.Stub { } public String[] getPackagesForUid(int uid) { + uid = UserId.getAppId(uid); // reader synchronized (mPackages) { Object obj = mSettings.getUserIdLPr(uid); @@ -2091,7 +2112,7 @@ public class PackageManagerService extends IPackageManager.Stub { public String getNameForUid(int uid) { // reader synchronized (mPackages) { - Object obj = mSettings.getUserIdLPr(uid); + Object obj = mSettings.getUserIdLPr(UserId.getAppId(uid)); if (obj instanceof SharedUserSetting) { final SharedUserSetting sus = (SharedUserSetting) obj; return sus.name + ":" + sus.userId; @@ -2110,7 +2131,7 @@ public class PackageManagerService extends IPackageManager.Stub { // reader synchronized (mPackages) { final SharedUserSetting suid = mSettings.getSharedUserLPw(sharedUserName, 0, false); - if(suid == null) { + if (suid == null) { return -1; } return suid.userId; @@ -2252,6 +2273,9 @@ public class PackageManagerService extends IPackageManager.Stub { comp = intent.getComponent(); } } + + final int userId = UserId.getUserId(Binder.getCallingUid()); + if (comp != null) { final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); final ActivityInfo ai = getActivityInfo(comp, flags); @@ -2603,6 +2627,7 @@ public class PackageManagerService extends IPackageManager.Stub { Arrays.sort(keys); int i = getContinuationPoint(keys, lastRead); final int N = keys.length; + final int userId = UserId.getUserId(Binder.getCallingUid()); while (i < N) { final String packageName = keys[i++]; @@ -2616,7 +2641,7 @@ public class PackageManagerService extends IPackageManager.Stub { } else { final PackageParser.Package p = mPackages.get(packageName); if (p != null) { - ai = PackageParser.generateApplicationInfo(p, flags); + ai = PackageParser.generateApplicationInfo(p, flags, userId); } } @@ -2639,12 +2664,13 @@ public class PackageManagerService extends IPackageManager.Stub { // reader synchronized (mPackages) { final Iterator<PackageParser.Package> i = mPackages.values().iterator(); + final int userId = UserId.getUserId(Binder.getCallingUid()); while (i.hasNext()) { final PackageParser.Package p = i.next(); if (p.applicationInfo != null && (p.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) != 0 && (!mSafeMode || isSystemApp(p))) { - finalList.add(PackageParser.generateApplicationInfo(p, flags)); + finalList.add(PackageParser.generateApplicationInfo(p, flags, userId)); } } } @@ -2660,7 +2686,8 @@ public class PackageManagerService extends IPackageManager.Stub { && mSettings.isEnabledLPr(provider.info, flags) && (!mSafeMode || (provider.info.applicationInfo.flags &ApplicationInfo.FLAG_SYSTEM) != 0) - ? PackageParser.generateProviderInfo(provider, flags) + ? PackageParser.generateProviderInfo(provider, flags, + UserId.getUserId(Binder.getCallingUid())) : null; } } @@ -2674,7 +2701,7 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { final Iterator<Map.Entry<String, PackageParser.Provider>> i = mProviders.entrySet() .iterator(); - + final int userId = UserId.getUserId(Binder.getCallingUid()); while (i.hasNext()) { Map.Entry<String, PackageParser.Provider> entry = i.next(); PackageParser.Provider p = entry.getValue(); @@ -2683,7 +2710,7 @@ public class PackageManagerService extends IPackageManager.Stub { && (!mSafeMode || (p.info.applicationInfo.flags &ApplicationInfo.FLAG_SYSTEM) != 0)) { outNames.add(entry.getKey()); - outInfo.add(PackageParser.generateProviderInfo(p, 0)); + outInfo.add(PackageParser.generateProviderInfo(p, 0, userId)); } } } @@ -2696,19 +2723,21 @@ public class PackageManagerService extends IPackageManager.Stub { // reader synchronized (mPackages) { final Iterator<PackageParser.Provider> i = mProvidersByComponent.values().iterator(); + final int userId = UserId.getUserId(Binder.getCallingUid()); while (i.hasNext()) { final PackageParser.Provider p = i.next(); if (p.info.authority != null && (processName == null || (p.info.processName.equals(processName) - && p.info.applicationInfo.uid == uid)) + && UserId.getAppId(p.info.applicationInfo.uid) + == UserId.getAppId(uid))) && mSettings.isEnabledLPr(p.info, flags) && (!mSafeMode || (p.info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)) { if (finalList == null) { finalList = new ArrayList<ProviderInfo>(3); } - finalList.add(PackageParser.generateProviderInfo(p, flags)); + finalList.add(PackageParser.generateProviderInfo(p, flags, userId)); } } } @@ -4461,8 +4490,8 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } final ResolveInfo res = new ResolveInfo(); - res.activityInfo = PackageParser.generateActivityInfo(activity, - mFlags); + res.activityInfo = PackageParser.generateActivityInfo(activity, mFlags, + Binder.getOrigCallingUser()); if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) { res.filter = info; } @@ -4637,8 +4666,8 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } final ResolveInfo res = new ResolveInfo(); - res.serviceInfo = PackageParser.generateServiceInfo(service, - mFlags); + res.serviceInfo = PackageParser.generateServiceInfo(service, mFlags, + Binder.getOrigCallingUser()); if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) { res.filter = filter; } @@ -4742,8 +4771,10 @@ public class PackageManagerService extends IPackageManager.Stub { intent.setPackage(targetPkg); } intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + // TODO: Fix the userId argument am.broadcastIntent(null, intent, null, finishedReceiver, - 0, null, null, null, finishedReceiver != null, false); + 0, null, null, null, finishedReceiver != null, false, + Binder.getOrigCallingUser()); } catch (RemoteException ex) { } } @@ -7584,7 +7615,7 @@ public class PackageManagerService extends IPackageManager.Stub { "Unknown component: " + packageName + "/" + className); } - if (!allowedByPermission && (uid != pkgSetting.userId)) { + if (!allowedByPermission && (!UserId.isSameApp(uid, pkgSetting.userId))) { throw new SecurityException( "Permission Denial: attempt to change component state from pid=" + Binder.getCallingPid() @@ -8673,4 +8704,8 @@ public class PackageManagerService extends IPackageManager.Stub { return mSettings.getVerifierDeviceIdentityLPw(); } } + + public List<UserInfo> getUsers() { + return mUserManager.getUsers(); + } } diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java index 36442a0..3616aa2 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -62,6 +62,7 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import libcore.io.IoUtils; @@ -713,8 +714,7 @@ final class Settings { mBackupStoppedPackagesFilename.delete(); FileUtils.setPermissions(mStoppedPackagesFilename.toString(), FileUtils.S_IRUSR|FileUtils.S_IWUSR - |FileUtils.S_IRGRP|FileUtils.S_IWGRP - |FileUtils.S_IROTH, + |FileUtils.S_IRGRP|FileUtils.S_IWGRP, -1, -1); // Done, all is good! @@ -930,7 +930,7 @@ final class Settings { } if (mRenamedPackages.size() > 0) { - for (HashMap.Entry<String, String> e : mRenamedPackages.entrySet()) { + for (Map.Entry<String, String> e : mRenamedPackages.entrySet()) { serializer.startTag(null, "renamed-package"); serializer.attribute(null, "new", e.getKey()); serializer.attribute(null, "old", e.getValue()); @@ -951,8 +951,7 @@ final class Settings { mBackupSettingsFilename.delete(); FileUtils.setPermissions(mSettingsFilename.toString(), FileUtils.S_IRUSR|FileUtils.S_IWUSR - |FileUtils.S_IRGRP|FileUtils.S_IWGRP - |FileUtils.S_IROTH, + |FileUtils.S_IRGRP|FileUtils.S_IWGRP, -1, -1); // Write package list file now, use a JournaledFile. @@ -1007,8 +1006,7 @@ final class Settings { FileUtils.setPermissions(mPackageListFilename.toString(), FileUtils.S_IRUSR|FileUtils.S_IWUSR - |FileUtils.S_IRGRP|FileUtils.S_IWGRP - |FileUtils.S_IROTH, + |FileUtils.S_IRGRP|FileUtils.S_IWGRP, -1, -1); writeStoppedLPr(); @@ -2147,7 +2145,7 @@ final class Settings { printedSomething = false; if (mRenamedPackages.size() > 0) { - for (final HashMap.Entry<String, String> e : mRenamedPackages.entrySet()) { + for (final Map.Entry<String, String> e : mRenamedPackages.entrySet()) { if (packageName != null && !packageName.equals(e.getKey()) && !packageName.equals(e.getValue())) { continue; @@ -2261,4 +2259,4 @@ final class Settings { pw.println("Settings parse messages:"); pw.print(mReadMessages.toString()); } -}
\ No newline at end of file +} diff --git a/services/java/com/android/server/pm/UserManager.java b/services/java/com/android/server/pm/UserManager.java index 76fa5ab..5eacf4a 100644 --- a/services/java/com/android/server/pm/UserManager.java +++ b/services/java/com/android/server/pm/UserManager.java @@ -24,6 +24,7 @@ import android.content.pm.UserInfo; import android.os.Environment; import android.os.FileUtils; import android.os.SystemClock; +import android.os.UserId; import android.util.Log; import android.util.Slog; import android.util.SparseArray; @@ -140,6 +141,13 @@ public class UserManager { fallbackToSingleUser(); } catch (XmlPullParserException pe) { fallbackToSingleUser(); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + } + } } } @@ -162,9 +170,10 @@ public class UserManager { * </user> */ private void writeUser(UserInfo userInfo) { + FileOutputStream fos = null; try { final File mUserFile = new File(mUsersDir, userInfo.id + ".xml"); - final FileOutputStream fos = new FileOutputStream(mUserFile); + fos = new FileOutputStream(mUserFile); final BufferedOutputStream bos = new BufferedOutputStream(fos); // XmlSerializer serializer = XmlUtils.serializerInstance(); @@ -186,6 +195,13 @@ public class UserManager { serializer.endDocument(); } catch (IOException ioe) { Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe); + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException ioe) { + } + } } } @@ -198,8 +214,9 @@ public class UserManager { * </users> */ private void writeUserList() { + FileOutputStream fos = null; try { - final FileOutputStream fos = new FileOutputStream(mUserListFile); + fos = new FileOutputStream(mUserListFile); final BufferedOutputStream bos = new BufferedOutputStream(fos); // XmlSerializer serializer = XmlUtils.serializerInstance(); @@ -222,6 +239,13 @@ public class UserManager { serializer.endDocument(); } catch (IOException ioe) { Slog.e(LOG_TAG, "Error writing user list"); + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException ioe) { + } + } } } @@ -265,13 +289,19 @@ public class UserManager { } } } - fis.close(); UserInfo userInfo = new UserInfo(id, name, flags); return userInfo; } catch (IOException ioe) { } catch (XmlPullParserException pe) { + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + } + } } return null; } @@ -317,7 +347,7 @@ public class UserManager { // Don't do it for the primary user, it will become recursive. if (userId == 0) continue; - mInstaller.createUserData(packageName, PackageManager.getUid(userId, uid), + mInstaller.createUserData(packageName, UserId.getUid(userId, uid), userId); } } @@ -354,6 +384,8 @@ public class UserManager { /** * Returns the next available user id, filling in any holes in the ids. + * TODO: May not be a good idea to recycle ids, in case it results in confusion + * for data and battery stats collection, or unexpected cross-talk. * @return */ private int getNextAvailableId() { @@ -377,14 +409,8 @@ public class UserManager { FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH, -1, -1); - // Create the individual data directories - for (ApplicationInfo app : apps) { - if (app.uid > android.os.Process.FIRST_APPLICATION_UID - && app.uid < PackageManager.PER_USER_RANGE) { - mInstaller.createUserData(app.packageName, - PackageManager.getUid(id, app.uid), id); - } - } + mInstaller.cloneUserData(0, id, false); + final long stopTime = SystemClock.elapsedRealtime(); Log.i(LOG_TAG, "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms"); diff --git a/services/java/com/android/server/wm/DragState.java b/services/java/com/android/server/wm/DragState.java index 73cd64e..a19035a 100644 --- a/services/java/com/android/server/wm/DragState.java +++ b/services/java/com/android/server/wm/DragState.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import com.android.server.wm.WindowManagerService.DragInputEventReceiver; import com.android.server.wm.WindowManagerService.H; import android.content.ClipData; @@ -28,7 +29,6 @@ import android.os.RemoteException; import android.util.Slog; import android.view.DragEvent; import android.view.InputChannel; -import android.view.InputQueue; import android.view.Surface; import android.view.View; import android.view.WindowManager; @@ -50,6 +50,7 @@ class DragState { float mCurrentX, mCurrentY; float mThumbOffsetX, mThumbOffsetY; InputChannel mServerChannel, mClientChannel; + DragInputEventReceiver mInputEventReceiver; InputApplicationHandle mDragApplicationHandle; InputWindowHandle mDragWindowHandle; WindowState mTargetWindow; @@ -90,8 +91,8 @@ class DragState { mServerChannel = channels[0]; mClientChannel = channels[1]; mService.mInputManager.registerInputChannel(mServerChannel, null); - InputQueue.registerInputChannel(mClientChannel, mService.mDragInputHandler, - mService.mH.getLooper().getQueue()); + mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel, + mService.mH.getLooper()); mDragApplicationHandle = new InputApplicationHandle(null); mDragApplicationHandle.name = "drag"; @@ -139,7 +140,8 @@ class DragState { Slog.e(WindowManagerService.TAG, "Unregister of nonexistent drag input channel"); } else { mService.mInputManager.unregisterInputChannel(mServerChannel); - InputQueue.unregisterInputChannel(mClientChannel); + mInputEventReceiver.dispose(); + mInputEventReceiver = null; mClientChannel.dispose(); mServerChannel.dispose(); mClientChannel = null; diff --git a/services/java/com/android/server/wm/FakeWindowImpl.java b/services/java/com/android/server/wm/FakeWindowImpl.java index 0e72f7d..121ce18 100644 --- a/services/java/com/android/server/wm/FakeWindowImpl.java +++ b/services/java/com/android/server/wm/FakeWindowImpl.java @@ -20,7 +20,7 @@ import android.os.Looper; import android.os.Process; import android.util.Slog; import android.view.InputChannel; -import android.view.InputHandler; +import android.view.InputEventReceiver; import android.view.InputQueue; import android.view.WindowManagerPolicy; @@ -29,11 +29,13 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { final InputChannel mServerChannel, mClientChannel; final InputApplicationHandle mApplicationHandle; final InputWindowHandle mWindowHandle; + final InputEventReceiver mInputEventReceiver; final int mWindowLayer; boolean mTouchFullscreen; - public FakeWindowImpl(WindowManagerService service, Looper looper, InputHandler inputHandler, + public FakeWindowImpl(WindowManagerService service, + Looper looper, InputEventReceiver.Factory inputEventReceiverFactory, String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen) { mService = service; @@ -42,7 +44,9 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { mServerChannel = channels[0]; mClientChannel = channels[1]; mService.mInputManager.registerInputChannel(mServerChannel, null); - InputQueue.registerInputChannel(mClientChannel, inputHandler, looper.getQueue()); + + mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver( + mClientChannel, looper); mApplicationHandle = new InputApplicationHandle(null); mApplicationHandle.name = name; @@ -87,8 +91,8 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { public void dismiss() { synchronized (mService.mWindowMap) { if (mService.removeFakeWindowLocked(this)) { + mInputEventReceiver.dispose(); mService.mInputManager.unregisterInputChannel(mServerChannel); - InputQueue.unregisterInputChannel(mClientChannel); mClientChannel.dispose(); mServerChannel.dispose(); } diff --git a/services/java/com/android/server/wm/ScreenRotationAnimation.java b/services/java/com/android/server/wm/ScreenRotationAnimation.java index 8fc9a70..55fb038 100644 --- a/services/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/java/com/android/server/wm/ScreenRotationAnimation.java @@ -17,13 +17,8 @@ package com.android.server.wm; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; import android.graphics.Matrix; -import android.graphics.Paint; import android.graphics.PixelFormat; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.util.Slog; import android.view.Surface; @@ -34,7 +29,8 @@ import android.view.animation.Transformation; class ScreenRotationAnimation { static final String TAG = "ScreenRotationAnimation"; - static final boolean DEBUG = false; + static final boolean DEBUG_STATE = false; + static final boolean DEBUG_TRANSFORMS = false; static final int FREEZE_LAYER = WindowManagerService.TYPE_LAYER_MULTIPLIER * 200; @@ -49,11 +45,51 @@ class ScreenRotationAnimation { int mOriginalWidth, mOriginalHeight; int mCurRotation; - Animation mExitAnimation; + // For all animations, "exit" is for the UI elements that are going + // away (that is the snapshot of the old screen), and "enter" is for + // the new UI elements that are appearing (that is the active windows + // in their final orientation). + + // The starting animation for the exiting and entering elements. This + // animation applies a transformation while the rotation is in progress. + // It is started immediately, before the new entering UI is ready. + Animation mStartExitAnimation; + final Transformation mStartExitTransformation = new Transformation(); + Animation mStartEnterAnimation; + final Transformation mStartEnterTransformation = new Transformation(); + + // The finishing animation for the exiting and entering elements. This + // animation needs to undo the transformation of the starting animation. + // It starts running once the new rotation UI elements are ready to be + // displayed. + Animation mFinishExitAnimation; + final Transformation mFinishExitTransformation = new Transformation(); + Animation mFinishEnterAnimation; + final Transformation mFinishEnterTransformation = new Transformation(); + + // The current active animation to move from the old to the new rotated + // state. Which animation is run here will depend on the old and new + // rotations. + Animation mRotateExitAnimation; + final Transformation mRotateExitTransformation = new Transformation(); + Animation mRotateEnterAnimation; + final Transformation mRotateEnterTransformation = new Transformation(); + + // A previously running rotate animation. This will be used if we need + // to switch to a new rotation before finishing the previous one. + Animation mLastRotateExitAnimation; + final Transformation mLastRotateExitTransformation = new Transformation(); + Animation mLastRotateEnterAnimation; + final Transformation mLastRotateEnterTransformation = new Transformation(); + + // Complete transformations being applied. final Transformation mExitTransformation = new Transformation(); - Animation mEnterAnimation; final Transformation mEnterTransformation = new Transformation(); + boolean mStarted; + boolean mAnimRunning; + boolean mFinishAnimReady; + long mFinishAnimStartTime; final Matrix mSnapshotInitialMatrix = new Matrix(); final Matrix mSnapshotFinalMatrix = new Matrix(); @@ -133,7 +169,7 @@ class ScreenRotationAnimation { mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); mSurface.setAlpha(alpha); - if (DEBUG) { + if (DEBUG_TRANSFORMS) { float[] srcPnts = new float[] { 0, 0, mWidth, mHeight }; float[] dstPnts = new float[4]; matrix.mapPoints(dstPnts, srcPnts); @@ -167,7 +203,7 @@ class ScreenRotationAnimation { } // Must be called while in a transaction. - public void setRotation(int rotation) { + private void setRotation(int rotation) { mCurRotation = rotation; // Compute the transformation matrix that must be applied @@ -176,46 +212,78 @@ class ScreenRotationAnimation { int delta = deltaRotation(rotation, mSnapshotRotation); createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); - if (DEBUG) Slog.v(TAG, "**** ROTATION: " + delta); + if (DEBUG_STATE) Slog.v(TAG, "**** ROTATION: " + delta); setSnapshotTransform(mSnapshotInitialMatrix, 1.0f); } + // Must be called while in a transaction. + public boolean setRotation(int rotation, SurfaceSession session, + long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight) { + setRotation(rotation); + return startAnimation(session, maxAnimationDuration, animationScale, + finalWidth, finalHeight, false); + } + /** * Returns true if animating. */ - public boolean dismiss(SurfaceSession session, long maxAnimationDuration, - float animationScale, int finalWidth, int finalHeight) { + private boolean startAnimation(SurfaceSession session, long maxAnimationDuration, + float animationScale, int finalWidth, int finalHeight, boolean dismissing) { if (mSurface == null) { // Can't do animation. return false; } + if (mStarted) { + return true; + } + + mStarted = true; + + boolean firstStart = false; // Figure out how the screen has moved from the original rotation. int delta = deltaRotation(mCurRotation, mOriginalRotation); + if (mFinishExitAnimation == null && (!dismissing || delta != Surface.ROTATION_0)) { + if (DEBUG_STATE) Slog.v(TAG, "Creating start and finish animations"); + firstStart = true; + mStartExitAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_start_exit); + mStartEnterAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_start_enter); + mFinishExitAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_finish_exit); + mFinishEnterAnimation = AnimationUtils.loadAnimation(mContext, + com.android.internal.R.anim.screen_rotate_finish_enter); + } + + if (DEBUG_STATE) Slog.v(TAG, "Rotation delta: " + delta + " finalWidth=" + + finalWidth + " finalHeight=" + finalHeight + + " origWidth=" + mOriginalWidth + " origHeight=" + mOriginalHeight); + switch (delta) { case Surface.ROTATION_0: - mExitAnimation = AnimationUtils.loadAnimation(mContext, + mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_0_exit); - mEnterAnimation = AnimationUtils.loadAnimation(mContext, + mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_0_enter); break; case Surface.ROTATION_90: - mExitAnimation = AnimationUtils.loadAnimation(mContext, + mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_plus_90_exit); - mEnterAnimation = AnimationUtils.loadAnimation(mContext, + mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_plus_90_enter); break; case Surface.ROTATION_180: - mExitAnimation = AnimationUtils.loadAnimation(mContext, + mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_180_exit); - mEnterAnimation = AnimationUtils.loadAnimation(mContext, + mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_180_enter); break; case Surface.ROTATION_270: - mExitAnimation = AnimationUtils.loadAnimation(mContext, + mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_minus_90_exit); - mEnterAnimation = AnimationUtils.loadAnimation(mContext, + mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, com.android.internal.R.anim.screen_rotate_minus_90_enter); break; } @@ -224,35 +292,85 @@ class ScreenRotationAnimation { // means to allow supplying the last and next size. In this definition // "%p" is the original (let's call it "previous") size, and "%" is the // screen's current/new size. - mEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); - mExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); - mStarted = false; + if (firstStart) { + if (DEBUG_STATE) Slog.v(TAG, "Initializing start and finish animations"); + mStartEnterAnimation.initialize(finalWidth, finalHeight, + mOriginalWidth, mOriginalHeight); + mStartExitAnimation.initialize(finalWidth, finalHeight, + mOriginalWidth, mOriginalHeight); + mFinishEnterAnimation.initialize(finalWidth, finalHeight, + mOriginalWidth, mOriginalHeight); + mFinishExitAnimation.initialize(finalWidth, finalHeight, + mOriginalWidth, mOriginalHeight); + } + mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); + mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); + mAnimRunning = false; + mFinishAnimReady = false; + mFinishAnimStartTime = -1; + + if (firstStart) { + mStartExitAnimation.restrictDuration(maxAnimationDuration); + mStartExitAnimation.scaleCurrentDuration(animationScale); + mStartEnterAnimation.restrictDuration(maxAnimationDuration); + mStartEnterAnimation.scaleCurrentDuration(animationScale); + mFinishExitAnimation.restrictDuration(maxAnimationDuration); + mFinishExitAnimation.scaleCurrentDuration(animationScale); + mFinishEnterAnimation.restrictDuration(maxAnimationDuration); + mFinishEnterAnimation.scaleCurrentDuration(animationScale); + } + mRotateExitAnimation.restrictDuration(maxAnimationDuration); + mRotateExitAnimation.scaleCurrentDuration(animationScale); + mRotateEnterAnimation.restrictDuration(maxAnimationDuration); + mRotateEnterAnimation.scaleCurrentDuration(animationScale); + + if (mBlackFrame == null) { + if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i( + WindowManagerService.TAG, + ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation"); + Surface.openTransaction(); - mExitAnimation.restrictDuration(maxAnimationDuration); - mExitAnimation.scaleCurrentDuration(animationScale); - mEnterAnimation.restrictDuration(maxAnimationDuration); - mEnterAnimation.scaleCurrentDuration(animationScale); + try { + Rect outer = new Rect(-finalWidth*1, -finalHeight*1, finalWidth*2, finalHeight*2); + Rect inner = new Rect(0, 0, finalWidth, finalHeight); + mBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER); + } catch (Surface.OutOfResourcesException e) { + Slog.w(TAG, "Unable to allocate black surface", e); + } finally { + Surface.closeTransaction(); + if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i( + WindowManagerService.TAG, + "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation"); + } + } - if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG, - ">>> OPEN TRANSACTION ScreenRotationAnimation.dismiss"); - Surface.openTransaction(); + return true; + } - try { - Rect outer = new Rect(-finalWidth, -finalHeight, finalWidth * 2, finalHeight * 2); - Rect inner = new Rect(0, 0, finalWidth, finalHeight); - mBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER); - } catch (Surface.OutOfResourcesException e) { - Slog.w(TAG, "Unable to allocate black surface", e); - } finally { - Surface.closeTransaction(); - if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG, - "<<< CLOSE TRANSACTION ScreenRotationAnimation.dismiss"); + /** + * Returns true if animating. + */ + public boolean dismiss(SurfaceSession session, long maxAnimationDuration, + float animationScale, int finalWidth, int finalHeight) { + if (DEBUG_STATE) Slog.v(TAG, "Dismiss!"); + if (mSurface == null) { + // Can't do animation. + return false; } - + if (!mStarted) { + startAnimation(session, maxAnimationDuration, animationScale, finalWidth, finalHeight, + true); + } + if (!mStarted) { + return false; + } + if (DEBUG_STATE) Slog.v(TAG, "Setting mFinishAnimReady = true"); + mFinishAnimReady = true; return true; } public void kill() { + if (DEBUG_STATE) Slog.v(TAG, "Kill!"); if (mSurface != null) { if (WindowManagerService.SHOW_TRANSACTIONS || WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG, @@ -262,74 +380,198 @@ class ScreenRotationAnimation { } if (mBlackFrame != null) { mBlackFrame.kill(); + mBlackFrame = null; + } + if (mStartExitAnimation != null) { + mStartExitAnimation.cancel(); + mStartExitAnimation = null; + } + if (mStartEnterAnimation != null) { + mStartEnterAnimation.cancel(); + mStartEnterAnimation = null; } - if (mExitAnimation != null) { - mExitAnimation.cancel(); - mExitAnimation = null; + if (mFinishExitAnimation != null) { + mFinishExitAnimation.cancel(); + mFinishExitAnimation = null; } - if (mEnterAnimation != null) { - mEnterAnimation.cancel(); - mEnterAnimation = null; + if (mStartEnterAnimation != null) { + mStartEnterAnimation.cancel(); + mStartEnterAnimation = null; + } + if (mRotateExitAnimation != null) { + mRotateExitAnimation.cancel(); + mRotateExitAnimation = null; + } + if (mRotateEnterAnimation != null) { + mRotateEnterAnimation.cancel(); + mRotateEnterAnimation = null; } } public boolean isAnimating() { - return mEnterAnimation != null || mExitAnimation != null; + return mStartEnterAnimation != null || mStartExitAnimation != null + && mFinishEnterAnimation != null || mFinishExitAnimation != null + && mRotateEnterAnimation != null || mRotateExitAnimation != null; } public boolean stepAnimation(long now) { - if (mEnterAnimation == null && mExitAnimation == null) { + if (!isAnimating()) { + if (DEBUG_STATE) Slog.v(TAG, "Step: no animations running"); return false; } - if (!mStarted) { - if (mEnterAnimation != null) { - mEnterAnimation.setStartTime(now); + if (!mAnimRunning) { + if (DEBUG_STATE) Slog.v(TAG, "Step: starting start, finish, rotate"); + if (mStartEnterAnimation != null) { + mStartEnterAnimation.setStartTime(now); } - if (mExitAnimation != null) { - mExitAnimation.setStartTime(now); + if (mStartExitAnimation != null) { + mStartExitAnimation.setStartTime(now); } - mStarted = true; - } - - mExitTransformation.clear(); - boolean moreExit = false; - if (mExitAnimation != null) { - moreExit = mExitAnimation.getTransformation(now, mExitTransformation); - if (DEBUG) Slog.v(TAG, "Stepped exit: " + mExitTransformation); - if (!moreExit) { - if (DEBUG) Slog.v(TAG, "Exit animation done!"); - mExitAnimation.cancel(); - mExitAnimation = null; - mExitTransformation.clear(); - if (mSurface != null) { - mSurface.hide(); - } + if (mFinishEnterAnimation != null) { + mFinishEnterAnimation.setStartTime(0); + } + if (mFinishExitAnimation != null) { + mFinishExitAnimation.setStartTime(0); + } + if (mRotateEnterAnimation != null) { + mRotateEnterAnimation.setStartTime(now); } + if (mRotateExitAnimation != null) { + mRotateExitAnimation.setStartTime(now); + } + mAnimRunning = true; } - mEnterTransformation.clear(); - boolean moreEnter = false; - if (mEnterAnimation != null) { - moreEnter = mEnterAnimation.getTransformation(now, mEnterTransformation); - if (!moreEnter) { - mEnterAnimation.cancel(); - mEnterAnimation = null; - mEnterTransformation.clear(); - if (mBlackFrame != null) { - mBlackFrame.hide(); - } - } else { - if (mBlackFrame != null) { - mBlackFrame.setMatrix(mEnterTransformation.getMatrix()); - } + if (mFinishAnimReady && mFinishAnimStartTime < 0) { + if (DEBUG_STATE) Slog.v(TAG, "Step: finish anim now ready"); + mFinishAnimStartTime = now; + } + + // If the start animation is no longer running, we want to keep its + // transformation intact until the finish animation also completes. + + boolean moreStartExit = false; + if (mStartExitAnimation != null) { + mStartExitTransformation.clear(); + moreStartExit = mStartExitAnimation.getTransformation(now, mStartExitTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start exit: " + mStartExitTransformation); + if (!moreStartExit) { + if (DEBUG_STATE) Slog.v(TAG, "Start exit animation done!"); + mStartExitAnimation.cancel(); + mStartExitAnimation = null; + } + } + + boolean moreStartEnter = false; + if (mStartEnterAnimation != null) { + mStartEnterTransformation.clear(); + moreStartEnter = mStartEnterAnimation.getTransformation(now, mStartEnterTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start enter: " + mStartEnterTransformation); + if (!moreStartEnter) { + if (DEBUG_STATE) Slog.v(TAG, "Start enter animation done!"); + mStartEnterAnimation.cancel(); + mStartEnterAnimation = null; + } + } + + long finishNow = mFinishAnimReady ? (now - mFinishAnimStartTime) : 0; + if (DEBUG_STATE) Slog.v(TAG, "Step: finishNow=" + finishNow); + + mFinishExitTransformation.clear(); + boolean moreFinishExit = false; + if (mFinishExitAnimation != null) { + moreFinishExit = mFinishExitAnimation.getTransformation(finishNow, mFinishExitTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish exit: " + mFinishExitTransformation); + if (!moreStartExit && !moreFinishExit) { + if (DEBUG_STATE) Slog.v(TAG, "Finish exit animation done, clearing start/finish anims!"); + mStartExitTransformation.clear(); + mFinishExitAnimation.cancel(); + mFinishExitAnimation = null; + mFinishExitTransformation.clear(); + } + } + + mFinishEnterTransformation.clear(); + boolean moreFinishEnter = false; + if (mFinishEnterAnimation != null) { + moreFinishEnter = mFinishEnterAnimation.getTransformation(finishNow, mFinishEnterTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish enter: " + mFinishEnterTransformation); + if (!moreStartEnter && !moreFinishEnter) { + if (DEBUG_STATE) Slog.v(TAG, "Finish enter animation done, clearing start/finish anims!"); + mStartEnterTransformation.clear(); + mFinishEnterAnimation.cancel(); + mFinishEnterAnimation = null; + mFinishEnterTransformation.clear(); + } + } + + mRotateExitTransformation.clear(); + boolean moreRotateExit = false; + if (mRotateExitAnimation != null) { + moreRotateExit = mRotateExitAnimation.getTransformation(now, mRotateExitTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate exit: " + mRotateExitTransformation); + } + + if (!moreFinishExit && !moreRotateExit) { + if (DEBUG_STATE) Slog.v(TAG, "Rotate exit animation done!"); + mRotateExitAnimation.cancel(); + mRotateExitAnimation = null; + mRotateExitTransformation.clear(); + } + + mRotateEnterTransformation.clear(); + boolean moreRotateEnter = false; + if (mRotateEnterAnimation != null) { + moreRotateEnter = mRotateEnterAnimation.getTransformation(now, mRotateEnterTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate enter: " + mRotateEnterTransformation); + } + + if (!moreFinishEnter && !moreRotateEnter) { + if (DEBUG_STATE) Slog.v(TAG, "Rotate enter animation done!"); + mRotateEnterAnimation.cancel(); + mRotateEnterAnimation = null; + mRotateEnterTransformation.clear(); + } + + mExitTransformation.set(mRotateExitTransformation); + mExitTransformation.compose(mStartExitTransformation); + mExitTransformation.compose(mFinishExitTransformation); + + mEnterTransformation.set(mRotateEnterTransformation); + mEnterTransformation.compose(mStartEnterTransformation); + mEnterTransformation.compose(mFinishEnterTransformation); + + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Final exit: " + mExitTransformation); + if (DEBUG_TRANSFORMS) Slog.v(TAG, "Final enter: " + mEnterTransformation); + + if (!moreStartExit && !moreFinishExit && !moreRotateExit) { + if (mSurface != null) { + if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, hiding screenshot surface"); + mSurface.hide(); + } + } + + if (!moreStartEnter && !moreFinishEnter && !moreRotateEnter) { + if (mBlackFrame != null) { + if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, hiding black frame"); + mBlackFrame.hide(); + } + } else { + if (mBlackFrame != null) { + mBlackFrame.setMatrix(mEnterTransformation.getMatrix()); } } mSnapshotFinalMatrix.setConcat(mExitTransformation.getMatrix(), mSnapshotInitialMatrix); setSnapshotTransform(mSnapshotFinalMatrix, mExitTransformation.getAlpha()); - return moreEnter || moreExit; + final boolean more = moreStartEnter || moreStartExit || moreFinishEnter || moreFinishExit + || moreRotateEnter || moreRotateExit || !mFinishAnimReady; + + if (DEBUG_STATE) Slog.v(TAG, "Step: more=" + more); + + return more; } public Transformation getEnterTransformation() { diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index bd33e0c..620d74c 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -36,7 +36,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import com.android.internal.app.IBatteryStats; import com.android.internal.policy.PolicyManager; import com.android.internal.policy.impl.PhoneWindowManager; -import com.android.internal.view.BaseInputHandler; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; import com.android.internal.view.IInputMethodManager; @@ -96,6 +95,7 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseIntArray; import android.util.TypedValue; +import android.view.Choreographer; import android.view.Display; import android.view.Gravity; import android.view.IApplicationToken; @@ -107,8 +107,7 @@ import android.view.IWindowSession; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; -import android.view.InputHandler; -import android.view.InputQueue; +import android.view.InputEventReceiver; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; @@ -143,7 +142,8 @@ import java.util.List; /** {@hide} */ public class WindowManagerService extends IWindowManager.Stub - implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs { + implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs, + Choreographer.OnAnimateListener { static final String TAG = "WindowManager"; static final boolean DEBUG = false; static final boolean DEBUG_ADD_REMOVE = false; @@ -458,7 +458,7 @@ public class WindowManagerService extends IWindowManager.Stub int mDeferredRotationPauseCount; boolean mLayoutNeeded = true; - boolean mAnimationPending = false; + boolean mTraversalScheduled = false; boolean mDisplayFrozen = false; boolean mWaitingForConfig = false; boolean mWindowsFreezingScreen = false; @@ -505,7 +505,9 @@ public class WindowManagerService extends IWindowManager.Stub final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics(); final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics(); - H mH = new H(); + final H mH = new H(); + + final Choreographer mChoreographer = Choreographer.getInstance(); WindowState mCurrentFocus = null; WindowState mLastFocus = null; @@ -561,6 +563,7 @@ public class WindowManagerService extends IWindowManager.Stub float mWindowAnimationScale = 1.0f; float mTransitionAnimationScale = 1.0f; + float mAnimatorDurationScale = 1.0f; final InputManager mInputManager; @@ -571,18 +574,25 @@ public class WindowManagerService extends IWindowManager.Stub boolean mTurnOnScreen; DragState mDragState = null; - final InputHandler mDragInputHandler = new BaseInputHandler() { + + final class DragInputEventReceiver extends InputEventReceiver { + public DragInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + @Override - public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) { + public void onInputEvent(InputEvent event) { boolean handled = false; try { - if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0 + if (event instanceof MotionEvent + && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0 && mDragState != null) { + final MotionEvent motionEvent = (MotionEvent)event; boolean endDrag = false; - final float newX = event.getRawX(); - final float newY = event.getRawY(); + final float newX = motionEvent.getRawX(); + final float newY = motionEvent.getRawY(); - switch (event.getAction()) { + switch (motionEvent.getAction()) { case MotionEvent.ACTION_DOWN: { if (DEBUG_DRAG) { Slog.w(TAG, "Unexpected ACTION_DOWN in drag layer"); @@ -623,10 +633,10 @@ public class WindowManagerService extends IWindowManager.Stub } catch (Exception e) { Slog.e(TAG, "Exception caught by drag handleMotion", e); } finally { - finishedCallback.finished(handled); + finishInputEvent(event, handled); } } - }; + } /** * Whether the UI is currently running in touch mode (not showing @@ -686,6 +696,7 @@ public class WindowManagerService extends IWindowManager.Stub Looper.prepare(); WindowManagerService s = new WindowManagerService(mContext, mPM, mHaveInputMethods, mAllowBootMessages); + s.mChoreographer.addOnAnimateListener(s); android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_DISPLAY); android.os.Process.setCanSelfBackground(false); @@ -769,6 +780,8 @@ public class WindowManagerService extends IWindowManager.Stub Settings.System.WINDOW_ANIMATION_SCALE, mWindowAnimationScale); mTransitionAnimationScale = Settings.System.getFloat(context.getContentResolver(), Settings.System.TRANSITION_ANIMATION_SCALE, mTransitionAnimationScale); + mAnimatorDurationScale = Settings.System.getFloat(context.getContentResolver(), + Settings.System.ANIMATOR_DURATION_SCALE, mTransitionAnimationScale); // Track changes to DevicePolicyManager state so we can enable/disable keyguard. IntentFilter filter = new IntentFilter(); @@ -3448,7 +3461,7 @@ public class WindowManagerService extends IWindowManager.Stub // the value of the previous configuration. mTempConfiguration.setToDefaults(); mTempConfiguration.fontScale = currentConfig.fontScale; - if (computeNewConfigurationLocked(mTempConfiguration)) { + if (computeScreenConfigurationLocked(mTempConfiguration)) { if (currentConfig.diff(mTempConfiguration) != 0) { mWaitingForConfig = true; mLayoutNeeded = true; @@ -4652,6 +4665,7 @@ public class WindowManagerService extends IWindowManager.Stub switch (which) { case 0: mWindowAnimationScale = fixScale(scale); break; case 1: mTransitionAnimationScale = fixScale(scale); break; + case 2: mAnimatorDurationScale = fixScale(scale); break; } // Persist setting @@ -4671,6 +4685,9 @@ public class WindowManagerService extends IWindowManager.Stub if (scales.length >= 2) { mTransitionAnimationScale = fixScale(scales[1]); } + if (scales.length >= 3) { + mAnimatorDurationScale = fixScale(scales[2]); + } } // Persist setting @@ -4681,12 +4698,14 @@ public class WindowManagerService extends IWindowManager.Stub switch (which) { case 0: return mWindowAnimationScale; case 1: return mTransitionAnimationScale; + case 2: return mAnimatorDurationScale; } return 0; } public float[] getAnimationScales() { - return new float[] { mWindowAnimationScale, mTransitionAnimationScale }; + return new float[] { mWindowAnimationScale, mTransitionAnimationScale, + mAnimatorDurationScale }; } public int getSwitchState(int sw) { @@ -5364,6 +5383,14 @@ public class WindowManagerService extends IWindowManager.Stub startFreezingDisplayLocked(inTransaction); mInputManager.setDisplayOrientation(0, rotation); + // We need to update our screen size information to match the new + // rotation. Note that this is redundant with the later call to + // sendNewConfiguration() that must be called after this function + // returns... however we need to do the screen size part of that + // before then so we have the correct size to use when initializiation + // the rotation animation for the new rotation. + computeScreenConfigurationLocked(null); + if (!inTransaction) { if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setRotationUnchecked"); @@ -5374,7 +5401,11 @@ public class WindowManagerService extends IWindowManager.Stub // it doesn't support hardware OpenGL emulation yet. if (CUSTOM_SCREEN_ROTATION && mScreenRotationAnimation != null && mScreenRotationAnimation.hasScreenshot()) { - mScreenRotationAnimation.setRotation(rotation); + if (mScreenRotationAnimation.setRotation(rotation, mFxSession, + MAX_ANIMATION_DURATION, mTransitionAnimationScale, + mCurDisplayWidth, mCurDisplayHeight)) { + mChoreographer.scheduleAnimation(); + } } Surface.setOrientation(0, rotation); } finally { @@ -5862,7 +5893,7 @@ public class WindowManagerService extends IWindowManager.Stub Configuration computeNewConfigurationLocked() { Configuration config = new Configuration(); config.fontScale = 0; - if (!computeNewConfigurationLocked(config)) { + if (!computeScreenConfigurationLocked(config)) { return null; } return config; @@ -6013,12 +6044,10 @@ public class WindowManagerService extends IWindowManager.Stub return sw; } - boolean computeNewConfigurationLocked(Configuration config) { + boolean computeScreenConfigurationLocked(Configuration config) { if (mDisplay == null) { return false; } - - mInputManager.getInputConfiguration(config); // Use the effective "visual" dimensions based on current rotation final boolean rotated = (mRotation == Surface.ROTATION_90 @@ -6052,13 +6081,17 @@ public class WindowManagerService extends IWindowManager.Stub final int dw = mCurDisplayWidth; final int dh = mCurDisplayHeight; - int orientation = Configuration.ORIENTATION_SQUARE; - if (dw < dh) { - orientation = Configuration.ORIENTATION_PORTRAIT; - } else if (dw > dh) { - orientation = Configuration.ORIENTATION_LANDSCAPE; + if (config != null) { + mInputManager.getInputConfiguration(config); + + int orientation = Configuration.ORIENTATION_SQUARE; + if (dw < dh) { + orientation = Configuration.ORIENTATION_PORTRAIT; + } else if (dw > dh) { + orientation = Configuration.ORIENTATION_LANDSCAPE; + } + config.orientation = orientation; } - config.orientation = orientation; // Update real display metrics. mDisplay.getMetricsWithSize(mRealDisplayMetrics, mCurDisplayWidth, mCurDisplayHeight); @@ -6080,36 +6113,39 @@ public class WindowManagerService extends IWindowManager.Stub mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(dm, mCompatDisplayMetrics); - config.screenWidthDp = (int)(mPolicy.getConfigDisplayWidth(dw, dh, mRotation) - / dm.density); - config.screenHeightDp = (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation) - / dm.density); - computeSmallestWidthAndScreenLayout(rotated, dw, dh, dm.density, config); + if (config != null) { + config.screenWidthDp = (int)(mPolicy.getConfigDisplayWidth(dw, dh, mRotation) + / dm.density); + config.screenHeightDp = (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation) + / dm.density); + computeSmallestWidthAndScreenLayout(rotated, dw, dh, dm.density, config); - config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale); - config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale); - config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, dm, dw, dh); + config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale); + config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale); + config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, dm, dw, dh); - // Determine whether a hard keyboard is available and enabled. - boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS; - if (hardKeyboardAvailable != mHardKeyboardAvailable) { - mHardKeyboardAvailable = hardKeyboardAvailable; - mHardKeyboardEnabled = hardKeyboardAvailable; + // Determine whether a hard keyboard is available and enabled. + boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS; + if (hardKeyboardAvailable != mHardKeyboardAvailable) { + mHardKeyboardAvailable = hardKeyboardAvailable; + mHardKeyboardEnabled = hardKeyboardAvailable; - mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); - mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); - } - if (!mHardKeyboardEnabled) { - config.keyboard = Configuration.KEYBOARD_NOKEYS; + mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); + mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); + } + if (!mHardKeyboardEnabled) { + config.keyboard = Configuration.KEYBOARD_NOKEYS; + } + + // Update value of keyboardHidden, hardKeyboardHidden and navigationHidden + // based on whether a hard or soft keyboard is present, whether navigation keys + // are present and the lid switch state. + config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO; + config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; + config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO; + mPolicy.adjustConfigurationLw(config); } - // Update value of keyboardHidden, hardKeyboardHidden and navigationHidden - // based on whether a hard or soft keyboard is present, whether navigation keys - // are present and the lid switch state. - config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO; - config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; - config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO; - mPolicy.adjustConfigurationLw(config); return true; } @@ -6491,7 +6527,7 @@ public class WindowManagerService extends IWindowManager.Stub final class H extends Handler { public static final int REPORT_FOCUS_CHANGE = 2; public static final int REPORT_LOSING_FOCUS = 3; - public static final int ANIMATE = 4; + public static final int DO_TRAVERSAL = 4; public static final int ADD_STARTING = 5; public static final int REMOVE_STARTING = 6; public static final int FINISHED_STARTING = 7; @@ -6585,9 +6621,9 @@ public class WindowManagerService extends IWindowManager.Stub } } break; - case ANIMATE: { + case DO_TRAVERSAL: { synchronized(mWindowMap) { - mAnimationPending = false; + mTraversalScheduled = false; performLayoutAndPlaceSurfacesLocked(); } } break; @@ -6803,12 +6839,14 @@ public class WindowManagerService extends IWindowManager.Stub Settings.System.WINDOW_ANIMATION_SCALE, mWindowAnimationScale); Settings.System.putFloat(mContext.getContentResolver(), Settings.System.TRANSITION_ANIMATION_SCALE, mTransitionAnimationScale); + Settings.System.putFloat(mContext.getContentResolver(), + Settings.System.ANIMATOR_DURATION_SCALE, mAnimatorDurationScale); break; } case FORCE_GC: { synchronized(mWindowMap) { - if (mAnimationPending) { + if (mChoreographer.isAnimationScheduled()) { // If we are animating, don't do the gc now but // delay a bit so we don't interrupt the animation. mH.sendMessageDelayed(mH.obtainMessage(H.FORCE_GC), @@ -7116,7 +7154,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean configChanged = updateOrientationFromAppTokensLocked(false); mTempConfiguration.setToDefaults(); mTempConfiguration.fontScale = mCurConfiguration.fontScale; - if (computeNewConfigurationLocked(mTempConfiguration)) { + if (computeScreenConfigurationLocked(mTempConfiguration)) { if (mCurConfiguration.diff(mTempConfiguration) != 0) { configChanged = true; } @@ -7367,7 +7405,7 @@ public class WindowManagerService extends IWindowManager.Stub } else { mInLayout = false; if (mLayoutNeeded) { - requestAnimationLocked(0); + requestTraversalLocked(); } } if (mWindowsChanged && !mWindowChangeListeners.isEmpty()) { @@ -8800,10 +8838,9 @@ public class WindowManagerService extends IWindowManager.Stub needRelayout = adjustWallpaperWindowsLocked() != 0; } if (needRelayout) { - requestAnimationLocked(0); + requestTraversalLocked(); } else if (animating) { - final int refreshTimeUs = (int)(1000 / mDisplay.getRefreshRate()); - requestAnimationLocked(currentTime + refreshTimeUs - SystemClock.uptimeMillis()); + mChoreographer.scheduleAnimation(); } // Finally update all input windows now that the window changes have stabilized. @@ -8922,10 +8959,17 @@ public class WindowManagerService extends IWindowManager.Stub } } - void requestAnimationLocked(long delay) { - if (!mAnimationPending) { - mAnimationPending = true; - mH.sendMessageDelayed(mH.obtainMessage(H.ANIMATE), delay); + void requestTraversalLocked() { + if (!mTraversalScheduled) { + mTraversalScheduled = true; + mH.sendEmptyMessage(H.DO_TRAVERSAL); + } + } + + @Override + public void onAnimate() { + synchronized(mWindowMap) { + performLayoutAndPlaceSurfacesLocked(); } } @@ -9245,7 +9289,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ORIENTATION) Slog.i(TAG, "**** Dismissing screen rotation animation"); if (mScreenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION, mTransitionAnimationScale, mCurDisplayWidth, mCurDisplayHeight)) { - requestAnimationLocked(0); + mChoreographer.scheduleAnimation(); } else { mScreenRotationAnimation = null; updateRotation = true; @@ -9391,11 +9435,13 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public FakeWindow addFakeWindow(Looper looper, InputHandler inputHandler, + public FakeWindow addFakeWindow(Looper looper, + InputEventReceiver.Factory inputEventReceiverFactory, String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen) { synchronized (mWindowMap) { - FakeWindowImpl fw = new FakeWindowImpl(this, looper, inputHandler, name, windowType, + FakeWindowImpl fw = new FakeWindowImpl(this, looper, inputEventReceiverFactory, + name, windowType, layoutParamsFlags, canReceiveKeys, hasFocus, touchFullscreen); int i=0; while (i<mFakeWindows.size()) { @@ -9735,9 +9781,10 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mLastWindowForcedOrientation"); pw.print(mLastWindowForcedOrientation); pw.print(" mForcedAppOrientation="); pw.println(mForcedAppOrientation); pw.print(" mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount); - pw.print(" mAnimationPending="); pw.print(mAnimationPending); + pw.print(" mTraversalScheduled="); pw.print(mTraversalScheduled); pw.print(" mWindowAnimationScale="); pw.print(mWindowAnimationScale); pw.print(" mTransitionWindowAnimationScale="); pw.println(mTransitionAnimationScale); + pw.print(" mAnimatorDurationScale="); pw.println(mAnimatorDurationScale); pw.print(" mNextAppTransition=0x"); pw.print(Integer.toHexString(mNextAppTransition)); pw.print(" mAppTransitionReady="); pw.println(mAppTransitionReady); diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index 1067cad..6868cf6 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -1593,7 +1593,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { mService.applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_ENTER, true); } if (requestAnim) { - mService.requestAnimationLocked(0); + mService.mChoreographer.scheduleAnimation(); } return true; } @@ -1634,7 +1634,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { } } if (requestAnim) { - mService.requestAnimationLocked(0); + mService.mChoreographer.scheduleAnimation(); } return true; } diff --git a/services/jni/com_android_server_PowerManagerService.cpp b/services/jni/com_android_server_PowerManagerService.cpp index 5005864..d2b3118 100644 --- a/services/jni/com_android_server_PowerManagerService.cpp +++ b/services/jni/com_android_server_PowerManagerService.cpp @@ -28,6 +28,8 @@ #include <surfaceflinger/ISurfaceComposer.h> #include <surfaceflinger/SurfaceComposerClient.h> +#include <private/gui/ComposerService.h> + #include "com_android_server_PowerManagerService.h" namespace android { diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index 8659025..dd6c426 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -522,7 +522,7 @@ bool SensorService::SensorRecord::removeConnection( SensorService::SensorEventConnection::SensorEventConnection( const sp<SensorService>& service) - : mService(service), mChannel(new SensorChannel()) + : mService(service), mChannel(new BitTube()) { } @@ -604,7 +604,7 @@ status_t SensorService::SensorEventConnection::sendEvents( return size < 0 ? status_t(size) : status_t(NO_ERROR); } -sp<SensorChannel> SensorService::SensorEventConnection::getSensorChannel() const +sp<BitTube> SensorService::SensorEventConnection::getSensorChannel() const { return mChannel; } diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h index 85f4ecb..e357f96 100644 --- a/services/sensorservice/SensorService.h +++ b/services/sensorservice/SensorService.h @@ -29,7 +29,7 @@ #include <binder/BinderService.h> #include <gui/Sensor.h> -#include <gui/SensorChannel.h> +#include <gui/BitTube.h> #include <gui/ISensorServer.h> #include <gui/ISensorEventConnection.h> @@ -71,12 +71,12 @@ class SensorService : class SensorEventConnection : public BnSensorEventConnection { virtual ~SensorEventConnection(); virtual void onFirstRef(); - virtual sp<SensorChannel> getSensorChannel() const; + virtual sp<BitTube> 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; + sp<BitTube> const mChannel; mutable Mutex mConnectionLock; // protected by SensorService::mLock diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk index f63c0c1..42e280f 100644 --- a/services/surfaceflinger/Android.mk +++ b/services/surfaceflinger/Android.mk @@ -2,36 +2,42 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ - Layer.cpp \ - LayerBase.cpp \ - LayerDim.cpp \ - LayerScreenshot.cpp \ - DdmConnection.cpp \ - DisplayHardware/DisplayHardware.cpp \ + EventThread.cpp \ + Layer.cpp \ + LayerBase.cpp \ + LayerDim.cpp \ + LayerScreenshot.cpp \ + DdmConnection.cpp \ + DisplayHardware/DisplayHardware.cpp \ DisplayHardware/DisplayHardwareBase.cpp \ - DisplayHardware/HWComposer.cpp \ - GLExtensions.cpp \ - MessageQueue.cpp \ - SurfaceFlinger.cpp \ - SurfaceTextureLayer.cpp \ - Transform.cpp \ + DisplayHardware/HWComposer.cpp \ + DisplayHardware/VSyncBarrier.cpp \ + DisplayEventConnection.cpp \ + GLExtensions.cpp \ + MessageQueue.cpp \ + SurfaceFlinger.cpp \ + SurfaceTextureLayer.cpp \ + Transform.cpp \ LOCAL_CFLAGS:= -DLOG_TAG=\"SurfaceFlinger\" LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES +ifeq ($(TARGET_HAS_WAITFORVSYNC), true) + LOCAL_CFLAGS += -DHAS_WAITFORVSYNC +endif + ifeq ($(TARGET_BOARD_PLATFORM), omap3) LOCAL_CFLAGS += -DNO_RGBX_8888 endif ifeq ($(TARGET_BOARD_PLATFORM), omap4) LOCAL_CFLAGS += -DHAS_CONTEXT_PRIORITY + LOCAL_CFLAGS += -DUSE_TRIPLE_BUFFERING endif ifeq ($(TARGET_BOARD_PLATFORM), s5pc110) LOCAL_CFLAGS += -DHAS_CONTEXT_PRIORITY -DNEVER_DEFAULT_TO_ASYNC_MODE - LOCAL_CFLAGS += -DREFRESH_RATE=56 endif - LOCAL_SHARED_LIBRARIES := \ libcutils \ libhardware \ diff --git a/services/surfaceflinger/DisplayEventConnection.cpp b/services/surfaceflinger/DisplayEventConnection.cpp new file mode 100644 index 0000000..77ecbd2 --- /dev/null +++ b/services/surfaceflinger/DisplayEventConnection.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdint.h> +#include <sys/types.h> + +#include <gui/IDisplayEventConnection.h> +#include <gui/BitTube.h> +#include <gui/DisplayEventReceiver.h> + +#include <utils/Errors.h> + +#include "SurfaceFlinger.h" +#include "DisplayEventConnection.h" +#include "EventThread.h" + +// --------------------------------------------------------------------------- + +namespace android { + +// --------------------------------------------------------------------------- + +DisplayEventConnection::DisplayEventConnection( + const sp<EventThread>& eventThread) + : mEventThread(eventThread), mChannel(new BitTube()) +{ +} + +DisplayEventConnection::~DisplayEventConnection() { + mEventThread->unregisterDisplayEventConnection(this); +} + +void DisplayEventConnection::onFirstRef() { + // NOTE: mEventThread doesn't hold a strong reference on us + mEventThread->registerDisplayEventConnection(this); +} + +sp<BitTube> DisplayEventConnection::getDataChannel() const { + return mChannel; +} + +void DisplayEventConnection::setVsyncRate(uint32_t count) { + mEventThread->setVsyncRate(count, this); +} + +void DisplayEventConnection::requestNextVsync() { + mEventThread->requestNextVsync(this); +} + +status_t DisplayEventConnection::postEvent(const DisplayEventReceiver::Event& event) +{ + ssize_t size = mChannel->write(&event, sizeof(DisplayEventReceiver::Event)); + return size < 0 ? status_t(size) : status_t(NO_ERROR); +} + +// --------------------------------------------------------------------------- + +}; // namespace android diff --git a/services/surfaceflinger/DisplayEventConnection.h b/services/surfaceflinger/DisplayEventConnection.h new file mode 100644 index 0000000..cc3ee36 --- /dev/null +++ b/services/surfaceflinger/DisplayEventConnection.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_SURFACE_FLINGER_DISPLAY_EVENT_CONNECTION_H +#define ANDROID_SURFACE_FLINGER_DISPLAY_EVENT_CONNECTION_H + +#include <stdint.h> +#include <sys/types.h> + +#include <gui/IDisplayEventConnection.h> + +#include <utils/Errors.h> +#include <gui/DisplayEventReceiver.h> + +// --------------------------------------------------------------------------- + +namespace android { + +// --------------------------------------------------------------------------- + +class BitTube; +class EventThread; + +// --------------------------------------------------------------------------- + +class DisplayEventConnection : public BnDisplayEventConnection { +public: + DisplayEventConnection(const sp<EventThread>& flinger); + + status_t postEvent(const DisplayEventReceiver::Event& event); + +private: + virtual ~DisplayEventConnection(); + virtual void onFirstRef(); + virtual sp<BitTube> getDataChannel() const; + virtual void setVsyncRate(uint32_t count); + virtual void requestNextVsync(); // asynchronous + + sp<EventThread> const mEventThread; + sp<BitTube> const mChannel; +}; + +// --------------------------------------------------------------------------- + +}; // namespace android + +// --------------------------------------------------------------------------- + +#endif /* ANDROID_SURFACE_FLINGER_DISPLAY_EVENT_CONNECTION_H */ diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp index 61096e5..986aec5 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp +++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp @@ -140,6 +140,7 @@ void DisplayHardware::init(uint32_t dpy) mDpiX = mNativeWindow->xdpi; mDpiY = mNativeWindow->ydpi; mRefreshRate = fbDev->fps; + mNextFakeVSync = 0; /* FIXME: this is a temporary HACK until we are able to report the refresh rate @@ -152,6 +153,8 @@ void DisplayHardware::init(uint32_t dpy) #warning "refresh rate set via makefile to REFRESH_RATE" #endif + mRefreshPeriod = nsecs_t(1e9 / mRefreshRate); + EGLint w, h, dummy; EGLint numConfigs=0; EGLSurface surface; @@ -346,12 +349,52 @@ uint32_t DisplayHardware::getPageFlipCount() const { return mPageFlipCount; } -status_t DisplayHardware::compositionComplete() const { - return mNativeWindow->compositionComplete(); +// this needs to be thread safe +nsecs_t DisplayHardware::waitForRefresh() const { + nsecs_t timestamp; + if (mVSync.wait(×tamp) < 0) { + // vsync not supported! + usleep( getDelayToNextVSyncUs(×tamp) ); + } + mLastHwVSync = timestamp; // FIXME: Not thread safe + return timestamp; +} + +nsecs_t DisplayHardware::getRefreshTimestamp() const { + // this returns the last refresh timestamp. + // if the last one is not available, we estimate it based on + // the refresh period and whatever closest timestamp we have. + nsecs_t now = systemTime(); + return now - ((now - mLastHwVSync) % mRefreshPeriod); +} + +nsecs_t DisplayHardware::getRefreshPeriod() const { + return mRefreshPeriod; } -int DisplayHardware::getCurrentBufferIndex() const { - return mNativeWindow->getCurrentBufferIndex(); +int32_t DisplayHardware::getDelayToNextVSyncUs(nsecs_t* timestamp) const { + Mutex::Autolock _l(mFakeVSyncMutex); + const nsecs_t period = mRefreshPeriod; + const nsecs_t now = systemTime(CLOCK_MONOTONIC); + nsecs_t next_vsync = mNextFakeVSync; + nsecs_t sleep = next_vsync - now; + if (sleep < 0) { + // we missed, find where the next vsync should be + sleep = (period - ((now - next_vsync) % period)); + next_vsync = now + sleep; + } + mNextFakeVSync = next_vsync + period; + timestamp[0] = next_vsync; + + // round to next microsecond + int32_t sleep_us = (sleep + 999LL) / 1000LL; + + // guaranteed to be > 0 + return sleep_us; +} + +status_t DisplayHardware::compositionComplete() const { + return mNativeWindow->compositionComplete(); } void DisplayHardware::flip(const Region& dirty) const diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.h b/services/surfaceflinger/DisplayHardware/DisplayHardware.h index f02c954..02be4dc 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardware.h +++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.h @@ -32,6 +32,7 @@ #include "GLExtensions.h" #include "DisplayHardware/DisplayHardwareBase.h" +#include "DisplayHardware/VSyncBarrier.h" namespace android { @@ -74,6 +75,11 @@ public: uint32_t getMaxTextureSize() const; uint32_t getMaxViewportDims() const; + // waits for the next vsync and returns the timestamp of when it happened + nsecs_t waitForRefresh() const; + nsecs_t getRefreshPeriod() const; + nsecs_t getRefreshTimestamp() const; + uint32_t getPageFlipCount() const; EGLDisplay getEGLDisplay() const { return mDisplay; } @@ -89,12 +95,10 @@ public: } inline Rect bounds() const { return getBounds(); } - // only for debugging - int getCurrentBufferIndex() const; - private: void init(uint32_t displayIndex) __attribute__((noinline)); void fini() __attribute__((noinline)); + int32_t getDelayToNextVSyncUs(nsecs_t* timestamp) const; sp<SurfaceFlinger> mFlinger; EGLDisplay mDisplay; @@ -112,7 +116,13 @@ private: mutable uint32_t mPageFlipCount; GLint mMaxViewportDims[2]; GLint mMaxTextureSize; - + VSyncBarrier mVSync; + + mutable Mutex mFakeVSyncMutex; + mutable nsecs_t mNextFakeVSync; + nsecs_t mRefreshPeriod; + mutable nsecs_t mLastHwVSync; + HWComposer* mHwc; sp<FramebufferNativeWindow> mNativeWindow; diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp index f4afeea..69f1aca 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp +++ b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * Copyright (C) 2012 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,7 +14,6 @@ * limitations under the License. */ -#include <assert.h> #include <errno.h> #include <stdlib.h> #include <stdio.h> @@ -22,15 +21,6 @@ #include <unistd.h> #include <fcntl.h> -#include <signal.h> -#include <termios.h> -#include <sys/ioctl.h> -#include <sys/mman.h> -#include <sys/time.h> -#include <sys/types.h> -#include <sys/resource.h> - -#include <linux/unistd.h> #include <utils/Log.h> @@ -45,39 +35,22 @@ static char const * const kWakeFileName = "/sys/power/wait_for_fb_wake"; // ---------------------------------------------------------------------------- -DisplayHardwareBase::DisplayEventThreadBase::DisplayEventThreadBase( +DisplayHardwareBase::DisplayEventThread::DisplayEventThread( const sp<SurfaceFlinger>& flinger) : Thread(false), mFlinger(flinger) { } -DisplayHardwareBase::DisplayEventThreadBase::~DisplayEventThreadBase() { +DisplayHardwareBase::DisplayEventThread::~DisplayEventThread() { } -// ---------------------------------------------------------------------------- - -DisplayHardwareBase::DisplayEventThread::DisplayEventThread( - const sp<SurfaceFlinger>& flinger) - : DisplayEventThreadBase(flinger) -{ +status_t DisplayHardwareBase::DisplayEventThread::initCheck() const { + return ((access(kSleepFileName, R_OK) == 0 && + access(kWakeFileName, R_OK) == 0)) ? NO_ERROR : NO_INIT; } -DisplayHardwareBase::DisplayEventThread::~DisplayEventThread() -{ -} +bool DisplayHardwareBase::DisplayEventThread::threadLoop() { -bool DisplayHardwareBase::DisplayEventThread::threadLoop() -{ - int err = 0; - char buf; - int fd; - - fd = open(kSleepFileName, O_RDONLY, 0); - do { - err = read(fd, &buf, 1); - } while (err < 0 && errno == EINTR); - close(fd); - ALOGW_IF(err<0, "ANDROID_WAIT_FOR_FB_SLEEP failed (%s)", strerror(errno)); - if (err >= 0) { + if (waitForFbSleep() == NO_ERROR) { sp<SurfaceFlinger> flinger = mFlinger.promote(); ALOGD("About to give-up screen, flinger = %p", flinger.get()); if (flinger != 0) { @@ -85,39 +58,51 @@ bool DisplayHardwareBase::DisplayEventThread::threadLoop() flinger->screenReleased(0); mBarrier.wait(); } + if (waitForFbWake() == NO_ERROR) { + sp<SurfaceFlinger> flinger = mFlinger.promote(); + ALOGD("Screen about to return, flinger = %p", flinger.get()); + if (flinger != 0) { + flinger->screenAcquired(0); + } + return true; + } } - fd = open(kWakeFileName, O_RDONLY, 0); + + // error, exit the thread + return false; +} + +status_t DisplayHardwareBase::DisplayEventThread::waitForFbSleep() { + int err = 0; + char buf; + int fd = open(kSleepFileName, O_RDONLY, 0); + // if the file doesn't exist, the error will be caught in read() below do { - err = read(fd, &buf, 1); + err = read(fd, &buf, 1); } while (err < 0 && errno == EINTR); close(fd); - ALOGW_IF(err<0, "ANDROID_WAIT_FOR_FB_WAKE failed (%s)", strerror(errno)); - if (err >= 0) { - sp<SurfaceFlinger> flinger = mFlinger.promote(); - ALOGD("Screen about to return, flinger = %p", flinger.get()); - if (flinger != 0) - flinger->screenAcquired(0); - } - return true; + ALOGE_IF(err<0, "*** ANDROID_WAIT_FOR_FB_SLEEP failed (%s)", strerror(errno)); + return err < 0 ? -errno : int(NO_ERROR); } -status_t DisplayHardwareBase::DisplayEventThread::releaseScreen() const -{ - mBarrier.open(); - return NO_ERROR; +status_t DisplayHardwareBase::DisplayEventThread::waitForFbWake() { + int err = 0; + char buf; + int fd = open(kWakeFileName, O_RDONLY, 0); + // if the file doesn't exist, the error will be caught in read() below + do { + err = read(fd, &buf, 1); + } while (err < 0 && errno == EINTR); + close(fd); + ALOGE_IF(err<0, "*** ANDROID_WAIT_FOR_FB_WAKE failed (%s)", strerror(errno)); + return err < 0 ? -errno : int(NO_ERROR); } -status_t DisplayHardwareBase::DisplayEventThread::readyToRun() -{ +status_t DisplayHardwareBase::DisplayEventThread::releaseScreen() const { + mBarrier.open(); return NO_ERROR; } -status_t DisplayHardwareBase::DisplayEventThread::initCheck() const -{ - return ((access(kSleepFileName, R_OK) == 0 && - access(kWakeFileName, R_OK) == 0)) ? NO_ERROR : NO_INIT; -} - // ---------------------------------------------------------------------------- DisplayHardwareBase::DisplayHardwareBase(const sp<SurfaceFlinger>& flinger, @@ -127,35 +112,35 @@ DisplayHardwareBase::DisplayHardwareBase(const sp<SurfaceFlinger>& flinger, mDisplayEventThread = new DisplayEventThread(flinger); } -DisplayHardwareBase::~DisplayHardwareBase() -{ +void DisplayHardwareBase::startSleepManagement() const { + if (mDisplayEventThread->initCheck() == NO_ERROR) { + mDisplayEventThread->run("DisplayEventThread", PRIORITY_URGENT_DISPLAY); + } else { + ALOGW("/sys/power/wait_for_fb_{wake|sleep} don't exist"); + } +} + +DisplayHardwareBase::~DisplayHardwareBase() { // request exit mDisplayEventThread->requestExitAndWait(); } -bool DisplayHardwareBase::canDraw() const -{ +bool DisplayHardwareBase::canDraw() const { return mScreenAcquired; } -void DisplayHardwareBase::releaseScreen() const -{ +void DisplayHardwareBase::releaseScreen() const { status_t err = mDisplayEventThread->releaseScreen(); if (err >= 0) { mScreenAcquired = false; } } -void DisplayHardwareBase::acquireScreen() const -{ - status_t err = mDisplayEventThread->acquireScreen(); - if (err >= 0) { - mScreenAcquired = true; - } +void DisplayHardwareBase::acquireScreen() const { + mScreenAcquired = true; } -bool DisplayHardwareBase::isScreenAcquired() const -{ +bool DisplayHardwareBase::isScreenAcquired() const { return mScreenAcquired; } diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h index ef2df43..fba211b 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h +++ b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 The Android Open Source Project + * Copyright (C) 2012 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. @@ -20,8 +20,6 @@ #include <stdint.h> #include <utils/RefBase.h> #include <utils/threads.h> -#include <linux/kd.h> -#include <linux/vt.h> #include "Barrier.h" namespace android { @@ -31,11 +29,13 @@ class SurfaceFlinger; class DisplayHardwareBase { public: - DisplayHardwareBase( - const sp<SurfaceFlinger>& flinger, - uint32_t displayIndex); + DisplayHardwareBase( + const sp<SurfaceFlinger>& flinger, + uint32_t displayIndex); - ~DisplayHardwareBase(); + ~DisplayHardwareBase(); + + void startSleepManagement() const; // console management void releaseScreen() const; @@ -46,34 +46,21 @@ public: private: - class DisplayEventThreadBase : public Thread { - protected: + class DisplayEventThread : public Thread { wp<SurfaceFlinger> mFlinger; - public: - DisplayEventThreadBase(const sp<SurfaceFlinger>& flinger); - virtual ~DisplayEventThreadBase(); - virtual void onFirstRef() { - run("DisplayEventThread", PRIORITY_URGENT_DISPLAY); - } - virtual status_t acquireScreen() const { return NO_ERROR; }; - virtual status_t releaseScreen() const { return NO_ERROR; }; - virtual status_t initCheck() const = 0; - }; - - class DisplayEventThread : public DisplayEventThreadBase - { mutable Barrier mBarrier; + status_t waitForFbSleep(); + status_t waitForFbWake(); public: - DisplayEventThread(const sp<SurfaceFlinger>& flinger); + DisplayEventThread(const sp<SurfaceFlinger>& flinger); virtual ~DisplayEventThread(); virtual bool threadLoop(); - virtual status_t readyToRun(); - virtual status_t releaseScreen() const; - virtual status_t initCheck() const; + status_t releaseScreen() const; + status_t initCheck() const; }; - sp<DisplayEventThreadBase> mDisplayEventThread; - mutable int mScreenAcquired; + sp<DisplayEventThread> mDisplayEventThread; + mutable int mScreenAcquired; }; }; // namespace android diff --git a/services/surfaceflinger/DisplayHardware/VSyncBarrier.cpp b/services/surfaceflinger/DisplayHardware/VSyncBarrier.cpp new file mode 100644 index 0000000..187da20 --- /dev/null +++ b/services/surfaceflinger/DisplayHardware/VSyncBarrier.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdint.h> +#include <sys/types.h> + +#include <utils/Errors.h> + +#include <fcntl.h> +#include <sys/ioctl.h> +#include <linux/fb.h> + +#include "DisplayHardware/VSyncBarrier.h" + +#ifndef FBIO_WAITFORVSYNC +#define FBIO_WAITFORVSYNC _IOW('F', 0x20, __u32) +#endif + +namespace android { +// --------------------------------------------------------------------------- + +VSyncBarrier::VSyncBarrier() : mFd(-EINVAL) { +#if HAS_WAITFORVSYNC + mFd = open("/dev/graphics/fb0", O_RDWR); + if (mFd < 0) { + mFd = -errno; + } + // try to see if FBIO_WAITFORVSYNC is supported + uint32_t crt = 0; + int err = ioctl(mFd, FBIO_WAITFORVSYNC, &crt); + if (err < 0) { + close(mFd); + mFd = -EINVAL; + } +#endif +} + +VSyncBarrier::~VSyncBarrier() { + if (mFd >= 0) { + close(mFd); + } +} + +status_t VSyncBarrier::initCheck() const { + return mFd < 0 ? mFd : status_t(NO_ERROR); +} + +// this must be thread-safe +status_t VSyncBarrier::wait(nsecs_t* timestamp) const { + if (mFd < 0) { + return mFd; + } + + int err; + uint32_t crt = 0; + do { + err = ioctl(mFd, FBIO_WAITFORVSYNC, &crt); + } while (err<0 && errno==EINTR); + if (err < 0) { + return -errno; + } + // ideally this would come from the driver + timestamp[0] = systemTime(); + return NO_ERROR; +} + +// --------------------------------------------------------------------------- +}; // namespace android diff --git a/services/surfaceflinger/DisplayHardware/VSyncBarrier.h b/services/surfaceflinger/DisplayHardware/VSyncBarrier.h new file mode 100644 index 0000000..3c32950 --- /dev/null +++ b/services/surfaceflinger/DisplayHardware/VSyncBarrier.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_SURFACE_FLINGER_VSYNCBARRIER_H_ +#define ANDROID_SURFACE_FLINGER_VSYNCBARRIER_H_ + +#include <stdint.h> +#include <sys/types.h> + +#include <utils/Errors.h> +#include <utils/Timers.h> + +namespace android { +// --------------------------------------------------------------------------- + +class VSyncBarrier { + int mFd; +public: + VSyncBarrier(); + ~VSyncBarrier(); + status_t initCheck() const; + status_t wait(nsecs_t* timestamp) const; +}; + +// --------------------------------------------------------------------------- +}; // namespace android + +#endif /* ANDROID_SURFACE_FLINGER_VSYNCBARRIER_H_ */ diff --git a/services/surfaceflinger/EventThread.cpp b/services/surfaceflinger/EventThread.cpp new file mode 100644 index 0000000..af0da0b --- /dev/null +++ b/services/surfaceflinger/EventThread.cpp @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdint.h> +#include <sys/types.h> + +#include <gui/IDisplayEventConnection.h> +#include <gui/DisplayEventReceiver.h> + +#include <utils/Errors.h> + +#include "DisplayHardware/DisplayHardware.h" +#include "DisplayEventConnection.h" +#include "EventThread.h" +#include "SurfaceFlinger.h" + +// --------------------------------------------------------------------------- + +namespace android { + +// --------------------------------------------------------------------------- + +EventThread::EventThread(const sp<SurfaceFlinger>& flinger) + : mFlinger(flinger), + mHw(flinger->graphicPlane(0).displayHardware()), + mLastVSyncTimestamp(0), + mDeliveredEvents(0) +{ +} + +void EventThread::onFirstRef() { + run("EventThread", PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE); +} + +sp<DisplayEventConnection> EventThread::createEventConnection() const { + return new DisplayEventConnection(const_cast<EventThread*>(this)); +} + +nsecs_t EventThread::getLastVSyncTimestamp() const { + Mutex::Autolock _l(mLock); + return mLastVSyncTimestamp; +} + +nsecs_t EventThread::getVSyncPeriod() const { + return mHw.getRefreshPeriod(); + +} + +status_t EventThread::registerDisplayEventConnection( + const sp<DisplayEventConnection>& connection) { + Mutex::Autolock _l(mLock); + ConnectionInfo info; + mDisplayEventConnections.add(connection, info); + mCondition.signal(); + return NO_ERROR; +} + +status_t EventThread::unregisterDisplayEventConnection( + const wp<DisplayEventConnection>& connection) { + Mutex::Autolock _l(mLock); + mDisplayEventConnections.removeItem(connection); + mCondition.signal(); + return NO_ERROR; +} + +void EventThread::removeDisplayEventConnection( + const wp<DisplayEventConnection>& connection) { + Mutex::Autolock _l(mLock); + mDisplayEventConnections.removeItem(connection); +} + +EventThread::ConnectionInfo* EventThread::getConnectionInfoLocked( + const wp<DisplayEventConnection>& connection) { + ssize_t index = mDisplayEventConnections.indexOfKey(connection); + if (index < 0) return NULL; + return &mDisplayEventConnections.editValueAt(index); +} + +void EventThread::setVsyncRate(uint32_t count, + const wp<DisplayEventConnection>& connection) { + if (int32_t(count) >= 0) { // server must protect against bad params + Mutex::Autolock _l(mLock); + ConnectionInfo* info = getConnectionInfoLocked(connection); + if (info) { + const int32_t new_count = (count == 0) ? -1 : count; + if (info->count != new_count) { + info->count = new_count; + mCondition.signal(); + } + } + } +} + +void EventThread::requestNextVsync( + const wp<DisplayEventConnection>& connection) { + Mutex::Autolock _l(mLock); + ConnectionInfo* info = getConnectionInfoLocked(connection); + if (info && info->count < 0) { + info->count = 0; + mCondition.signal(); + } +} + +bool EventThread::threadLoop() { + + nsecs_t timestamp; + DisplayEventReceiver::Event vsync; + Vector< wp<DisplayEventConnection> > displayEventConnections; + + { // scope for the lock + Mutex::Autolock _l(mLock); + do { + // see if we need to wait for the VSYNC at all + do { + bool waitForNextVsync = false; + size_t count = mDisplayEventConnections.size(); + for (size_t i=0 ; i<count ; i++) { + const ConnectionInfo& info( + mDisplayEventConnections.valueAt(i)); + if (info.count >= 0) { + // at least one continuous mode or active one-shot event + waitForNextVsync = true; + break; + } + } + + if (waitForNextVsync) + break; + + mCondition.wait(mLock); + } while(true); + + // at least one listener requested VSYNC + mLock.unlock(); + timestamp = mHw.waitForRefresh(); + mLock.lock(); + mDeliveredEvents++; + mLastVSyncTimestamp = timestamp; + + // now see if we still need to report this VSYNC event + const size_t count = mDisplayEventConnections.size(); + for (size_t i=0 ; i<count ; i++) { + bool reportVsync = false; + const ConnectionInfo& info( + mDisplayEventConnections.valueAt(i)); + if (info.count >= 1) { + if (info.count==1 || (mDeliveredEvents % info.count) == 0) { + // continuous event, and time to report it + reportVsync = true; + } + } else if (info.count >= -1) { + ConnectionInfo& info( + mDisplayEventConnections.editValueAt(i)); + if (info.count == 0) { + // fired this time around + reportVsync = true; + } + info.count--; + } + if (reportVsync) { + displayEventConnections.add(mDisplayEventConnections.keyAt(i)); + } + } + } while (!displayEventConnections.size()); + + // dispatch vsync events to listeners... + vsync.header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC; + vsync.header.timestamp = timestamp; + vsync.vsync.count = mDeliveredEvents; + } + + const size_t count = displayEventConnections.size(); + for (size_t i=0 ; i<count ; i++) { + sp<DisplayEventConnection> conn(displayEventConnections[i].promote()); + // make sure the connection didn't die + if (conn != NULL) { + status_t err = conn->postEvent(vsync); + if (err == -EAGAIN || err == -EWOULDBLOCK) { + // The destination doesn't accept events anymore, it's probably + // full. For now, we just drop the events on the floor. + // Note that some events cannot be dropped and would have to be + // re-sent later. Right-now we don't have the ability to do + // this, but it doesn't matter for VSYNC. + } else if (err < 0) { + // handle any other error on the pipe as fatal. the only + // reasonable thing to do is to clean-up this connection. + // The most common error we'll get here is -EPIPE. + removeDisplayEventConnection(displayEventConnections[i]); + } + } else { + // somehow the connection is dead, but we still have it in our list + // just clean the list. + removeDisplayEventConnection(displayEventConnections[i]); + } + } + + // clear all our references without holding mLock + displayEventConnections.clear(); + + return true; +} + +status_t EventThread::readyToRun() { + ALOGI("EventThread ready to run."); + return NO_ERROR; +} + +void EventThread::dump(String8& result, char* buffer, size_t SIZE) const { + Mutex::Autolock _l(mLock); + result.append("VSYNC state:\n"); + snprintf(buffer, SIZE, " numListeners=%u, events-delivered: %u\n", + mDisplayEventConnections.size(), mDeliveredEvents); + result.append(buffer); +} + +// --------------------------------------------------------------------------- + +}; // namespace android diff --git a/services/surfaceflinger/EventThread.h b/services/surfaceflinger/EventThread.h new file mode 100644 index 0000000..3a3071e --- /dev/null +++ b/services/surfaceflinger/EventThread.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_SURFACE_FLINGER_EVENT_THREAD_H +#define ANDROID_SURFACE_FLINGER_EVENT_THREAD_H + +#include <stdint.h> +#include <sys/types.h> + +#include <gui/IDisplayEventConnection.h> + +#include <utils/Errors.h> +#include <utils/threads.h> +#include <utils/KeyedVector.h> + +#include "DisplayEventConnection.h" + +// --------------------------------------------------------------------------- + +namespace android { + +// --------------------------------------------------------------------------- + +class SurfaceFlinger; +class DisplayHardware; +class DisplayEventConnection; + +// --------------------------------------------------------------------------- + +class EventThread : public Thread { + friend class DisplayEventConnection; + +public: + EventThread(const sp<SurfaceFlinger>& flinger); + + sp<DisplayEventConnection> createEventConnection() const; + + status_t registerDisplayEventConnection( + const sp<DisplayEventConnection>& connection); + + status_t unregisterDisplayEventConnection( + const wp<DisplayEventConnection>& connection); + + void setVsyncRate(uint32_t count, + const wp<DisplayEventConnection>& connection); + + void requestNextVsync(const wp<DisplayEventConnection>& connection); + + nsecs_t getLastVSyncTimestamp() const; + + nsecs_t getVSyncPeriod() const; + + void dump(String8& result, char* buffer, size_t SIZE) const; + +private: + virtual bool threadLoop(); + virtual status_t readyToRun(); + virtual void onFirstRef(); + + struct ConnectionInfo { + ConnectionInfo() : count(-1) { } + + // count >= 1 : continuous event. count is the vsync rate + // count == 0 : one-shot event that has not fired + // count ==-1 : one-shot event that fired this round / disabled + // count ==-2 : one-shot event that fired the round before + int32_t count; + }; + + void removeDisplayEventConnection( + const wp<DisplayEventConnection>& connection); + + ConnectionInfo* getConnectionInfoLocked( + const wp<DisplayEventConnection>& connection); + + // constants + sp<SurfaceFlinger> mFlinger; + const DisplayHardware& mHw; + + mutable Mutex mLock; + mutable Condition mCondition; + + // protected by mLock + KeyedVector< wp<DisplayEventConnection>, ConnectionInfo > mDisplayEventConnections; + nsecs_t mLastVSyncTimestamp; + + // main thread only + size_t mDeliveredEvents; +}; + +// --------------------------------------------------------------------------- + +}; // namespace android + +// --------------------------------------------------------------------------- + +#endif /* ANDROID_SURFACE_FLINGER_EVENT_THREAD_H */ diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index d4c4b1f..3e6b872 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -38,6 +38,7 @@ #include "Layer.h" #include "SurfaceFlinger.h" #include "SurfaceTextureLayer.h" +#include <math.h> #define DEBUG_RESIZE 0 @@ -54,6 +55,9 @@ Layer::Layer(SurfaceFlinger* flinger, mCurrentTransform(0), mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE), mCurrentOpacity(true), + mRefreshPending(0), + mFrameLatencyNeeded(false), + mFrameLatencyOffset(0), mFormat(PIXEL_FORMAT_NONE), mGLExtensions(GLExtensions::getInstance()), mOpaqueLayer(true), @@ -65,6 +69,17 @@ Layer::Layer(SurfaceFlinger* flinger, glGenTextures(1, &mTextureName); } +void Layer::onLayerDisplayed() { + if (mFrameLatencyNeeded) { + const DisplayHardware& hw(graphicPlane(0).displayHardware()); + mFrameStats[mFrameLatencyOffset].timestamp = mSurfaceTexture->getTimestamp(); + mFrameStats[mFrameLatencyOffset].set = systemTime(); + mFrameStats[mFrameLatencyOffset].vsync = hw.getRefreshTimestamp(); + mFrameLatencyOffset = (mFrameLatencyOffset + 1) % 128; + mFrameLatencyNeeded = false; + } +} + void Layer::onFirstRef() { LayerBaseClient::onFirstRef(); @@ -83,7 +98,12 @@ void Layer::onFirstRef() mSurfaceTexture = new SurfaceTextureLayer(mTextureName, this); mSurfaceTexture->setFrameAvailableListener(new FrameQueuedListener(this)); mSurfaceTexture->setSynchronousMode(true); +#ifdef USE_TRIPLE_BUFFERING +#warning "using triple buffering" + mSurfaceTexture->setBufferCountServer(3); +#else mSurfaceTexture->setBufferCountServer(2); +#endif } Layer::~Layer() @@ -94,7 +114,7 @@ Layer::~Layer() void Layer::onFrameQueued() { android_atomic_inc(&mQueuedFrames); - mFlinger->signalEvent(); + mFlinger->signalLayerUpdate(); } // called with SurfaceFlinger::mStateLock as soon as the layer is entered @@ -388,16 +408,37 @@ bool Layer::isCropped() const { // pageflip handling... // ---------------------------------------------------------------------------- +bool Layer::onPreComposition() +{ + // if there was more than one pending update, request a refresh + if (mRefreshPending >= 2) { + mRefreshPending = 0; + return true; + } + mRefreshPending = 0; + return false; +} + void Layer::lockPageFlip(bool& recomputeVisibleRegions) { if (mQueuedFrames > 0) { + + // if we've already called updateTexImage() without going through + // a composition step, we have to skip this layer at this point + // because we cannot call updateTeximage() without a corresponding + // compositionComplete() call. + // we'll trigger an update in onPreComposition(). + if (mRefreshPending++) { + return; + } + // Capture the old state of the layer for comparisons later const bool oldOpacity = isOpaque(); sp<GraphicBuffer> oldActiveBuffer = mActiveBuffer; // signal another event if we have more frames pending if (android_atomic_dec(&mQueuedFrames) > 1) { - mFlinger->signalEvent(); + mFlinger->signalLayerUpdate(); } if (mSurfaceTexture->updateTexImage() < NO_ERROR) { @@ -408,6 +449,7 @@ void Layer::lockPageFlip(bool& recomputeVisibleRegions) // update the active buffer mActiveBuffer = mSurfaceTexture->getCurrentBuffer(); + mFrameLatencyNeeded = true; const Rect crop(mSurfaceTexture->getCurrentCrop()); const uint32_t transform(mSurfaceTexture->getCurrentTransform()); @@ -499,6 +541,10 @@ void Layer::lockPageFlip(bool& recomputeVisibleRegions) void Layer::unlockPageFlip( const Transform& planeTransform, Region& outDirtyRegion) { + if (mRefreshPending >= 2) { + return; + } + Region dirtyRegion(mPostedDirtyRegion); if (!dirtyRegion.isEmpty()) { mPostedDirtyRegion.clear(); @@ -532,9 +578,9 @@ void Layer::dump(String8& result, char* buffer, size_t SIZE) const snprintf(buffer, SIZE, " " "format=%2d, activeBuffer=[%4ux%4u:%4u,%3X]," - " transform-hint=0x%02x, queued-frames=%d\n", + " transform-hint=0x%02x, queued-frames=%d, mRefreshPending=%d\n", mFormat, w0, h0, s0,f0, - getTransformHint(), mQueuedFrames); + getTransformHint(), mQueuedFrames, mRefreshPending); result.append(buffer); @@ -543,6 +589,32 @@ void Layer::dump(String8& result, char* buffer, size_t SIZE) const } } +void Layer::dumpStats(String8& result, char* buffer, size_t SIZE) const +{ + LayerBaseClient::dumpStats(result, buffer, SIZE); + const size_t o = mFrameLatencyOffset; + const DisplayHardware& hw(graphicPlane(0).displayHardware()); + const nsecs_t period = hw.getRefreshPeriod(); + result.appendFormat("%lld\n", period); + for (size_t i=0 ; i<128 ; i++) { + const size_t index = (o+i) % 128; + const nsecs_t time_app = mFrameStats[index].timestamp; + const nsecs_t time_set = mFrameStats[index].set; + const nsecs_t time_vsync = mFrameStats[index].vsync; + result.appendFormat("%lld\t%lld\t%lld\n", + time_app, + time_vsync, + time_set); + } + result.append("\n"); +} + +void Layer::clearStats() +{ + LayerBaseClient::clearStats(); + memset(mFrameStats, 0, sizeof(mFrameStats)); +} + uint32_t Layer::getEffectiveUsage(uint32_t usage) const { // TODO: should we do something special if mSecure is set? diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 2b9471b..bf30608 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -34,6 +34,7 @@ #include "LayerBase.h" #include "SurfaceTextureLayer.h" #include "Transform.h" +#include <utils/Timers.h> namespace android { @@ -78,12 +79,17 @@ public: // LayerBaseClient interface virtual wp<IBinder> getSurfaceTextureBinder() const; + virtual void onLayerDisplayed(); + virtual bool onPreComposition(); + // only for debugging inline const sp<GraphicBuffer>& getActiveBuffer() const { return mActiveBuffer; } protected: virtual void onFirstRef(); virtual void dump(String8& result, char* scratch, size_t size) const; + virtual void dumpStats(String8& result, char* buffer, size_t SIZE) const; + virtual void clearStats(); private: friend class SurfaceTextureLayer; @@ -110,6 +116,19 @@ private: uint32_t mCurrentTransform; uint32_t mCurrentScalingMode; bool mCurrentOpacity; + size_t mRefreshPending; + bool mFrameLatencyNeeded; + int mFrameLatencyOffset; + + struct Statistics { + Statistics() : timestamp(0), set(0), vsync(0) { } + nsecs_t timestamp; // buffer timestamp + nsecs_t set; // buffer displayed timestamp + nsecs_t vsync; // vsync immediately before set + }; + + // protected by mLock + Statistics mFrameStats[128]; // constants PixelFormat mFormat; @@ -121,9 +140,6 @@ private: bool mSecure; // no screenshots bool mProtectedByApp; // application requires protected path to external sink Region mPostedDirtyRegion; - - // binder thread, transaction thread - mutable Mutex mLock; }; // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp index f04add1..e764001 100644 --- a/services/surfaceflinger/LayerBase.cpp +++ b/services/surfaceflinger/LayerBase.cpp @@ -47,8 +47,7 @@ LayerBase::LayerBase(SurfaceFlinger* flinger, DisplayID display) mOrientation(0), mPlaneOrientation(0), mTransactionFlags(0), - mPremultipliedAlpha(true), mName("unnamed"), mDebug(false), - mInvalidate(0) + mPremultipliedAlpha(true), mName("unnamed"), mDebug(false) { const DisplayHardware& hw(flinger->graphicPlane(0).displayHardware()); mFlags = hw.getFlags(); @@ -240,7 +239,7 @@ void LayerBase::validateVisibility(const Transform& planeTransform) for (size_t i=0 ; i<4 ; i++) mVertices[i][1] = hw_h - mVertices[i][1]; - if (UNLIKELY(transformed)) { + if (CC_UNLIKELY(transformed)) { // NOTE: here we could also punt if we have too many rectangles // in the transparent region if (tr.preserveRects()) { @@ -262,23 +261,11 @@ void LayerBase::validateVisibility(const Transform& planeTransform) mTransformedBounds = tr.makeBounds(w, h); } -void LayerBase::lockPageFlip(bool& recomputeVisibleRegions) -{ +void LayerBase::lockPageFlip(bool& recomputeVisibleRegions) { } void LayerBase::unlockPageFlip( - const Transform& planeTransform, Region& outDirtyRegion) -{ - if ((android_atomic_and(~1, &mInvalidate)&1) == 1) { - outDirtyRegion.orSelf(visibleRegionScreen); - } -} - -void LayerBase::invalidate() -{ - if ((android_atomic_or(1, &mInvalidate)&1) == 0) { - mFlinger->signalEvent(); - } + const Transform& planeTransform, Region& outDirtyRegion) { } void LayerBase::drawRegion(const Region& reg) const @@ -416,7 +403,7 @@ void LayerBase::drawWithOpenGL(const Region& clip) const const State& s(drawingState()); GLenum src = mPremultipliedAlpha ? GL_ONE : GL_SRC_ALPHA; - if (UNLIKELY(s.alpha < 0xFF)) { + if (CC_UNLIKELY(s.alpha < 0xFF)) { const GLfloat alpha = s.alpha * (1.0f/255.0f); if (mPremultipliedAlpha) { glColor4f(alpha, alpha, alpha, alpha); @@ -471,13 +458,21 @@ void LayerBase::drawWithOpenGL(const Region& clip) const void LayerBase::dump(String8& result, char* buffer, size_t SIZE) const { const Layer::State& s(drawingState()); + + snprintf(buffer, SIZE, + "+ %s %p (%s)\n", + getTypeId(), this, getName().string()); + result.append(buffer); + + s.transparentRegion.dump(result, "transparentRegion"); + transparentRegionScreen.dump(result, "transparentRegionScreen"); + visibleRegionScreen.dump(result, "visibleRegionScreen"); + snprintf(buffer, SIZE, - "+ %s %p (%s)\n" " " "z=%9d, pos=(%g,%g), size=(%4d,%4d), " "isOpaque=%1d, needsDithering=%1d, invalidate=%1d, " "alpha=0x%02x, flags=0x%08x, tr=[%.2f, %.2f][%.2f, %.2f]\n", - getTypeId(), this, getName().string(), s.z, s.transform.tx(), s.transform.ty(), s.w, s.h, isOpaque(), needsDithering(), contentDirty, s.alpha, s.flags, @@ -486,11 +481,15 @@ void LayerBase::dump(String8& result, char* buffer, size_t SIZE) const result.append(buffer); } -void LayerBase::shortDump(String8& result, char* scratch, size_t size) const -{ +void LayerBase::shortDump(String8& result, char* scratch, size_t size) const { LayerBase::dump(result, scratch, size); } +void LayerBase::dumpStats(String8& result, char* scratch, size_t SIZE) const { +} + +void LayerBase::clearStats() { +} // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h index 7f62145..b8f7680 100644 --- a/services/surfaceflinger/LayerBase.h +++ b/services/surfaceflinger/LayerBase.h @@ -103,8 +103,6 @@ public: Rect visibleBounds() const; void drawRegion(const Region& reg) const; - void invalidate(); - virtual sp<LayerBaseClient> getLayerBaseClient() const { return 0; } virtual sp<Layer> getLayer() const { return 0; } @@ -204,11 +202,22 @@ public: /** called with the state lock when the surface is removed from the * current list */ - virtual void onRemoved() { }; - + virtual void onRemoved() { } + + /** called after page-flip + */ + virtual void onLayerDisplayed() { } + + /** called before composition. + * returns true if the layer has pending updates. + */ + virtual bool onPreComposition() { return false; } + /** always call base class first */ virtual void dump(String8& result, char* scratch, size_t size) const; virtual void shortDump(String8& result, char* scratch, size_t size) const; + virtual void dumpStats(String8& result, char* buffer, size_t SIZE) const; + virtual void clearStats(); enum { // flags for doTransaction() @@ -271,10 +280,6 @@ protected: mutable bool mDebug; - // atomic - volatile int32_t mInvalidate; - - public: // called from class SurfaceFlinger virtual ~LayerBase(); diff --git a/services/surfaceflinger/MessageQueue.cpp b/services/surfaceflinger/MessageQueue.cpp index 9441019..290fff4 100644 --- a/services/surfaceflinger/MessageQueue.cpp +++ b/services/surfaceflinger/MessageQueue.cpp @@ -18,178 +18,146 @@ #include <errno.h> #include <sys/types.h> +#include <binder/IPCThreadState.h> + #include <utils/threads.h> #include <utils/Timers.h> #include <utils/Log.h> -#include <binder/IPCThreadState.h> + +#include <gui/IDisplayEventConnection.h> +#include <gui/BitTube.h> #include "MessageQueue.h" +#include "EventThread.h" +#include "SurfaceFlinger.h" namespace android { // --------------------------------------------------------------------------- -void MessageList::insert(const sp<MessageBase>& node) -{ - LIST::iterator cur(mList.begin()); - LIST::iterator end(mList.end()); - while (cur != end) { - if (*node < **cur) { - mList.insert(cur, node); - return; - } - ++cur; +MessageBase::MessageBase() + : MessageHandler() { +} + +MessageBase::~MessageBase() { +} + +void MessageBase::handleMessage(const Message&) { + this->handler(); + barrier.open(); +}; + +// --------------------------------------------------------------------------- + +void MessageQueue::Handler::signalRefresh() { + if ((android_atomic_or(eventMaskRefresh, &mEventMask) & eventMaskRefresh) == 0) { + mQueue.mLooper->sendMessage(this, Message(MessageQueue::REFRESH)); } - mList.insert(++end, node); } -void MessageList::remove(MessageList::LIST::iterator pos) -{ - mList.erase(pos); +void MessageQueue::Handler::signalInvalidate() { + if ((android_atomic_or(eventMaskInvalidate, &mEventMask) & eventMaskInvalidate) == 0) { + mQueue.mLooper->sendMessage(this, Message(MessageQueue::INVALIDATE)); + } +} + +void MessageQueue::Handler::handleMessage(const Message& message) { + switch (message.what) { + case INVALIDATE: + android_atomic_and(~eventMaskInvalidate, &mEventMask); + mQueue.mFlinger->onMessageReceived(message.what); + break; + case REFRESH: + android_atomic_and(~eventMaskRefresh, &mEventMask); + mQueue.mFlinger->onMessageReceived(message.what); + break; + } } // --------------------------------------------------------------------------- MessageQueue::MessageQueue() - : mInvalidate(false) { - mInvalidateMessage = new MessageBase(INVALIDATE); } -MessageQueue::~MessageQueue() +MessageQueue::~MessageQueue() { +} + +void MessageQueue::init(const sp<SurfaceFlinger>& flinger) { + mFlinger = flinger; + mLooper = new Looper(true); + mHandler = new Handler(*this); } -sp<MessageBase> MessageQueue::waitMessage(nsecs_t timeout) +void MessageQueue::setEventThread(const sp<EventThread>& eventThread) { - sp<MessageBase> result; + mEventThread = eventThread; + mEvents = eventThread->createEventConnection(); + mEventTube = mEvents->getDataChannel(); + mLooper->addFd(mEventTube->getFd(), 0, ALOOPER_EVENT_INPUT, + MessageQueue::cb_eventReceiver, this); +} - bool again; +void MessageQueue::waitMessage() { do { - const nsecs_t timeoutTime = systemTime() + timeout; - while (true) { - Mutex::Autolock _l(mLock); - nsecs_t now = systemTime(); - nsecs_t nextEventTime = -1; - - LIST::iterator cur(mMessages.begin()); - if (cur != mMessages.end()) { - result = *cur; - } - - if (result != 0) { - if (result->when <= now) { - // there is a message to deliver - mMessages.remove(cur); - break; - } - nextEventTime = result->when; - result = 0; - } - - // 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; - } - } - - if (nextEventTime >= 0) { - //ALOGD("nextEventTime = %lld ms", nextEventTime); - if (nextEventTime > 0) { - // we're about to wait, flush the binder command buffer - IPCThreadState::self()->flushCommands(); - const nsecs_t reltime = nextEventTime - systemTime(); - if (reltime > 0) { - mCondition.waitRelative(mLock, reltime); - } - } - } else { - //ALOGD("going to wait"); - // we're about to wait, flush the binder command buffer - IPCThreadState::self()->flushCommands(); - mCondition.wait(mLock); - } - } - // here we're not holding the lock anymore - - if (result == 0) - break; - - again = result->handler(); - if (again) { - // the message has been processed. release our reference to it - // without holding the lock. - result->notify(); - result = 0; + IPCThreadState::self()->flushCommands(); + int32_t ret = mLooper->pollOnce(-1); + switch (ret) { + case ALOOPER_POLL_WAKE: + case ALOOPER_POLL_CALLBACK: + continue; + case ALOOPER_POLL_ERROR: + ALOGE("ALOOPER_POLL_ERROR"); + case ALOOPER_POLL_TIMEOUT: + // timeout (should not happen) + continue; + default: + // should not happen + ALOGE("Looper::pollOnce() returned unknown status %d", ret); + continue; } - - } while (again); - - return result; + } while (true); } status_t MessageQueue::postMessage( - const sp<MessageBase>& message, nsecs_t relTime, uint32_t flags) + const sp<MessageBase>& messageHandler, nsecs_t relTime) { - return queueMessage(message, relTime, flags); + const Message dummyMessage; + if (relTime > 0) { + mLooper->sendMessageDelayed(relTime, messageHandler, dummyMessage); + } else { + mLooper->sendMessage(messageHandler, dummyMessage); + } + return NO_ERROR; } -status_t MessageQueue::invalidate() { - Mutex::Autolock _l(mLock); - mInvalidate = true; - mCondition.signal(); - return NO_ERROR; +void MessageQueue::invalidate() { +// mHandler->signalInvalidate(); + mEvents->requestNextVsync(); } -status_t MessageQueue::queueMessage( - const sp<MessageBase>& message, nsecs_t relTime, uint32_t flags) -{ - Mutex::Autolock _l(mLock); - message->when = systemTime() + relTime; - mMessages.insert(message); - - //ALOGD("MessageQueue::queueMessage time = %lld ms", message->when); - //dumpLocked(message); - - mCondition.signal(); - return NO_ERROR; +void MessageQueue::refresh() { + mEvents->requestNextVsync(); } -void MessageQueue::dump(const sp<MessageBase>& message) -{ - Mutex::Autolock _l(mLock); - dumpLocked(message); +int MessageQueue::cb_eventReceiver(int fd, int events, void* data) { + MessageQueue* queue = reinterpret_cast<MessageQueue *>(data); + return queue->eventReceiver(fd, events); } -void MessageQueue::dumpLocked(const sp<MessageBase>& message) -{ - LIST::const_iterator cur(mMessages.begin()); - LIST::const_iterator end(mMessages.end()); - int c = 0; - while (cur != end) { - const char tick = (*cur == message) ? '>' : ' '; - ALOGD("%c %d: msg{.what=%08x, when=%lld}", - tick, c, (*cur)->what, (*cur)->when); - ++cur; - c++; +int MessageQueue::eventReceiver(int fd, int events) { + ssize_t n; + DisplayEventReceiver::Event buffer[8]; + while ((n = DisplayEventReceiver::getEvents(mEventTube, buffer, 8)) > 0) { + for (int i=0 ; i<n ; i++) { + if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) { + mHandler->signalRefresh(); + break; + } + } } + return 1; } // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/MessageQueue.h b/services/surfaceflinger/MessageQueue.h index 890f809..ea29e7e 100644 --- a/services/surfaceflinger/MessageQueue.h +++ b/services/surfaceflinger/MessageQueue.h @@ -23,100 +23,85 @@ #include <utils/threads.h> #include <utils/Timers.h> -#include <utils/List.h> +#include <utils/Looper.h> + +#include <gui/DisplayEventReceiver.h> #include "Barrier.h" namespace android { -// --------------------------------------------------------------------------- - -class MessageBase; - -class MessageList -{ - List< sp<MessageBase> > mList; - typedef List< sp<MessageBase> > LIST; -public: - inline LIST::iterator begin() { return mList.begin(); } - inline LIST::const_iterator begin() const { return mList.begin(); } - inline LIST::iterator end() { return mList.end(); } - inline LIST::const_iterator end() const { return mList.end(); } - inline bool isEmpty() const { return mList.empty(); } - void insert(const sp<MessageBase>& node); - void remove(LIST::iterator pos); -}; +class IDisplayEventConnection; +class EventThread; +class SurfaceFlinger; -// ============================================================================ +// --------------------------------------------------------------------------- -class MessageBase : - public LightRefBase<MessageBase> +class MessageBase : public MessageHandler { public: - nsecs_t when; - uint32_t what; - int32_t arg0; - - MessageBase() : when(0), what(0), arg0(0) { } - MessageBase(uint32_t what, int32_t arg0=0) - : when(0), what(what), arg0(arg0) { } + MessageBase(); // return true if message has a handler - virtual bool handler() { return false; } + virtual bool handler() = 0; // 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() { } + virtual ~MessageBase(); private: + virtual void handleMessage(const Message& message); + mutable Barrier barrier; - friend class LightRefBase<MessageBase>; }; -inline bool operator < (const MessageBase& lhs, const MessageBase& rhs) { - return lhs.when < rhs.when; -} - // --------------------------------------------------------------------------- -class MessageQueue -{ - typedef List< sp<MessageBase> > LIST; -public: +class MessageQueue { + class Handler : public MessageHandler { + enum { + eventMaskInvalidate = 0x1, + eventMaskRefresh = 0x2 + }; + MessageQueue& mQueue; + int32_t mEventMask; + public: + Handler(MessageQueue& queue) : mQueue(queue), mEventMask(0) { } + virtual void handleMessage(const Message& message); + void signalRefresh(); + void signalInvalidate(); + }; - MessageQueue(); - ~MessageQueue(); + friend class Handler; + + sp<SurfaceFlinger> mFlinger; + sp<Looper> mLooper; + sp<EventThread> mEventThread; + sp<IDisplayEventConnection> mEvents; + sp<BitTube> mEventTube; + sp<Handler> mHandler; - // pre-defined messages + + static int cb_eventReceiver(int fd, int events, void* data); + int eventReceiver(int fd, int events); + +public: enum { - INVALIDATE = '_upd' + INVALIDATE = 0, + REFRESH = 1, }; - sp<MessageBase> waitMessage(nsecs_t timeout = -1); - - status_t postMessage(const sp<MessageBase>& message, - nsecs_t reltime=0, uint32_t flags = 0); - - status_t invalidate(); - - void dump(const sp<MessageBase>& message); + MessageQueue(); + ~MessageQueue(); + void init(const sp<SurfaceFlinger>& flinger); + void setEventThread(const sp<EventThread>& events); -private: - status_t queueMessage(const sp<MessageBase>& message, - nsecs_t reltime, uint32_t flags); - void dumpLocked(const sp<MessageBase>& message); - - Mutex mLock; - Condition mCondition; - MessageList mMessages; - bool mInvalidate; - sp<MessageBase> mInvalidateMessage; + void waitMessage(); + status_t postMessage(const sp<MessageBase>& message, nsecs_t reltime=0); + void invalidate(); + void refresh(); }; // --------------------------------------------------------------------------- diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index 98277b4..ab09bfa 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -34,18 +34,21 @@ #include <binder/MemoryHeapBase.h> #include <binder/PermissionCache.h> +#include <gui/IDisplayEventConnection.h> + #include <utils/String8.h> #include <utils/String16.h> #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 "DisplayEventConnection.h" +#include "EventThread.h" #include "GLExtensions.h" #include "DdmConnection.h" #include "Layer.h" @@ -56,15 +59,9 @@ #include "DisplayHardware/DisplayHardware.h" #include "DisplayHardware/HWComposer.h" +#include <private/android_filesystem_config.h> #include <private/surfaceflinger/SharedBufferStack.h> -/* ideally AID_GRAPHICS would be in a semi-public header - * or there would be a way to map a user/group name to its id - */ -#ifndef AID_GRAPHICS -#define AID_GRAPHICS 1003 -#endif - #define EGL_VERSION_HW_ANDROID 0x3143 #define DISPLAY_COUNT 1 @@ -128,11 +125,34 @@ void SurfaceFlinger::init() ALOGI_IF(mDebugDDMS, "DDMS debugging enabled"); } +void SurfaceFlinger::onFirstRef() +{ + mEventQueue.init(this); + + run("SurfaceFlinger", PRIORITY_URGENT_DISPLAY); + + // Wait for the main thread to be done with its initialization + mReadyToRunBarrier.wait(); +} + + SurfaceFlinger::~SurfaceFlinger() { glDeleteTextures(1, &mWormholeTexName); } +void SurfaceFlinger::binderDied(const wp<IBinder>& who) +{ + // the window manager died on us. prepare its eulogy. + + // reset screen orientation + Vector<ComposerState> state; + setTransactionState(state, eOrientationDefault, 0); + + // restart the boot-animation + property_set("ctl.start", "bootanim"); +} + sp<IMemoryHeap> SurfaceFlinger::getCblk() const { return mServerHeap; @@ -186,25 +206,6 @@ void SurfaceFlinger::bootFinished() property_set("ctl.stop", "bootanim"); } -void SurfaceFlinger::binderDied(const wp<IBinder>& who) -{ - // the window manager died on us. prepare its eulogy. - - // reset screen orientation - setOrientation(0, eOrientationDefault, 0); - - // restart the boot-animation - property_set("ctl.start", "bootanim"); -} - -void SurfaceFlinger::onFirstRef() -{ - run("SurfaceFlinger", PRIORITY_URGENT_DISPLAY); - - // Wait for the main thread to be done with its initialization - mReadyToRunBarrier.wait(); -} - static inline uint16_t pack565(int r, int g, int b) { return (r<<11)|(g<<5)|b; } @@ -295,12 +296,18 @@ status_t SurfaceFlinger::readyToRun() // put the origin in the left-bottom corner glOrthof(0, w, 0, h, 0, 1); // l=0, r=w ; b=0, t=h - mReadyToRunBarrier.open(); + + // start the EventThread + mEventThread = new EventThread(this); + mEventQueue.setEventThread(mEventThread); + hw.startSleepManagement(); /* * We're now ready to accept clients... */ + mReadyToRunBarrier.open(); + // start boot animation property_set("ctl.start", "bootanim"); @@ -308,29 +315,6 @@ status_t SurfaceFlinger::readyToRun() } // ---------------------------------------------------------------------------- -#if 0 -#pragma mark - -#pragma mark Events Handler -#endif - -void SurfaceFlinger::waitForEvent() -{ - while (true) { - nsecs_t timeout = -1; - sp<MessageBase> msg = mEventQueue.waitMessage(timeout); - if (msg != 0) { - switch (msg->what) { - case MessageQueue::INVALIDATE: - // invalidate message, just return to the main loop - return; - } - } - } -} - -void SurfaceFlinger::signalEvent() { - mEventQueue.invalidate(); -} bool SurfaceFlinger::authenticateSurfaceTexture( const sp<ISurfaceTexture>& surfaceTexture) const { @@ -373,92 +357,121 @@ bool SurfaceFlinger::authenticateSurfaceTexture( return false; } +// ---------------------------------------------------------------------------- + +sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection() { + return mEventThread->createEventConnection(); +} + +// ---------------------------------------------------------------------------- + +void SurfaceFlinger::waitForEvent() { + mEventQueue.waitMessage(); +} + +void SurfaceFlinger::signalTransaction() { + mEventQueue.invalidate(); +} + +void SurfaceFlinger::signalLayerUpdate() { + mEventQueue.invalidate(); +} + +void SurfaceFlinger::signalRefresh() { + mEventQueue.refresh(); +} + status_t SurfaceFlinger::postMessageAsync(const sp<MessageBase>& msg, - nsecs_t reltime, uint32_t flags) -{ - return mEventQueue.postMessage(msg, reltime, flags); + nsecs_t reltime, uint32_t flags) { + return mEventQueue.postMessage(msg, reltime); } status_t SurfaceFlinger::postMessageSync(const sp<MessageBase>& msg, - nsecs_t reltime, uint32_t flags) -{ - status_t res = mEventQueue.postMessage(msg, reltime, flags); + nsecs_t reltime, uint32_t flags) { + status_t res = mEventQueue.postMessage(msg, reltime); if (res == NO_ERROR) { msg->wait(); } return res; } -// ---------------------------------------------------------------------------- -#if 0 -#pragma mark - -#pragma mark Main loop -#endif - bool SurfaceFlinger::threadLoop() { waitForEvent(); + return true; +} - // check for transactions - if (UNLIKELY(mConsoleSignals)) { - handleConsoleEvents(); - } - - // if we're in a global transaction, don't do anything. - const uint32_t mask = eTransactionNeeded | eTraversalNeeded; - uint32_t transactionFlags = peekTransactionFlags(mask); - if (UNLIKELY(transactionFlags)) { - handleTransaction(transactionFlags); - } +void SurfaceFlinger::onMessageReceived(int32_t what) +{ + switch (what) { + case MessageQueue::REFRESH: { +// case MessageQueue::INVALIDATE: { + // check for transactions + if (CC_UNLIKELY(mConsoleSignals)) { + handleConsoleEvents(); + } - // post surfaces (if needed) - handlePageFlip(); + // if we're in a global transaction, don't do anything. + const uint32_t mask = eTransactionNeeded | eTraversalNeeded; + uint32_t transactionFlags = peekTransactionFlags(mask); + if (CC_UNLIKELY(transactionFlags)) { + handleTransaction(transactionFlags); + } - if (mDirtyRegion.isEmpty()) { - // nothing new to do. - return true; - } + // post surfaces (if needed) + handlePageFlip(); - if (UNLIKELY(mHwWorkListDirty)) { - // build the h/w work list - handleWorkList(); - } +// signalRefresh(); +// +// } break; +// +// case MessageQueue::REFRESH: { - const DisplayHardware& hw(graphicPlane(0).displayHardware()); - if (LIKELY(hw.canDraw())) { - // repaint the framebuffer (if needed) + handleRefresh(); - const int index = hw.getCurrentBufferIndex(); - GraphicLog& logger(GraphicLog::getInstance()); + const DisplayHardware& hw(graphicPlane(0).displayHardware()); - logger.log(GraphicLog::SF_REPAINT, index); - handleRepaint(); +// if (mDirtyRegion.isEmpty()) { +// return; +// } - // inform the h/w that we're done compositing - logger.log(GraphicLog::SF_COMPOSITION_COMPLETE, index); - hw.compositionComplete(); + if (CC_UNLIKELY(mHwWorkListDirty)) { + // build the h/w work list + handleWorkList(); + } - logger.log(GraphicLog::SF_SWAP_BUFFERS, index); - postFramebuffer(); + if (CC_LIKELY(hw.canDraw())) { + // repaint the framebuffer (if needed) + handleRepaint(); + // inform the h/w that we're done compositing + hw.compositionComplete(); + postFramebuffer(); + } else { + // pretend we did the post + hw.compositionComplete(); + } - logger.log(GraphicLog::SF_REPAINT_DONE, index); - } else { - // pretend we did the post - hw.compositionComplete(); - usleep(16667); // 60 fps period + } break; } - return true; } void SurfaceFlinger::postFramebuffer() { - // this should never happen. we do the flip anyways so we don't - // risk to cause a deadlock with hwc - ALOGW_IF(mSwapRegion.isEmpty(), "mSwapRegion is empty"); + // mSwapRegion can be empty here is some cases, for instance if a hidden + // or fully transparent window is updating. + // in that case, we need to flip anyways to not risk a deadlock with + // h/w composer. + const DisplayHardware& hw(graphicPlane(0).displayHardware()); const nsecs_t now = systemTime(); mDebugInSwapBuffers = now; hw.flip(mSwapRegion); + + size_t numLayers = mVisibleLayersSortedByZ.size(); + for (size_t i = 0; i < numLayers; i++) { + mVisibleLayersSortedByZ[i]->onLayerDisplayed(); + } + mLastSwapBufferTime = systemTime() - now; mDebugInSwapBuffers = 0; mSwapRegion.clear(); @@ -625,7 +638,7 @@ void SurfaceFlinger::computeVisibleRegions( // handle hidden surfaces by setting the visible region to empty - if (LIKELY(!(s.flags & ISurfaceComposer::eLayerHidden) && s.alpha)) { + if (CC_LIKELY(!(s.flags & ISurfaceComposer::eLayerHidden) && s.alpha)) { const bool translucent = !layer->isOpaque(); const Rect bounds(layer->visibleBounds()); visibleRegion.set(bounds); @@ -725,13 +738,13 @@ void SurfaceFlinger::commitTransaction() void SurfaceFlinger::handlePageFlip() { - bool visibleRegions = mVisibleRegionsDirty; + const DisplayHardware& hw = graphicPlane(0).displayHardware(); + const Region screenRegion(hw.bounds()); + const LayerVector& currentLayers(mDrawingState.layersSortedByZ); - visibleRegions |= lockPageFlip(currentLayers); + const bool visibleRegions = lockPageFlip(currentLayers); - const DisplayHardware& hw = graphicPlane(0).displayHardware(); - const Region screenRegion(hw.bounds()); - if (visibleRegions) { + if (visibleRegions || mVisibleRegionsDirty) { Region opaqueRegion; computeVisibleRegions(currentLayers, mDirtyRegion, opaqueRegion); @@ -778,7 +791,7 @@ void SurfaceFlinger::unlockPageFlip(const LayerVector& currentLayers) { const GraphicPlane& plane(graphicPlane(0)); const Transform& planeTransform(plane.transform()); - size_t count = currentLayers.size(); + const 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]); @@ -786,6 +799,23 @@ void SurfaceFlinger::unlockPageFlip(const LayerVector& currentLayers) } } +void SurfaceFlinger::handleRefresh() +{ + bool needInvalidate = false; + const LayerVector& currentLayers(mDrawingState.layersSortedByZ); + const size_t count = currentLayers.size(); + for (size_t i=0 ; i<count ; i++) { + const sp<LayerBase>& layer(currentLayers[i]); + if (layer->onPreComposition()) { + needInvalidate = true; + } + } + if (needInvalidate) { + signalLayerUpdate(); + } +} + + void SurfaceFlinger::handleWorkList() { mHwWorkListDirty = false; @@ -810,7 +840,7 @@ void SurfaceFlinger::handleRepaint() // compute the invalid region mSwapRegion.orSelf(mDirtyRegion); - if (UNLIKELY(mDebugRegion)) { + if (CC_UNLIKELY(mDebugRegion)) { debugFlashRegions(); } @@ -964,7 +994,7 @@ void SurfaceFlinger::composeSurfaces(const Region& dirty) HWComposer& hwc(hw.getHwComposer()); const size_t fbLayerCount = hwc.getLayerCount(HWC_FRAMEBUFFER); - if (UNLIKELY(fbLayerCount && !mWormholeRegion.isEmpty())) { + if (CC_UNLIKELY(fbLayerCount && !mWormholeRegion.isEmpty())) { // should never happen unless the window manager has a bug // draw something... drawWormhole(); @@ -1049,7 +1079,7 @@ void SurfaceFlinger::drawWormhole() const const int32_t width = hw.getWidth(); const int32_t height = hw.getHeight(); - if (LIKELY(!mDebugBackground)) { + if (CC_LIKELY(!mDebugBackground)) { glClearColor(0,0,0,0); Region::const_iterator it = region.begin(); Region::const_iterator const end = region.end(); @@ -1093,23 +1123,6 @@ void SurfaceFlinger::drawWormhole() const } } -void SurfaceFlinger::debugShowFPS() const -{ - static int mFrameCount; - static int mLastFrameCount = 0; - static nsecs_t mLastFpsTime = 0; - static float mFps = 0; - mFrameCount++; - nsecs_t now = systemTime(); - nsecs_t diff = now - mLastFpsTime; - if (diff > ms2ns(250)) { - mFps = ((mFrameCount - mLastFrameCount) * float(s2ns(1))) / diff; - mLastFpsTime = now; - mLastFrameCount = mFrameCount; - } - // XXX: mFPS has the value we want - } - status_t SurfaceFlinger::addLayer(const sp<LayerBase>& layer) { Mutex::Autolock _l(mStateLock); @@ -1200,7 +1213,7 @@ uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags) { uint32_t old = android_atomic_or(flags, &mTransactionFlags); if ((old & flags)==0) { // wake the server up - signalEvent(); + signalTransaction(); } return old; } @@ -1250,26 +1263,6 @@ void SurfaceFlinger::setTransactionState(const Vector<ComposerState>& state, } } -int SurfaceFlinger::setOrientation(DisplayID dpy, - int orientation, uint32_t flags) -{ - if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT)) - return BAD_VALUE; - - Mutex::Autolock _l(mStateLock); - if (mCurrentState.orientation != orientation) { - if (uint32_t(orientation)<=eOrientation270 || orientation==42) { - mCurrentState.orientationFlags = flags; - mCurrentState.orientation = orientation; - setTransactionFlags(eTransactionNeeded); - mTransactionCV.wait(mStateLock); - } else { - orientation = BAD_VALUE; - } - } - return orientation; -} - sp<ISurface> SurfaceFlinger::createSurface( ISurfaceComposerClient::surface_data_t* params, const String8& name, @@ -1352,7 +1345,7 @@ sp<Layer> SurfaceFlinger::createNormalSurface( sp<Layer> layer = new Layer(this, display, client); status_t err = layer->setBuffers(w, h, format, flags); - if (LIKELY(err != NO_ERROR)) { + if (CC_LIKELY(err != NO_ERROR)) { ALOGE("createNormalSurfaceLocked() failed (%s)", strerror(-err)); layer.clear(); } @@ -1471,14 +1464,14 @@ void SurfaceFlinger::screenReleased(int dpy) { // this may be called by a signal handler, we can't do too much in here android_atomic_or(eConsoleReleased, &mConsoleSignals); - signalEvent(); + signalTransaction(); } void SurfaceFlinger::screenAcquired(int dpy) { // this may be called by a signal handler, we can't do too much in here android_atomic_or(eConsoleAcquired, &mConsoleSignals); - signalEvent(); + signalTransaction(); } status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) @@ -1494,14 +1487,6 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) IPCThreadState::self()->getCallingUid()); result.append(buffer); } else { - - // figure out if we're stuck somewhere - const nsecs_t now = systemTime(); - const nsecs_t inSwapBuffers(mDebugInSwapBuffers); - const nsecs_t inTransaction(mDebugInTransaction); - nsecs_t inSwapBuffersDuration = (inSwapBuffers) ? now-inSwapBuffers : 0; - nsecs_t inTransactionDuration = (inTransaction) ? now-inTransaction : 0; - // Try to get the main lock, but don't insist if we can't // (this would indicate SF is stuck, but we want to be able to // print something in dumpsys). @@ -1517,110 +1502,205 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) result.append(buffer); } - /* - * Dump the visible layer list - */ - const LayerVector& currentLayers = mCurrentState.layersSortedByZ; - const size_t count = currentLayers.size(); - snprintf(buffer, SIZE, "Visible layers (count = %d)\n", count); - result.append(buffer); - for (size_t i=0 ; i<count ; i++) { - const sp<LayerBase>& layer(currentLayers[i]); - layer->dump(result, buffer, SIZE); - const Layer::State& s(layer->drawingState()); - s.transparentRegion.dump(result, "transparentRegion"); - layer->transparentRegionScreen.dump(result, "transparentRegionScreen"); - layer->visibleRegionScreen.dump(result, "visibleRegionScreen"); - } - - /* - * Dump the layers in the purgatory - */ + bool dumpAll = true; + size_t index = 0; + size_t numArgs = args.size(); + if (numArgs) { + dumpAll = false; - const size_t purgatorySize = mLayerPurgatory.size(); - snprintf(buffer, SIZE, "Purgatory state (%d entries)\n", purgatorySize); - result.append(buffer); - for (size_t i=0 ; i<purgatorySize ; i++) { - const sp<LayerBase>& layer(mLayerPurgatory.itemAt(i)); - layer->shortDump(result, buffer, SIZE); - } + if ((index < numArgs) && + (args[index] == String16("--list"))) { + index++; + listLayersLocked(args, index, result, buffer, SIZE); + } - /* - * Dump SurfaceFlinger global state - */ + if ((index < numArgs) && + (args[index] == String16("--latency"))) { + index++; + dumpStatsLocked(args, index, result, buffer, SIZE); + } - snprintf(buffer, SIZE, "SurfaceFlinger global state:\n"); - result.append(buffer); + if ((index < numArgs) && + (args[index] == String16("--latency-clear"))) { + index++; + clearStatsLocked(args, index, result, buffer, SIZE); + } + } - const GLExtensions& extensions(GLExtensions::getInstance()); - snprintf(buffer, SIZE, "GLES: %s, %s, %s\n", - extensions.getVendor(), - extensions.getRenderer(), - extensions.getVersion()); - result.append(buffer); + if (dumpAll) { + dumpAllLocked(result, buffer, SIZE); + } - snprintf(buffer, SIZE, "EGL : %s\n", - eglQueryString(graphicPlane(0).getEGLDisplay(), - EGL_VERSION_HW_ANDROID)); - result.append(buffer); + if (locked) { + mStateLock.unlock(); + } + } + write(fd, result.string(), result.size()); + return NO_ERROR; +} - snprintf(buffer, SIZE, "EXTS: %s\n", extensions.getExtension()); +void SurfaceFlinger::listLayersLocked(const Vector<String16>& args, size_t& index, + String8& result, char* buffer, size_t SIZE) const +{ + const LayerVector& currentLayers = mCurrentState.layersSortedByZ; + const size_t count = currentLayers.size(); + for (size_t i=0 ; i<count ; i++) { + const sp<LayerBase>& layer(currentLayers[i]); + snprintf(buffer, SIZE, "%s\n", layer->getName().string()); result.append(buffer); + } +} - mWormholeRegion.dump(result, "WormholeRegion"); - const DisplayHardware& hw(graphicPlane(0).displayHardware()); - snprintf(buffer, SIZE, - " orientation=%d, canDraw=%d\n", - mCurrentState.orientation, hw.canDraw()); - result.append(buffer); - snprintf(buffer, SIZE, - " last eglSwapBuffers() time: %f us\n" - " last transaction time : %f us\n" - " refresh-rate : %f fps\n" - " x-dpi : %f\n" - " y-dpi : %f\n", - mLastSwapBufferTime/1000.0, - mLastTransactionTime/1000.0, - hw.getRefreshRate(), - hw.getDpiX(), - hw.getDpiY()); - result.append(buffer); +void SurfaceFlinger::dumpStatsLocked(const Vector<String16>& args, size_t& index, + String8& result, char* buffer, size_t SIZE) const +{ + String8 name; + if (index < args.size()) { + name = String8(args[index]); + index++; + } - if (inSwapBuffersDuration || !locked) { - snprintf(buffer, SIZE, " eglSwapBuffers time: %f us\n", - inSwapBuffersDuration/1000.0); + const LayerVector& currentLayers = mCurrentState.layersSortedByZ; + const size_t count = currentLayers.size(); + for (size_t i=0 ; i<count ; i++) { + const sp<LayerBase>& layer(currentLayers[i]); + if (name.isEmpty()) { + snprintf(buffer, SIZE, "%s\n", layer->getName().string()); result.append(buffer); } + if (name.isEmpty() || (name == layer->getName())) { + layer->dumpStats(result, buffer, SIZE); + } + } +} - if (inTransactionDuration || !locked) { - snprintf(buffer, SIZE, " transaction time: %f us\n", - inTransactionDuration/1000.0); - result.append(buffer); +void SurfaceFlinger::clearStatsLocked(const Vector<String16>& args, size_t& index, + String8& result, char* buffer, size_t SIZE) const +{ + String8 name; + if (index < args.size()) { + name = String8(args[index]); + index++; + } + + const LayerVector& currentLayers = mCurrentState.layersSortedByZ; + const size_t count = currentLayers.size(); + for (size_t i=0 ; i<count ; i++) { + const sp<LayerBase>& layer(currentLayers[i]); + if (name.isEmpty() || (name == layer->getName())) { + layer->clearStats(); } + } +} - /* - * Dump HWComposer state - */ - HWComposer& hwc(hw.getHwComposer()); - snprintf(buffer, SIZE, " h/w composer %s and %s\n", - hwc.initCheck()==NO_ERROR ? "present" : "not present", - (mDebugDisableHWC || mDebugRegion) ? "disabled" : "enabled"); - result.append(buffer); - hwc.dump(result, buffer, SIZE, mVisibleLayersSortedByZ); +void SurfaceFlinger::dumpAllLocked( + String8& result, char* buffer, size_t SIZE) const +{ + // figure out if we're stuck somewhere + const nsecs_t now = systemTime(); + const nsecs_t inSwapBuffers(mDebugInSwapBuffers); + const nsecs_t inTransaction(mDebugInTransaction); + nsecs_t inSwapBuffersDuration = (inSwapBuffers) ? now-inSwapBuffers : 0; + nsecs_t inTransactionDuration = (inTransaction) ? now-inTransaction : 0; - /* - * Dump gralloc state - */ - const GraphicBufferAllocator& alloc(GraphicBufferAllocator::get()); - alloc.dump(result); - hw.dump(result); + /* + * Dump the visible layer list + */ + const LayerVector& currentLayers = mCurrentState.layersSortedByZ; + const size_t count = currentLayers.size(); + snprintf(buffer, SIZE, "Visible layers (count = %d)\n", count); + result.append(buffer); + for (size_t i=0 ; i<count ; i++) { + const sp<LayerBase>& layer(currentLayers[i]); + layer->dump(result, buffer, SIZE); + } - if (locked) { - mStateLock.unlock(); - } + /* + * Dump the layers in the purgatory + */ + + const size_t purgatorySize = mLayerPurgatory.size(); + snprintf(buffer, SIZE, "Purgatory state (%d entries)\n", purgatorySize); + result.append(buffer); + for (size_t i=0 ; i<purgatorySize ; i++) { + const sp<LayerBase>& layer(mLayerPurgatory.itemAt(i)); + layer->shortDump(result, buffer, SIZE); } - write(fd, result.string(), result.size()); - return NO_ERROR; + + /* + * Dump SurfaceFlinger global state + */ + + snprintf(buffer, SIZE, "SurfaceFlinger global state:\n"); + result.append(buffer); + + const GLExtensions& extensions(GLExtensions::getInstance()); + snprintf(buffer, SIZE, "GLES: %s, %s, %s\n", + extensions.getVendor(), + extensions.getRenderer(), + extensions.getVersion()); + result.append(buffer); + + snprintf(buffer, SIZE, "EGL : %s\n", + eglQueryString(graphicPlane(0).getEGLDisplay(), + EGL_VERSION_HW_ANDROID)); + result.append(buffer); + + snprintf(buffer, SIZE, "EXTS: %s\n", extensions.getExtension()); + result.append(buffer); + + mWormholeRegion.dump(result, "WormholeRegion"); + const DisplayHardware& hw(graphicPlane(0).displayHardware()); + snprintf(buffer, SIZE, + " orientation=%d, canDraw=%d\n", + mCurrentState.orientation, hw.canDraw()); + result.append(buffer); + snprintf(buffer, SIZE, + " last eglSwapBuffers() time: %f us\n" + " last transaction time : %f us\n" + " transaction-flags : %08x\n" + " refresh-rate : %f fps\n" + " x-dpi : %f\n" + " y-dpi : %f\n", + mLastSwapBufferTime/1000.0, + mLastTransactionTime/1000.0, + mTransactionFlags, + hw.getRefreshRate(), + hw.getDpiX(), + hw.getDpiY()); + result.append(buffer); + + snprintf(buffer, SIZE, " eglSwapBuffers time: %f us\n", + inSwapBuffersDuration/1000.0); + result.append(buffer); + + snprintf(buffer, SIZE, " transaction time: %f us\n", + inTransactionDuration/1000.0); + result.append(buffer); + + /* + * VSYNC state + */ + mEventThread->dump(result, buffer, SIZE); + + /* + * Dump HWComposer state + */ + HWComposer& hwc(hw.getHwComposer()); + snprintf(buffer, SIZE, "h/w composer state:\n"); + result.append(buffer); + snprintf(buffer, SIZE, " h/w composer %s and %s\n", + hwc.initCheck()==NO_ERROR ? "present" : "not present", + (mDebugDisableHWC || mDebugRegion) ? "disabled" : "enabled"); + result.append(buffer); + hwc.dump(result, buffer, SIZE, mVisibleLayersSortedByZ); + + /* + * Dump gralloc state + */ + const GraphicBufferAllocator& alloc(GraphicBufferAllocator::get()); + alloc.dump(result); + hw.dump(result); } status_t SurfaceFlinger::onTransact( @@ -1665,7 +1745,7 @@ status_t SurfaceFlinger::onTransact( status_t err = BnSurfaceComposer::onTransact(code, data, reply, flags); if (err == UNKNOWN_TRANSACTION || err == PERMISSION_DENIED) { CHECK_INTERFACE(ISurfaceComposer, data, reply); - if (UNLIKELY(!PermissionCache::checkCallingPermission(sHardwareTest))) { + if (CC_UNLIKELY(!PermissionCache::checkCallingPermission(sHardwareTest))) { IPCThreadState* ipc = IPCThreadState::self(); const int pid = ipc->getCallingPid(); const int uid = ipc->getCallingUid(); @@ -1696,11 +1776,6 @@ 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 1008: // toggle use of hw composer n = data.readInt32(); mDebugDisableHWC = n ? 1 : 0; @@ -1734,7 +1809,7 @@ void SurfaceFlinger::repaintEverything() { const DisplayHardware& hw(graphicPlane(0).displayHardware()); const Rect bounds(hw.getBounds()); setInvalidateRegion(Region(bounds)); - signalEvent(); + signalTransaction(); } void SurfaceFlinger::setInvalidateRegion(const Region& reg) { @@ -2210,7 +2285,7 @@ status_t SurfaceFlinger::turnElectronBeamOnImplLocked(int32_t mode) // make sure to redraw the whole screen when the animation is done mDirtyRegion.set(hw.bounds()); - signalEvent(); + signalTransaction(); return NO_ERROR; } @@ -2250,7 +2325,7 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy, status_t result = PERMISSION_DENIED; // only one display supported for now - if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT)) + if (CC_UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT)) return BAD_VALUE; if (!GLExtensions::getInstance().haveFramebufferObject()) @@ -2372,7 +2447,7 @@ status_t SurfaceFlinger::captureScreen(DisplayID dpy, uint32_t minLayerZ, uint32_t maxLayerZ) { // only one display supported for now - if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT)) + if (CC_UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT)) return BAD_VALUE; if (!GLExtensions::getInstance().haveFramebufferObject()) @@ -2500,7 +2575,7 @@ status_t Client::onTransact( const int pid = ipc->getCallingPid(); const int uid = ipc->getCallingUid(); const int self_pid = getpid(); - if (UNLIKELY(pid != self_pid && uid != AID_GRAPHICS && uid != 0)) { + if (CC_UNLIKELY(pid != self_pid && uid != AID_GRAPHICS && uid != 0)) { // we're called from a different process, do the real check if (!PermissionCache::checkCallingPermission(sAccessSurfaceFlinger)) { diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 17b80a6..fcd9361 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -20,6 +20,8 @@ #include <stdint.h> #include <sys/types.h> +#include <cutils/compiler.h> + #include <utils/Atomic.h> #include <utils/Errors.h> #include <utils/KeyedVector.h> @@ -46,14 +48,13 @@ namespace android { class Client; class DisplayHardware; +class DisplayEventConnection; +class EventThread; class Layer; class LayerDim; class LayerScreenshot; struct surface_flinger_cblk_t; -#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) -#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) - // --------------------------------------------------------------------------- class Client : public BnSurfaceComposerClient @@ -169,8 +170,8 @@ public: virtual void bootFinished(); virtual void setTransactionState(const Vector<ComposerState>& state, int orientation, uint32_t flags); - virtual int setOrientation(DisplayID dpy, int orientation, uint32_t flags); virtual bool authenticateSurfaceTexture(const sp<ISurfaceTexture>& surface) const; + virtual sp<IDisplayEventConnection> createDisplayEventConnection(); virtual status_t captureScreen(DisplayID dpy, sp<IMemoryHeap>* heap, @@ -189,6 +190,8 @@ public: status_t renderScreenToTextureLocked(DisplayID dpy, GLuint* textureName, GLfloat* uOut, GLfloat* vOut); + void onMessageReceived(int32_t what); + status_t postMessageAsync(const sp<MessageBase>& msg, nsecs_t reltime=0, uint32_t flags = 0); @@ -222,6 +225,7 @@ private: private: friend class Client; + friend class DisplayEventConnection; friend class LayerBase; friend class LayerBaseClient; friend class Layer; @@ -281,7 +285,10 @@ private: public: // hack to work around gcc 4.0.3 bug const GraphicPlane& graphicPlane(int dpy) const; GraphicPlane& graphicPlane(int dpy); - void signalEvent(); + + void signalTransaction(); + void signalLayerUpdate(); + void signalRefresh(); void repaintEverything(); private: @@ -298,6 +305,7 @@ private: void handlePageFlip(); bool lockPageFlip(const LayerVector& currentLayers); void unlockPageFlip(const LayerVector& currentLayers); + void handleRefresh(); void handleWorkList(); void handleRepaint(); void postFramebuffer(); @@ -332,9 +340,15 @@ private: status_t electronBeamOnAnimationImplLocked(); void debugFlashRegions(); - void debugShowFPS() const; void drawWormhole() const; + void listLayersLocked(const Vector<String16>& args, size_t& index, + String8& result, char* buffer, size_t SIZE) const; + void dumpStatsLocked(const Vector<String16>& args, size_t& index, + String8& result, char* buffer, size_t SIZE) const; + void clearStatsLocked(const Vector<String16>& args, size_t& index, + String8& result, char* buffer, size_t SIZE) const; + void dumpAllLocked(String8& result, char* buffer, size_t SIZE) const; mutable MessageQueue mEventQueue; @@ -362,6 +376,7 @@ private: GLuint mWormholeTexName; GLuint mProtectedTexName; nsecs_t mBootTime; + sp<EventThread> mEventThread; // Can only accessed from the main thread, these members // don't need synchronization diff --git a/services/surfaceflinger/SurfaceTextureLayer.cpp b/services/surfaceflinger/SurfaceTextureLayer.cpp index 259b937..49e8e63 100644 --- a/services/surfaceflinger/SurfaceTextureLayer.cpp +++ b/services/surfaceflinger/SurfaceTextureLayer.cpp @@ -94,6 +94,10 @@ status_t SurfaceTextureLayer::connect(int api, *outTransform = orientation; } switch(api) { + case NATIVE_WINDOW_API_CPU: + // SurfaceTextureClient supports only 2 buffers for CPU connections + this->setBufferCountServer(2); + break; case NATIVE_WINDOW_API_MEDIA: case NATIVE_WINDOW_API_CAMERA: // Camera preview and videos are rate-limited on the producer diff --git a/services/surfaceflinger/tests/vsync/Android.mk b/services/surfaceflinger/tests/vsync/Android.mk new file mode 100644 index 0000000..9181760 --- /dev/null +++ b/services/surfaceflinger/tests/vsync/Android.mk @@ -0,0 +1,18 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + vsync.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + libutils \ + libbinder \ + libui \ + libgui + +LOCAL_MODULE:= test-vsync-events + +LOCAL_MODULE_TAGS := tests + +include $(BUILD_EXECUTABLE) diff --git a/services/surfaceflinger/tests/vsync/vsync.cpp b/services/surfaceflinger/tests/vsync/vsync.cpp new file mode 100644 index 0000000..b0d54c4 --- /dev/null +++ b/services/surfaceflinger/tests/vsync/vsync.cpp @@ -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. + */ + +#include <gui/DisplayEventReceiver.h> +#include <utils/Looper.h> + +using namespace android; + +int receiver(int fd, int events, void* data) +{ + DisplayEventReceiver* q = (DisplayEventReceiver*)data; + + ssize_t n; + DisplayEventReceiver::Event buffer[1]; + + static nsecs_t oldTimeStamp = 0; + + while ((n = q->getEvents(buffer, 1)) > 0) { + for (int i=0 ; i<n ; i++) { + if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) { + printf("event vsync: count=%d\t", buffer[i].vsync.count); + } + if (oldTimeStamp) { + float t = float(buffer[i].header.timestamp - oldTimeStamp) / s2ns(1); + printf("%f ms (%f Hz)\n", t*1000, 1.0/t); + } + oldTimeStamp = buffer[i].header.timestamp; + } + } + if (n<0) { + printf("error reading events (%s)\n", strerror(-n)); + } + return 1; +} + +int main(int argc, char** argv) +{ + DisplayEventReceiver myDisplayEvent; + + + sp<Looper> loop = new Looper(false); + loop->addFd(myDisplayEvent.getFd(), 0, ALOOPER_EVENT_INPUT, receiver, + &myDisplayEvent); + + myDisplayEvent.setVsyncRate(1); + + 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/tests/waitforvsync/Android.mk b/services/surfaceflinger/tests/waitforvsync/Android.mk new file mode 100644 index 0000000..c25f5ab --- /dev/null +++ b/services/surfaceflinger/tests/waitforvsync/Android.mk @@ -0,0 +1,14 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + waitforvsync.cpp + +LOCAL_SHARED_LIBRARIES := \ + libcutils \ + +LOCAL_MODULE:= test-waitforvsync + +LOCAL_MODULE_TAGS := tests + +include $(BUILD_EXECUTABLE) diff --git a/services/surfaceflinger/tests/waitforvsync/waitforvsync.cpp b/services/surfaceflinger/tests/waitforvsync/waitforvsync.cpp new file mode 100644 index 0000000..279b88b --- /dev/null +++ b/services/surfaceflinger/tests/waitforvsync/waitforvsync.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdint.h> +#include <sys/types.h> + +#include <fcntl.h> +#include <sys/ioctl.h> +#include <linux/fb.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> + +#ifndef FBIO_WAITFORVSYNC +#define FBIO_WAITFORVSYNC _IOW('F', 0x20, __u32) +#endif + +int main(int argc, char** argv) { + int fd = open("/dev/graphics/fb0", O_RDWR); + if (fd >= 0) { + do { + uint32_t crt = 0; + int err = ioctl(fd, FBIO_WAITFORVSYNC, &crt); + if (err < 0) { + printf("FBIO_WAITFORVSYNC error: %s\n", strerror(errno)); + break; + } + } while(1); + close(fd); + } + return 0; +} diff --git a/services/tests/servicestests/res/raw/netstats_uid_v4 b/services/tests/servicestests/res/raw/netstats_uid_v4 Binary files differnew file mode 100644 index 0000000..e75fc1c --- /dev/null +++ b/services/tests/servicestests/res/raw/netstats_uid_v4 diff --git a/services/tests/servicestests/res/raw/netstats_v1 b/services/tests/servicestests/res/raw/netstats_v1 Binary files differnew file mode 100644 index 0000000..e80860a --- /dev/null +++ b/services/tests/servicestests/res/raw/netstats_v1 diff --git a/services/tests/servicestests/src/com/android/server/EntropyServiceTest.java b/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java index 636ba21..21a8ec2 100644 --- a/services/tests/servicestests/src/com/android/server/EntropyServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java @@ -23,9 +23,9 @@ import android.test.AndroidTestCase; import java.io.File; /** - * Tests for {@link com.android.server.EntropyService} + * Tests for {@link com.android.server.EntropyMixer} */ -public class EntropyServiceTest extends AndroidTestCase { +public class EntropyMixerTest extends AndroidTestCase { public void testInitialWrite() throws Exception { File dir = getContext().getDir("testInitialWrite", Context.MODE_PRIVATE); @@ -34,7 +34,7 @@ public class EntropyServiceTest extends AndroidTestCase { assertEquals(0, FileUtils.readTextFile(file, 0, null).length()); // The constructor has the side effect of writing to file - new EntropyService("/dev/null", file.getCanonicalPath()); + new EntropyMixer("/dev/null", file.getCanonicalPath()); assertTrue(FileUtils.readTextFile(file, 0, null).length() > 0); } diff --git a/services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java b/services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java new file mode 100644 index 0000000..275d807 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/NativeDaemonConnectorTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static com.android.server.NativeDaemonConnector.appendEscaped; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; + +/** + * Tests for {@link NativeDaemonConnector}. + */ +@MediumTest +public class NativeDaemonConnectorTest extends AndroidTestCase { + private static final String TAG = "NativeDaemonConnectorTest"; + + public void testArgumentNormal() throws Exception { + final StringBuilder builder = new StringBuilder(); + + builder.setLength(0); + appendEscaped(builder, ""); + assertEquals("", builder.toString()); + + builder.setLength(0); + appendEscaped(builder, "foo"); + assertEquals("foo", builder.toString()); + + builder.setLength(0); + appendEscaped(builder, "foo\"bar"); + assertEquals("foo\\\"bar", builder.toString()); + + builder.setLength(0); + appendEscaped(builder, "foo\\bar\\\"baz"); + assertEquals("foo\\\\bar\\\\\\\"baz", builder.toString()); + } + + public void testArgumentWithSpaces() throws Exception { + final StringBuilder builder = new StringBuilder(); + + builder.setLength(0); + appendEscaped(builder, "foo bar"); + assertEquals("\"foo bar\"", builder.toString()); + + builder.setLength(0); + appendEscaped(builder, "foo\"bar\\baz foo"); + assertEquals("\"foo\\\"bar\\\\baz foo\"", builder.toString()); + } + + public void testArgumentWithUtf() throws Exception { + final StringBuilder builder = new StringBuilder(); + + builder.setLength(0); + appendEscaped(builder, "caf\u00E9 c\u00F6ffee"); + assertEquals("\"caf\u00E9 c\u00F6ffee\"", builder.toString()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java index 7c61e9a..e863f8b 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java @@ -21,7 +21,6 @@ import static android.content.Intent.EXTRA_UID; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkPolicy.LIMIT_DISABLED; -import static android.net.NetworkPolicy.SNOOZE_NEVER; import static android.net.NetworkPolicy.WARNING_DISABLED; import static android.net.NetworkPolicyManager.POLICY_NONE; import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; @@ -29,6 +28,8 @@ import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static android.net.NetworkPolicyManager.computeLastCycleBoundary; import static android.net.NetworkPolicyManager.computeNextCycleBoundary; +import static android.net.TrafficStats.KB_IN_BYTES; +import static android.net.TrafficStats.MB_IN_BYTES; import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT; @@ -101,10 +102,6 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { private static final long TEST_START = 1194220800000L; private static final String TEST_IFACE = "test0"; - private static final long KB_IN_BYTES = 1024; - private static final long MB_IN_BYTES = KB_IN_BYTES * 1024; - private static final long GB_IN_BYTES = MB_IN_BYTES * 1024; - private static NetworkTemplate sTemplateWifi = NetworkTemplate.buildTemplateWifi(); private BroadcastInterceptingContext mServiceContext; @@ -256,41 +253,49 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { } public void testPidForegroundCombined() throws Exception { + IdleFuture idle; + // push all uid into background + idle = expectIdle(); mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false); mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false); mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, false); - waitUntilIdle(); + idle.get(); assertFalse(mService.isUidForeground(UID_A)); assertFalse(mService.isUidForeground(UID_B)); // push one of the shared pids into foreground + idle = expectIdle(); mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true); - waitUntilIdle(); + idle.get(); assertTrue(mService.isUidForeground(UID_A)); assertFalse(mService.isUidForeground(UID_B)); // and swap another uid into foreground + idle = expectIdle(); mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false); mProcessObserver.onForegroundActivitiesChanged(PID_3, UID_B, true); - waitUntilIdle(); + idle.get(); assertFalse(mService.isUidForeground(UID_A)); assertTrue(mService.isUidForeground(UID_B)); // push both pid into foreground + idle = expectIdle(); mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true); mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, true); - waitUntilIdle(); + idle.get(); assertTrue(mService.isUidForeground(UID_A)); // pull one out, should still be foreground + idle = expectIdle(); mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false); - waitUntilIdle(); + idle.get(); assertTrue(mService.isUidForeground(UID_A)); // pull final pid out, should now be background + idle = expectIdle(); mProcessObserver.onForegroundActivitiesChanged(PID_2, UID_A, false); - waitUntilIdle(); + idle.get(); assertFalse(mService.isUidForeground(UID_A)); } @@ -434,7 +439,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { final long expectedCycle = parseTime("2007-11-05T00:00:00.000Z"); final NetworkPolicy policy = new NetworkPolicy( - sTemplateWifi, 5, 1024L, 1024L, SNOOZE_NEVER); + sTemplateWifi, 5, 1024L, 1024L, false); final long actualCycle = computeLastCycleBoundary(currentTime, policy); assertTimeEquals(expectedCycle, actualCycle); } @@ -445,7 +450,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { final long expectedCycle = parseTime("2007-10-20T00:00:00.000Z"); final NetworkPolicy policy = new NetworkPolicy( - sTemplateWifi, 20, 1024L, 1024L, SNOOZE_NEVER); + sTemplateWifi, 20, 1024L, 1024L, false); final long actualCycle = computeLastCycleBoundary(currentTime, policy); assertTimeEquals(expectedCycle, actualCycle); } @@ -456,7 +461,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { final long expectedCycle = parseTime("2007-01-30T00:00:00.000Z"); final NetworkPolicy policy = new NetworkPolicy( - sTemplateWifi, 30, 1024L, 1024L, SNOOZE_NEVER); + sTemplateWifi, 30, 1024L, 1024L, false); final long actualCycle = computeLastCycleBoundary(currentTime, policy); assertTimeEquals(expectedCycle, actualCycle); } @@ -467,14 +472,14 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { final long expectedCycle = parseTime("2007-02-28T23:59:59.000Z"); final NetworkPolicy policy = new NetworkPolicy( - sTemplateWifi, 30, 1024L, 1024L, SNOOZE_NEVER); + sTemplateWifi, 30, 1024L, 1024L, false); final long actualCycle = computeLastCycleBoundary(currentTime, policy); assertTimeEquals(expectedCycle, actualCycle); } public void testNextCycleSane() throws Exception { final NetworkPolicy policy = new NetworkPolicy( - sTemplateWifi, 31, WARNING_DISABLED, LIMIT_DISABLED, SNOOZE_NEVER); + sTemplateWifi, 31, WARNING_DISABLED, LIMIT_DISABLED, false); final LinkedHashSet<Long> seen = new LinkedHashSet<Long>(); // walk forwards, ensuring that cycle boundaries don't get stuck @@ -489,7 +494,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { public void testLastCycleSane() throws Exception { final NetworkPolicy policy = new NetworkPolicy( - sTemplateWifi, 31, WARNING_DISABLED, LIMIT_DISABLED, SNOOZE_NEVER); + sTemplateWifi, 31, WARNING_DISABLED, LIMIT_DISABLED, false); final LinkedHashSet<Long> seen = new LinkedHashSet<Long>(); // walk backwards, ensuring that cycle boundaries look sane @@ -547,7 +552,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { replay(); setNetworkPolicies(new NetworkPolicy( - sTemplateWifi, CYCLE_DAY, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, SNOOZE_NEVER)); + sTemplateWifi, CYCLE_DAY, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, false)); future.get(); verifyAndReset(); } @@ -604,8 +609,8 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { future = expectMeteredIfacesChanged(); replay(); - setNetworkPolicies(new NetworkPolicy( - sTemplateWifi, CYCLE_DAY, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, SNOOZE_NEVER)); + setNetworkPolicies(new NetworkPolicy(sTemplateWifi, CYCLE_DAY, 1 * MB_IN_BYTES, + 2 * MB_IN_BYTES, false)); future.get(); verifyAndReset(); } @@ -697,13 +702,51 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { tagFuture = expectEnqueueNotification(); replay(); - mService.snoozePolicy(sTemplateWifi); + mService.snoozeLimit(sTemplateWifi); assertNotificationType(TYPE_LIMIT_SNOOZED, tagFuture.get()); future.get(); verifyAndReset(); } } + public void testMeteredNetworkWithoutLimit() throws Exception { + NetworkState[] state = null; + NetworkStats stats = null; + Future<Void> future; + Future<String> tagFuture; + + final long TIME_FEB_15 = 1171497600000L; + final long TIME_MAR_10 = 1173484800000L; + final int CYCLE_DAY = 15; + + setCurrentTimeMillis(TIME_MAR_10); + + // bring up wifi network with metered policy + state = new NetworkState[] { buildWifi() }; + stats = new NetworkStats(getElapsedRealtime(), 1) + .addIfaceValues(TEST_IFACE, 0L, 0L, 0L, 0L); + + { + expectCurrentTime(); + expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce(); + expect(mStatsService.getSummaryForNetwork(sTemplateWifi, TIME_FEB_15, currentTimeMillis())) + .andReturn(stats).atLeastOnce(); + expectPolicyDataEnable(TYPE_WIFI, true); + + expectRemoveInterfaceQuota(TEST_IFACE); + expectSetInterfaceQuota(TEST_IFACE, Long.MAX_VALUE); + + expectClearNotifications(); + future = expectMeteredIfacesChanged(TEST_IFACE); + + replay(); + setNetworkPolicies(new NetworkPolicy(sTemplateWifi, CYCLE_DAY, WARNING_DISABLED, + LIMIT_DISABLED, true)); + future.get(); + verifyAndReset(); + } + } + private static long parseTime(String time) { final Time result = new Time(); result.parse3339(time); @@ -850,10 +893,10 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { /** * Wait until {@link #mService} internal {@link Handler} is idle. */ - private void waitUntilIdle() throws Exception { + private IdleFuture expectIdle() { final IdleFuture future = new IdleFuture(); mService.addIdleHandler(future); - future.get(); + return future; } private static void assertTimeEquals(long expected, long actual) { diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java index fbc171b..daf2018 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java @@ -31,6 +31,7 @@ import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStatsHistory.FIELD_ALL; import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.NetworkTemplate.buildTemplateWifi; +import static android.net.TrafficStats.MB_IN_BYTES; import static android.net.TrafficStats.UID_REMOVED; import static android.net.TrafficStats.UID_TETHERING; import static android.text.format.DateUtils.DAY_IN_MILLIS; @@ -39,6 +40,7 @@ import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.text.format.DateUtils.WEEK_IN_MILLIS; import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL; import static org.easymock.EasyMock.anyLong; +import static org.easymock.EasyMock.aryEq; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.eq; @@ -63,10 +65,12 @@ import android.os.INetworkManagementService; import android.telephony.TelephonyManager; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.Suppress; import android.util.TrustedTime; import com.android.server.net.NetworkStatsService; import com.android.server.net.NetworkStatsService.NetworkStatsSettings; +import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config; import org.easymock.Capture; import org.easymock.EasyMock; @@ -282,13 +286,6 @@ public class NetworkStatsServiceTest extends AndroidTestCase { mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN)); verifyAndReset(); - // talk with zombie service to assert stats have gone; and assert that - // we persisted them to file. - expectDefaultSettings(); - replay(); - assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0); - verifyAndReset(); - assertStatsFilesExist(true); // boot through serviceReady() again @@ -319,6 +316,8 @@ public class NetworkStatsServiceTest extends AndroidTestCase { } + // TODO: simulate reboot to test bucket resize + @Suppress public void testStatsBucketResize() throws Exception { NetworkStatsHistory history = null; @@ -602,7 +601,6 @@ public class NetworkStatsServiceTest extends AndroidTestCase { assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 1280L, 10L, 10); verifyAndReset(); - } public void testSummaryForAllUid() throws Exception { @@ -755,11 +753,15 @@ public class NetworkStatsServiceTest extends AndroidTestCase { expectDefaultSettings(); expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L)); - expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)); + + final NetworkStats uidStats = new NetworkStats(getElapsedRealtime(), 1) + .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L); final String[] tetherIfacePairs = new String[] { TEST_IFACE, "wlan0" }; - expectNetworkStatsPoll(tetherIfacePairs, new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, 0L)); + final NetworkStats tetherStats = new NetworkStats(getElapsedRealtime(), 1) + .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, 0L); + + expectNetworkStatsUidDetail(uidStats, tetherIfacePairs, tetherStats); + expectNetworkStatsPoll(); replay(); mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL)); @@ -802,10 +804,15 @@ public class NetworkStatsServiceTest extends AndroidTestCase { mNetManager.setGlobalAlert(anyLong()); expectLastCall().atLeastOnce(); + + expect(mNetManager.isBandwidthControlEnabled()).andReturn(true).atLeastOnce(); } private void expectNetworkState(NetworkState... state) throws Exception { expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce(); + + final LinkProperties linkProp = state.length > 0 ? state[0].linkProperties : null; + expect(mConnManager.getActiveLinkProperties()).andReturn(linkProp).atLeastOnce(); } private void expectNetworkStatsSummary(NetworkStats summary) throws Exception { @@ -813,23 +820,35 @@ public class NetworkStatsServiceTest extends AndroidTestCase { } private void expectNetworkStatsUidDetail(NetworkStats detail) throws Exception { + expectNetworkStatsUidDetail(detail, new String[0], new NetworkStats(0L, 0)); + } + + private void expectNetworkStatsUidDetail( + NetworkStats detail, String[] tetherIfacePairs, NetworkStats tetherStats) + throws Exception { expect(mNetManager.getNetworkStatsUidDetail(eq(UID_ALL))).andReturn(detail).atLeastOnce(); + + // also include tethering details, since they are folded into UID + expect(mConnManager.getTetheredIfacePairs()).andReturn(tetherIfacePairs).atLeastOnce(); + expect(mNetManager.getNetworkStatsTethering(aryEq(tetherIfacePairs))) + .andReturn(tetherStats).atLeastOnce(); } private void expectDefaultSettings() throws Exception { expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS); } - private void expectSettings(long persistThreshold, long bucketDuration, long maxHistory) + private void expectSettings(long persistBytes, long bucketDuration, long deleteAge) throws Exception { expect(mSettings.getPollInterval()).andReturn(HOUR_IN_MILLIS).anyTimes(); - expect(mSettings.getPersistThreshold()).andReturn(persistThreshold).anyTimes(); - expect(mSettings.getNetworkBucketDuration()).andReturn(bucketDuration).anyTimes(); - expect(mSettings.getNetworkMaxHistory()).andReturn(maxHistory).anyTimes(); - expect(mSettings.getUidBucketDuration()).andReturn(bucketDuration).anyTimes(); - expect(mSettings.getUidMaxHistory()).andReturn(maxHistory).anyTimes(); - expect(mSettings.getTagMaxHistory()).andReturn(maxHistory).anyTimes(); expect(mSettings.getTimeCacheMaxAge()).andReturn(DAY_IN_MILLIS).anyTimes(); + expect(mSettings.getGlobalAlertBytes()).andReturn(MB_IN_BYTES).anyTimes(); + expect(mSettings.getSampleEnabled()).andReturn(true).anyTimes(); + + final Config config = new Config(bucketDuration, persistBytes, deleteAge, deleteAge); + expect(mSettings.getDevConfig()).andReturn(config).anyTimes(); + expect(mSettings.getUidConfig()).andReturn(config).anyTimes(); + expect(mSettings.getUidTagConfig()).andReturn(config).anyTimes(); } private void expectCurrentTime() throws Exception { @@ -841,27 +860,16 @@ public class NetworkStatsServiceTest extends AndroidTestCase { } private void expectNetworkStatsPoll() throws Exception { - expectNetworkStatsPoll(new String[0], new NetworkStats(getElapsedRealtime(), 0)); - } - - private void expectNetworkStatsPoll(String[] tetherIfacePairs, NetworkStats tetherStats) - throws Exception { mNetManager.setGlobalAlert(anyLong()); expectLastCall().anyTimes(); - expect(mConnManager.getTetheredIfacePairs()).andReturn(tetherIfacePairs).anyTimes(); - expect(mNetManager.getNetworkStatsTethering(eq(tetherIfacePairs))) - .andReturn(tetherStats).anyTimes(); } private void assertStatsFilesExist(boolean exist) { - final File networkFile = new File(mStatsDir, "netstats.bin"); - final File uidFile = new File(mStatsDir, "netstats_uid.bin"); + final File basePath = new File(mStatsDir, "netstats"); if (exist) { - assertTrue(networkFile.exists()); - assertTrue(uidFile.exists()); + assertTrue(basePath.list().length > 0); } else { - assertFalse(networkFile.exists()); - assertFalse(uidFile.exists()); + assertTrue(basePath.list().length == 0); } } diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java new file mode 100644 index 0000000..7f05f56 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2012 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.net; + +import static android.net.NetworkTemplate.buildTemplateMobileAll; +import static android.text.format.DateUtils.MINUTE_IN_MILLIS; + +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.NetworkIdentity; +import android.net.NetworkStats; +import android.net.NetworkTemplate; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; + +import com.android.frameworks.servicestests.R; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +import libcore.io.IoUtils; +import libcore.io.Streams; + +/** + * Tests for {@link NetworkStatsCollection}. + */ +@MediumTest +public class NetworkStatsCollectionTest extends AndroidTestCase { + + private static final String TEST_FILE = "test.bin"; + private static final String TEST_IMSI = "310260000000000"; + + public void testReadLegacyNetwork() throws Exception { + final File testFile = new File(getContext().getFilesDir(), TEST_FILE); + stageFile(R.raw.netstats_v1, testFile); + + final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS); + collection.readLegacyNetwork(testFile); + + // verify that history read correctly + assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), + 636014522L, 709291L, 88037144L, 518820L); + + // now export into a unified format + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + collection.write(new DataOutputStream(bos)); + + // clear structure completely + collection.reset(); + assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), + 0L, 0L, 0L, 0L); + + // and read back into structure, verifying that totals are same + collection.read(new ByteArrayInputStream(bos.toByteArray())); + assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), + 636014522L, 709291L, 88037144L, 518820L); + } + + public void testReadLegacyUid() throws Exception { + final File testFile = new File(getContext().getFilesDir(), TEST_FILE); + stageFile(R.raw.netstats_uid_v4, testFile); + + final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS); + collection.readLegacyUid(testFile, false); + + // verify that history read correctly + assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), + 637073904L, 711398L, 88342093L, 521006L); + + // now export into a unified format + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + collection.write(new DataOutputStream(bos)); + + // clear structure completely + collection.reset(); + assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), + 0L, 0L, 0L, 0L); + + // and read back into structure, verifying that totals are same + collection.read(new ByteArrayInputStream(bos.toByteArray())); + assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), + 637073904L, 711398L, 88342093L, 521006L); + } + + public void testReadLegacyUidTags() throws Exception { + final File testFile = new File(getContext().getFilesDir(), TEST_FILE); + stageFile(R.raw.netstats_uid_v4, testFile); + + final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS); + collection.readLegacyUid(testFile, true); + + // verify that history read correctly + assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI), + 77017831L, 100995L, 35436758L, 92344L); + + // now export into a unified format + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + collection.write(new DataOutputStream(bos)); + + // clear structure completely + collection.reset(); + assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI), + 0L, 0L, 0L, 0L); + + // and read back into structure, verifying that totals are same + collection.read(new ByteArrayInputStream(bos.toByteArray())); + assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI), + 77017831L, 100995L, 35436758L, 92344L); + } + + /** + * Copy a {@link Resources#openRawResource(int)} into {@link File} for + * testing purposes. + */ + private void stageFile(int rawId, File file) throws Exception { + new File(file.getParent()).mkdirs(); + InputStream in = null; + OutputStream out = null; + try { + in = getContext().getResources().openRawResource(rawId); + out = new FileOutputStream(file); + Streams.copy(in, out); + } finally { + IoUtils.closeQuietly(in); + IoUtils.closeQuietly(out); + } + } + + public static NetworkIdentitySet buildWifiIdent() { + final NetworkIdentitySet set = new NetworkIdentitySet(); + set.add(new NetworkIdentity(ConnectivityManager.TYPE_WIFI, 0, null, false)); + return set; + } + + private static void assertSummaryTotal(NetworkStatsCollection collection, + NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) { + final NetworkStats.Entry entry = collection.getSummary( + template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null); + assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets); + } + + private static void assertSummaryTotalIncludingTags(NetworkStatsCollection collection, + NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) { + final NetworkStats.Entry entry = collection.getSummary( + template, Long.MIN_VALUE, Long.MAX_VALUE).getTotalIncludingTags(null); + assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets); + } + + private static void assertEntry( + NetworkStats.Entry entry, long rxBytes, long rxPackets, long txBytes, long txPackets) { + assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes); + assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets); + assertEquals("unexpected txBytes", txBytes, entry.txBytes); + assertEquals("unexpected txPackets", txPackets, entry.txPackets); + } +} |