diff options
Diffstat (limited to 'services')
124 files changed, 15681 insertions, 11646 deletions
diff --git a/services/audioflinger/A2dpAudioInterface.cpp b/services/audioflinger/A2dpAudioInterface.cpp deleted file mode 100644 index d926cb1..0000000 --- a/services/audioflinger/A2dpAudioInterface.cpp +++ /dev/null @@ -1,498 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <math.h> - -//#define LOG_NDEBUG 0 -#define LOG_TAG "A2dpAudioInterface" -#include <utils/Log.h> -#include <utils/String8.h> - -#include "A2dpAudioInterface.h" -#include "audio/liba2dp.h" -#include <hardware_legacy/power.h> - -namespace android { - -static const char *sA2dpWakeLock = "A2dpOutputStream"; -#define MAX_WRITE_RETRIES 5 - -// ---------------------------------------------------------------------------- - -//AudioHardwareInterface* A2dpAudioInterface::createA2dpInterface() -//{ -// AudioHardwareInterface* hw = 0; -// -// hw = AudioHardwareInterface::create(); -// LOGD("new A2dpAudioInterface(hw: %p)", hw); -// hw = new A2dpAudioInterface(hw); -// return hw; -//} - -A2dpAudioInterface::A2dpAudioInterface(AudioHardwareInterface* hw) : - mOutput(0), mHardwareInterface(hw), mBluetoothEnabled(true), mSuspended(false) -{ -} - -A2dpAudioInterface::~A2dpAudioInterface() -{ - closeOutputStream((AudioStreamOut *)mOutput); - delete mHardwareInterface; -} - -status_t A2dpAudioInterface::initCheck() -{ - if (mHardwareInterface == 0) return NO_INIT; - return mHardwareInterface->initCheck(); -} - -AudioStreamOut* A2dpAudioInterface::openOutputStream( - uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status) -{ - if (!AudioSystem::isA2dpDevice((AudioSystem::audio_devices)devices)) { - LOGV("A2dpAudioInterface::openOutputStream() open HW device: %x", devices); - return mHardwareInterface->openOutputStream(devices, format, channels, sampleRate, status); - } - - status_t err = 0; - - // only one output stream allowed - if (mOutput) { - if (status) - *status = -1; - return NULL; - } - - // create new output stream - A2dpAudioStreamOut* out = new A2dpAudioStreamOut(); - if ((err = out->set(devices, format, channels, sampleRate)) == NO_ERROR) { - mOutput = out; - mOutput->setBluetoothEnabled(mBluetoothEnabled); - mOutput->setSuspended(mSuspended); - } else { - delete out; - } - - if (status) - *status = err; - return mOutput; -} - -void A2dpAudioInterface::closeOutputStream(AudioStreamOut* out) { - if (mOutput == 0 || mOutput != out) { - mHardwareInterface->closeOutputStream(out); - } - else { - delete mOutput; - mOutput = 0; - } -} - - -AudioStreamIn* A2dpAudioInterface::openInputStream( - uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status, - AudioSystem::audio_in_acoustics acoustics) -{ - return mHardwareInterface->openInputStream(devices, format, channels, sampleRate, status, acoustics); -} - -void A2dpAudioInterface::closeInputStream(AudioStreamIn* in) -{ - return mHardwareInterface->closeInputStream(in); -} - -status_t A2dpAudioInterface::setMode(int mode) -{ - return mHardwareInterface->setMode(mode); -} - -status_t A2dpAudioInterface::setMicMute(bool state) -{ - return mHardwareInterface->setMicMute(state); -} - -status_t A2dpAudioInterface::getMicMute(bool* state) -{ - return mHardwareInterface->getMicMute(state); -} - -status_t A2dpAudioInterface::setParameters(const String8& keyValuePairs) -{ - AudioParameter param = AudioParameter(keyValuePairs); - String8 value; - String8 key; - status_t status = NO_ERROR; - - LOGV("setParameters() %s", keyValuePairs.string()); - - key = "bluetooth_enabled"; - if (param.get(key, value) == NO_ERROR) { - mBluetoothEnabled = (value == "true"); - if (mOutput) { - mOutput->setBluetoothEnabled(mBluetoothEnabled); - } - param.remove(key); - } - key = String8("A2dpSuspended"); - if (param.get(key, value) == NO_ERROR) { - mSuspended = (value == "true"); - if (mOutput) { - mOutput->setSuspended(mSuspended); - } - param.remove(key); - } - - if (param.size()) { - status_t hwStatus = mHardwareInterface->setParameters(param.toString()); - if (status == NO_ERROR) { - status = hwStatus; - } - } - - return status; -} - -String8 A2dpAudioInterface::getParameters(const String8& keys) -{ - AudioParameter param = AudioParameter(keys); - AudioParameter a2dpParam = AudioParameter(); - String8 value; - String8 key; - - key = "bluetooth_enabled"; - if (param.get(key, value) == NO_ERROR) { - value = mBluetoothEnabled ? "true" : "false"; - a2dpParam.add(key, value); - param.remove(key); - } - key = "A2dpSuspended"; - if (param.get(key, value) == NO_ERROR) { - value = mSuspended ? "true" : "false"; - a2dpParam.add(key, value); - param.remove(key); - } - - String8 keyValuePairs = a2dpParam.toString(); - - if (param.size()) { - if (keyValuePairs != "") { - keyValuePairs += ";"; - } - keyValuePairs += mHardwareInterface->getParameters(param.toString()); - } - - LOGV("getParameters() %s", keyValuePairs.string()); - return keyValuePairs; -} - -size_t A2dpAudioInterface::getInputBufferSize(uint32_t sampleRate, int format, int channelCount) -{ - return mHardwareInterface->getInputBufferSize(sampleRate, format, channelCount); -} - -status_t A2dpAudioInterface::setVoiceVolume(float v) -{ - return mHardwareInterface->setVoiceVolume(v); -} - -status_t A2dpAudioInterface::setMasterVolume(float v) -{ - return mHardwareInterface->setMasterVolume(v); -} - -status_t A2dpAudioInterface::dump(int fd, const Vector<String16>& args) -{ - return mHardwareInterface->dumpState(fd, args); -} - -// ---------------------------------------------------------------------------- - -A2dpAudioInterface::A2dpAudioStreamOut::A2dpAudioStreamOut() : - mFd(-1), mStandby(true), mStartCount(0), mRetryCount(0), mData(NULL), - // assume BT enabled to start, this is safe because its only the - // enabled->disabled transition we are worried about - mBluetoothEnabled(true), mDevice(0), mClosing(false), mSuspended(false) -{ - // use any address by default - strcpy(mA2dpAddress, "00:00:00:00:00:00"); - init(); -} - -status_t A2dpAudioInterface::A2dpAudioStreamOut::set( - uint32_t device, int *pFormat, uint32_t *pChannels, uint32_t *pRate) -{ - int lFormat = pFormat ? *pFormat : 0; - uint32_t lChannels = pChannels ? *pChannels : 0; - uint32_t lRate = pRate ? *pRate : 0; - - LOGD("A2dpAudioStreamOut::set %x, %d, %d, %d\n", device, lFormat, lChannels, lRate); - - // fix up defaults - if (lFormat == 0) lFormat = format(); - if (lChannels == 0) lChannels = channels(); - if (lRate == 0) lRate = sampleRate(); - - // check values - if ((lFormat != format()) || - (lChannels != channels()) || - (lRate != sampleRate())){ - if (pFormat) *pFormat = format(); - if (pChannels) *pChannels = channels(); - if (pRate) *pRate = sampleRate(); - return BAD_VALUE; - } - - if (pFormat) *pFormat = lFormat; - if (pChannels) *pChannels = lChannels; - if (pRate) *pRate = lRate; - - mDevice = device; - mBufferDurationUs = ((bufferSize() * 1000 )/ frameSize() / sampleRate()) * 1000; - return NO_ERROR; -} - -A2dpAudioInterface::A2dpAudioStreamOut::~A2dpAudioStreamOut() -{ - LOGV("A2dpAudioStreamOut destructor"); - close(); - LOGV("A2dpAudioStreamOut destructor returning from close()"); -} - -ssize_t A2dpAudioInterface::A2dpAudioStreamOut::write(const void* buffer, size_t bytes) -{ - status_t status = -1; - { - Mutex::Autolock lock(mLock); - - size_t remaining = bytes; - - if (!mBluetoothEnabled || mClosing || mSuspended) { - LOGV("A2dpAudioStreamOut::write(), but bluetooth disabled \ - mBluetoothEnabled %d, mClosing %d, mSuspended %d", - mBluetoothEnabled, mClosing, mSuspended); - goto Error; - } - - if (mStandby) { - acquire_wake_lock (PARTIAL_WAKE_LOCK, sA2dpWakeLock); - mStandby = false; - mLastWriteTime = systemTime(); - } - - status = init(); - if (status < 0) - goto Error; - - int retries = MAX_WRITE_RETRIES; - while (remaining > 0 && retries) { - status = a2dp_write(mData, buffer, remaining); - if (status < 0) { - LOGE("a2dp_write failed err: %d\n", status); - goto Error; - } - if (status == 0) { - retries--; - } - remaining -= status; - buffer = (char *)buffer + status; - } - - // if A2DP sink runs abnormally fast, sleep a little so that audioflinger mixer thread - // does no spin and starve other threads. - // NOTE: It is likely that the A2DP headset is being disconnected - nsecs_t now = systemTime(); - if ((uint32_t)ns2us(now - mLastWriteTime) < (mBufferDurationUs >> 2)) { - LOGV("A2DP sink runs too fast"); - usleep(mBufferDurationUs - (uint32_t)ns2us(now - mLastWriteTime)); - } - mLastWriteTime = now; - return bytes; - - } -Error: - - standby(); - - // Simulate audio output timing in case of error - usleep(mBufferDurationUs); - - return status; -} - -status_t A2dpAudioInterface::A2dpAudioStreamOut::init() -{ - if (!mData) { - status_t status = a2dp_init(44100, 2, &mData); - if (status < 0) { - LOGE("a2dp_init failed err: %d\n", status); - mData = NULL; - return status; - } - a2dp_set_sink(mData, mA2dpAddress); - } - - return 0; -} - -status_t A2dpAudioInterface::A2dpAudioStreamOut::standby() -{ - Mutex::Autolock lock(mLock); - return standby_l(); -} - -status_t A2dpAudioInterface::A2dpAudioStreamOut::standby_l() -{ - int result = NO_ERROR; - - if (!mStandby) { - LOGV_IF(mClosing || !mBluetoothEnabled, "Standby skip stop: closing %d enabled %d", - mClosing, mBluetoothEnabled); - if (!mClosing && mBluetoothEnabled) { - result = a2dp_stop(mData); - } - release_wake_lock(sA2dpWakeLock); - mStandby = true; - } - - return result; -} - -status_t A2dpAudioInterface::A2dpAudioStreamOut::setParameters(const String8& keyValuePairs) -{ - AudioParameter param = AudioParameter(keyValuePairs); - String8 value; - String8 key = String8("a2dp_sink_address"); - status_t status = NO_ERROR; - int device; - LOGV("A2dpAudioStreamOut::setParameters() %s", keyValuePairs.string()); - - if (param.get(key, value) == NO_ERROR) { - if (value.length() != strlen("00:00:00:00:00:00")) { - status = BAD_VALUE; - } else { - setAddress(value.string()); - } - param.remove(key); - } - key = String8("closing"); - if (param.get(key, value) == NO_ERROR) { - mClosing = (value == "true"); - if (mClosing) { - standby(); - } - param.remove(key); - } - key = AudioParameter::keyRouting; - if (param.getInt(key, device) == NO_ERROR) { - if (AudioSystem::isA2dpDevice((AudioSystem::audio_devices)device)) { - mDevice = device; - status = NO_ERROR; - } else { - status = BAD_VALUE; - } - param.remove(key); - } - - if (param.size()) { - status = BAD_VALUE; - } - return status; -} - -String8 A2dpAudioInterface::A2dpAudioStreamOut::getParameters(const String8& keys) -{ - AudioParameter param = AudioParameter(keys); - String8 value; - String8 key = String8("a2dp_sink_address"); - - if (param.get(key, value) == NO_ERROR) { - value = mA2dpAddress; - param.add(key, value); - } - key = AudioParameter::keyRouting; - if (param.get(key, value) == NO_ERROR) { - param.addInt(key, (int)mDevice); - } - - LOGV("A2dpAudioStreamOut::getParameters() %s", param.toString().string()); - return param.toString(); -} - -status_t A2dpAudioInterface::A2dpAudioStreamOut::setAddress(const char* address) -{ - Mutex::Autolock lock(mLock); - - if (strlen(address) != strlen("00:00:00:00:00:00")) - return -EINVAL; - - strcpy(mA2dpAddress, address); - if (mData) - a2dp_set_sink(mData, mA2dpAddress); - - return NO_ERROR; -} - -status_t A2dpAudioInterface::A2dpAudioStreamOut::setBluetoothEnabled(bool enabled) -{ - LOGD("setBluetoothEnabled %d", enabled); - - Mutex::Autolock lock(mLock); - - mBluetoothEnabled = enabled; - if (!enabled) { - return close_l(); - } - return NO_ERROR; -} - -status_t A2dpAudioInterface::A2dpAudioStreamOut::setSuspended(bool onOff) -{ - LOGV("setSuspended %d", onOff); - mSuspended = onOff; - standby(); - return NO_ERROR; -} - -status_t A2dpAudioInterface::A2dpAudioStreamOut::close() -{ - Mutex::Autolock lock(mLock); - LOGV("A2dpAudioStreamOut::close() calling close_l()"); - return close_l(); -} - -status_t A2dpAudioInterface::A2dpAudioStreamOut::close_l() -{ - standby_l(); - if (mData) { - LOGV("A2dpAudioStreamOut::close_l() calling a2dp_cleanup(mData)"); - a2dp_cleanup(mData); - mData = NULL; - } - return NO_ERROR; -} - -status_t A2dpAudioInterface::A2dpAudioStreamOut::dump(int fd, const Vector<String16>& args) -{ - return NO_ERROR; -} - -status_t A2dpAudioInterface::A2dpAudioStreamOut::getRenderPosition(uint32_t *driverFrames) -{ - //TODO: enable when supported by driver - return INVALID_OPERATION; -} - -}; // namespace android diff --git a/services/audioflinger/A2dpAudioInterface.h b/services/audioflinger/A2dpAudioInterface.h deleted file mode 100644 index dbe2c6a..0000000 --- a/services/audioflinger/A2dpAudioInterface.h +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef A2DP_AUDIO_HARDWARE_H -#define A2DP_AUDIO_HARDWARE_H - -#include <stdint.h> -#include <sys/types.h> - -#include <utils/threads.h> - -#include <hardware_legacy/AudioHardwareBase.h> - - -namespace android { - -class A2dpAudioInterface : public AudioHardwareBase -{ - class A2dpAudioStreamOut; - -public: - A2dpAudioInterface(AudioHardwareInterface* hw); - virtual ~A2dpAudioInterface(); - virtual status_t initCheck(); - - virtual status_t setVoiceVolume(float volume); - virtual status_t setMasterVolume(float volume); - - virtual status_t setMode(int mode); - - // mic mute - virtual status_t setMicMute(bool state); - virtual status_t getMicMute(bool* state); - - virtual status_t setParameters(const String8& keyValuePairs); - virtual String8 getParameters(const String8& keys); - - virtual size_t getInputBufferSize(uint32_t sampleRate, int format, int channelCount); - - // create I/O streams - virtual AudioStreamOut* openOutputStream( - uint32_t devices, - int *format=0, - uint32_t *channels=0, - uint32_t *sampleRate=0, - status_t *status=0); - virtual void closeOutputStream(AudioStreamOut* out); - - virtual AudioStreamIn* openInputStream( - uint32_t devices, - int *format, - uint32_t *channels, - uint32_t *sampleRate, - status_t *status, - AudioSystem::audio_in_acoustics acoustics); - virtual void closeInputStream(AudioStreamIn* in); -// static AudioHardwareInterface* createA2dpInterface(); - -protected: - virtual status_t dump(int fd, const Vector<String16>& args); - -private: - class A2dpAudioStreamOut : public AudioStreamOut { - public: - A2dpAudioStreamOut(); - virtual ~A2dpAudioStreamOut(); - status_t set(uint32_t device, - int *pFormat, - uint32_t *pChannels, - uint32_t *pRate); - virtual uint32_t sampleRate() const { return 44100; } - // SBC codec wants a multiple of 512 - virtual size_t bufferSize() const { return 512 * 20; } - virtual uint32_t channels() const { return AudioSystem::CHANNEL_OUT_STEREO; } - virtual int format() const { return AudioSystem::PCM_16_BIT; } - virtual uint32_t latency() const { return ((1000*bufferSize())/frameSize())/sampleRate() + 200; } - virtual status_t setVolume(float left, float right) { return INVALID_OPERATION; } - virtual ssize_t write(const void* buffer, size_t bytes); - status_t standby(); - virtual status_t dump(int fd, const Vector<String16>& args); - virtual status_t setParameters(const String8& keyValuePairs); - virtual String8 getParameters(const String8& keys); - virtual status_t getRenderPosition(uint32_t *dspFrames); - - private: - friend class A2dpAudioInterface; - status_t init(); - status_t close(); - status_t close_l(); - status_t setAddress(const char* address); - status_t setBluetoothEnabled(bool enabled); - status_t setSuspended(bool onOff); - status_t standby_l(); - - private: - int mFd; - bool mStandby; - int mStartCount; - int mRetryCount; - char mA2dpAddress[20]; - void* mData; - Mutex mLock; - bool mBluetoothEnabled; - uint32_t mDevice; - bool mClosing; - bool mSuspended; - nsecs_t mLastWriteTime; - uint32_t mBufferDurationUs; - }; - - friend class A2dpAudioStreamOut; - - A2dpAudioStreamOut* mOutput; - AudioHardwareInterface *mHardwareInterface; - char mA2dpAddress[20]; - bool mBluetoothEnabled; - bool mSuspended; -}; - - -// ---------------------------------------------------------------------------- - -}; // namespace android - -#endif // A2DP_AUDIO_HARDWARE_H diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk index 69a4adc..2222e8b 100644 --- a/services/audioflinger/Android.mk +++ b/services/audioflinger/Android.mk @@ -1,77 +1,5 @@ LOCAL_PATH:= $(call my-dir) -#AUDIO_POLICY_TEST := true -#ENABLE_AUDIO_DUMP := true - -include $(CLEAR_VARS) - - -ifeq ($(AUDIO_POLICY_TEST),true) - ENABLE_AUDIO_DUMP := true -endif - - -LOCAL_SRC_FILES:= \ - AudioHardwareGeneric.cpp \ - AudioHardwareStub.cpp \ - AudioHardwareInterface.cpp - -ifeq ($(ENABLE_AUDIO_DUMP),true) - LOCAL_SRC_FILES += AudioDumpInterface.cpp - LOCAL_CFLAGS += -DENABLE_AUDIO_DUMP -endif - -LOCAL_SHARED_LIBRARIES := \ - libcutils \ - libutils \ - libbinder \ - libmedia \ - libhardware_legacy - -ifeq ($(strip $(BOARD_USES_GENERIC_AUDIO)),true) - LOCAL_CFLAGS += -DGENERIC_AUDIO -endif - -LOCAL_MODULE:= libaudiointerface - -ifeq ($(BOARD_HAVE_BLUETOOTH),true) - LOCAL_SRC_FILES += A2dpAudioInterface.cpp - LOCAL_SHARED_LIBRARIES += liba2dp - LOCAL_CFLAGS += -DWITH_BLUETOOTH -DWITH_A2DP - LOCAL_C_INCLUDES += $(call include-path-for, bluez) -endif - -include $(BUILD_STATIC_LIBRARY) - - -include $(CLEAR_VARS) - -LOCAL_SRC_FILES:= \ - AudioPolicyManagerBase.cpp - -LOCAL_SHARED_LIBRARIES := \ - libcutils \ - libutils \ - libmedia - -ifeq ($(TARGET_SIMULATOR),true) - LOCAL_LDLIBS += -ldl -else - LOCAL_SHARED_LIBRARIES += libdl -endif - -LOCAL_MODULE:= libaudiopolicybase - -ifeq ($(BOARD_HAVE_BLUETOOTH),true) - LOCAL_CFLAGS += -DWITH_A2DP -endif - -ifeq ($(AUDIO_POLICY_TEST),true) - LOCAL_CFLAGS += -DAUDIO_POLICY_TEST -endif - -include $(BUILD_STATIC_LIBRARY) - include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ @@ -87,15 +15,12 @@ LOCAL_SHARED_LIBRARIES := \ libutils \ libbinder \ libmedia \ + libhardware \ libhardware_legacy \ libeffects -ifeq ($(strip $(BOARD_USES_GENERIC_AUDIO)),true) - LOCAL_STATIC_LIBRARIES += libaudiointerface libaudiopolicybase - LOCAL_CFLAGS += -DGENERIC_AUDIO -else - LOCAL_SHARED_LIBRARIES += libaudio libaudiopolicy -endif +LOCAL_STATIC_LIBRARIES := \ + libmedia_helper ifeq ($(TARGET_SIMULATOR),true) LOCAL_LDLIBS += -ldl @@ -105,15 +30,6 @@ endif LOCAL_MODULE:= libaudioflinger -ifeq ($(BOARD_HAVE_BLUETOOTH),true) - LOCAL_CFLAGS += -DWITH_BLUETOOTH -DWITH_A2DP - LOCAL_SHARED_LIBRARIES += liba2dp -endif - -ifeq ($(AUDIO_POLICY_TEST),true) - LOCAL_CFLAGS += -DAUDIO_POLICY_TEST -endif - ifeq ($(TARGET_SIMULATOR),true) ifeq ($(HOST_OS),linux) LOCAL_LDLIBS += -lrt -lpthread diff --git a/services/audioflinger/AudioDumpInterface.cpp b/services/audioflinger/AudioDumpInterface.cpp deleted file mode 100644 index 6c11114..0000000 --- a/services/audioflinger/AudioDumpInterface.cpp +++ /dev/null @@ -1,573 +0,0 @@ -/* //device/servers/AudioFlinger/AudioDumpInterface.cpp -** -** Copyright 2008, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -#define LOG_TAG "AudioFlingerDump" -//#define LOG_NDEBUG 0 - -#include <stdint.h> -#include <sys/types.h> -#include <utils/Log.h> - -#include <stdlib.h> -#include <unistd.h> - -#include "AudioDumpInterface.h" - -namespace android { - -// ---------------------------------------------------------------------------- - -AudioDumpInterface::AudioDumpInterface(AudioHardwareInterface* hw) - : mPolicyCommands(String8("")), mFileName(String8("")) -{ - if(hw == 0) { - LOGE("Dump construct hw = 0"); - } - mFinalInterface = hw; - LOGV("Constructor %p, mFinalInterface %p", this, mFinalInterface); -} - - -AudioDumpInterface::~AudioDumpInterface() -{ - for (size_t i = 0; i < mOutputs.size(); i++) { - closeOutputStream((AudioStreamOut *)mOutputs[i]); - } - - for (size_t i = 0; i < mInputs.size(); i++) { - closeInputStream((AudioStreamIn *)mInputs[i]); - } - - if(mFinalInterface) delete mFinalInterface; -} - - -AudioStreamOut* AudioDumpInterface::openOutputStream( - uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status) -{ - AudioStreamOut* outFinal = NULL; - int lFormat = AudioSystem::PCM_16_BIT; - uint32_t lChannels = AudioSystem::CHANNEL_OUT_STEREO; - uint32_t lRate = 44100; - - - outFinal = mFinalInterface->openOutputStream(devices, format, channels, sampleRate, status); - if (outFinal != 0) { - lFormat = outFinal->format(); - lChannels = outFinal->channels(); - lRate = outFinal->sampleRate(); - } else { - if (format != 0) { - if (*format != 0) { - lFormat = *format; - } else { - *format = lFormat; - } - } - if (channels != 0) { - if (*channels != 0) { - lChannels = *channels; - } else { - *channels = lChannels; - } - } - if (sampleRate != 0) { - if (*sampleRate != 0) { - lRate = *sampleRate; - } else { - *sampleRate = lRate; - } - } - if (status) *status = NO_ERROR; - } - LOGV("openOutputStream(), outFinal %p", outFinal); - - AudioStreamOutDump *dumOutput = new AudioStreamOutDump(this, mOutputs.size(), outFinal, - devices, lFormat, lChannels, lRate); - mOutputs.add(dumOutput); - - return dumOutput; -} - -void AudioDumpInterface::closeOutputStream(AudioStreamOut* out) -{ - AudioStreamOutDump *dumpOut = (AudioStreamOutDump *)out; - - if (mOutputs.indexOf(dumpOut) < 0) { - LOGW("Attempt to close invalid output stream"); - return; - } - - LOGV("closeOutputStream() output %p", out); - - dumpOut->standby(); - if (dumpOut->finalStream() != NULL) { - mFinalInterface->closeOutputStream(dumpOut->finalStream()); - } - - mOutputs.remove(dumpOut); - delete dumpOut; -} - -AudioStreamIn* AudioDumpInterface::openInputStream(uint32_t devices, int *format, uint32_t *channels, - uint32_t *sampleRate, status_t *status, AudioSystem::audio_in_acoustics acoustics) -{ - AudioStreamIn* inFinal = NULL; - int lFormat = AudioSystem::PCM_16_BIT; - uint32_t lChannels = AudioSystem::CHANNEL_IN_MONO; - uint32_t lRate = 8000; - - inFinal = mFinalInterface->openInputStream(devices, format, channels, sampleRate, status, acoustics); - if (inFinal != 0) { - lFormat = inFinal->format(); - lChannels = inFinal->channels(); - lRate = inFinal->sampleRate(); - } else { - if (format != 0) { - if (*format != 0) { - lFormat = *format; - } else { - *format = lFormat; - } - } - if (channels != 0) { - if (*channels != 0) { - lChannels = *channels; - } else { - *channels = lChannels; - } - } - if (sampleRate != 0) { - if (*sampleRate != 0) { - lRate = *sampleRate; - } else { - *sampleRate = lRate; - } - } - if (status) *status = NO_ERROR; - } - LOGV("openInputStream(), inFinal %p", inFinal); - - AudioStreamInDump *dumInput = new AudioStreamInDump(this, mInputs.size(), inFinal, - devices, lFormat, lChannels, lRate); - mInputs.add(dumInput); - - return dumInput; -} -void AudioDumpInterface::closeInputStream(AudioStreamIn* in) -{ - AudioStreamInDump *dumpIn = (AudioStreamInDump *)in; - - if (mInputs.indexOf(dumpIn) < 0) { - LOGW("Attempt to close invalid input stream"); - return; - } - dumpIn->standby(); - if (dumpIn->finalStream() != NULL) { - mFinalInterface->closeInputStream(dumpIn->finalStream()); - } - - mInputs.remove(dumpIn); - delete dumpIn; -} - - -status_t AudioDumpInterface::setParameters(const String8& keyValuePairs) -{ - AudioParameter param = AudioParameter(keyValuePairs); - String8 value; - int valueInt; - LOGV("setParameters %s", keyValuePairs.string()); - - if (param.get(String8("test_cmd_file_name"), value) == NO_ERROR) { - mFileName = value; - param.remove(String8("test_cmd_file_name")); - } - if (param.get(String8("test_cmd_policy"), value) == NO_ERROR) { - Mutex::Autolock _l(mLock); - param.remove(String8("test_cmd_policy")); - mPolicyCommands = param.toString(); - LOGV("test_cmd_policy command %s written", mPolicyCommands.string()); - return NO_ERROR; - } - - if (mFinalInterface != 0 ) return mFinalInterface->setParameters(keyValuePairs); - return NO_ERROR; -} - -String8 AudioDumpInterface::getParameters(const String8& keys) -{ - AudioParameter param = AudioParameter(keys); - AudioParameter response; - String8 value; - -// LOGV("getParameters %s", keys.string()); - if (param.get(String8("test_cmd_policy"), value) == NO_ERROR) { - Mutex::Autolock _l(mLock); - if (mPolicyCommands.length() != 0) { - response = AudioParameter(mPolicyCommands); - response.addInt(String8("test_cmd_policy"), 1); - } else { - response.addInt(String8("test_cmd_policy"), 0); - } - param.remove(String8("test_cmd_policy")); -// LOGV("test_cmd_policy command %s read", mPolicyCommands.string()); - } - - if (param.get(String8("test_cmd_file_name"), value) == NO_ERROR) { - response.add(String8("test_cmd_file_name"), mFileName); - param.remove(String8("test_cmd_file_name")); - } - - String8 keyValuePairs = response.toString(); - - if (param.size() && mFinalInterface != 0 ) { - keyValuePairs += ";"; - keyValuePairs += mFinalInterface->getParameters(param.toString()); - } - - return keyValuePairs; -} - -status_t AudioDumpInterface::setMode(int mode) -{ - return mFinalInterface->setMode(mode); -} - -size_t AudioDumpInterface::getInputBufferSize(uint32_t sampleRate, int format, int channelCount) -{ - return mFinalInterface->getInputBufferSize(sampleRate, format, channelCount); -} - -// ---------------------------------------------------------------------------- - -AudioStreamOutDump::AudioStreamOutDump(AudioDumpInterface *interface, - int id, - AudioStreamOut* finalStream, - uint32_t devices, - int format, - uint32_t channels, - uint32_t sampleRate) - : mInterface(interface), mId(id), - mSampleRate(sampleRate), mFormat(format), mChannels(channels), mLatency(0), mDevice(devices), - mBufferSize(1024), mFinalStream(finalStream), mFile(0), mFileCount(0) -{ - LOGV("AudioStreamOutDump Constructor %p, mInterface %p, mFinalStream %p", this, mInterface, mFinalStream); -} - - -AudioStreamOutDump::~AudioStreamOutDump() -{ - LOGV("AudioStreamOutDump destructor"); - Close(); -} - -ssize_t AudioStreamOutDump::write(const void* buffer, size_t bytes) -{ - ssize_t ret; - - if (mFinalStream) { - ret = mFinalStream->write(buffer, bytes); - } else { - usleep((((bytes * 1000) / frameSize()) / sampleRate()) * 1000); - ret = bytes; - } - if(!mFile) { - if (mInterface->fileName() != "") { - char name[255]; - sprintf(name, "%s_out_%d_%d.pcm", mInterface->fileName().string(), mId, ++mFileCount); - mFile = fopen(name, "wb"); - LOGV("Opening dump file %s, fh %p", name, mFile); - } - } - if (mFile) { - fwrite(buffer, bytes, 1, mFile); - } - return ret; -} - -status_t AudioStreamOutDump::standby() -{ - LOGV("AudioStreamOutDump standby(), mFile %p, mFinalStream %p", mFile, mFinalStream); - - Close(); - if (mFinalStream != 0 ) return mFinalStream->standby(); - return NO_ERROR; -} - -uint32_t AudioStreamOutDump::sampleRate() const -{ - if (mFinalStream != 0 ) return mFinalStream->sampleRate(); - return mSampleRate; -} - -size_t AudioStreamOutDump::bufferSize() const -{ - if (mFinalStream != 0 ) return mFinalStream->bufferSize(); - return mBufferSize; -} - -uint32_t AudioStreamOutDump::channels() const -{ - if (mFinalStream != 0 ) return mFinalStream->channels(); - return mChannels; -} -int AudioStreamOutDump::format() const -{ - if (mFinalStream != 0 ) return mFinalStream->format(); - return mFormat; -} -uint32_t AudioStreamOutDump::latency() const -{ - if (mFinalStream != 0 ) return mFinalStream->latency(); - return 0; -} -status_t AudioStreamOutDump::setVolume(float left, float right) -{ - if (mFinalStream != 0 ) return mFinalStream->setVolume(left, right); - return NO_ERROR; -} -status_t AudioStreamOutDump::setParameters(const String8& keyValuePairs) -{ - LOGV("AudioStreamOutDump::setParameters %s", keyValuePairs.string()); - - if (mFinalStream != 0 ) { - return mFinalStream->setParameters(keyValuePairs); - } - - AudioParameter param = AudioParameter(keyValuePairs); - String8 value; - int valueInt; - status_t status = NO_ERROR; - - if (param.getInt(String8("set_id"), valueInt) == NO_ERROR) { - mId = valueInt; - } - - if (param.getInt(String8("format"), valueInt) == NO_ERROR) { - if (mFile == 0) { - mFormat = valueInt; - } else { - status = INVALID_OPERATION; - } - } - if (param.getInt(String8("channels"), valueInt) == NO_ERROR) { - if (valueInt == AudioSystem::CHANNEL_OUT_STEREO || valueInt == AudioSystem::CHANNEL_OUT_MONO) { - mChannels = valueInt; - } else { - status = BAD_VALUE; - } - } - if (param.getInt(String8("sampling_rate"), valueInt) == NO_ERROR) { - if (valueInt > 0 && valueInt <= 48000) { - if (mFile == 0) { - mSampleRate = valueInt; - } else { - status = INVALID_OPERATION; - } - } else { - status = BAD_VALUE; - } - } - return status; -} - -String8 AudioStreamOutDump::getParameters(const String8& keys) -{ - if (mFinalStream != 0 ) return mFinalStream->getParameters(keys); - - AudioParameter param = AudioParameter(keys); - return param.toString(); -} - -status_t AudioStreamOutDump::dump(int fd, const Vector<String16>& args) -{ - if (mFinalStream != 0 ) return mFinalStream->dump(fd, args); - return NO_ERROR; -} - -void AudioStreamOutDump::Close() -{ - if(mFile) { - fclose(mFile); - mFile = 0; - } -} - -status_t AudioStreamOutDump::getRenderPosition(uint32_t *dspFrames) -{ - if (mFinalStream != 0 ) return mFinalStream->getRenderPosition(dspFrames); - return INVALID_OPERATION; -} - -// ---------------------------------------------------------------------------- - -AudioStreamInDump::AudioStreamInDump(AudioDumpInterface *interface, - int id, - AudioStreamIn* finalStream, - uint32_t devices, - int format, - uint32_t channels, - uint32_t sampleRate) - : mInterface(interface), mId(id), - mSampleRate(sampleRate), mFormat(format), mChannels(channels), mDevice(devices), - mBufferSize(1024), mFinalStream(finalStream), mFile(0), mFileCount(0) -{ - LOGV("AudioStreamInDump Constructor %p, mInterface %p, mFinalStream %p", this, mInterface, mFinalStream); -} - - -AudioStreamInDump::~AudioStreamInDump() -{ - Close(); -} - -ssize_t AudioStreamInDump::read(void* buffer, ssize_t bytes) -{ - ssize_t ret; - - if (mFinalStream) { - ret = mFinalStream->read(buffer, bytes); - if(!mFile) { - if (mInterface->fileName() != "") { - char name[255]; - sprintf(name, "%s_in_%d_%d.pcm", mInterface->fileName().string(), mId, ++mFileCount); - mFile = fopen(name, "wb"); - LOGV("Opening input dump file %s, fh %p", name, mFile); - } - } - if (mFile) { - fwrite(buffer, bytes, 1, mFile); - } - } else { - usleep((((bytes * 1000) / frameSize()) / sampleRate()) * 1000); - ret = bytes; - if(!mFile) { - char name[255]; - strcpy(name, "/sdcard/music/sine440"); - if (channels() == AudioSystem::CHANNEL_IN_MONO) { - strcat(name, "_mo"); - } else { - strcat(name, "_st"); - } - if (format() == AudioSystem::PCM_16_BIT) { - strcat(name, "_16b"); - } else { - strcat(name, "_8b"); - } - if (sampleRate() < 16000) { - strcat(name, "_8k"); - } else if (sampleRate() < 32000) { - strcat(name, "_22k"); - } else if (sampleRate() < 48000) { - strcat(name, "_44k"); - } else { - strcat(name, "_48k"); - } - strcat(name, ".wav"); - mFile = fopen(name, "rb"); - LOGV("Opening input read file %s, fh %p", name, mFile); - if (mFile) { - fseek(mFile, AUDIO_DUMP_WAVE_HDR_SIZE, SEEK_SET); - } - } - if (mFile) { - ssize_t bytesRead = fread(buffer, bytes, 1, mFile); - if (bytesRead >=0 && bytesRead < bytes) { - fseek(mFile, AUDIO_DUMP_WAVE_HDR_SIZE, SEEK_SET); - fread((uint8_t *)buffer+bytesRead, bytes-bytesRead, 1, mFile); - } - } - } - - return ret; -} - -status_t AudioStreamInDump::standby() -{ - LOGV("AudioStreamInDump standby(), mFile %p, mFinalStream %p", mFile, mFinalStream); - - Close(); - if (mFinalStream != 0 ) return mFinalStream->standby(); - return NO_ERROR; -} - -status_t AudioStreamInDump::setGain(float gain) -{ - if (mFinalStream != 0 ) return mFinalStream->setGain(gain); - return NO_ERROR; -} - -uint32_t AudioStreamInDump::sampleRate() const -{ - if (mFinalStream != 0 ) return mFinalStream->sampleRate(); - return mSampleRate; -} - -size_t AudioStreamInDump::bufferSize() const -{ - if (mFinalStream != 0 ) return mFinalStream->bufferSize(); - return mBufferSize; -} - -uint32_t AudioStreamInDump::channels() const -{ - if (mFinalStream != 0 ) return mFinalStream->channels(); - return mChannels; -} - -int AudioStreamInDump::format() const -{ - if (mFinalStream != 0 ) return mFinalStream->format(); - return mFormat; -} - -status_t AudioStreamInDump::setParameters(const String8& keyValuePairs) -{ - LOGV("AudioStreamInDump::setParameters()"); - if (mFinalStream != 0 ) return mFinalStream->setParameters(keyValuePairs); - return NO_ERROR; -} - -String8 AudioStreamInDump::getParameters(const String8& keys) -{ - if (mFinalStream != 0 ) return mFinalStream->getParameters(keys); - - AudioParameter param = AudioParameter(keys); - return param.toString(); -} - -unsigned int AudioStreamInDump::getInputFramesLost() const -{ - if (mFinalStream != 0 ) return mFinalStream->getInputFramesLost(); - return 0; -} - -status_t AudioStreamInDump::dump(int fd, const Vector<String16>& args) -{ - if (mFinalStream != 0 ) return mFinalStream->dump(fd, args); - return NO_ERROR; -} - -void AudioStreamInDump::Close() -{ - if(mFile) { - fclose(mFile); - mFile = 0; - } -} -}; // namespace android diff --git a/services/audioflinger/AudioDumpInterface.h b/services/audioflinger/AudioDumpInterface.h deleted file mode 100644 index 814ce5f..0000000 --- a/services/audioflinger/AudioDumpInterface.h +++ /dev/null @@ -1,170 +0,0 @@ -/* //device/servers/AudioFlinger/AudioDumpInterface.h -** -** Copyright 2008, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -#ifndef ANDROID_AUDIO_DUMP_INTERFACE_H -#define ANDROID_AUDIO_DUMP_INTERFACE_H - -#include <stdint.h> -#include <sys/types.h> -#include <utils/String8.h> -#include <utils/SortedVector.h> - -#include <hardware_legacy/AudioHardwareBase.h> - -namespace android { - -#define AUDIO_DUMP_WAVE_HDR_SIZE 44 - -class AudioDumpInterface; - -class AudioStreamOutDump : public AudioStreamOut { -public: - AudioStreamOutDump(AudioDumpInterface *interface, - int id, - AudioStreamOut* finalStream, - uint32_t devices, - int format, - uint32_t channels, - uint32_t sampleRate); - ~AudioStreamOutDump(); - - virtual ssize_t write(const void* buffer, size_t bytes); - virtual uint32_t sampleRate() const; - virtual size_t bufferSize() const; - virtual uint32_t channels() const; - virtual int format() const; - virtual uint32_t latency() const; - virtual status_t setVolume(float left, float right); - virtual status_t standby(); - virtual status_t setParameters(const String8& keyValuePairs); - virtual String8 getParameters(const String8& keys); - virtual status_t dump(int fd, const Vector<String16>& args); - void Close(void); - AudioStreamOut* finalStream() { return mFinalStream; } - uint32_t device() { return mDevice; } - int getId() { return mId; } - virtual status_t getRenderPosition(uint32_t *dspFrames); - -private: - AudioDumpInterface *mInterface; - int mId; - uint32_t mSampleRate; // - uint32_t mFormat; // - uint32_t mChannels; // output configuration - uint32_t mLatency; // - uint32_t mDevice; // current device this output is routed to - size_t mBufferSize; - AudioStreamOut *mFinalStream; - FILE *mFile; // output file - int mFileCount; -}; - -class AudioStreamInDump : public AudioStreamIn { -public: - AudioStreamInDump(AudioDumpInterface *interface, - int id, - AudioStreamIn* finalStream, - uint32_t devices, - int format, - uint32_t channels, - uint32_t sampleRate); - ~AudioStreamInDump(); - - virtual uint32_t sampleRate() const; - virtual size_t bufferSize() const; - virtual uint32_t channels() const; - virtual int format() const; - - virtual status_t setGain(float gain); - virtual ssize_t read(void* buffer, ssize_t bytes); - virtual status_t standby(); - virtual status_t setParameters(const String8& keyValuePairs); - virtual String8 getParameters(const String8& keys); - virtual unsigned int getInputFramesLost() const; - virtual status_t dump(int fd, const Vector<String16>& args); - void Close(void); - AudioStreamIn* finalStream() { return mFinalStream; } - uint32_t device() { return mDevice; } - -private: - AudioDumpInterface *mInterface; - int mId; - uint32_t mSampleRate; // - uint32_t mFormat; // - uint32_t mChannels; // output configuration - uint32_t mDevice; // current device this output is routed to - size_t mBufferSize; - AudioStreamIn *mFinalStream; - FILE *mFile; // output file - int mFileCount; -}; - -class AudioDumpInterface : public AudioHardwareBase -{ - -public: - AudioDumpInterface(AudioHardwareInterface* hw); - virtual AudioStreamOut* openOutputStream( - uint32_t devices, - int *format=0, - uint32_t *channels=0, - uint32_t *sampleRate=0, - status_t *status=0); - virtual void closeOutputStream(AudioStreamOut* out); - - virtual ~AudioDumpInterface(); - - virtual status_t initCheck() - {return mFinalInterface->initCheck();} - virtual status_t setVoiceVolume(float volume) - {return mFinalInterface->setVoiceVolume(volume);} - virtual status_t setMasterVolume(float volume) - {return mFinalInterface->setMasterVolume(volume);} - - virtual status_t setMode(int mode); - - // mic mute - virtual status_t setMicMute(bool state) - {return mFinalInterface->setMicMute(state);} - virtual status_t getMicMute(bool* state) - {return mFinalInterface->getMicMute(state);} - - virtual status_t setParameters(const String8& keyValuePairs); - virtual String8 getParameters(const String8& keys); - - virtual size_t getInputBufferSize(uint32_t sampleRate, int format, int channelCount); - - virtual AudioStreamIn* openInputStream(uint32_t devices, int *format, uint32_t *channels, - uint32_t *sampleRate, status_t *status, AudioSystem::audio_in_acoustics acoustics); - virtual void closeInputStream(AudioStreamIn* in); - - virtual status_t dump(int fd, const Vector<String16>& args) { return mFinalInterface->dumpState(fd, args); } - - String8 fileName() const { return mFileName; } -protected: - - AudioHardwareInterface *mFinalInterface; - SortedVector<AudioStreamOutDump *> mOutputs; - SortedVector<AudioStreamInDump *> mInputs; - Mutex mLock; - String8 mPolicyCommands; - String8 mFileName; -}; - -}; // namespace android - -#endif // ANDROID_AUDIO_DUMP_INTERFACE_H diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index 2b08ab5..f8ad5bb 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -31,7 +31,9 @@ #include <binder/IPCThreadState.h> #include <utils/String16.h> #include <utils/threads.h> +#include <utils/Atomic.h> +#include <cutils/bitops.h> #include <cutils/properties.h> #include <media/AudioTrack.h> @@ -40,15 +42,13 @@ #include <private/media/AudioTrackShared.h> #include <private/media/AudioEffectShared.h> -#include <hardware_legacy/AudioHardwareInterface.h> + +#include <hardware/audio.h> +#include <hardware/audio_hal.h> #include "AudioMixer.h" #include "AudioFlinger.h" -#ifdef WITH_A2DP -#include "A2dpAudioInterface.h" -#endif - #include <media/EffectsFactoryApi.h> #include <media/EffectVisualizerApi.h> @@ -136,34 +136,109 @@ static void addBatteryData(uint32_t params) { service->addBatteryData(params); } +static int load_audio_interface(const char *if_name, const hw_module_t **mod, + audio_hw_device_t **dev) +{ + int rc; + + rc = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, if_name, mod); + if (rc) + goto out; + + rc = audio_hw_device_open(*mod, dev); + LOGE_IF(rc, "couldn't open audio hw device in %s.%s (%s)", + AUDIO_HARDWARE_MODULE_ID, if_name, strerror(-rc)); + if (rc) + goto out; + + return 0; + +out: + *mod = NULL; + *dev = NULL; + return rc; +} + +static const char *audio_interfaces[] = { + "primary", + "a2dp", + "usb", +}; +#define ARRAY_SIZE(x) (sizeof((x))/sizeof(((x)[0]))) + // ---------------------------------------------------------------------------- AudioFlinger::AudioFlinger() : BnAudioFlinger(), - mAudioHardware(0), mMasterVolume(1.0f), mMasterMute(false), mNextUniqueId(1) + mPrimaryHardwareDev(0), mMasterVolume(1.0f), mMasterMute(false), mNextUniqueId(1) +{ +} + +void AudioFlinger::onFirstRef() { + int rc = 0; + Mutex::Autolock _l(mLock); + /* TODO: move all this work into an Init() function */ mHardwareStatus = AUDIO_HW_IDLE; - mAudioHardware = AudioHardwareInterface::create(); + for (size_t i = 0; i < ARRAY_SIZE(audio_interfaces); i++) { + const hw_module_t *mod; + audio_hw_device_t *dev; + + rc = load_audio_interface(audio_interfaces[i], &mod, &dev); + if (rc) + continue; + + LOGI("Loaded %s audio interface from %s (%s)", audio_interfaces[i], + mod->name, mod->id); + mAudioHwDevs.push(dev); + + if (!mPrimaryHardwareDev) { + mPrimaryHardwareDev = dev; + LOGI("Using '%s' (%s.%s) as the primary audio interface", + mod->name, mod->id, audio_interfaces[i]); + } + } mHardwareStatus = AUDIO_HW_INIT; - if (mAudioHardware->initCheck() == NO_ERROR) { - AutoMutex lock(mHardwareLock); - mMode = AudioSystem::MODE_NORMAL; - mHardwareStatus = AUDIO_HW_SET_MODE; - mAudioHardware->setMode(mMode); - mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME; - mAudioHardware->setMasterVolume(1.0f); - mHardwareStatus = AUDIO_HW_IDLE; - } else { - LOGE("Couldn't even initialize the stubbed audio hardware!"); + + if (!mPrimaryHardwareDev || mAudioHwDevs.size() == 0) { + LOGE("Primary audio interface not found"); + return; } + + for (size_t i = 0; i < mAudioHwDevs.size(); i++) { + audio_hw_device_t *dev = mAudioHwDevs[i]; + + mHardwareStatus = AUDIO_HW_INIT; + rc = dev->init_check(dev); + if (rc == 0) { + AutoMutex lock(mHardwareLock); + + mMode = AUDIO_MODE_NORMAL; + mHardwareStatus = AUDIO_HW_SET_MODE; + dev->set_mode(dev, mMode); + mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME; + dev->set_master_volume(dev, 1.0f); + mHardwareStatus = AUDIO_HW_IDLE; + } + } +} + +status_t AudioFlinger::initCheck() const +{ + Mutex::Autolock _l(mLock); + if (mPrimaryHardwareDev == NULL || mAudioHwDevs.size() == 0) + return NO_INIT; + return NO_ERROR; } AudioFlinger::~AudioFlinger() { + int num_devs = mAudioHwDevs.size(); + while (!mRecordThreads.isEmpty()) { // closeInput() will remove first entry from mRecordThreads closeInput(mRecordThreads.keyAt(0)); @@ -172,12 +247,24 @@ AudioFlinger::~AudioFlinger() // closeOutput() will remove first entry from mPlaybackThreads closeOutput(mPlaybackThreads.keyAt(0)); } - if (mAudioHardware) { - delete mAudioHardware; + + for (int i = 0; i < num_devs; i++) { + audio_hw_device_t *dev = mAudioHwDevs[i]; + audio_hw_device_close(dev); } + mAudioHwDevs.clear(); } - +audio_hw_device_t* AudioFlinger::findSuitableHwDev_l(uint32_t devices) +{ + /* first matching HW device is returned */ + for (size_t i = 0; i < mAudioHwDevs.size(); i++) { + audio_hw_device_t *dev = mAudioHwDevs[i]; + if ((dev->get_supported_devices(dev) & devices) == devices) + return dev; + } + return NULL; +} status_t AudioFlinger::dumpClients(int fd, const Vector<String16>& args) { @@ -276,8 +363,10 @@ status_t AudioFlinger::dump(int fd, const Vector<String16>& args) mRecordThreads.valueAt(i)->dump(fd, args); } - if (mAudioHardware) { - mAudioHardware->dumpState(fd, args); + // dump all hardware devs + for (size_t i = 0; i < mAudioHwDevs.size(); i++) { + audio_hw_device_t *dev = mAudioHwDevs[i]; + dev->dump(dev, fd); } if (locked) mLock.unlock(); } @@ -308,7 +397,7 @@ sp<IAudioTrack> AudioFlinger::createTrack( status_t lStatus; int lSessionId; - if (streamType >= AudioSystem::NUM_STREAM_TYPES) { + if (streamType >= AUDIO_STREAM_CNT) { LOGE("invalid stream type"); lStatus = BAD_VALUE; goto Exit; @@ -334,7 +423,7 @@ sp<IAudioTrack> AudioFlinger::createTrack( } LOGV("createTrack() sessionId: %d", (sessionId == NULL) ? -2 : *sessionId); - if (sessionId != NULL && *sessionId != AudioSystem::SESSION_OUTPUT_MIX) { + if (sessionId != NULL && *sessionId != AUDIO_SESSION_OUTPUT_MIX) { for (size_t i = 0; i < mPlaybackThreads.size(); i++) { sp<PlaybackThread> t = mPlaybackThreads.valueAt(i); if (mPlaybackThreads.keyAt(i) != output) { @@ -453,7 +542,7 @@ status_t AudioFlinger::setMasterVolume(float value) { // scope for the lock AutoMutex lock(mHardwareLock); mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME; - if (mAudioHardware->setMasterVolume(value) == NO_ERROR) { + if (mPrimaryHardwareDev->set_master_volume(mPrimaryHardwareDev, value) == NO_ERROR) { value = 1.0f; } mHardwareStatus = AUDIO_HW_IDLE; @@ -475,7 +564,7 @@ status_t AudioFlinger::setMode(int mode) if (!settingsAllowed()) { return PERMISSION_DENIED; } - if ((mode < 0) || (mode >= AudioSystem::NUM_MODES)) { + if ((mode < 0) || (mode >= AUDIO_MODE_CNT)) { LOGW("Illegal value: setMode(%d)", mode); return BAD_VALUE; } @@ -483,7 +572,7 @@ status_t AudioFlinger::setMode(int mode) { // scope for the lock AutoMutex lock(mHardwareLock); mHardwareStatus = AUDIO_HW_SET_MODE; - ret = mAudioHardware->setMode(mode); + ret = mPrimaryHardwareDev->set_mode(mPrimaryHardwareDev, mode); mHardwareStatus = AUDIO_HW_IDLE; } @@ -506,16 +595,16 @@ status_t AudioFlinger::setMicMute(bool state) AutoMutex lock(mHardwareLock); mHardwareStatus = AUDIO_HW_SET_MIC_MUTE; - status_t ret = mAudioHardware->setMicMute(state); + status_t ret = mPrimaryHardwareDev->set_mic_mute(mPrimaryHardwareDev, state); mHardwareStatus = AUDIO_HW_IDLE; return ret; } bool AudioFlinger::getMicMute() const { - bool state = AudioSystem::MODE_INVALID; + bool state = AUDIO_MODE_INVALID; mHardwareStatus = AUDIO_HW_GET_MIC_MUTE; - mAudioHardware->getMicMute(&state); + mPrimaryHardwareDev->get_mic_mute(mPrimaryHardwareDev, &state); mHardwareStatus = AUDIO_HW_IDLE; return state; } @@ -552,7 +641,7 @@ status_t AudioFlinger::setStreamVolume(int stream, float value, int output) return PERMISSION_DENIED; } - if (stream < 0 || uint32_t(stream) >= AudioSystem::NUM_STREAM_TYPES) { + if (stream < 0 || uint32_t(stream) >= AUDIO_STREAM_CNT) { return BAD_VALUE; } @@ -585,8 +674,8 @@ status_t AudioFlinger::setStreamMute(int stream, bool muted) return PERMISSION_DENIED; } - if (stream < 0 || uint32_t(stream) >= AudioSystem::NUM_STREAM_TYPES || - uint32_t(stream) == AudioSystem::ENFORCED_AUDIBLE) { + if (stream < 0 || uint32_t(stream) >= AUDIO_STREAM_CNT || + uint32_t(stream) == AUDIO_STREAM_ENFORCED_AUDIBLE) { return BAD_VALUE; } @@ -600,7 +689,7 @@ status_t AudioFlinger::setStreamMute(int stream, bool muted) float AudioFlinger::streamVolume(int stream, int output) const { - if (stream < 0 || uint32_t(stream) >= AudioSystem::NUM_STREAM_TYPES) { + if (stream < 0 || uint32_t(stream) >= AUDIO_STREAM_CNT) { return 0.0f; } @@ -621,7 +710,7 @@ float AudioFlinger::streamVolume(int stream, int output) const bool AudioFlinger::streamMute(int stream) const { - if (stream < 0 || stream >= (int)AudioSystem::NUM_STREAM_TYPES) { + if (stream < 0 || stream >= (int)AUDIO_STREAM_CNT) { return true; } @@ -643,9 +732,14 @@ status_t AudioFlinger::setParameters(int ioHandle, const String8& keyValuePairs) if (ioHandle == 0) { AutoMutex lock(mHardwareLock); mHardwareStatus = AUDIO_SET_PARAMETER; - result = mAudioHardware->setParameters(keyValuePairs); + status_t final_result = NO_ERROR; + for (size_t i = 0; i < mAudioHwDevs.size(); i++) { + audio_hw_device_t *dev = mAudioHwDevs[i]; + result = dev->set_parameters(dev, keyValuePairs.string()); + final_result = result ?: final_result; + } mHardwareStatus = AUDIO_HW_IDLE; - return result; + return final_result; } // hold a strong ref on thread in case closeOutput() or closeInput() is called @@ -671,7 +765,15 @@ String8 AudioFlinger::getParameters(int ioHandle, const String8& keys) // ioHandle, keys.string(), gettid(), IPCThreadState::self()->getCallingPid()); if (ioHandle == 0) { - return mAudioHardware->getParameters(keys); + String8 out_s8; + + for (size_t i = 0; i < mAudioHwDevs.size(); i++) { + audio_hw_device_t *dev = mAudioHwDevs[i]; + char *s = dev->get_parameters(dev, keys.string()); + out_s8 += String8(s); + free(s); + } + return out_s8; } Mutex::Autolock _l(mLock); @@ -689,7 +791,7 @@ String8 AudioFlinger::getParameters(int ioHandle, const String8& keys) size_t AudioFlinger::getInputBufferSize(uint32_t sampleRate, int format, int channelCount) { - return mAudioHardware->getInputBufferSize(sampleRate, format, channelCount); + return mPrimaryHardwareDev->get_input_buffer_size(mPrimaryHardwareDev, sampleRate, format, channelCount); } unsigned int AudioFlinger::getInputFramesLost(int ioHandle) @@ -716,7 +818,7 @@ status_t AudioFlinger::setVoiceVolume(float value) AutoMutex lock(mHardwareLock); mHardwareStatus = AUDIO_SET_VOICE_VOLUME; - status_t ret = mAudioHardware->setVoiceVolume(value); + status_t ret = mPrimaryHardwareDev->set_voice_volume(mPrimaryHardwareDev, value); mHardwareStatus = AUDIO_HW_IDLE; return ret; @@ -967,7 +1069,7 @@ AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinge mMasterVolume = mAudioFlinger->masterVolume(); mMasterMute = mAudioFlinger->masterMute(); - for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) { + for (int stream = 0; stream < AUDIO_STREAM_CNT; stream++) { mStreamTypes[stream].volume = mAudioFlinger->streamVolumeInternal(stream); mStreamTypes[stream].mute = mAudioFlinger->streamMute(stream); } @@ -1130,12 +1232,12 @@ sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTra // conflicts will happen when tracks are moved from one output to another by audio policy // manager uint32_t strategy = - AudioSystem::getStrategyForStream((AudioSystem::stream_type)streamType); + AudioSystem::getStrategyForStream((audio_stream_type_t)streamType); for (size_t i = 0; i < mTracks.size(); ++i) { sp<Track> t = mTracks[i]; if (t != 0) { if (sessionId == t->sessionId() && - strategy != AudioSystem::getStrategyForStream((AudioSystem::stream_type)t->type())) { + strategy != AudioSystem::getStrategyForStream((audio_stream_type_t)t->type())) { lStatus = BAD_VALUE; goto Exit; } @@ -1154,7 +1256,7 @@ sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTra if (chain != 0) { LOGV("createTrack_l() setting main buffer %p", chain->inBuffer()); track->setMainBuffer(chain->inBuffer()); - chain->setStrategy(AudioSystem::getStrategyForStream((AudioSystem::stream_type)track->type())); + chain->setStrategy(AudioSystem::getStrategyForStream((audio_stream_type_t)track->type())); } } lStatus = NO_ERROR; @@ -1169,7 +1271,7 @@ Exit: uint32_t AudioFlinger::PlaybackThread::latency() const { if (mOutput) { - return mOutput->latency(); + return mOutput->stream->get_latency(mOutput->stream); } else { return 0; @@ -1263,7 +1365,13 @@ void AudioFlinger::PlaybackThread::destroyTrack_l(const sp<Track>& track) String8 AudioFlinger::PlaybackThread::getParameters(const String8& keys) { - return mOutput->getParameters(keys); + String8 out_s8; + char *s; + + s = mOutput->stream->common.get_parameters(&mOutput->stream->common, keys.string()); + out_s8 = String8(s); + free(s); + return out_s8; } // destroyTrack_l() must be called with AudioFlinger::mLock held @@ -1295,12 +1403,12 @@ void AudioFlinger::PlaybackThread::audioConfigChanged_l(int event, int param) { void AudioFlinger::PlaybackThread::readOutputParameters() { - mSampleRate = mOutput->sampleRate(); - mChannels = mOutput->channels(); - mChannelCount = (uint16_t)AudioSystem::popCount(mChannels); - mFormat = mOutput->format(); - mFrameSize = (uint16_t)mOutput->frameSize(); - mFrameCount = mOutput->bufferSize() / mFrameSize; + mSampleRate = mOutput->stream->common.get_sample_rate(&mOutput->stream->common); + mChannels = mOutput->stream->common.get_channels(&mOutput->stream->common); + mChannelCount = (uint16_t)popcount(mChannels); + mFormat = mOutput->stream->common.get_format(&mOutput->stream->common); + mFrameSize = (uint16_t)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. @@ -1328,9 +1436,9 @@ status_t AudioFlinger::PlaybackThread::getRenderPosition(uint32_t *halFrames, ui if (mOutput == 0) { return INVALID_OPERATION; } - *halFrames = mBytesWritten/mOutput->frameSize(); + *halFrames = mBytesWritten / audio_stream_frame_size(&mOutput->stream->common); - return mOutput->getRenderPosition(dspFrames); + return mOutput->stream->get_render_position(mOutput->stream, dspFrames); } uint32_t AudioFlinger::PlaybackThread::hasAudioSession(int sessionId) @@ -1355,19 +1463,19 @@ uint32_t AudioFlinger::PlaybackThread::hasAudioSession(int sessionId) uint32_t AudioFlinger::PlaybackThread::getStrategyForSession_l(int sessionId) { - // session AudioSystem::SESSION_OUTPUT_MIX is placed in same strategy as MUSIC stream so that + // session AUDIO_SESSION_OUTPUT_MIX is placed in same strategy as MUSIC stream so that // it is moved to correct output by audio policy manager when A2DP is connected or disconnected - if (sessionId == AudioSystem::SESSION_OUTPUT_MIX) { - return AudioSystem::getStrategyForStream(AudioSystem::MUSIC); + if (sessionId == AUDIO_SESSION_OUTPUT_MIX) { + return AudioSystem::getStrategyForStream(AUDIO_STREAM_MUSIC); } for (size_t i = 0; i < mTracks.size(); i++) { sp<Track> track = mTracks[i]; if (sessionId == track->sessionId() && !(track->mCblk->flags & CBLK_INVALID_MSK)) { - return AudioSystem::getStrategyForStream((AudioSystem::stream_type) track->type()); + return AudioSystem::getStrategyForStream((audio_stream_type_t) track->type()); } } - return AudioSystem::getStrategyForStream(AudioSystem::MUSIC); + return AudioSystem::getStrategyForStream(AUDIO_STREAM_MUSIC); } sp<AudioFlinger::EffectChain> AudioFlinger::PlaybackThread::getEffectChain(int sessionId) @@ -1460,7 +1568,7 @@ bool AudioFlinger::MixerThread::threadLoop() mSuspended) { if (!mStandby) { LOGV("Audio hardware entering standby, mixer %p, mSuspended %d\n", this, mSuspended); - mOutput->standby(); + mOutput->stream->common.standby(&mOutput->stream->common); mStandby = true; mBytesWritten = 0; } @@ -1537,7 +1645,7 @@ bool AudioFlinger::MixerThread::threadLoop() mInWrite = true; mBytesWritten += mixBufferSize; - int bytesWritten = (int)mOutput->write(mMixBuffer, mixBufferSize); + int bytesWritten = (int)mOutput->stream->write(mOutput->stream, mMixBuffer, mixBufferSize); if (bytesWritten < 0) mBytesWritten -= mixBufferSize; mNumWrites++; mInWrite = false; @@ -1572,7 +1680,7 @@ bool AudioFlinger::MixerThread::threadLoop() } if (!mStandby) { - mOutput->standby(); + mOutput->stream->common.standby(&mOutput->stream->common); } LOGV("MixerThread %p exiting", this); @@ -1596,7 +1704,7 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track masterVolume = 0; } // Delegate master volume control to effect in output mix effect chain if needed - sp<EffectChain> chain = getEffectChain_l(AudioSystem::SESSION_OUTPUT_MIX); + sp<EffectChain> chain = getEffectChain_l(AUDIO_SESSION_OUTPUT_MIX); if (chain != 0) { uint32_t v = (uint32_t)(masterVolume * (1 << 24)); chain->setVolume_l(&v, &v); @@ -1738,7 +1846,7 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track LOGV("BUFFER TIMEOUT: remove(%d) from active list on thread %p", track->name(), this); tracksToRemove->add(track); // indicate to client process that the track was disabled because of underrun - cblk->flags |= CBLK_DISABLED_ON; + android_atomic_or(CBLK_DISABLED_ON, &cblk->flags); } else if (mixerStatus != MIXER_TRACKS_READY) { mixerStatus = MIXER_TRACKS_ENABLED; } @@ -1787,10 +1895,8 @@ void AudioFlinger::MixerThread::invalidateTracks(int streamType) for (size_t i = 0; i < size; i++) { sp<Track> t = mTracks[i]; if (t->type() == streamType) { - t->mCblk->lock.lock(); - t->mCblk->flags |= CBLK_INVALID_ON; + android_atomic_or(CBLK_INVALID_ON, &t->mCblk->flags); t->mCblk->cv.signal(); - t->mCblk->lock.unlock(); } } } @@ -1824,14 +1930,14 @@ bool AudioFlinger::MixerThread::checkForNewParameters_l() reconfig = true; } if (param.getInt(String8(AudioParameter::keyFormat), value) == NO_ERROR) { - if (value != AudioSystem::PCM_16_BIT) { + if (value != AUDIO_FORMAT_PCM_16_BIT) { status = BAD_VALUE; } else { reconfig = true; } } if (param.getInt(String8(AudioParameter::keyChannels), value) == NO_ERROR) { - if (value != AudioSystem::CHANNEL_OUT_STEREO) { + if (value != AUDIO_CHANNEL_OUT_STEREO) { status = BAD_VALUE; } else { reconfig = true; @@ -1853,12 +1959,12 @@ bool AudioFlinger::MixerThread::checkForNewParameters_l() if (mDevice != value) { uint32_t params = 0; // check whether speaker is on - if (value & AudioSystem::DEVICE_OUT_SPEAKER) { + if (value & AUDIO_DEVICE_OUT_SPEAKER) { params |= IMediaPlayerService::kBatteryDataSpeakerOn; } int deviceWithoutSpeaker - = AudioSystem::DEVICE_OUT_ALL & ~AudioSystem::DEVICE_OUT_SPEAKER; + = AUDIO_DEVICE_OUT_ALL & ~AUDIO_DEVICE_OUT_SPEAKER; // check if any other device (except speaker) is on if (value & deviceWithoutSpeaker ) { params |= IMediaPlayerService::kBatteryDataOtherAudioDeviceOn; @@ -1878,12 +1984,14 @@ bool AudioFlinger::MixerThread::checkForNewParameters_l() } if (status == NO_ERROR) { - status = mOutput->setParameters(keyValuePair); + status = mOutput->stream->common.set_parameters(&mOutput->stream->common, + keyValuePair.string()); if (!mStandby && status == INVALID_OPERATION) { - mOutput->standby(); + mOutput->stream->common.standby(&mOutput->stream->common); mStandby = true; mBytesWritten = 0; - status = mOutput->setParameters(keyValuePair); + status = mOutput->stream->common.set_parameters(&mOutput->stream->common, + keyValuePair.string()); } if (status == NO_ERROR && reconfig) { delete mAudioMixer; @@ -1927,7 +2035,7 @@ status_t AudioFlinger::MixerThread::dumpInternals(int fd, const Vector<String16> uint32_t AudioFlinger::MixerThread::activeSleepTimeUs() { - return (uint32_t)(mOutput->latency() * 1000) / 2; + return (uint32_t)(mOutput->stream->get_latency(mOutput->stream) * 1000) / 2; } uint32_t AudioFlinger::MixerThread::idleSleepTimeUs() @@ -1977,12 +2085,12 @@ int32_t mul(int16_t in, int16_t v) void AudioFlinger::DirectOutputThread::applyVolume(uint16_t leftVol, uint16_t rightVol, bool ramp) { // Do not apply volume on compressed audio - if (!AudioSystem::isLinearPCM(mFormat)) { + if (!audio_is_linear_pcm(mFormat)) { return; } // convert to signed 16 bit before volume calculation - if (mFormat == AudioSystem::PCM_8_BIT) { + if (mFormat == AUDIO_FORMAT_PCM_8_BIT) { size_t count = mFrameCount * mChannelCount; uint8_t *src = (uint8_t *)mMixBuffer + count-1; int16_t *dst = mMixBuffer + count-1; @@ -2035,7 +2143,7 @@ void AudioFlinger::DirectOutputThread::applyVolume(uint16_t leftVol, uint16_t ri } // convert back to unsigned 8 bit after volume calculation - if (mFormat == AudioSystem::PCM_8_BIT) { + if (mFormat == AUDIO_FORMAT_PCM_8_BIT) { size_t count = mFrameCount * mChannelCount; int16_t *src = mMixBuffer; uint8_t *dst = (uint8_t *)mMixBuffer; @@ -2091,7 +2199,7 @@ bool AudioFlinger::DirectOutputThread::threadLoop() // wait until we have something to do... if (!mStandby) { LOGV("Audio hardware entering standby, mixer %p\n", this); - mOutput->standby(); + mOutput->stream->common.standby(&mOutput->stream->common); mStandby = true; mBytesWritten = 0; } @@ -2176,7 +2284,7 @@ bool AudioFlinger::DirectOutputThread::threadLoop() // If audio HAL implements volume control, // force software volume to nominal value - if (mOutput->setVolume(left, right) == NO_ERROR) { + if (mOutput->stream->set_volume(mOutput->stream, left, right) == NO_ERROR) { left = 1.0f; right = 1.0f; } @@ -2277,7 +2385,7 @@ bool AudioFlinger::DirectOutputThread::threadLoop() } else { sleepTime = idleSleepTime; } - } else if (mBytesWritten != 0 && AudioSystem::isLinearPCM(mFormat)) { + } else if (mBytesWritten != 0 && audio_is_linear_pcm(mFormat)) { memset (mMixBuffer, 0, mFrameCount * mFrameSize); sleepTime = 0; } @@ -2299,7 +2407,7 @@ bool AudioFlinger::DirectOutputThread::threadLoop() mLastWriteTime = systemTime(); mInWrite = true; mBytesWritten += mixBufferSize; - int bytesWritten = (int)mOutput->write(mMixBuffer, mixBufferSize); + int bytesWritten = (int)mOutput->stream->write(mOutput->stream, mMixBuffer, mixBufferSize); if (bytesWritten < 0) mBytesWritten -= mixBufferSize; mNumWrites++; mInWrite = false; @@ -2321,7 +2429,7 @@ bool AudioFlinger::DirectOutputThread::threadLoop() } if (!mStandby) { - mOutput->standby(); + mOutput->stream->common.standby(&mOutput->stream->common); } LOGV("DirectOutputThread %p exiting", this); @@ -2361,12 +2469,14 @@ bool AudioFlinger::DirectOutputThread::checkForNewParameters_l() } } if (status == NO_ERROR) { - status = mOutput->setParameters(keyValuePair); + status = mOutput->stream->common.set_parameters(&mOutput->stream->common, + keyValuePair.string()); if (!mStandby && status == INVALID_OPERATION) { - mOutput->standby(); + mOutput->stream->common.standby(&mOutput->stream->common); mStandby = true; mBytesWritten = 0; - status = mOutput->setParameters(keyValuePair); + status = mOutput->stream->common.set_parameters(&mOutput->stream->common, + keyValuePair.string()); } if (status == NO_ERROR && reconfig) { readOutputParameters(); @@ -2386,8 +2496,8 @@ bool AudioFlinger::DirectOutputThread::checkForNewParameters_l() uint32_t AudioFlinger::DirectOutputThread::activeSleepTimeUs() { uint32_t time; - if (AudioSystem::isLinearPCM(mFormat)) { - time = (uint32_t)(mOutput->latency() * 1000) / 2; + if (audio_is_linear_pcm(mFormat)) { + time = (uint32_t)(mOutput->stream->get_latency(mOutput->stream) * 1000) / 2; } else { time = 10000; } @@ -2397,7 +2507,7 @@ uint32_t AudioFlinger::DirectOutputThread::activeSleepTimeUs() uint32_t AudioFlinger::DirectOutputThread::idleSleepTimeUs() { uint32_t time; - if (AudioSystem::isLinearPCM(mFormat)) { + if (audio_is_linear_pcm(mFormat)) { time = (uint32_t)(((mFrameCount * 1000) / mSampleRate) * 1000) / 2; } else { time = 10000; @@ -2408,7 +2518,7 @@ uint32_t AudioFlinger::DirectOutputThread::idleSleepTimeUs() uint32_t AudioFlinger::DirectOutputThread::suspendSleepTimeUs() { uint32_t time; - if (AudioSystem::isLinearPCM(mFormat)) { + if (audio_is_linear_pcm(mFormat)) { time = (uint32_t)(((mFrameCount * 1000) / mSampleRate) * 1000); } else { time = 10000; @@ -2589,7 +2699,7 @@ void AudioFlinger::DuplicatingThread::addOutputTrack(MixerThread *thread) mChannelCount, frameCount); if (outputTrack->cblk() != NULL) { - thread->setStreamVolume(AudioSystem::NUM_STREAM_TYPES, 1.0f); + thread->setStreamVolume(AUDIO_STREAM_CNT, 1.0f); mOutputTracks.add(outputTrack); LOGV("addOutputTrack() track %p, on thread %p", outputTrack, thread); updateWaitTime(); @@ -2834,7 +2944,7 @@ AudioFlinger::PlaybackThread::Track::Track( 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 - mCblk->frameSize = AudioSystem::isLinearPCM(format) ? channelCount * sizeof(int16_t) : sizeof(int8_t); + mCblk->frameSize = audio_is_linear_pcm(format) ? channelCount * sizeof(int16_t) : sizeof(int8_t); } } @@ -2865,7 +2975,7 @@ void AudioFlinger::PlaybackThread::Track::destroy() if (!isOutputTrack()) { if (mState == ACTIVE || mState == RESUMING) { AudioSystem::stopOutput(thread->id(), - (AudioSystem::stream_type)mStreamType, + (audio_stream_type_t)mStreamType, mSessionId); // to track the speaker usage @@ -2949,7 +3059,7 @@ bool AudioFlinger::PlaybackThread::Track::isReady() const { if (mCblk->framesReady() >= mCblk->frameCount || (mCblk->flags & CBLK_FORCEREADY_MSK)) { mFillingUpStatus = FS_FILLED; - mCblk->flags &= ~CBLK_FORCEREADY_MSK; + android_atomic_and(~CBLK_FORCEREADY_MSK, &mCblk->flags); return true; } return false; @@ -2977,7 +3087,7 @@ status_t AudioFlinger::PlaybackThread::Track::start() if (!isOutputTrack() && state != ACTIVE && state != RESUMING) { thread->mLock.unlock(); status = AudioSystem::startOutput(thread->id(), - (AudioSystem::stream_type)mStreamType, + (audio_stream_type_t)mStreamType, mSessionId); thread->mLock.lock(); @@ -3017,7 +3127,7 @@ void AudioFlinger::PlaybackThread::Track::stop() if (!isOutputTrack() && (state == ACTIVE || state == RESUMING)) { thread->mLock.unlock(); AudioSystem::stopOutput(thread->id(), - (AudioSystem::stream_type)mStreamType, + (audio_stream_type_t)mStreamType, mSessionId); thread->mLock.lock(); @@ -3039,7 +3149,7 @@ void AudioFlinger::PlaybackThread::Track::pause() if (!isOutputTrack()) { thread->mLock.unlock(); AudioSystem::stopOutput(thread->id(), - (AudioSystem::stream_type)mStreamType, + (audio_stream_type_t)mStreamType, mSessionId); thread->mLock.lock(); @@ -3063,14 +3173,12 @@ void AudioFlinger::PlaybackThread::Track::flush() // STOPPED state mState = STOPPED; - mCblk->lock.lock(); - // NOTE: reset() will reset cblk->user and cblk->server with - // the risk that at the same time, the AudioMixer is trying to read - // data. In this case, getNextBuffer() would return a NULL pointer - // as audio buffer => the AudioMixer code MUST always test that pointer - // returned by getNextBuffer() is not NULL! - reset(); - mCblk->lock.unlock(); + // do not reset the track if it is still in the process of being stopped or paused. + // this will be done by prepareTracks_l() when the track is stopped. + PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); + if (playbackThread->mActiveTracks.indexOf(this) < 0) { + reset(); + } } } @@ -3082,8 +3190,8 @@ void AudioFlinger::PlaybackThread::Track::reset() TrackBase::reset(); // Force underrun condition to avoid false underrun callback until first data is // written to buffer - mCblk->flags |= CBLK_UNDERRUN_ON; - mCblk->flags &= ~CBLK_FORCEREADY_MSK; + android_atomic_and(~CBLK_FORCEREADY_MSK, &mCblk->flags); + android_atomic_or(CBLK_UNDERRUN_ON, &mCblk->flags); mFillingUpStatus = FS_FILLING; mResetDone = true; } @@ -3135,9 +3243,9 @@ AudioFlinger::RecordThread::RecordTrack::RecordTrack( { if (mCblk != NULL) { LOGV("RecordTrack constructor, size %d", (int)mBufferEnd - (int)mBuffer); - if (format == AudioSystem::PCM_16_BIT) { + if (format == AUDIO_FORMAT_PCM_16_BIT) { mCblk->frameSize = channelCount * sizeof(int16_t); - } else if (format == AudioSystem::PCM_8_BIT) { + } else if (format == AUDIO_FORMAT_PCM_8_BIT) { mCblk->frameSize = channelCount * sizeof(int8_t); } else { mCblk->frameSize = sizeof(int8_t); @@ -3212,7 +3320,7 @@ void AudioFlinger::RecordThread::RecordTrack::stop() TrackBase::reset(); // Force overerrun condition to avoid false overrun callback until first data is // read from buffer - mCblk->flags |= CBLK_UNDERRUN_ON; + android_atomic_or(CBLK_UNDERRUN_ON, &mCblk->flags); } } @@ -3240,7 +3348,7 @@ AudioFlinger::PlaybackThread::OutputTrack::OutputTrack( int format, int channelCount, int frameCount) - : Track(thread, NULL, AudioSystem::NUM_STREAM_TYPES, sampleRate, format, channelCount, frameCount, NULL, 0), + : Track(thread, NULL, AUDIO_STREAM_CNT, sampleRate, format, channelCount, frameCount, NULL, 0), mActive(false), mSourceThread(sourceThread) { @@ -3612,7 +3720,7 @@ sp<IAudioRecord> AudioFlinger::openRecord( } // If no audio session id is provided, create one here - if (sessionId != NULL && *sessionId != AudioSystem::SESSION_OUTPUT_MIX) { + if (sessionId != NULL && *sessionId != AUDIO_SESSION_OUTPUT_MIX) { lSessionId = *sessionId; } else { lSessionId = nextUniqueId_l(); @@ -3682,7 +3790,7 @@ AudioFlinger::RecordThread::RecordThread(const sp<AudioFlinger>& audioFlinger, A ThreadBase(audioFlinger, id), mInput(input), mResampler(0), mRsmpOutBuffer(0), mRsmpInBuffer(0) { - mReqChannelCount = AudioSystem::popCount(channels); + mReqChannelCount = popcount(channels); mReqSampleRate = sampleRate; readInputParameters(); } @@ -3724,7 +3832,7 @@ bool AudioFlinger::RecordThread::threadLoop() checkForNewParameters_l(); if (mActiveTrack == 0 && mConfigEvents.isEmpty()) { if (!mStandby) { - mInput->standby(); + mInput->stream->common.standby(&mInput->stream->common); mStandby = true; } @@ -3739,7 +3847,7 @@ bool AudioFlinger::RecordThread::threadLoop() if (mActiveTrack != 0) { if (mActiveTrack->mState == TrackBase::PAUSING) { if (!mStandby) { - mInput->standby(); + mInput->stream->common.standby(&mInput->stream->common); mStandby = true; } mActiveTrack.clear(); @@ -3784,7 +3892,7 @@ bool AudioFlinger::RecordThread::threadLoop() mRsmpInIndex += framesIn; framesOut -= framesIn; if ((int)mChannelCount == mReqChannelCount || - mFormat != AudioSystem::PCM_16_BIT) { + mFormat != AUDIO_FORMAT_PCM_16_BIT) { memcpy(dst, src, framesIn * mFrameSize); } else { int16_t *src16 = (int16_t *)src; @@ -3804,11 +3912,11 @@ bool AudioFlinger::RecordThread::threadLoop() } if (framesOut && mFrameCount == mRsmpInIndex) { if (framesOut == mFrameCount && - ((int)mChannelCount == mReqChannelCount || mFormat != AudioSystem::PCM_16_BIT)) { - mBytesRead = mInput->read(buffer.raw, mInputBytes); + ((int)mChannelCount == mReqChannelCount || mFormat != AUDIO_FORMAT_PCM_16_BIT)) { + mBytesRead = mInput->stream->read(mInput->stream, buffer.raw, mInputBytes); framesOut = 0; } else { - mBytesRead = mInput->read(mRsmpInBuffer, mInputBytes); + mBytesRead = mInput->stream->read(mInput->stream, mRsmpInBuffer, mInputBytes); mRsmpInIndex = 0; } if (mBytesRead < 0) { @@ -3816,7 +3924,7 @@ bool AudioFlinger::RecordThread::threadLoop() if (mActiveTrack->mState == TrackBase::ACTIVE) { // Force input into standby so that it tries to // recover at next read attempt - mInput->standby(); + mInput->stream->common.standby(&mInput->stream->common); usleep(5000); } mRsmpInIndex = mFrameCount; @@ -3871,7 +3979,7 @@ bool AudioFlinger::RecordThread::threadLoop() } if (!mStandby) { - mInput->standby(); + mInput->stream->common.standby(&mInput->stream->common); } mActiveTrack.clear(); @@ -4003,13 +4111,13 @@ status_t AudioFlinger::RecordThread::getNextBuffer(AudioBufferProvider::Buffer* int channelCount; if (framesReady == 0) { - mBytesRead = mInput->read(mRsmpInBuffer, mInputBytes); + mBytesRead = mInput->stream->read(mInput->stream, mRsmpInBuffer, mInputBytes); if (mBytesRead < 0) { LOGE("RecordThread::getNextBuffer() Error reading audio input"); if (mActiveTrack->mState == TrackBase::ACTIVE) { // Force input into standby so that it tries to // recover at next read attempt - mInput->standby(); + mInput->stream->common.standby(&mInput->stream->common); usleep(5000); } buffer->raw = 0; @@ -4062,7 +4170,7 @@ bool AudioFlinger::RecordThread::checkForNewParameters_l() reconfig = true; } if (param.getInt(String8(AudioParameter::keyChannels), value) == NO_ERROR) { - reqChannelCount = AudioSystem::popCount(value); + reqChannelCount = popcount(value); reconfig = true; } if (param.getInt(String8(AudioParameter::keyFrameCount), value) == NO_ERROR) { @@ -4076,16 +4184,18 @@ bool AudioFlinger::RecordThread::checkForNewParameters_l() } } if (status == NO_ERROR) { - status = mInput->setParameters(keyValuePair); + status = mInput->stream->common.set_parameters(&mInput->stream->common, keyValuePair.string()); if (status == INVALID_OPERATION) { - mInput->standby(); - status = mInput->setParameters(keyValuePair); + mInput->stream->common.standby(&mInput->stream->common); + status = mInput->stream->common.set_parameters(&mInput->stream->common, keyValuePair.string()); } if (reconfig) { if (status == BAD_VALUE && - reqFormat == mInput->format() && reqFormat == AudioSystem::PCM_16_BIT && - ((int)mInput->sampleRate() <= 2 * reqSamplingRate) && - (AudioSystem::popCount(mInput->channels()) < 3) && (reqChannelCount < 3)) { + reqFormat == mInput->stream->common.get_format(&mInput->stream->common) && + reqFormat == AUDIO_FORMAT_PCM_16_BIT && + ((int)mInput->stream->common.get_sample_rate(&mInput->stream->common) <= (2 * reqSamplingRate)) && + (popcount(mInput->stream->common.get_channels(&mInput->stream->common)) < 3) && + (reqChannelCount < 3)) { status = NO_ERROR; } if (status == NO_ERROR) { @@ -4106,7 +4216,13 @@ bool AudioFlinger::RecordThread::checkForNewParameters_l() String8 AudioFlinger::RecordThread::getParameters(const String8& keys) { - return mInput->getParameters(keys); + char *s; + String8 out_s8; + + s = mInput->stream->common.get_parameters(&mInput->stream->common, keys.string()); + out_s8 = String8(s); + free(s); + return out_s8; } void AudioFlinger::RecordThread::audioConfigChanged_l(int event, int param) { @@ -4138,12 +4254,12 @@ void AudioFlinger::RecordThread::readInputParameters() if (mResampler) delete mResampler; mResampler = 0; - mSampleRate = mInput->sampleRate(); - mChannels = mInput->channels(); - mChannelCount = (uint16_t)AudioSystem::popCount(mChannels); - mFormat = mInput->format(); - mFrameSize = (uint16_t)mInput->frameSize(); - mInputBytes = mInput->bufferSize(); + mSampleRate = mInput->stream->common.get_sample_rate(&mInput->stream->common); + mChannels = mInput->stream->common.get_channels(&mInput->stream->common); + mChannelCount = (uint16_t)popcount(mChannels); + mFormat = mInput->stream->common.get_format(&mInput->stream->common); + mFrameSize = (uint16_t)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]; @@ -4173,7 +4289,7 @@ void AudioFlinger::RecordThread::readInputParameters() unsigned int AudioFlinger::RecordThread::getInputFramesLost() { - return mInput->getInputFramesLost(); + return mInput->stream->get_input_frames_lost(mInput->stream); } // ---------------------------------------------------------------------------- @@ -4192,6 +4308,8 @@ int AudioFlinger::openOutput(uint32_t *pDevices, uint32_t format = pFormat ? *pFormat : 0; uint32_t channels = pChannels ? *pChannels : 0; uint32_t latency = pLatencyMs ? *pLatencyMs : 0; + audio_stream_out_t *outStream; + audio_hw_device_t *outHwDev; LOGV("openOutput(), Device %x, SamplingRate %d, Format %d, Channels %x, flags %x", pDevices ? *pDevices : 0, @@ -4203,26 +4321,30 @@ int AudioFlinger::openOutput(uint32_t *pDevices, if (pDevices == NULL || *pDevices == 0) { return 0; } + Mutex::Autolock _l(mLock); - AudioStreamOut *output = mAudioHardware->openOutputStream(*pDevices, - (int *)&format, - &channels, - &samplingRate, - &status); + outHwDev = findSuitableHwDev_l(*pDevices); + if (outHwDev == NULL) + return 0; + + status = outHwDev->open_output_stream(outHwDev, *pDevices, (int *)&format, + &channels, &samplingRate, &outStream); LOGV("openOutput() openOutputStream returned output %p, SamplingRate %d, Format %d, Channels %x, status %d", - output, + outStream, samplingRate, format, channels, status); mHardwareStatus = AUDIO_HW_IDLE; - if (output != 0) { + if (outStream != NULL) { + AudioStreamOut *output = new AudioStreamOut(outHwDev, outStream); int id = nextUniqueId_l(); - if ((flags & AudioSystem::OUTPUT_FLAG_DIRECT) || - (format != AudioSystem::PCM_16_BIT) || - (channels != AudioSystem::CHANNEL_OUT_STEREO)) { + + if ((flags & AUDIO_POLICY_OUTPUT_FLAG_DIRECT) || + (format != AUDIO_FORMAT_PCM_16_BIT) || + (channels != AUDIO_CHANNEL_OUT_STEREO)) { thread = new DirectOutputThread(this, output, id, *pDevices); LOGV("openOutput() created direct output: ID %d thread %p", id, thread); } else { @@ -4293,7 +4415,9 @@ status_t AudioFlinger::closeOutput(int output) thread->exit(); if (thread->type() != PlaybackThread::DUPLICATING) { - mAudioHardware->closeOutputStream(thread->getOutput()); + AudioStreamOut *out = thread->getOutput(); + out->hwDev->close_output_stream(out->hwDev, out->stream); + delete out; } return NO_ERROR; } @@ -4343,20 +4467,25 @@ int AudioFlinger::openInput(uint32_t *pDevices, uint32_t reqSamplingRate = samplingRate; uint32_t reqFormat = format; uint32_t reqChannels = channels; + audio_stream_in_t *inStream; + audio_hw_device_t *inHwDev; if (pDevices == NULL || *pDevices == 0) { return 0; } + Mutex::Autolock _l(mLock); - AudioStreamIn *input = mAudioHardware->openInputStream(*pDevices, - (int *)&format, - &channels, - &samplingRate, - &status, - (AudioSystem::audio_in_acoustics)acoustics); + inHwDev = findSuitableHwDev_l(*pDevices); + if (inHwDev == NULL) + return 0; + + status = inHwDev->open_input_stream(inHwDev, *pDevices, (int *)&format, + &channels, &samplingRate, + (audio_in_acoustics_t)acoustics, + &inStream); LOGV("openInput() openInputStream returned input %p, SamplingRate %d, Format %d, Channels %x, acoustics %x, status %d", - input, + inStream, samplingRate, format, channels, @@ -4366,20 +4495,20 @@ int AudioFlinger::openInput(uint32_t *pDevices, // If the input could not be opened with the requested parameters and we can handle the conversion internally, // try to open again with the proposed parameters. The AudioFlinger can resample the input and do mono to stereo // or stereo to mono conversions on 16 bit PCM inputs. - if (input == 0 && status == BAD_VALUE && - reqFormat == format && format == AudioSystem::PCM_16_BIT && + if (inStream == NULL && status == BAD_VALUE && + reqFormat == format && format == AUDIO_FORMAT_PCM_16_BIT && (samplingRate <= 2 * reqSamplingRate) && - (AudioSystem::popCount(channels) < 3) && (AudioSystem::popCount(reqChannels) < 3)) { + (popcount(channels) < 3) && (popcount(reqChannels) < 3)) { LOGV("openInput() reopening with proposed sampling rate and channels"); - input = mAudioHardware->openInputStream(*pDevices, - (int *)&format, - &channels, - &samplingRate, - &status, - (AudioSystem::audio_in_acoustics)acoustics); + status = inHwDev->open_input_stream(inHwDev, *pDevices, (int *)&format, + &channels, &samplingRate, + (audio_in_acoustics_t)acoustics, + &inStream); } - if (input != 0) { + if (inStream != NULL) { + AudioStreamIn *input = new AudioStreamIn(inHwDev, inStream); + int id = nextUniqueId_l(); // Start record thread thread = new RecordThread(this, input, reqSamplingRate, reqChannels, id); @@ -4389,7 +4518,7 @@ int AudioFlinger::openInput(uint32_t *pDevices, if (pFormat) *pFormat = format; if (pChannels) *pChannels = reqChannels; - input->standby(); + input->stream->common.standby(&input->stream->common); // notify client processes of the new input creation thread->audioConfigChanged_l(AudioSystem::INPUT_OPENED); @@ -4418,7 +4547,9 @@ status_t AudioFlinger::closeInput(int input) } thread->exit(); - mAudioHardware->closeInputStream(thread->getInput()); + AudioStreamIn *in = thread->getInput(); + in->hwDev->close_input_stream(in->hwDev, in->stream); + delete in; return NO_ERROR; } @@ -4572,14 +4703,14 @@ sp<IEffect> AudioFlinger::createEffect(pid_t pid, } // check audio settings permission for global effects - if (sessionId == AudioSystem::SESSION_OUTPUT_MIX && !settingsAllowed()) { + if (sessionId == AUDIO_SESSION_OUTPUT_MIX && !settingsAllowed()) { lStatus = PERMISSION_DENIED; goto Exit; } - // Session AudioSystem::SESSION_OUTPUT_STAGE is reserved for output stage effects + // Session AUDIO_SESSION_OUTPUT_STAGE is reserved for output stage effects // that can only be created by audio policy manager (running in same process) - if (sessionId == AudioSystem::SESSION_OUTPUT_STAGE && getpid() != pid) { + if (sessionId == AUDIO_SESSION_OUTPUT_STAGE && getpid() != pid) { lStatus = PERMISSION_DENIED; goto Exit; } @@ -4593,12 +4724,12 @@ sp<IEffect> AudioFlinger::createEffect(pid_t pid, } if (output == 0) { - if (sessionId == AudioSystem::SESSION_OUTPUT_STAGE) { + if (sessionId == AUDIO_SESSION_OUTPUT_STAGE) { // output must be specified by AudioPolicyManager when using session - // AudioSystem::SESSION_OUTPUT_STAGE + // AUDIO_SESSION_OUTPUT_STAGE lStatus = BAD_VALUE; goto Exit; - } else if (sessionId == AudioSystem::SESSION_OUTPUT_MIX) { + } else if (sessionId == AUDIO_SESSION_OUTPUT_MIX) { // if the output returned by getOutputForEffect() is removed before we lock the // mutex below, the call to checkPlaybackThread_l(output) below will detect it // and we will exit safely @@ -4646,7 +4777,7 @@ sp<IEffect> AudioFlinger::createEffect(pid_t pid, // an auxiliary version of this effect type is available found = true; memcpy(&d, &desc, sizeof(effect_descriptor_t)); - if (sessionId != AudioSystem::SESSION_OUTPUT_MIX || + if (sessionId != AUDIO_SESSION_OUTPUT_MIX || (desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { break; } @@ -4659,14 +4790,14 @@ sp<IEffect> AudioFlinger::createEffect(pid_t pid, } // For same effect type, chose auxiliary version over insert version if // connect to output mix (Compliance to OpenSL ES) - if (sessionId == AudioSystem::SESSION_OUTPUT_MIX && + if (sessionId == AUDIO_SESSION_OUTPUT_MIX && (d.flags & EFFECT_FLAG_TYPE_MASK) != EFFECT_FLAG_TYPE_AUXILIARY) { memcpy(&desc, &d, sizeof(effect_descriptor_t)); } } // Do not allow auxiliary effects on a session different from 0 (output mix) - if (sessionId != AudioSystem::SESSION_OUTPUT_MIX && + if (sessionId != AUDIO_SESSION_OUTPUT_MIX && (desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { lStatus = INVALID_OPERATION; goto Exit; @@ -4839,7 +4970,7 @@ sp<AudioFlinger::EffectHandle> AudioFlinger::PlaybackThread::createEffect_l( // Do not allow auxiliary effect on session other than 0 if ((desc->flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY && - sessionId != AudioSystem::SESSION_OUTPUT_MIX) { + sessionId != AUDIO_SESSION_OUTPUT_MIX) { LOGW("createEffect_l() Cannot add auxiliary effect %s to session %d", desc->name, sessionId); lStatus = BAD_VALUE; @@ -4848,7 +4979,7 @@ sp<AudioFlinger::EffectHandle> AudioFlinger::PlaybackThread::createEffect_l( // Do not allow effects with session ID 0 on direct output or duplicating threads // TODO: add rule for hw accelerated effects on direct outputs with non PCM format - if (sessionId == AudioSystem::SESSION_OUTPUT_MIX && mType != MIXER) { + if (sessionId == AUDIO_SESSION_OUTPUT_MIX && mType != MIXER) { LOGW("createEffect_l() Cannot add auxiliary effect %s to session %d", desc->name, sessionId); lStatus = BAD_VALUE; @@ -5035,13 +5166,13 @@ status_t AudioFlinger::PlaybackThread::addEffectChain_l(const sp<EffectChain>& c chain->setInBuffer(buffer, ownsBuffer); chain->setOutBuffer(mMixBuffer); - // Effect chain for session AudioSystem::SESSION_OUTPUT_STAGE is inserted at end of effect + // Effect chain for session AUDIO_SESSION_OUTPUT_STAGE is inserted at end of effect // chains list in order to be processed last as it contains output stage effects - // Effect chain for session AudioSystem::SESSION_OUTPUT_MIX is inserted before - // session AudioSystem::SESSION_OUTPUT_STAGE to be processed + // Effect chain for session AUDIO_SESSION_OUTPUT_MIX is inserted before + // session AUDIO_SESSION_OUTPUT_STAGE to be processed // after track specific effects and before output stage - // It is therefore mandatory that AudioSystem::SESSION_OUTPUT_MIX == 0 and - // that AudioSystem::SESSION_OUTPUT_STAGE < AudioSystem::SESSION_OUTPUT_MIX + // It is therefore mandatory that AUDIO_SESSION_OUTPUT_MIX == 0 and + // that AUDIO_SESSION_OUTPUT_STAGE < AUDIO_SESSION_OUTPUT_MIX // Effect chain for other sessions are inserted at beginning of effect // chains list to be processed before output mix effects. Relative order between other // sessions is not important @@ -5121,8 +5252,8 @@ status_t AudioFlinger::PlaybackThread::attachAuxEffect_l( if (EffectId == 0) { track->setAuxBuffer(0, NULL); } else { - // Auxiliary effects are always in audio session AudioSystem::SESSION_OUTPUT_MIX - sp<EffectModule> effect = getEffect_l(AudioSystem::SESSION_OUTPUT_MIX, EffectId); + // Auxiliary effects are always in audio session AUDIO_SESSION_OUTPUT_MIX + sp<EffectModule> effect = getEffect_l(AUDIO_SESSION_OUTPUT_MIX, EffectId); if (effect != 0) { if ((effect->desc().flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) { track->setAuxBuffer(EffectId, (int32_t *)effect->inBuffer()); @@ -5406,7 +5537,7 @@ status_t AudioFlinger::EffectModule::configure() mConfig.outputCfg.bufferProvider.releaseBuffer = NULL; mConfig.inputCfg.accessMode = EFFECT_BUFFER_ACCESS_READ; // Insert effect: - // - in session AudioSystem::SESSION_OUTPUT_MIX or AudioSystem::SESSION_OUTPUT_STAGE, + // - in session AUDIO_SESSION_OUTPUT_MIX or AUDIO_SESSION_OUTPUT_STAGE, // always overwrites output buffer: input buffer == output buffer // - in other sessions: // last effect in the chain accumulates in output buffer: input buffer != output buffer @@ -5687,17 +5818,17 @@ status_t AudioFlinger::EffectModule::setMode(uint32_t mode) // update this table when AudioSystem::audio_devices or audio_device_e (in EffectApi.h) are modified const uint32_t AudioFlinger::EffectModule::sDeviceConvTable[] = { - DEVICE_EARPIECE, // AudioSystem::DEVICE_OUT_EARPIECE - DEVICE_SPEAKER, // AudioSystem::DEVICE_OUT_SPEAKER - DEVICE_WIRED_HEADSET, // case AudioSystem::DEVICE_OUT_WIRED_HEADSET - DEVICE_WIRED_HEADPHONE, // AudioSystem::DEVICE_OUT_WIRED_HEADPHONE - DEVICE_BLUETOOTH_SCO, // AudioSystem::DEVICE_OUT_BLUETOOTH_SCO - DEVICE_BLUETOOTH_SCO_HEADSET, // AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_HEADSET - DEVICE_BLUETOOTH_SCO_CARKIT, // AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT - DEVICE_BLUETOOTH_A2DP, // AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP - DEVICE_BLUETOOTH_A2DP_HEADPHONES, // AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES - DEVICE_BLUETOOTH_A2DP_SPEAKER, // AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER - DEVICE_AUX_DIGITAL // AudioSystem::DEVICE_OUT_AUX_DIGITAL + DEVICE_EARPIECE, // AUDIO_DEVICE_OUT_EARPIECE + DEVICE_SPEAKER, // AUDIO_DEVICE_OUT_SPEAKER + DEVICE_WIRED_HEADSET, // case AUDIO_DEVICE_OUT_WIRED_HEADSET + DEVICE_WIRED_HEADPHONE, // AUDIO_DEVICE_OUT_WIRED_HEADPHONE + DEVICE_BLUETOOTH_SCO, // AUDIO_DEVICE_OUT_BLUETOOTH_SCO + DEVICE_BLUETOOTH_SCO_HEADSET, // AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET + DEVICE_BLUETOOTH_SCO_CARKIT, // AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT + DEVICE_BLUETOOTH_A2DP, // AUDIO_DEVICE_OUT_BLUETOOTH_A2DP + DEVICE_BLUETOOTH_A2DP_HEADPHONES, // AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES + DEVICE_BLUETOOTH_A2DP_SPEAKER, // AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER + DEVICE_AUX_DIGITAL // AUDIO_DEVICE_OUT_AUX_DIGITAL }; uint32_t AudioFlinger::EffectModule::deviceAudioSystemToEffectApi(uint32_t device) @@ -5707,7 +5838,7 @@ uint32_t AudioFlinger::EffectModule::deviceAudioSystemToEffectApi(uint32_t devic const uint32_t i = 31 - __builtin_clz(device); device &= ~(1 << i); if (i >= sizeof(sDeviceConvTable)/sizeof(uint32_t)) { - LOGE("device convertion error for AudioSystem device 0x%08x", device); + LOGE("device conversion error for AudioSystem device 0x%08x", device); return 0; } deviceOut |= (uint32_t)sDeviceConvTable[i]; @@ -5717,10 +5848,10 @@ uint32_t AudioFlinger::EffectModule::deviceAudioSystemToEffectApi(uint32_t devic // update this table when AudioSystem::audio_mode or audio_mode_e (in EffectApi.h) are modified const uint32_t AudioFlinger::EffectModule::sModeConvTable[] = { - AUDIO_MODE_NORMAL, // AudioSystem::MODE_NORMAL - AUDIO_MODE_RINGTONE, // AudioSystem::MODE_RINGTONE - AUDIO_MODE_IN_CALL, // AudioSystem::MODE_IN_CALL - AUDIO_MODE_IN_CALL // AudioSystem::MODE_IN_COMMUNICATION, same conversion as for MODE_IN_CALL + AUDIO_EFFECT_MODE_NORMAL, // AUDIO_MODE_NORMAL + AUDIO_EFFECT_MODE_RINGTONE, // AUDIO_MODE_RINGTONE + AUDIO_EFFECT_MODE_IN_CALL, // AUDIO_MODE_IN_CALL + AUDIO_EFFECT_MODE_IN_CALL // AUDIO_MODE_IN_COMMUNICATION, same conversion as for AUDIO_MODE_IN_CALL }; int AudioFlinger::EffectModule::modeAudioSystemToEffectApi(uint32_t mode) @@ -6030,7 +6161,7 @@ AudioFlinger::EffectChain::EffectChain(const wp<ThreadBase>& wThread, mVolumeCtrlIdx(-1), mLeftVolume(UINT_MAX), mRightVolume(UINT_MAX), mNewLeftVolume(UINT_MAX), mNewRightVolume(UINT_MAX) { - mStrategy = AudioSystem::getStrategyForStream(AudioSystem::MUSIC); + mStrategy = AudioSystem::getStrategyForStream(AUDIO_STREAM_MUSIC); } AudioFlinger::EffectChain::~EffectChain() @@ -6081,8 +6212,8 @@ void AudioFlinger::EffectChain::process_l() return; } PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); - bool isGlobalSession = (mSessionId == AudioSystem::SESSION_OUTPUT_MIX) || - (mSessionId == AudioSystem::SESSION_OUTPUT_STAGE); + bool isGlobalSession = (mSessionId == AUDIO_SESSION_OUTPUT_MIX) || + (mSessionId == AUDIO_SESSION_OUTPUT_STAGE); bool tracksOnSession = false; if (!isGlobalSession) { tracksOnSession = diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h index ec3d202..22e5116 100644 --- a/services/audioflinger/AudioFlinger.h +++ b/services/audioflinger/AudioFlinger.h @@ -32,12 +32,14 @@ #include <utils/Errors.h> #include <utils/threads.h> #include <utils/SortedVector.h> +#include <utils/TypeHelpers.h> #include <utils/Vector.h> #include <binder/BinderService.h> #include <binder/MemoryDealer.h> -#include <hardware_legacy/AudioHardwareInterface.h> +#include <hardware/audio.h> +#include <hardware/audio_hal.h> #include "AudioBufferProvider.h" @@ -49,7 +51,6 @@ class AudioMixer; class AudioBuffer; class AudioResampler; - // ---------------------------------------------------------------------------- #define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) @@ -211,6 +212,9 @@ private: AudioFlinger(); virtual ~AudioFlinger(); + status_t initCheck() const; + virtual void onFirstRef(); + audio_hw_device_t* findSuitableHwDev_l(uint32_t devices); // Internal dump utilites. status_t dumpPermissionDenial(int fd, const Vector<String16>& args); @@ -268,6 +272,8 @@ private: class EffectModule; class EffectHandle; class EffectChain; + struct AudioStreamOut; + struct AudioStreamIn; class ThreadBase : public Thread { public: @@ -495,7 +501,7 @@ private: void reset(); bool isOutputTrack() const { - return (mStreamType == AudioSystem::NUM_STREAM_TYPES); + return (mStreamType == AUDIO_STREAM_CNT); } // we don't really need a lock for these @@ -689,7 +695,7 @@ private: SortedVector< sp<Track> > mTracks; // mStreamTypes[] uses 1 additionnal stream type internally for the OutputTrack used by DuplicatingThread - stream_type_t mStreamTypes[AudioSystem::NUM_STREAM_TYPES + 1]; + stream_type_t mStreamTypes[AUDIO_STREAM_CNT + 1]; AudioStreamOut* mOutput; float mMasterVolume; nsecs_t mLastWriteTime; @@ -1159,21 +1165,37 @@ private: uint32_t mStrategy; // strategy for this effect chain }; + struct AudioStreamOut { + audio_hw_device_t *hwDev; + audio_stream_out_t *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; + + AudioStreamIn(audio_hw_device_t *dev, audio_stream_in_t *in) : + hwDev(dev), stream(in) {} + }; + friend class RecordThread; friend class PlaybackThread; - mutable Mutex mLock; DefaultKeyedVector< pid_t, wp<Client> > mClients; mutable Mutex mHardwareLock; - AudioHardwareInterface* mAudioHardware; + audio_hw_device_t* mPrimaryHardwareDev; + Vector<audio_hw_device_t*> mAudioHwDevs; mutable int mHardwareStatus; DefaultKeyedVector< int, sp<PlaybackThread> > mPlaybackThreads; - PlaybackThread::stream_type_t mStreamTypes[AudioSystem::NUM_STREAM_TYPES]; + PlaybackThread::stream_type_t mStreamTypes[AUDIO_STREAM_CNT]; float mMasterVolume; bool mMasterMute; @@ -1185,6 +1207,7 @@ private: }; + // ---------------------------------------------------------------------------- }; // namespace android diff --git a/services/audioflinger/AudioHardwareGeneric.cpp b/services/audioflinger/AudioHardwareGeneric.cpp deleted file mode 100644 index d63c031..0000000 --- a/services/audioflinger/AudioHardwareGeneric.cpp +++ /dev/null @@ -1,411 +0,0 @@ -/* -** -** Copyright 2007, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -#include <stdint.h> -#include <sys/types.h> - -#include <stdlib.h> -#include <stdio.h> -#include <unistd.h> -#include <sched.h> -#include <fcntl.h> -#include <sys/ioctl.h> - -#define LOG_TAG "AudioHardware" -#include <utils/Log.h> -#include <utils/String8.h> - -#include "AudioHardwareGeneric.h" -#include <media/AudioRecord.h> - -namespace android { - -// ---------------------------------------------------------------------------- - -static char const * const kAudioDeviceName = "/dev/eac"; - -// ---------------------------------------------------------------------------- - -AudioHardwareGeneric::AudioHardwareGeneric() - : mOutput(0), mInput(0), mFd(-1), mMicMute(false) -{ - mFd = ::open(kAudioDeviceName, O_RDWR); -} - -AudioHardwareGeneric::~AudioHardwareGeneric() -{ - if (mFd >= 0) ::close(mFd); - closeOutputStream((AudioStreamOut *)mOutput); - closeInputStream((AudioStreamIn *)mInput); -} - -status_t AudioHardwareGeneric::initCheck() -{ - if (mFd >= 0) { - if (::access(kAudioDeviceName, O_RDWR) == NO_ERROR) - return NO_ERROR; - } - return NO_INIT; -} - -AudioStreamOut* AudioHardwareGeneric::openOutputStream( - uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status) -{ - AutoMutex lock(mLock); - - // only one output stream allowed - if (mOutput) { - if (status) { - *status = INVALID_OPERATION; - } - return 0; - } - - // create new output stream - AudioStreamOutGeneric* out = new AudioStreamOutGeneric(); - status_t lStatus = out->set(this, mFd, devices, format, channels, sampleRate); - if (status) { - *status = lStatus; - } - if (lStatus == NO_ERROR) { - mOutput = out; - } else { - delete out; - } - return mOutput; -} - -void AudioHardwareGeneric::closeOutputStream(AudioStreamOut* out) { - if (mOutput && out == mOutput) { - delete mOutput; - mOutput = 0; - } -} - -AudioStreamIn* AudioHardwareGeneric::openInputStream( - uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, - status_t *status, AudioSystem::audio_in_acoustics acoustics) -{ - // check for valid input source - if (!AudioSystem::isInputDevice((AudioSystem::audio_devices)devices)) { - return 0; - } - - AutoMutex lock(mLock); - - // only one input stream allowed - if (mInput) { - if (status) { - *status = INVALID_OPERATION; - } - return 0; - } - - // create new output stream - AudioStreamInGeneric* in = new AudioStreamInGeneric(); - status_t lStatus = in->set(this, mFd, devices, format, channels, sampleRate, acoustics); - if (status) { - *status = lStatus; - } - if (lStatus == NO_ERROR) { - mInput = in; - } else { - delete in; - } - return mInput; -} - -void AudioHardwareGeneric::closeInputStream(AudioStreamIn* in) { - if (mInput && in == mInput) { - delete mInput; - mInput = 0; - } -} - -status_t AudioHardwareGeneric::setVoiceVolume(float v) -{ - // Implement: set voice volume - return NO_ERROR; -} - -status_t AudioHardwareGeneric::setMasterVolume(float v) -{ - // Implement: set master volume - // return error - software mixer will handle it - return INVALID_OPERATION; -} - -status_t AudioHardwareGeneric::setMicMute(bool state) -{ - mMicMute = state; - return NO_ERROR; -} - -status_t AudioHardwareGeneric::getMicMute(bool* state) -{ - *state = mMicMute; - return NO_ERROR; -} - -status_t AudioHardwareGeneric::dumpInternals(int fd, const Vector<String16>& args) -{ - const size_t SIZE = 256; - char buffer[SIZE]; - String8 result; - result.append("AudioHardwareGeneric::dumpInternals\n"); - snprintf(buffer, SIZE, "\tmFd: %d mMicMute: %s\n", mFd, mMicMute? "true": "false"); - result.append(buffer); - ::write(fd, result.string(), result.size()); - return NO_ERROR; -} - -status_t AudioHardwareGeneric::dump(int fd, const Vector<String16>& args) -{ - dumpInternals(fd, args); - if (mInput) { - mInput->dump(fd, args); - } - if (mOutput) { - mOutput->dump(fd, args); - } - return NO_ERROR; -} - -// ---------------------------------------------------------------------------- - -status_t AudioStreamOutGeneric::set( - AudioHardwareGeneric *hw, - int fd, - uint32_t devices, - int *pFormat, - uint32_t *pChannels, - uint32_t *pRate) -{ - int lFormat = pFormat ? *pFormat : 0; - uint32_t lChannels = pChannels ? *pChannels : 0; - uint32_t lRate = pRate ? *pRate : 0; - - // fix up defaults - if (lFormat == 0) lFormat = format(); - if (lChannels == 0) lChannels = channels(); - if (lRate == 0) lRate = sampleRate(); - - // check values - if ((lFormat != format()) || - (lChannels != channels()) || - (lRate != sampleRate())) { - if (pFormat) *pFormat = format(); - if (pChannels) *pChannels = channels(); - if (pRate) *pRate = sampleRate(); - return BAD_VALUE; - } - - if (pFormat) *pFormat = lFormat; - if (pChannels) *pChannels = lChannels; - if (pRate) *pRate = lRate; - - mAudioHardware = hw; - mFd = fd; - mDevice = devices; - return NO_ERROR; -} - -AudioStreamOutGeneric::~AudioStreamOutGeneric() -{ -} - -ssize_t AudioStreamOutGeneric::write(const void* buffer, size_t bytes) -{ - Mutex::Autolock _l(mLock); - return ssize_t(::write(mFd, buffer, bytes)); -} - -status_t AudioStreamOutGeneric::standby() -{ - // Implement: audio hardware to standby mode - return NO_ERROR; -} - -status_t AudioStreamOutGeneric::dump(int fd, const Vector<String16>& args) -{ - const size_t SIZE = 256; - char buffer[SIZE]; - String8 result; - snprintf(buffer, SIZE, "AudioStreamOutGeneric::dump\n"); - result.append(buffer); - snprintf(buffer, SIZE, "\tsample rate: %d\n", sampleRate()); - result.append(buffer); - snprintf(buffer, SIZE, "\tbuffer size: %d\n", bufferSize()); - result.append(buffer); - snprintf(buffer, SIZE, "\tchannels: %d\n", channels()); - result.append(buffer); - snprintf(buffer, SIZE, "\tformat: %d\n", format()); - result.append(buffer); - snprintf(buffer, SIZE, "\tdevice: %d\n", mDevice); - result.append(buffer); - snprintf(buffer, SIZE, "\tmAudioHardware: %p\n", mAudioHardware); - result.append(buffer); - snprintf(buffer, SIZE, "\tmFd: %d\n", mFd); - result.append(buffer); - ::write(fd, result.string(), result.size()); - return NO_ERROR; -} - -status_t AudioStreamOutGeneric::setParameters(const String8& keyValuePairs) -{ - AudioParameter param = AudioParameter(keyValuePairs); - String8 key = String8(AudioParameter::keyRouting); - status_t status = NO_ERROR; - int device; - LOGV("setParameters() %s", keyValuePairs.string()); - - if (param.getInt(key, device) == NO_ERROR) { - mDevice = device; - param.remove(key); - } - - if (param.size()) { - status = BAD_VALUE; - } - return status; -} - -String8 AudioStreamOutGeneric::getParameters(const String8& keys) -{ - AudioParameter param = AudioParameter(keys); - String8 value; - String8 key = String8(AudioParameter::keyRouting); - - if (param.get(key, value) == NO_ERROR) { - param.addInt(key, (int)mDevice); - } - - LOGV("getParameters() %s", param.toString().string()); - return param.toString(); -} - -status_t AudioStreamOutGeneric::getRenderPosition(uint32_t *dspFrames) -{ - return INVALID_OPERATION; -} - -// ---------------------------------------------------------------------------- - -// record functions -status_t AudioStreamInGeneric::set( - AudioHardwareGeneric *hw, - int fd, - uint32_t devices, - int *pFormat, - uint32_t *pChannels, - uint32_t *pRate, - AudioSystem::audio_in_acoustics acoustics) -{ - if (pFormat == 0 || pChannels == 0 || pRate == 0) return BAD_VALUE; - LOGV("AudioStreamInGeneric::set(%p, %d, %d, %d, %u)", hw, fd, *pFormat, *pChannels, *pRate); - // check values - if ((*pFormat != format()) || - (*pChannels != channels()) || - (*pRate != sampleRate())) { - LOGE("Error opening input channel"); - *pFormat = format(); - *pChannels = channels(); - *pRate = sampleRate(); - return BAD_VALUE; - } - - mAudioHardware = hw; - mFd = fd; - mDevice = devices; - return NO_ERROR; -} - -AudioStreamInGeneric::~AudioStreamInGeneric() -{ -} - -ssize_t AudioStreamInGeneric::read(void* buffer, ssize_t bytes) -{ - AutoMutex lock(mLock); - if (mFd < 0) { - LOGE("Attempt to read from unopened device"); - return NO_INIT; - } - return ::read(mFd, buffer, bytes); -} - -status_t AudioStreamInGeneric::dump(int fd, const Vector<String16>& args) -{ - const size_t SIZE = 256; - char buffer[SIZE]; - String8 result; - snprintf(buffer, SIZE, "AudioStreamInGeneric::dump\n"); - result.append(buffer); - snprintf(buffer, SIZE, "\tsample rate: %d\n", sampleRate()); - result.append(buffer); - snprintf(buffer, SIZE, "\tbuffer size: %d\n", bufferSize()); - result.append(buffer); - snprintf(buffer, SIZE, "\tchannels: %d\n", channels()); - result.append(buffer); - snprintf(buffer, SIZE, "\tformat: %d\n", format()); - result.append(buffer); - snprintf(buffer, SIZE, "\tdevice: %d\n", mDevice); - result.append(buffer); - snprintf(buffer, SIZE, "\tmAudioHardware: %p\n", mAudioHardware); - result.append(buffer); - snprintf(buffer, SIZE, "\tmFd: %d\n", mFd); - result.append(buffer); - ::write(fd, result.string(), result.size()); - return NO_ERROR; -} - -status_t AudioStreamInGeneric::setParameters(const String8& keyValuePairs) -{ - AudioParameter param = AudioParameter(keyValuePairs); - String8 key = String8(AudioParameter::keyRouting); - status_t status = NO_ERROR; - int device; - LOGV("setParameters() %s", keyValuePairs.string()); - - if (param.getInt(key, device) == NO_ERROR) { - mDevice = device; - param.remove(key); - } - - if (param.size()) { - status = BAD_VALUE; - } - return status; -} - -String8 AudioStreamInGeneric::getParameters(const String8& keys) -{ - AudioParameter param = AudioParameter(keys); - String8 value; - String8 key = String8(AudioParameter::keyRouting); - - if (param.get(key, value) == NO_ERROR) { - param.addInt(key, (int)mDevice); - } - - LOGV("getParameters() %s", param.toString().string()); - return param.toString(); -} - -// ---------------------------------------------------------------------------- - -}; // namespace android diff --git a/services/audioflinger/AudioHardwareGeneric.h b/services/audioflinger/AudioHardwareGeneric.h deleted file mode 100644 index aa4e78d..0000000 --- a/services/audioflinger/AudioHardwareGeneric.h +++ /dev/null @@ -1,151 +0,0 @@ -/* -** -** Copyright 2007, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -#ifndef ANDROID_AUDIO_HARDWARE_GENERIC_H -#define ANDROID_AUDIO_HARDWARE_GENERIC_H - -#include <stdint.h> -#include <sys/types.h> - -#include <utils/threads.h> - -#include <hardware_legacy/AudioHardwareBase.h> - -namespace android { - -// ---------------------------------------------------------------------------- - -class AudioHardwareGeneric; - -class AudioStreamOutGeneric : public AudioStreamOut { -public: - AudioStreamOutGeneric() : mAudioHardware(0), mFd(-1) {} - virtual ~AudioStreamOutGeneric(); - - virtual status_t set( - AudioHardwareGeneric *hw, - int mFd, - uint32_t devices, - int *pFormat, - uint32_t *pChannels, - uint32_t *pRate); - - virtual uint32_t sampleRate() const { return 44100; } - virtual size_t bufferSize() const { return 4096; } - virtual uint32_t channels() const { return AudioSystem::CHANNEL_OUT_STEREO; } - virtual int format() const { return AudioSystem::PCM_16_BIT; } - virtual uint32_t latency() const { return 20; } - virtual status_t setVolume(float left, float right) { return INVALID_OPERATION; } - virtual ssize_t write(const void* buffer, size_t bytes); - virtual status_t standby(); - virtual status_t dump(int fd, const Vector<String16>& args); - virtual status_t setParameters(const String8& keyValuePairs); - virtual String8 getParameters(const String8& keys); - virtual status_t getRenderPosition(uint32_t *dspFrames); - -private: - AudioHardwareGeneric *mAudioHardware; - Mutex mLock; - int mFd; - uint32_t mDevice; -}; - -class AudioStreamInGeneric : public AudioStreamIn { -public: - AudioStreamInGeneric() : mAudioHardware(0), mFd(-1) {} - virtual ~AudioStreamInGeneric(); - - virtual status_t set( - AudioHardwareGeneric *hw, - int mFd, - uint32_t devices, - int *pFormat, - uint32_t *pChannels, - uint32_t *pRate, - AudioSystem::audio_in_acoustics acoustics); - - virtual uint32_t sampleRate() const { return 8000; } - virtual size_t bufferSize() const { return 320; } - virtual uint32_t channels() const { return AudioSystem::CHANNEL_IN_MONO; } - virtual int format() const { return AudioSystem::PCM_16_BIT; } - virtual status_t setGain(float gain) { return INVALID_OPERATION; } - virtual ssize_t read(void* buffer, ssize_t bytes); - virtual status_t dump(int fd, const Vector<String16>& args); - virtual status_t standby() { return NO_ERROR; } - virtual status_t setParameters(const String8& keyValuePairs); - virtual String8 getParameters(const String8& keys); - virtual unsigned int getInputFramesLost() const { return 0; } - -private: - AudioHardwareGeneric *mAudioHardware; - Mutex mLock; - int mFd; - uint32_t mDevice; -}; - - -class AudioHardwareGeneric : public AudioHardwareBase -{ -public: - AudioHardwareGeneric(); - virtual ~AudioHardwareGeneric(); - virtual status_t initCheck(); - virtual status_t setVoiceVolume(float volume); - virtual status_t setMasterVolume(float volume); - - // mic mute - virtual status_t setMicMute(bool state); - virtual status_t getMicMute(bool* state); - - // create I/O streams - virtual AudioStreamOut* openOutputStream( - uint32_t devices, - int *format=0, - uint32_t *channels=0, - uint32_t *sampleRate=0, - status_t *status=0); - virtual void closeOutputStream(AudioStreamOut* out); - - virtual AudioStreamIn* openInputStream( - uint32_t devices, - int *format, - uint32_t *channels, - uint32_t *sampleRate, - status_t *status, - AudioSystem::audio_in_acoustics acoustics); - virtual void closeInputStream(AudioStreamIn* in); - - void closeOutputStream(AudioStreamOutGeneric* out); - void closeInputStream(AudioStreamInGeneric* in); -protected: - virtual status_t dump(int fd, const Vector<String16>& args); - -private: - status_t dumpInternals(int fd, const Vector<String16>& args); - - Mutex mLock; - AudioStreamOutGeneric *mOutput; - AudioStreamInGeneric *mInput; - int mFd; - bool mMicMute; -}; - -// ---------------------------------------------------------------------------- - -}; // namespace android - -#endif // ANDROID_AUDIO_HARDWARE_GENERIC_H diff --git a/services/audioflinger/AudioHardwareInterface.cpp b/services/audioflinger/AudioHardwareInterface.cpp deleted file mode 100644 index f58e4c0..0000000 --- a/services/audioflinger/AudioHardwareInterface.cpp +++ /dev/null @@ -1,183 +0,0 @@ -/* -** -** Copyright 2007, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -#include <cutils/properties.h> -#include <string.h> -#include <unistd.h> -//#define LOG_NDEBUG 0 - -#define LOG_TAG "AudioHardwareInterface" -#include <utils/Log.h> -#include <utils/String8.h> - -#include "AudioHardwareStub.h" -#include "AudioHardwareGeneric.h" -#ifdef WITH_A2DP -#include "A2dpAudioInterface.h" -#endif - -#ifdef ENABLE_AUDIO_DUMP -#include "AudioDumpInterface.h" -#endif - - -// change to 1 to log routing calls -#define LOG_ROUTING_CALLS 1 - -namespace android { - -#if LOG_ROUTING_CALLS -static const char* routingModeStrings[] = -{ - "OUT OF RANGE", - "INVALID", - "CURRENT", - "NORMAL", - "RINGTONE", - "IN_CALL", - "IN_COMMUNICATION" -}; - -static const char* routeNone = "NONE"; - -static const char* displayMode(int mode) -{ - if ((mode < AudioSystem::MODE_INVALID) || (mode >= AudioSystem::NUM_MODES)) - return routingModeStrings[0]; - return routingModeStrings[mode+3]; -} -#endif - -// ---------------------------------------------------------------------------- - -AudioHardwareInterface* AudioHardwareInterface::create() -{ - /* - * FIXME: This code needs to instantiate the correct audio device - * interface. For now - we use compile-time switches. - */ - AudioHardwareInterface* hw = 0; - char value[PROPERTY_VALUE_MAX]; - -#ifdef GENERIC_AUDIO - hw = new AudioHardwareGeneric(); -#else - // if running in emulation - use the emulator driver - if (property_get("ro.kernel.qemu", value, 0)) { - LOGD("Running in emulation - using generic audio driver"); - hw = new AudioHardwareGeneric(); - } - else { - LOGV("Creating Vendor Specific AudioHardware"); - hw = createAudioHardware(); - } -#endif - if (hw->initCheck() != NO_ERROR) { - LOGW("Using stubbed audio hardware. No sound will be produced."); - delete hw; - hw = new AudioHardwareStub(); - } - -#ifdef WITH_A2DP - hw = new A2dpAudioInterface(hw); -#endif - -#ifdef ENABLE_AUDIO_DUMP - // This code adds a record of buffers in a file to write calls made by AudioFlinger. - // It replaces the current AudioHardwareInterface object by an intermediate one which - // will record buffers in a file (after sending them to hardware) for testing purpose. - // This feature is enabled by defining symbol ENABLE_AUDIO_DUMP. - // The output file is set with setParameters("test_cmd_file_name=<name>"). Pause are not recorded in the file. - LOGV("opening PCM dump interface"); - hw = new AudioDumpInterface(hw); // replace interface -#endif - return hw; -} - -AudioStreamOut::~AudioStreamOut() -{ -} - -AudioStreamIn::~AudioStreamIn() {} - -AudioHardwareBase::AudioHardwareBase() -{ - mMode = 0; -} - -status_t AudioHardwareBase::setMode(int mode) -{ -#if LOG_ROUTING_CALLS - LOGD("setMode(%s)", displayMode(mode)); -#endif - if ((mode < 0) || (mode >= AudioSystem::NUM_MODES)) - return BAD_VALUE; - if (mMode == mode) - return ALREADY_EXISTS; - mMode = mode; - return NO_ERROR; -} - -// default implementation -status_t AudioHardwareBase::setParameters(const String8& keyValuePairs) -{ - return NO_ERROR; -} - -// default implementation -String8 AudioHardwareBase::getParameters(const String8& keys) -{ - AudioParameter param = AudioParameter(keys); - return param.toString(); -} - -// default implementation -size_t AudioHardwareBase::getInputBufferSize(uint32_t sampleRate, int format, int channelCount) -{ - if (sampleRate != 8000) { - LOGW("getInputBufferSize bad sampling rate: %d", sampleRate); - return 0; - } - if (format != AudioSystem::PCM_16_BIT) { - LOGW("getInputBufferSize bad format: %d", format); - return 0; - } - if (channelCount != 1) { - LOGW("getInputBufferSize bad channel count: %d", channelCount); - return 0; - } - - return 320; -} - -status_t AudioHardwareBase::dumpState(int fd, const Vector<String16>& args) -{ - const size_t SIZE = 256; - char buffer[SIZE]; - String8 result; - snprintf(buffer, SIZE, "AudioHardwareBase::dumpState\n"); - result.append(buffer); - snprintf(buffer, SIZE, "\tmMode: %d\n", mMode); - result.append(buffer); - ::write(fd, result.string(), result.size()); - dump(fd, args); // Dump the state of the concrete child. - return NO_ERROR; -} - -// ---------------------------------------------------------------------------- - -}; // namespace android diff --git a/services/audioflinger/AudioHardwareStub.cpp b/services/audioflinger/AudioHardwareStub.cpp deleted file mode 100644 index d481150..0000000 --- a/services/audioflinger/AudioHardwareStub.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/* //device/servers/AudioFlinger/AudioHardwareStub.cpp -** -** Copyright 2007, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -#include <stdint.h> -#include <sys/types.h> - -#include <stdlib.h> -#include <unistd.h> -#include <utils/String8.h> - -#include "AudioHardwareStub.h" -#include <media/AudioRecord.h> - -namespace android { - -// ---------------------------------------------------------------------------- - -AudioHardwareStub::AudioHardwareStub() : mMicMute(false) -{ -} - -AudioHardwareStub::~AudioHardwareStub() -{ -} - -status_t AudioHardwareStub::initCheck() -{ - return NO_ERROR; -} - -AudioStreamOut* AudioHardwareStub::openOutputStream( - uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status) -{ - AudioStreamOutStub* out = new AudioStreamOutStub(); - status_t lStatus = out->set(format, channels, sampleRate); - if (status) { - *status = lStatus; - } - if (lStatus == NO_ERROR) - return out; - delete out; - return 0; -} - -void AudioHardwareStub::closeOutputStream(AudioStreamOut* out) -{ - delete out; -} - -AudioStreamIn* AudioHardwareStub::openInputStream( - uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, - status_t *status, AudioSystem::audio_in_acoustics acoustics) -{ - // check for valid input source - if (!AudioSystem::isInputDevice((AudioSystem::audio_devices)devices)) { - return 0; - } - - AudioStreamInStub* in = new AudioStreamInStub(); - status_t lStatus = in->set(format, channels, sampleRate, acoustics); - if (status) { - *status = lStatus; - } - if (lStatus == NO_ERROR) - return in; - delete in; - return 0; -} - -void AudioHardwareStub::closeInputStream(AudioStreamIn* in) -{ - delete in; -} - -status_t AudioHardwareStub::setVoiceVolume(float volume) -{ - return NO_ERROR; -} - -status_t AudioHardwareStub::setMasterVolume(float volume) -{ - return NO_ERROR; -} - -status_t AudioHardwareStub::dumpInternals(int fd, const Vector<String16>& args) -{ - const size_t SIZE = 256; - char buffer[SIZE]; - String8 result; - result.append("AudioHardwareStub::dumpInternals\n"); - snprintf(buffer, SIZE, "\tmMicMute: %s\n", mMicMute? "true": "false"); - result.append(buffer); - ::write(fd, result.string(), result.size()); - return NO_ERROR; -} - -status_t AudioHardwareStub::dump(int fd, const Vector<String16>& args) -{ - dumpInternals(fd, args); - return NO_ERROR; -} - -// ---------------------------------------------------------------------------- - -status_t AudioStreamOutStub::set(int *pFormat, uint32_t *pChannels, uint32_t *pRate) -{ - if (pFormat) *pFormat = format(); - if (pChannels) *pChannels = channels(); - if (pRate) *pRate = sampleRate(); - - return NO_ERROR; -} - -ssize_t AudioStreamOutStub::write(const void* buffer, size_t bytes) -{ - // fake timing for audio output - usleep(bytes * 1000000 / sizeof(int16_t) / AudioSystem::popCount(channels()) / sampleRate()); - return bytes; -} - -status_t AudioStreamOutStub::standby() -{ - return NO_ERROR; -} - -status_t AudioStreamOutStub::dump(int fd, const Vector<String16>& args) -{ - const size_t SIZE = 256; - char buffer[SIZE]; - String8 result; - snprintf(buffer, SIZE, "AudioStreamOutStub::dump\n"); - snprintf(buffer, SIZE, "\tsample rate: %d\n", sampleRate()); - snprintf(buffer, SIZE, "\tbuffer size: %d\n", bufferSize()); - snprintf(buffer, SIZE, "\tchannels: %d\n", channels()); - snprintf(buffer, SIZE, "\tformat: %d\n", format()); - result.append(buffer); - ::write(fd, result.string(), result.size()); - return NO_ERROR; -} - -String8 AudioStreamOutStub::getParameters(const String8& keys) -{ - AudioParameter param = AudioParameter(keys); - return param.toString(); -} - -status_t AudioStreamOutStub::getRenderPosition(uint32_t *dspFrames) -{ - return INVALID_OPERATION; -} - -// ---------------------------------------------------------------------------- - -status_t AudioStreamInStub::set(int *pFormat, uint32_t *pChannels, uint32_t *pRate, - AudioSystem::audio_in_acoustics acoustics) -{ - return NO_ERROR; -} - -ssize_t AudioStreamInStub::read(void* buffer, ssize_t bytes) -{ - // fake timing for audio input - usleep(bytes * 1000000 / sizeof(int16_t) / AudioSystem::popCount(channels()) / sampleRate()); - memset(buffer, 0, bytes); - return bytes; -} - -status_t AudioStreamInStub::dump(int fd, const Vector<String16>& args) -{ - const size_t SIZE = 256; - char buffer[SIZE]; - String8 result; - snprintf(buffer, SIZE, "AudioStreamInStub::dump\n"); - result.append(buffer); - snprintf(buffer, SIZE, "\tsample rate: %d\n", sampleRate()); - result.append(buffer); - snprintf(buffer, SIZE, "\tbuffer size: %d\n", bufferSize()); - result.append(buffer); - snprintf(buffer, SIZE, "\tchannels: %d\n", channels()); - result.append(buffer); - snprintf(buffer, SIZE, "\tformat: %d\n", format()); - result.append(buffer); - ::write(fd, result.string(), result.size()); - return NO_ERROR; -} - -String8 AudioStreamInStub::getParameters(const String8& keys) -{ - AudioParameter param = AudioParameter(keys); - return param.toString(); -} - -// ---------------------------------------------------------------------------- - -}; // namespace android diff --git a/services/audioflinger/AudioHardwareStub.h b/services/audioflinger/AudioHardwareStub.h deleted file mode 100644 index 06a29de..0000000 --- a/services/audioflinger/AudioHardwareStub.h +++ /dev/null @@ -1,106 +0,0 @@ -/* //device/servers/AudioFlinger/AudioHardwareStub.h -** -** Copyright 2007, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -#ifndef ANDROID_AUDIO_HARDWARE_STUB_H -#define ANDROID_AUDIO_HARDWARE_STUB_H - -#include <stdint.h> -#include <sys/types.h> - -#include <hardware_legacy/AudioHardwareBase.h> - -namespace android { - -// ---------------------------------------------------------------------------- - -class AudioStreamOutStub : public AudioStreamOut { -public: - virtual status_t set(int *pFormat, uint32_t *pChannels, uint32_t *pRate); - virtual uint32_t sampleRate() const { return 44100; } - virtual size_t bufferSize() const { return 4096; } - virtual uint32_t channels() const { return AudioSystem::CHANNEL_OUT_STEREO; } - virtual int format() const { return AudioSystem::PCM_16_BIT; } - virtual uint32_t latency() const { return 0; } - virtual status_t setVolume(float left, float right) { return NO_ERROR; } - virtual ssize_t write(const void* buffer, size_t bytes); - virtual status_t standby(); - virtual status_t dump(int fd, const Vector<String16>& args); - virtual status_t setParameters(const String8& keyValuePairs) { return NO_ERROR;} - virtual String8 getParameters(const String8& keys); - virtual status_t getRenderPosition(uint32_t *dspFrames); -}; - -class AudioStreamInStub : public AudioStreamIn { -public: - virtual status_t set(int *pFormat, uint32_t *pChannels, uint32_t *pRate, AudioSystem::audio_in_acoustics acoustics); - virtual uint32_t sampleRate() const { return 8000; } - virtual size_t bufferSize() const { return 320; } - virtual uint32_t channels() const { return AudioSystem::CHANNEL_IN_MONO; } - virtual int format() const { return AudioSystem::PCM_16_BIT; } - virtual status_t setGain(float gain) { return NO_ERROR; } - virtual ssize_t read(void* buffer, ssize_t bytes); - virtual status_t dump(int fd, const Vector<String16>& args); - virtual status_t standby() { return NO_ERROR; } - virtual status_t setParameters(const String8& keyValuePairs) { return NO_ERROR;} - virtual String8 getParameters(const String8& keys); - virtual unsigned int getInputFramesLost() const { return 0; } -}; - -class AudioHardwareStub : public AudioHardwareBase -{ -public: - AudioHardwareStub(); - virtual ~AudioHardwareStub(); - virtual status_t initCheck(); - virtual status_t setVoiceVolume(float volume); - virtual status_t setMasterVolume(float volume); - - // mic mute - virtual status_t setMicMute(bool state) { mMicMute = state; return NO_ERROR; } - virtual status_t getMicMute(bool* state) { *state = mMicMute ; return NO_ERROR; } - - // create I/O streams - virtual AudioStreamOut* openOutputStream( - uint32_t devices, - int *format=0, - uint32_t *channels=0, - uint32_t *sampleRate=0, - status_t *status=0); - virtual void closeOutputStream(AudioStreamOut* out); - - virtual AudioStreamIn* openInputStream( - uint32_t devices, - int *format, - uint32_t *channels, - uint32_t *sampleRate, - status_t *status, - AudioSystem::audio_in_acoustics acoustics); - virtual void closeInputStream(AudioStreamIn* in); - -protected: - virtual status_t dump(int fd, const Vector<String16>& args); - - bool mMicMute; -private: - status_t dumpInternals(int fd, const Vector<String16>& args); -}; - -// ---------------------------------------------------------------------------- - -}; // namespace android - -#endif // ANDROID_AUDIO_HARDWARE_STUB_H diff --git a/services/audioflinger/AudioPolicyManagerBase.cpp b/services/audioflinger/AudioPolicyManagerBase.cpp deleted file mode 100644 index f5e7343..0000000 --- a/services/audioflinger/AudioPolicyManagerBase.cpp +++ /dev/null @@ -1,2286 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "AudioPolicyManagerBase" -//#define LOG_NDEBUG 0 -#include <utils/Log.h> -#include <hardware_legacy/AudioPolicyManagerBase.h> -#include <media/mediarecorder.h> -#include <math.h> - -namespace android { - - -// ---------------------------------------------------------------------------- -// AudioPolicyInterface implementation -// ---------------------------------------------------------------------------- - - -status_t AudioPolicyManagerBase::setDeviceConnectionState(AudioSystem::audio_devices device, - AudioSystem::device_connection_state state, - const char *device_address) -{ - - LOGV("setDeviceConnectionState() device: %x, state %d, address %s", device, state, device_address); - - // connect/disconnect only 1 device at a time - if (AudioSystem::popCount(device) != 1) return BAD_VALUE; - - if (strlen(device_address) >= MAX_DEVICE_ADDRESS_LEN) { - LOGE("setDeviceConnectionState() invalid address: %s", device_address); - return BAD_VALUE; - } - - // handle output devices - if (AudioSystem::isOutputDevice(device)) { - -#ifndef WITH_A2DP - if (AudioSystem::isA2dpDevice(device)) { - LOGE("setDeviceConnectionState() invalid device: %x", device); - return BAD_VALUE; - } -#endif - - switch (state) - { - // handle output device connection - case AudioSystem::DEVICE_STATE_AVAILABLE: - if (mAvailableOutputDevices & device) { - LOGW("setDeviceConnectionState() device already connected: %x", device); - return INVALID_OPERATION; - } - LOGV("setDeviceConnectionState() connecting device %x", device); - - // register new device as available - mAvailableOutputDevices |= device; - -#ifdef WITH_A2DP - // handle A2DP device connection - if (AudioSystem::isA2dpDevice(device)) { - status_t status = handleA2dpConnection(device, device_address); - if (status != NO_ERROR) { - mAvailableOutputDevices &= ~device; - return status; - } - } else -#endif - { - if (AudioSystem::isBluetoothScoDevice(device)) { - LOGV("setDeviceConnectionState() BT SCO device, address %s", device_address); - // keep track of SCO device address - mScoDeviceAddress = String8(device_address, MAX_DEVICE_ADDRESS_LEN); - } - } - break; - // handle output device disconnection - case AudioSystem::DEVICE_STATE_UNAVAILABLE: { - if (!(mAvailableOutputDevices & device)) { - LOGW("setDeviceConnectionState() device not connected: %x", device); - return INVALID_OPERATION; - } - - - LOGV("setDeviceConnectionState() disconnecting device %x", device); - // remove device from available output devices - mAvailableOutputDevices &= ~device; - -#ifdef WITH_A2DP - // handle A2DP device disconnection - if (AudioSystem::isA2dpDevice(device)) { - status_t status = handleA2dpDisconnection(device, device_address); - if (status != NO_ERROR) { - mAvailableOutputDevices |= device; - return status; - } - } else -#endif - { - if (AudioSystem::isBluetoothScoDevice(device)) { - mScoDeviceAddress = ""; - } - } - } break; - - default: - LOGE("setDeviceConnectionState() invalid state: %x", state); - return BAD_VALUE; - } - - // request routing change if necessary - uint32_t newDevice = getNewDevice(mHardwareOutput, false); -#ifdef WITH_A2DP - checkA2dpSuspend(); - checkOutputForAllStrategies(); - // A2DP outputs must be closed after checkOutputForAllStrategies() is executed - if (state == AudioSystem::DEVICE_STATE_UNAVAILABLE && AudioSystem::isA2dpDevice(device)) { - closeA2dpOutputs(); - } -#endif - updateDeviceForStrategy(); - setOutputDevice(mHardwareOutput, newDevice); - - if (device == AudioSystem::DEVICE_OUT_WIRED_HEADSET) { - device = AudioSystem::DEVICE_IN_WIRED_HEADSET; - } else if (device == AudioSystem::DEVICE_OUT_BLUETOOTH_SCO || - device == AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_HEADSET || - device == AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT) { - device = AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET; - } else { - return NO_ERROR; - } - } - // handle input devices - if (AudioSystem::isInputDevice(device)) { - - switch (state) - { - // handle input device connection - case AudioSystem::DEVICE_STATE_AVAILABLE: { - if (mAvailableInputDevices & device) { - LOGW("setDeviceConnectionState() device already connected: %d", device); - return INVALID_OPERATION; - } - mAvailableInputDevices |= device; - } - break; - - // handle input device disconnection - case AudioSystem::DEVICE_STATE_UNAVAILABLE: { - if (!(mAvailableInputDevices & device)) { - LOGW("setDeviceConnectionState() device not connected: %d", device); - return INVALID_OPERATION; - } - mAvailableInputDevices &= ~device; - } break; - - default: - LOGE("setDeviceConnectionState() invalid state: %x", state); - return BAD_VALUE; - } - - audio_io_handle_t activeInput = getActiveInput(); - if (activeInput != 0) { - AudioInputDescriptor *inputDesc = mInputs.valueFor(activeInput); - uint32_t newDevice = getDeviceForInputSource(inputDesc->mInputSource); - if (newDevice != inputDesc->mDevice) { - LOGV("setDeviceConnectionState() changing device from %x to %x for input %d", - inputDesc->mDevice, newDevice, activeInput); - inputDesc->mDevice = newDevice; - AudioParameter param = AudioParameter(); - param.addInt(String8(AudioParameter::keyRouting), (int)newDevice); - mpClientInterface->setParameters(activeInput, param.toString()); - } - } - - return NO_ERROR; - } - - LOGW("setDeviceConnectionState() invalid device: %x", device); - return BAD_VALUE; -} - -AudioSystem::device_connection_state AudioPolicyManagerBase::getDeviceConnectionState(AudioSystem::audio_devices device, - const char *device_address) -{ - AudioSystem::device_connection_state state = AudioSystem::DEVICE_STATE_UNAVAILABLE; - String8 address = String8(device_address); - if (AudioSystem::isOutputDevice(device)) { - if (device & mAvailableOutputDevices) { -#ifdef WITH_A2DP - if (AudioSystem::isA2dpDevice(device) && - address != "" && mA2dpDeviceAddress != address) { - return state; - } -#endif - if (AudioSystem::isBluetoothScoDevice(device) && - address != "" && mScoDeviceAddress != address) { - return state; - } - state = AudioSystem::DEVICE_STATE_AVAILABLE; - } - } else if (AudioSystem::isInputDevice(device)) { - if (device & mAvailableInputDevices) { - state = AudioSystem::DEVICE_STATE_AVAILABLE; - } - } - - return state; -} - -void AudioPolicyManagerBase::setPhoneState(int state) -{ - LOGV("setPhoneState() state %d", state); - uint32_t newDevice = 0; - if (state < 0 || state >= AudioSystem::NUM_MODES) { - LOGW("setPhoneState() invalid state %d", state); - return; - } - - if (state == mPhoneState ) { - LOGW("setPhoneState() setting same state %d", state); - return; - } - - // if leaving call state, handle special case of active streams - // pertaining to sonification strategy see handleIncallSonification() - if (isInCall()) { - LOGV("setPhoneState() in call state management: new state is %d", state); - for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) { - handleIncallSonification(stream, false, true); - } - } - - // store previous phone state for management of sonification strategy below - int oldState = mPhoneState; - mPhoneState = state; - bool force = false; - - // are we entering or starting a call - if (!isStateInCall(oldState) && isStateInCall(state)) { - LOGV(" Entering call in setPhoneState()"); - // force routing command to audio hardware when starting a call - // even if no device change is needed - force = true; - } else if (isStateInCall(oldState) && !isStateInCall(state)) { - LOGV(" Exiting call in setPhoneState()"); - // force routing command to audio hardware when exiting a call - // even if no device change is needed - force = true; - } else if (isStateInCall(state) && (state != oldState)) { - LOGV(" Switching between telephony and VoIP in setPhoneState()"); - // force routing command to audio hardware when switching between telephony and VoIP - // even if no device change is needed - force = true; - } - - // check for device and output changes triggered by new phone state - newDevice = getNewDevice(mHardwareOutput, false); -#ifdef WITH_A2DP - checkA2dpSuspend(); - checkOutputForAllStrategies(); -#endif - updateDeviceForStrategy(); - - AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput); - - // force routing command to audio hardware when ending call - // even if no device change is needed - if (isStateInCall(oldState) && newDevice == 0) { - newDevice = hwOutputDesc->device(); - } - - // when changing from ring tone to in call mode, mute the ringing tone - // immediately and delay the route change to avoid sending the ring tone - // tail into the earpiece or headset. - int delayMs = 0; - if (isStateInCall(state) && oldState == AudioSystem::MODE_RINGTONE) { - // delay the device change command by twice the output latency to have some margin - // and be sure that audio buffers not yet affected by the mute are out when - // we actually apply the route change - delayMs = hwOutputDesc->mLatency*2; - setStreamMute(AudioSystem::RING, true, mHardwareOutput); - } - - // change routing is necessary - setOutputDevice(mHardwareOutput, newDevice, force, delayMs); - - // if entering in call state, handle special case of active streams - // pertaining to sonification strategy see handleIncallSonification() - if (isStateInCall(state)) { - LOGV("setPhoneState() in call state management: new state is %d", state); - // unmute the ringing tone after a sufficient delay if it was muted before - // setting output device above - if (oldState == AudioSystem::MODE_RINGTONE) { - setStreamMute(AudioSystem::RING, false, mHardwareOutput, MUTE_TIME_MS); - } - for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) { - handleIncallSonification(stream, true, true); - } - } - - // Flag that ringtone volume must be limited to music volume until we exit MODE_RINGTONE - if (state == AudioSystem::MODE_RINGTONE && - isStreamActive(AudioSystem::MUSIC, SONIFICATION_HEADSET_MUSIC_DELAY)) { - mLimitRingtoneVolume = true; - } else { - mLimitRingtoneVolume = false; - } -} - -void AudioPolicyManagerBase::setRingerMode(uint32_t mode, uint32_t mask) -{ - LOGV("setRingerMode() mode %x, mask %x", mode, mask); - - mRingerMode = mode; -} - -void AudioPolicyManagerBase::setForceUse(AudioSystem::force_use usage, AudioSystem::forced_config config) -{ - LOGV("setForceUse() usage %d, config %d, mPhoneState %d", usage, config, mPhoneState); - - bool forceVolumeReeval = false; - switch(usage) { - case AudioSystem::FOR_COMMUNICATION: - if (config != AudioSystem::FORCE_SPEAKER && config != AudioSystem::FORCE_BT_SCO && - config != AudioSystem::FORCE_NONE) { - LOGW("setForceUse() invalid config %d for FOR_COMMUNICATION", config); - return; - } - forceVolumeReeval = true; - mForceUse[usage] = config; - break; - case AudioSystem::FOR_MEDIA: - if (config != AudioSystem::FORCE_HEADPHONES && config != AudioSystem::FORCE_BT_A2DP && - config != AudioSystem::FORCE_WIRED_ACCESSORY && - config != AudioSystem::FORCE_ANALOG_DOCK && - config != AudioSystem::FORCE_DIGITAL_DOCK && config != AudioSystem::FORCE_NONE) { - LOGW("setForceUse() invalid config %d for FOR_MEDIA", config); - return; - } - mForceUse[usage] = config; - break; - case AudioSystem::FOR_RECORD: - if (config != AudioSystem::FORCE_BT_SCO && config != AudioSystem::FORCE_WIRED_ACCESSORY && - config != AudioSystem::FORCE_NONE) { - LOGW("setForceUse() invalid config %d for FOR_RECORD", config); - return; - } - mForceUse[usage] = config; - break; - case AudioSystem::FOR_DOCK: - if (config != AudioSystem::FORCE_NONE && config != AudioSystem::FORCE_BT_CAR_DOCK && - config != AudioSystem::FORCE_BT_DESK_DOCK && - config != AudioSystem::FORCE_WIRED_ACCESSORY && - config != AudioSystem::FORCE_ANALOG_DOCK && - config != AudioSystem::FORCE_DIGITAL_DOCK) { - LOGW("setForceUse() invalid config %d for FOR_DOCK", config); - } - forceVolumeReeval = true; - mForceUse[usage] = config; - break; - default: - LOGW("setForceUse() invalid usage %d", usage); - break; - } - - // check for device and output changes triggered by new phone state - uint32_t newDevice = getNewDevice(mHardwareOutput, false); -#ifdef WITH_A2DP - checkA2dpSuspend(); - checkOutputForAllStrategies(); -#endif - updateDeviceForStrategy(); - setOutputDevice(mHardwareOutput, newDevice); - if (forceVolumeReeval) { - applyStreamVolumes(mHardwareOutput, newDevice, 0, true); - } - - audio_io_handle_t activeInput = getActiveInput(); - if (activeInput != 0) { - AudioInputDescriptor *inputDesc = mInputs.valueFor(activeInput); - newDevice = getDeviceForInputSource(inputDesc->mInputSource); - if (newDevice != inputDesc->mDevice) { - LOGV("setForceUse() changing device from %x to %x for input %d", - inputDesc->mDevice, newDevice, activeInput); - inputDesc->mDevice = newDevice; - AudioParameter param = AudioParameter(); - param.addInt(String8(AudioParameter::keyRouting), (int)newDevice); - mpClientInterface->setParameters(activeInput, param.toString()); - } - } - -} - -AudioSystem::forced_config AudioPolicyManagerBase::getForceUse(AudioSystem::force_use usage) -{ - return mForceUse[usage]; -} - -void AudioPolicyManagerBase::setSystemProperty(const char* property, const char* value) -{ - LOGV("setSystemProperty() property %s, value %s", property, value); - if (strcmp(property, "ro.camera.sound.forced") == 0) { - if (atoi(value)) { - LOGV("ENFORCED_AUDIBLE cannot be muted"); - mStreams[AudioSystem::ENFORCED_AUDIBLE].mCanBeMuted = false; - } else { - LOGV("ENFORCED_AUDIBLE can be muted"); - mStreams[AudioSystem::ENFORCED_AUDIBLE].mCanBeMuted = true; - } - } -} - -audio_io_handle_t AudioPolicyManagerBase::getOutput(AudioSystem::stream_type stream, - uint32_t samplingRate, - uint32_t format, - uint32_t channels, - AudioSystem::output_flags flags) -{ - audio_io_handle_t output = 0; - uint32_t latency = 0; - routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream); - uint32_t device = getDeviceForStrategy(strategy); - LOGV("getOutput() stream %d, samplingRate %d, format %d, channels %x, flags %x", stream, samplingRate, format, channels, flags); - -#ifdef AUDIO_POLICY_TEST - if (mCurOutput != 0) { - LOGV("getOutput() test output mCurOutput %d, samplingRate %d, format %d, channels %x, mDirectOutput %d", - mCurOutput, mTestSamplingRate, mTestFormat, mTestChannels, mDirectOutput); - - if (mTestOutputs[mCurOutput] == 0) { - LOGV("getOutput() opening test output"); - AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor(); - outputDesc->mDevice = mTestDevice; - outputDesc->mSamplingRate = mTestSamplingRate; - outputDesc->mFormat = mTestFormat; - outputDesc->mChannels = mTestChannels; - outputDesc->mLatency = mTestLatencyMs; - outputDesc->mFlags = (AudioSystem::output_flags)(mDirectOutput ? AudioSystem::OUTPUT_FLAG_DIRECT : 0); - outputDesc->mRefCount[stream] = 0; - mTestOutputs[mCurOutput] = mpClientInterface->openOutput(&outputDesc->mDevice, - &outputDesc->mSamplingRate, - &outputDesc->mFormat, - &outputDesc->mChannels, - &outputDesc->mLatency, - outputDesc->mFlags); - if (mTestOutputs[mCurOutput]) { - AudioParameter outputCmd = AudioParameter(); - outputCmd.addInt(String8("set_id"),mCurOutput); - mpClientInterface->setParameters(mTestOutputs[mCurOutput],outputCmd.toString()); - addOutput(mTestOutputs[mCurOutput], outputDesc); - } - } - return mTestOutputs[mCurOutput]; - } -#endif //AUDIO_POLICY_TEST - - // open a direct output if required by specified parameters - if (needsDirectOuput(stream, samplingRate, format, channels, flags, device)) { - - LOGV("getOutput() opening direct output device %x", device); - AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor(); - outputDesc->mDevice = device; - outputDesc->mSamplingRate = samplingRate; - outputDesc->mFormat = format; - outputDesc->mChannels = channels; - outputDesc->mLatency = 0; - outputDesc->mFlags = (AudioSystem::output_flags)(flags | AudioSystem::OUTPUT_FLAG_DIRECT); - outputDesc->mRefCount[stream] = 0; - outputDesc->mStopTime[stream] = 0; - output = mpClientInterface->openOutput(&outputDesc->mDevice, - &outputDesc->mSamplingRate, - &outputDesc->mFormat, - &outputDesc->mChannels, - &outputDesc->mLatency, - outputDesc->mFlags); - - // only accept an output with the requeted parameters - if (output == 0 || - (samplingRate != 0 && samplingRate != outputDesc->mSamplingRate) || - (format != 0 && format != outputDesc->mFormat) || - (channels != 0 && channels != outputDesc->mChannels)) { - LOGV("getOutput() failed opening direct output: samplingRate %d, format %d, channels %d", - samplingRate, format, channels); - if (output != 0) { - mpClientInterface->closeOutput(output); - } - delete outputDesc; - return 0; - } - addOutput(output, outputDesc); - return output; - } - - if (channels != 0 && channels != AudioSystem::CHANNEL_OUT_MONO && - channels != AudioSystem::CHANNEL_OUT_STEREO) { - return 0; - } - // open a non direct output - - // get which output is suitable for the specified stream. The actual routing change will happen - // when startOutput() will be called - uint32_t a2dpDevice = device & AudioSystem::DEVICE_OUT_ALL_A2DP; - if (AudioSystem::popCount((AudioSystem::audio_devices)device) == 2) { -#ifdef WITH_A2DP - if (a2dpUsedForSonification() && a2dpDevice != 0) { - // if playing on 2 devices among which one is A2DP, use duplicated output - LOGV("getOutput() using duplicated output"); - LOGW_IF((mA2dpOutput == 0), "getOutput() A2DP device in multiple %x selected but A2DP output not opened", device); - output = mDuplicatedOutput; - } else -#endif - { - // if playing on 2 devices among which none is A2DP, use hardware output - output = mHardwareOutput; - } - LOGV("getOutput() using output %d for 2 devices %x", output, device); - } else { -#ifdef WITH_A2DP - if (a2dpDevice != 0) { - // if playing on A2DP device, use a2dp output - LOGW_IF((mA2dpOutput == 0), "getOutput() A2DP device %x selected but A2DP output not opened", device); - output = mA2dpOutput; - } else -#endif - { - // if playing on not A2DP device, use hardware output - output = mHardwareOutput; - } - } - - - LOGW_IF((output ==0), "getOutput() could not find output for stream %d, samplingRate %d, format %d, channels %x, flags %x", - stream, samplingRate, format, channels, flags); - - return output; -} - -status_t AudioPolicyManagerBase::startOutput(audio_io_handle_t output, - AudioSystem::stream_type stream, - int session) -{ - LOGV("startOutput() output %d, stream %d, session %d", output, stream, session); - ssize_t index = mOutputs.indexOfKey(output); - if (index < 0) { - LOGW("startOutput() unknow output %d", output); - return BAD_VALUE; - } - - AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index); - routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream); - -#ifdef WITH_A2DP - if (mA2dpOutput != 0 && !a2dpUsedForSonification() && strategy == STRATEGY_SONIFICATION) { - setStrategyMute(STRATEGY_MEDIA, true, mA2dpOutput); - } -#endif - - // incremenent usage count for this stream on the requested output: - // NOTE that the usage count is the same for duplicated output and hardware output which is - // necassary for a correct control of hardware output routing by startOutput() and stopOutput() - outputDesc->changeRefCount(stream, 1); - - setOutputDevice(output, getNewDevice(output)); - - // handle special case for sonification while in call - if (isInCall()) { - handleIncallSonification(stream, true, false); - } - - // apply volume rules for current stream and device if necessary - checkAndSetVolume(stream, mStreams[stream].mIndexCur, output, outputDesc->device()); - - return NO_ERROR; -} - -status_t AudioPolicyManagerBase::stopOutput(audio_io_handle_t output, - AudioSystem::stream_type stream, - int session) -{ - LOGV("stopOutput() output %d, stream %d, session %d", output, stream, session); - ssize_t index = mOutputs.indexOfKey(output); - if (index < 0) { - LOGW("stopOutput() unknow output %d", output); - return BAD_VALUE; - } - - AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index); - routing_strategy strategy = getStrategy((AudioSystem::stream_type)stream); - - // handle special case for sonification while in call - if (isInCall()) { - handleIncallSonification(stream, false, false); - } - - if (outputDesc->mRefCount[stream] > 0) { - // decrement usage count of this stream on the output - outputDesc->changeRefCount(stream, -1); - // store time at which the stream was stopped - see isStreamActive() - outputDesc->mStopTime[stream] = systemTime(); - - setOutputDevice(output, getNewDevice(output), false, outputDesc->mLatency*2); - -#ifdef WITH_A2DP - if (mA2dpOutput != 0 && !a2dpUsedForSonification() && - strategy == STRATEGY_SONIFICATION) { - setStrategyMute(STRATEGY_MEDIA, - false, - mA2dpOutput, - mOutputs.valueFor(mHardwareOutput)->mLatency*2); - } -#endif - if (output != mHardwareOutput) { - setOutputDevice(mHardwareOutput, getNewDevice(mHardwareOutput), true); - } - return NO_ERROR; - } else { - LOGW("stopOutput() refcount is already 0 for output %d", output); - return INVALID_OPERATION; - } -} - -void AudioPolicyManagerBase::releaseOutput(audio_io_handle_t output) -{ - LOGV("releaseOutput() %d", output); - ssize_t index = mOutputs.indexOfKey(output); - if (index < 0) { - LOGW("releaseOutput() releasing unknown output %d", output); - return; - } - -#ifdef AUDIO_POLICY_TEST - int testIndex = testOutputIndex(output); - if (testIndex != 0) { - AudioOutputDescriptor *outputDesc = mOutputs.valueAt(index); - if (outputDesc->refCount() == 0) { - mpClientInterface->closeOutput(output); - delete mOutputs.valueAt(index); - mOutputs.removeItem(output); - mTestOutputs[testIndex] = 0; - } - return; - } -#endif //AUDIO_POLICY_TEST - - if (mOutputs.valueAt(index)->mFlags & AudioSystem::OUTPUT_FLAG_DIRECT) { - mpClientInterface->closeOutput(output); - delete mOutputs.valueAt(index); - mOutputs.removeItem(output); - } -} - -audio_io_handle_t AudioPolicyManagerBase::getInput(int inputSource, - uint32_t samplingRate, - uint32_t format, - uint32_t channels, - AudioSystem::audio_in_acoustics acoustics) -{ - audio_io_handle_t input = 0; - uint32_t device = getDeviceForInputSource(inputSource); - - LOGV("getInput() inputSource %d, samplingRate %d, format %d, channels %x, acoustics %x", inputSource, samplingRate, format, channels, acoustics); - - if (device == 0) { - return 0; - } - - // adapt channel selection to input source - switch(inputSource) { - case AUDIO_SOURCE_VOICE_UPLINK: - channels = AudioSystem::CHANNEL_IN_VOICE_UPLINK; - break; - case AUDIO_SOURCE_VOICE_DOWNLINK: - channels = AudioSystem::CHANNEL_IN_VOICE_DNLINK; - break; - case AUDIO_SOURCE_VOICE_CALL: - channels = (AudioSystem::CHANNEL_IN_VOICE_UPLINK | AudioSystem::CHANNEL_IN_VOICE_DNLINK); - break; - default: - break; - } - - AudioInputDescriptor *inputDesc = new AudioInputDescriptor(); - - inputDesc->mInputSource = inputSource; - inputDesc->mDevice = device; - inputDesc->mSamplingRate = samplingRate; - inputDesc->mFormat = format; - inputDesc->mChannels = channels; - inputDesc->mAcoustics = acoustics; - inputDesc->mRefCount = 0; - input = mpClientInterface->openInput(&inputDesc->mDevice, - &inputDesc->mSamplingRate, - &inputDesc->mFormat, - &inputDesc->mChannels, - inputDesc->mAcoustics); - - // only accept input with the exact requested set of parameters - if (input == 0 || - (samplingRate != inputDesc->mSamplingRate) || - (format != inputDesc->mFormat) || - (channels != inputDesc->mChannels)) { - LOGV("getInput() failed opening input: samplingRate %d, format %d, channels %d", - samplingRate, format, channels); - if (input != 0) { - mpClientInterface->closeInput(input); - } - delete inputDesc; - return 0; - } - mInputs.add(input, inputDesc); - return input; -} - -status_t AudioPolicyManagerBase::startInput(audio_io_handle_t input) -{ - LOGV("startInput() input %d", input); - ssize_t index = mInputs.indexOfKey(input); - if (index < 0) { - LOGW("startInput() unknow input %d", input); - return BAD_VALUE; - } - AudioInputDescriptor *inputDesc = mInputs.valueAt(index); - -#ifdef AUDIO_POLICY_TEST - if (mTestInput == 0) -#endif //AUDIO_POLICY_TEST - { - // refuse 2 active AudioRecord clients at the same time - if (getActiveInput() != 0) { - LOGW("startInput() input %d failed: other input already started", input); - return INVALID_OPERATION; - } - } - - AudioParameter param = AudioParameter(); - param.addInt(String8(AudioParameter::keyRouting), (int)inputDesc->mDevice); - - param.addInt(String8(AudioParameter::keyInputSource), (int)inputDesc->mInputSource); - LOGV("AudioPolicyManager::startInput() input source = %d", inputDesc->mInputSource); - - mpClientInterface->setParameters(input, param.toString()); - - inputDesc->mRefCount = 1; - return NO_ERROR; -} - -status_t AudioPolicyManagerBase::stopInput(audio_io_handle_t input) -{ - LOGV("stopInput() input %d", input); - ssize_t index = mInputs.indexOfKey(input); - if (index < 0) { - LOGW("stopInput() unknow input %d", input); - return BAD_VALUE; - } - AudioInputDescriptor *inputDesc = mInputs.valueAt(index); - - if (inputDesc->mRefCount == 0) { - LOGW("stopInput() input %d already stopped", input); - return INVALID_OPERATION; - } else { - AudioParameter param = AudioParameter(); - param.addInt(String8(AudioParameter::keyRouting), 0); - mpClientInterface->setParameters(input, param.toString()); - inputDesc->mRefCount = 0; - return NO_ERROR; - } -} - -void AudioPolicyManagerBase::releaseInput(audio_io_handle_t input) -{ - LOGV("releaseInput() %d", input); - ssize_t index = mInputs.indexOfKey(input); - if (index < 0) { - LOGW("releaseInput() releasing unknown input %d", input); - return; - } - mpClientInterface->closeInput(input); - delete mInputs.valueAt(index); - mInputs.removeItem(input); - LOGV("releaseInput() exit"); -} - -void AudioPolicyManagerBase::initStreamVolume(AudioSystem::stream_type stream, - int indexMin, - int indexMax) -{ - LOGV("initStreamVolume() stream %d, min %d, max %d", stream , indexMin, indexMax); - if (indexMin < 0 || indexMin >= indexMax) { - LOGW("initStreamVolume() invalid index limits for stream %d, min %d, max %d", stream , indexMin, indexMax); - return; - } - mStreams[stream].mIndexMin = indexMin; - mStreams[stream].mIndexMax = indexMax; -} - -status_t AudioPolicyManagerBase::setStreamVolumeIndex(AudioSystem::stream_type stream, int index) -{ - - if ((index < mStreams[stream].mIndexMin) || (index > mStreams[stream].mIndexMax)) { - return BAD_VALUE; - } - - // Force max volume if stream cannot be muted - if (!mStreams[stream].mCanBeMuted) index = mStreams[stream].mIndexMax; - - LOGV("setStreamVolumeIndex() stream %d, index %d", stream, index); - mStreams[stream].mIndexCur = index; - - // compute and apply stream volume on all outputs according to connected device - status_t status = NO_ERROR; - for (size_t i = 0; i < mOutputs.size(); i++) { - status_t volStatus = checkAndSetVolume(stream, index, mOutputs.keyAt(i), mOutputs.valueAt(i)->device()); - if (volStatus != NO_ERROR) { - status = volStatus; - } - } - return status; -} - -status_t AudioPolicyManagerBase::getStreamVolumeIndex(AudioSystem::stream_type stream, int *index) -{ - if (index == 0) { - return BAD_VALUE; - } - LOGV("getStreamVolumeIndex() stream %d", stream); - *index = mStreams[stream].mIndexCur; - return NO_ERROR; -} - -audio_io_handle_t AudioPolicyManagerBase::getOutputForEffect(effect_descriptor_t *desc) -{ - LOGV("getOutputForEffect()"); - // apply simple rule where global effects are attached to the same output as MUSIC streams - return getOutput(AudioSystem::MUSIC); -} - -status_t AudioPolicyManagerBase::registerEffect(effect_descriptor_t *desc, - audio_io_handle_t output, - uint32_t strategy, - int session, - int id) -{ - ssize_t index = mOutputs.indexOfKey(output); - if (index < 0) { - LOGW("registerEffect() unknown output %d", output); - return INVALID_OPERATION; - } - - if (mTotalEffectsCpuLoad + desc->cpuLoad > getMaxEffectsCpuLoad()) { - LOGW("registerEffect() CPU Load limit exceeded for Fx %s, CPU %f MIPS", - desc->name, (float)desc->cpuLoad/10); - return INVALID_OPERATION; - } - if (mTotalEffectsMemory + desc->memoryUsage > getMaxEffectsMemory()) { - LOGW("registerEffect() memory limit exceeded for Fx %s, Memory %d KB", - desc->name, desc->memoryUsage); - return INVALID_OPERATION; - } - mTotalEffectsCpuLoad += desc->cpuLoad; - mTotalEffectsMemory += desc->memoryUsage; - LOGV("registerEffect() effect %s, output %d, strategy %d session %d id %d", - desc->name, output, strategy, session, id); - - LOGV("registerEffect() CPU %d, memory %d", desc->cpuLoad, desc->memoryUsage); - LOGV(" total CPU %d, total memory %d", mTotalEffectsCpuLoad, mTotalEffectsMemory); - - EffectDescriptor *pDesc = new EffectDescriptor(); - memcpy (&pDesc->mDesc, desc, sizeof(effect_descriptor_t)); - pDesc->mOutput = output; - pDesc->mStrategy = (routing_strategy)strategy; - pDesc->mSession = session; - mEffects.add(id, pDesc); - - return NO_ERROR; -} - -status_t AudioPolicyManagerBase::unregisterEffect(int id) -{ - ssize_t index = mEffects.indexOfKey(id); - if (index < 0) { - LOGW("unregisterEffect() unknown effect ID %d", id); - return INVALID_OPERATION; - } - - EffectDescriptor *pDesc = mEffects.valueAt(index); - - if (mTotalEffectsCpuLoad < pDesc->mDesc.cpuLoad) { - LOGW("unregisterEffect() CPU load %d too high for total %d", - pDesc->mDesc.cpuLoad, mTotalEffectsCpuLoad); - pDesc->mDesc.cpuLoad = mTotalEffectsCpuLoad; - } - mTotalEffectsCpuLoad -= pDesc->mDesc.cpuLoad; - if (mTotalEffectsMemory < pDesc->mDesc.memoryUsage) { - LOGW("unregisterEffect() memory %d too big for total %d", - pDesc->mDesc.memoryUsage, mTotalEffectsMemory); - pDesc->mDesc.memoryUsage = mTotalEffectsMemory; - } - mTotalEffectsMemory -= pDesc->mDesc.memoryUsage; - LOGV("unregisterEffect() effect %s, ID %d, CPU %d, memory %d", - pDesc->mDesc.name, id, pDesc->mDesc.cpuLoad, pDesc->mDesc.memoryUsage); - LOGV(" total CPU %d, total memory %d", mTotalEffectsCpuLoad, mTotalEffectsMemory); - - mEffects.removeItem(id); - delete pDesc; - - return NO_ERROR; -} - -bool AudioPolicyManagerBase::isStreamActive(int stream, uint32_t inPastMs) const -{ - nsecs_t sysTime = systemTime(); - for (size_t i = 0; i < mOutputs.size(); i++) { - if (mOutputs.valueAt(i)->mRefCount[stream] != 0 || - ns2ms(sysTime - mOutputs.valueAt(i)->mStopTime[stream]) < inPastMs) { - return true; - } - } - return false; -} - - -status_t AudioPolicyManagerBase::dump(int fd) -{ - const size_t SIZE = 256; - char buffer[SIZE]; - String8 result; - - snprintf(buffer, SIZE, "\nAudioPolicyManager Dump: %p\n", this); - result.append(buffer); - snprintf(buffer, SIZE, " Hardware Output: %d\n", mHardwareOutput); - result.append(buffer); -#ifdef WITH_A2DP - snprintf(buffer, SIZE, " A2DP Output: %d\n", mA2dpOutput); - result.append(buffer); - snprintf(buffer, SIZE, " Duplicated Output: %d\n", mDuplicatedOutput); - result.append(buffer); - snprintf(buffer, SIZE, " A2DP device address: %s\n", mA2dpDeviceAddress.string()); - result.append(buffer); -#endif - snprintf(buffer, SIZE, " SCO device address: %s\n", mScoDeviceAddress.string()); - result.append(buffer); - snprintf(buffer, SIZE, " Output devices: %08x\n", mAvailableOutputDevices); - result.append(buffer); - snprintf(buffer, SIZE, " Input devices: %08x\n", mAvailableInputDevices); - result.append(buffer); - snprintf(buffer, SIZE, " Phone state: %d\n", mPhoneState); - result.append(buffer); - snprintf(buffer, SIZE, " Ringer mode: %d\n", mRingerMode); - result.append(buffer); - snprintf(buffer, SIZE, " Force use for communications %d\n", mForceUse[AudioSystem::FOR_COMMUNICATION]); - result.append(buffer); - snprintf(buffer, SIZE, " Force use for media %d\n", mForceUse[AudioSystem::FOR_MEDIA]); - result.append(buffer); - snprintf(buffer, SIZE, " Force use for record %d\n", mForceUse[AudioSystem::FOR_RECORD]); - result.append(buffer); - snprintf(buffer, SIZE, " Force use for dock %d\n", mForceUse[AudioSystem::FOR_DOCK]); - result.append(buffer); - write(fd, result.string(), result.size()); - - snprintf(buffer, SIZE, "\nOutputs dump:\n"); - write(fd, buffer, strlen(buffer)); - for (size_t i = 0; i < mOutputs.size(); i++) { - snprintf(buffer, SIZE, "- Output %d dump:\n", mOutputs.keyAt(i)); - write(fd, buffer, strlen(buffer)); - mOutputs.valueAt(i)->dump(fd); - } - - snprintf(buffer, SIZE, "\nInputs dump:\n"); - write(fd, buffer, strlen(buffer)); - for (size_t i = 0; i < mInputs.size(); i++) { - snprintf(buffer, SIZE, "- Input %d dump:\n", mInputs.keyAt(i)); - write(fd, buffer, strlen(buffer)); - mInputs.valueAt(i)->dump(fd); - } - - snprintf(buffer, SIZE, "\nStreams dump:\n"); - write(fd, buffer, strlen(buffer)); - snprintf(buffer, SIZE, " Stream Index Min Index Max Index Cur Can be muted\n"); - write(fd, buffer, strlen(buffer)); - for (size_t i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) { - snprintf(buffer, SIZE, " %02d", i); - mStreams[i].dump(buffer + 3, SIZE); - write(fd, buffer, strlen(buffer)); - } - - snprintf(buffer, SIZE, "\nTotal Effects CPU: %f MIPS, Total Effects memory: %d KB\n", - (float)mTotalEffectsCpuLoad/10, mTotalEffectsMemory); - write(fd, buffer, strlen(buffer)); - - snprintf(buffer, SIZE, "Registered effects:\n"); - write(fd, buffer, strlen(buffer)); - for (size_t i = 0; i < mEffects.size(); i++) { - snprintf(buffer, SIZE, "- Effect %d dump:\n", mEffects.keyAt(i)); - write(fd, buffer, strlen(buffer)); - mEffects.valueAt(i)->dump(fd); - } - - - return NO_ERROR; -} - -// ---------------------------------------------------------------------------- -// AudioPolicyManagerBase -// ---------------------------------------------------------------------------- - -AudioPolicyManagerBase::AudioPolicyManagerBase(AudioPolicyClientInterface *clientInterface) - : -#ifdef AUDIO_POLICY_TEST - Thread(false), -#endif //AUDIO_POLICY_TEST - mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), - mLimitRingtoneVolume(false), mLastVoiceVolume(-1.0f), - mTotalEffectsCpuLoad(0), mTotalEffectsMemory(0), - mA2dpSuspended(false) -{ - mpClientInterface = clientInterface; - - for (int i = 0; i < AudioSystem::NUM_FORCE_USE; i++) { - mForceUse[i] = AudioSystem::FORCE_NONE; - } - - initializeVolumeCurves(); - - // devices available by default are speaker, ear piece and microphone - mAvailableOutputDevices = AudioSystem::DEVICE_OUT_EARPIECE | - AudioSystem::DEVICE_OUT_SPEAKER; - mAvailableInputDevices = AudioSystem::DEVICE_IN_BUILTIN_MIC; - -#ifdef WITH_A2DP - mA2dpOutput = 0; - mDuplicatedOutput = 0; - mA2dpDeviceAddress = String8(""); -#endif - mScoDeviceAddress = String8(""); - - // open hardware output - AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor(); - outputDesc->mDevice = (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER; - mHardwareOutput = mpClientInterface->openOutput(&outputDesc->mDevice, - &outputDesc->mSamplingRate, - &outputDesc->mFormat, - &outputDesc->mChannels, - &outputDesc->mLatency, - outputDesc->mFlags); - - if (mHardwareOutput == 0) { - LOGE("Failed to initialize hardware output stream, samplingRate: %d, format %d, channels %d", - outputDesc->mSamplingRate, outputDesc->mFormat, outputDesc->mChannels); - } else { - addOutput(mHardwareOutput, outputDesc); - setOutputDevice(mHardwareOutput, (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER, true); - //TODO: configure audio effect output stage here - } - - updateDeviceForStrategy(); -#ifdef AUDIO_POLICY_TEST - if (mHardwareOutput != 0) { - AudioParameter outputCmd = AudioParameter(); - outputCmd.addInt(String8("set_id"), 0); - mpClientInterface->setParameters(mHardwareOutput, outputCmd.toString()); - - mTestDevice = AudioSystem::DEVICE_OUT_SPEAKER; - mTestSamplingRate = 44100; - mTestFormat = AudioSystem::PCM_16_BIT; - mTestChannels = AudioSystem::CHANNEL_OUT_STEREO; - mTestLatencyMs = 0; - mCurOutput = 0; - mDirectOutput = false; - for (int i = 0; i < NUM_TEST_OUTPUTS; i++) { - mTestOutputs[i] = 0; - } - - const size_t SIZE = 256; - char buffer[SIZE]; - snprintf(buffer, SIZE, "AudioPolicyManagerTest"); - run(buffer, ANDROID_PRIORITY_AUDIO); - } -#endif //AUDIO_POLICY_TEST -} - -AudioPolicyManagerBase::~AudioPolicyManagerBase() -{ -#ifdef AUDIO_POLICY_TEST - exit(); -#endif //AUDIO_POLICY_TEST - for (size_t i = 0; i < mOutputs.size(); i++) { - mpClientInterface->closeOutput(mOutputs.keyAt(i)); - delete mOutputs.valueAt(i); - } - mOutputs.clear(); - for (size_t i = 0; i < mInputs.size(); i++) { - mpClientInterface->closeInput(mInputs.keyAt(i)); - delete mInputs.valueAt(i); - } - mInputs.clear(); -} - -status_t AudioPolicyManagerBase::initCheck() -{ - return (mHardwareOutput == 0) ? NO_INIT : NO_ERROR; -} - -#ifdef AUDIO_POLICY_TEST -bool AudioPolicyManagerBase::threadLoop() -{ - LOGV("entering threadLoop()"); - while (!exitPending()) - { - String8 command; - int valueInt; - String8 value; - - Mutex::Autolock _l(mLock); - mWaitWorkCV.waitRelative(mLock, milliseconds(50)); - - command = mpClientInterface->getParameters(0, String8("test_cmd_policy")); - AudioParameter param = AudioParameter(command); - - if (param.getInt(String8("test_cmd_policy"), valueInt) == NO_ERROR && - valueInt != 0) { - LOGV("Test command %s received", command.string()); - String8 target; - if (param.get(String8("target"), target) != NO_ERROR) { - target = "Manager"; - } - if (param.getInt(String8("test_cmd_policy_output"), valueInt) == NO_ERROR) { - param.remove(String8("test_cmd_policy_output")); - mCurOutput = valueInt; - } - if (param.get(String8("test_cmd_policy_direct"), value) == NO_ERROR) { - param.remove(String8("test_cmd_policy_direct")); - if (value == "false") { - mDirectOutput = false; - } else if (value == "true") { - mDirectOutput = true; - } - } - if (param.getInt(String8("test_cmd_policy_input"), valueInt) == NO_ERROR) { - param.remove(String8("test_cmd_policy_input")); - mTestInput = valueInt; - } - - if (param.get(String8("test_cmd_policy_format"), value) == NO_ERROR) { - param.remove(String8("test_cmd_policy_format")); - int format = AudioSystem::INVALID_FORMAT; - if (value == "PCM 16 bits") { - format = AudioSystem::PCM_16_BIT; - } else if (value == "PCM 8 bits") { - format = AudioSystem::PCM_8_BIT; - } else if (value == "Compressed MP3") { - format = AudioSystem::MP3; - } - if (format != AudioSystem::INVALID_FORMAT) { - if (target == "Manager") { - mTestFormat = format; - } else if (mTestOutputs[mCurOutput] != 0) { - AudioParameter outputParam = AudioParameter(); - outputParam.addInt(String8("format"), format); - mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString()); - } - } - } - if (param.get(String8("test_cmd_policy_channels"), value) == NO_ERROR) { - param.remove(String8("test_cmd_policy_channels")); - int channels = 0; - - if (value == "Channels Stereo") { - channels = AudioSystem::CHANNEL_OUT_STEREO; - } else if (value == "Channels Mono") { - channels = AudioSystem::CHANNEL_OUT_MONO; - } - if (channels != 0) { - if (target == "Manager") { - mTestChannels = channels; - } else if (mTestOutputs[mCurOutput] != 0) { - AudioParameter outputParam = AudioParameter(); - outputParam.addInt(String8("channels"), channels); - mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString()); - } - } - } - if (param.getInt(String8("test_cmd_policy_sampleRate"), valueInt) == NO_ERROR) { - param.remove(String8("test_cmd_policy_sampleRate")); - if (valueInt >= 0 && valueInt <= 96000) { - int samplingRate = valueInt; - if (target == "Manager") { - mTestSamplingRate = samplingRate; - } else if (mTestOutputs[mCurOutput] != 0) { - AudioParameter outputParam = AudioParameter(); - outputParam.addInt(String8("sampling_rate"), samplingRate); - mpClientInterface->setParameters(mTestOutputs[mCurOutput], outputParam.toString()); - } - } - } - - if (param.get(String8("test_cmd_policy_reopen"), value) == NO_ERROR) { - param.remove(String8("test_cmd_policy_reopen")); - - mpClientInterface->closeOutput(mHardwareOutput); - delete mOutputs.valueFor(mHardwareOutput); - mOutputs.removeItem(mHardwareOutput); - - AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor(); - outputDesc->mDevice = (uint32_t)AudioSystem::DEVICE_OUT_SPEAKER; - mHardwareOutput = mpClientInterface->openOutput(&outputDesc->mDevice, - &outputDesc->mSamplingRate, - &outputDesc->mFormat, - &outputDesc->mChannels, - &outputDesc->mLatency, - outputDesc->mFlags); - if (mHardwareOutput == 0) { - LOGE("Failed to reopen hardware output stream, samplingRate: %d, format %d, channels %d", - outputDesc->mSamplingRate, outputDesc->mFormat, outputDesc->mChannels); - } else { - AudioParameter outputCmd = AudioParameter(); - outputCmd.addInt(String8("set_id"), 0); - mpClientInterface->setParameters(mHardwareOutput, outputCmd.toString()); - addOutput(mHardwareOutput, outputDesc); - } - } - - - mpClientInterface->setParameters(0, String8("test_cmd_policy=")); - } - } - return false; -} - -void AudioPolicyManagerBase::exit() -{ - { - AutoMutex _l(mLock); - requestExit(); - mWaitWorkCV.signal(); - } - requestExitAndWait(); -} - -int AudioPolicyManagerBase::testOutputIndex(audio_io_handle_t output) -{ - for (int i = 0; i < NUM_TEST_OUTPUTS; i++) { - if (output == mTestOutputs[i]) return i; - } - return 0; -} -#endif //AUDIO_POLICY_TEST - -// --- - -void AudioPolicyManagerBase::addOutput(audio_io_handle_t id, AudioOutputDescriptor *outputDesc) -{ - outputDesc->mId = id; - mOutputs.add(id, outputDesc); -} - - -#ifdef WITH_A2DP -status_t AudioPolicyManagerBase::handleA2dpConnection(AudioSystem::audio_devices device, - const char *device_address) -{ - // when an A2DP device is connected, open an A2DP and a duplicated output - LOGV("opening A2DP output for device %s", device_address); - AudioOutputDescriptor *outputDesc = new AudioOutputDescriptor(); - outputDesc->mDevice = device; - mA2dpOutput = mpClientInterface->openOutput(&outputDesc->mDevice, - &outputDesc->mSamplingRate, - &outputDesc->mFormat, - &outputDesc->mChannels, - &outputDesc->mLatency, - outputDesc->mFlags); - if (mA2dpOutput) { - // add A2DP output descriptor - addOutput(mA2dpOutput, outputDesc); - - //TODO: configure audio effect output stage here - - // set initial stream volume for A2DP device - applyStreamVolumes(mA2dpOutput, device); - if (a2dpUsedForSonification()) { - mDuplicatedOutput = mpClientInterface->openDuplicateOutput(mA2dpOutput, mHardwareOutput); - } - if (mDuplicatedOutput != 0 || - !a2dpUsedForSonification()) { - // If both A2DP and duplicated outputs are open, send device address to A2DP hardware - // interface - AudioParameter param; - param.add(String8("a2dp_sink_address"), String8(device_address)); - mpClientInterface->setParameters(mA2dpOutput, param.toString()); - mA2dpDeviceAddress = String8(device_address, MAX_DEVICE_ADDRESS_LEN); - - if (a2dpUsedForSonification()) { - // add duplicated output descriptor - AudioOutputDescriptor *dupOutputDesc = new AudioOutputDescriptor(); - dupOutputDesc->mOutput1 = mOutputs.valueFor(mHardwareOutput); - dupOutputDesc->mOutput2 = mOutputs.valueFor(mA2dpOutput); - dupOutputDesc->mSamplingRate = outputDesc->mSamplingRate; - dupOutputDesc->mFormat = outputDesc->mFormat; - dupOutputDesc->mChannels = outputDesc->mChannels; - dupOutputDesc->mLatency = outputDesc->mLatency; - addOutput(mDuplicatedOutput, dupOutputDesc); - applyStreamVolumes(mDuplicatedOutput, device); - } - } else { - LOGW("getOutput() could not open duplicated output for %d and %d", - mHardwareOutput, mA2dpOutput); - mpClientInterface->closeOutput(mA2dpOutput); - mOutputs.removeItem(mA2dpOutput); - mA2dpOutput = 0; - delete outputDesc; - return NO_INIT; - } - } else { - LOGW("setDeviceConnectionState() could not open A2DP output for device %x", device); - delete outputDesc; - return NO_INIT; - } - AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput); - - if (!a2dpUsedForSonification()) { - // mute music on A2DP output if a notification or ringtone is playing - uint32_t refCount = hwOutputDesc->strategyRefCount(STRATEGY_SONIFICATION); - for (uint32_t i = 0; i < refCount; i++) { - setStrategyMute(STRATEGY_MEDIA, true, mA2dpOutput); - } - } - - mA2dpSuspended = false; - - return NO_ERROR; -} - -status_t AudioPolicyManagerBase::handleA2dpDisconnection(AudioSystem::audio_devices device, - const char *device_address) -{ - if (mA2dpOutput == 0) { - LOGW("setDeviceConnectionState() disconnecting A2DP and no A2DP output!"); - return INVALID_OPERATION; - } - - if (mA2dpDeviceAddress != device_address) { - LOGW("setDeviceConnectionState() disconnecting unknow A2DP sink address %s", device_address); - return INVALID_OPERATION; - } - - // mute media strategy to avoid outputting sound on hardware output while music stream - // is switched from A2DP output and before music is paused by music application - setStrategyMute(STRATEGY_MEDIA, true, mHardwareOutput); - setStrategyMute(STRATEGY_MEDIA, false, mHardwareOutput, MUTE_TIME_MS); - - if (!a2dpUsedForSonification()) { - // unmute music on A2DP output if a notification or ringtone is playing - uint32_t refCount = mOutputs.valueFor(mHardwareOutput)->strategyRefCount(STRATEGY_SONIFICATION); - for (uint32_t i = 0; i < refCount; i++) { - setStrategyMute(STRATEGY_MEDIA, false, mA2dpOutput); - } - } - mA2dpDeviceAddress = ""; - mA2dpSuspended = false; - return NO_ERROR; -} - -void AudioPolicyManagerBase::closeA2dpOutputs() -{ - - LOGV("setDeviceConnectionState() closing A2DP and duplicated output!"); - - if (mDuplicatedOutput != 0) { - AudioOutputDescriptor *dupOutputDesc = mOutputs.valueFor(mDuplicatedOutput); - AudioOutputDescriptor *hwOutputDesc = mOutputs.valueFor(mHardwareOutput); - // As all active tracks on duplicated output will be deleted, - // and as they were also referenced on hardware output, the reference - // count for their stream type must be adjusted accordingly on - // hardware output. - for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) { - int refCount = dupOutputDesc->mRefCount[i]; - hwOutputDesc->changeRefCount((AudioSystem::stream_type)i,-refCount); - } - - mpClientInterface->closeOutput(mDuplicatedOutput); - delete mOutputs.valueFor(mDuplicatedOutput); - mOutputs.removeItem(mDuplicatedOutput); - mDuplicatedOutput = 0; - } - if (mA2dpOutput != 0) { - AudioParameter param; - param.add(String8("closing"), String8("true")); - mpClientInterface->setParameters(mA2dpOutput, param.toString()); - - mpClientInterface->closeOutput(mA2dpOutput); - delete mOutputs.valueFor(mA2dpOutput); - mOutputs.removeItem(mA2dpOutput); - mA2dpOutput = 0; - } -} - -void AudioPolicyManagerBase::checkOutputForStrategy(routing_strategy strategy) -{ - uint32_t prevDevice = getDeviceForStrategy(strategy); - uint32_t curDevice = getDeviceForStrategy(strategy, false); - bool a2dpWasUsed = AudioSystem::isA2dpDevice((AudioSystem::audio_devices)(prevDevice & ~AudioSystem::DEVICE_OUT_SPEAKER)); - bool a2dpIsUsed = AudioSystem::isA2dpDevice((AudioSystem::audio_devices)(curDevice & ~AudioSystem::DEVICE_OUT_SPEAKER)); - audio_io_handle_t srcOutput = 0; - audio_io_handle_t dstOutput = 0; - - if (a2dpWasUsed && !a2dpIsUsed) { - bool dupUsed = a2dpUsedForSonification() && a2dpWasUsed && (AudioSystem::popCount(prevDevice) == 2); - dstOutput = mHardwareOutput; - if (dupUsed) { - LOGV("checkOutputForStrategy() moving strategy %d from duplicated", strategy); - srcOutput = mDuplicatedOutput; - } else { - LOGV("checkOutputForStrategy() moving strategy %d from a2dp", strategy); - srcOutput = mA2dpOutput; - } - } - if (a2dpIsUsed && !a2dpWasUsed) { - bool dupUsed = a2dpUsedForSonification() && a2dpIsUsed && (AudioSystem::popCount(curDevice) == 2); - srcOutput = mHardwareOutput; - if (dupUsed) { - LOGV("checkOutputForStrategy() moving strategy %d to duplicated", strategy); - dstOutput = mDuplicatedOutput; - } else { - LOGV("checkOutputForStrategy() moving strategy %d to a2dp", strategy); - dstOutput = mA2dpOutput; - } - } - - if (srcOutput != 0 && dstOutput != 0) { - // Move effects associated to this strategy from previous output to new output - for (size_t i = 0; i < mEffects.size(); i++) { - EffectDescriptor *desc = mEffects.valueAt(i); - if (desc->mSession != AudioSystem::SESSION_OUTPUT_STAGE && - desc->mStrategy == strategy && - desc->mOutput == srcOutput) { - LOGV("checkOutputForStrategy() moving effect %d to output %d", mEffects.keyAt(i), dstOutput); - mpClientInterface->moveEffects(desc->mSession, srcOutput, dstOutput); - desc->mOutput = dstOutput; - } - } - // Move tracks associated to this strategy from previous output to new output - for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) { - if (getStrategy((AudioSystem::stream_type)i) == strategy) { - mpClientInterface->setStreamOutput((AudioSystem::stream_type)i, dstOutput); - } - } - } -} - -void AudioPolicyManagerBase::checkOutputForAllStrategies() -{ - checkOutputForStrategy(STRATEGY_PHONE); - checkOutputForStrategy(STRATEGY_SONIFICATION); - checkOutputForStrategy(STRATEGY_MEDIA); - checkOutputForStrategy(STRATEGY_DTMF); -} - -void AudioPolicyManagerBase::checkA2dpSuspend() -{ - // suspend A2DP output if: - // (NOT already suspended) && - // ((SCO device is connected && - // (forced usage for communication || for record is SCO))) || - // (phone state is ringing || in call) - // - // restore A2DP output if: - // (Already suspended) && - // ((SCO device is NOT connected || - // (forced usage NOT for communication && NOT for record is SCO))) && - // (phone state is NOT ringing && NOT in call) - // - if (mA2dpOutput == 0) { - return; - } - - if (mA2dpSuspended) { - if (((mScoDeviceAddress == "") || - ((mForceUse[AudioSystem::FOR_COMMUNICATION] != AudioSystem::FORCE_BT_SCO) && - (mForceUse[AudioSystem::FOR_RECORD] != AudioSystem::FORCE_BT_SCO))) && - ((mPhoneState != AudioSystem::MODE_IN_CALL) && - (mPhoneState != AudioSystem::MODE_RINGTONE))) { - - mpClientInterface->restoreOutput(mA2dpOutput); - mA2dpSuspended = false; - } - } else { - if (((mScoDeviceAddress != "") && - ((mForceUse[AudioSystem::FOR_COMMUNICATION] == AudioSystem::FORCE_BT_SCO) || - (mForceUse[AudioSystem::FOR_RECORD] == AudioSystem::FORCE_BT_SCO))) || - ((mPhoneState == AudioSystem::MODE_IN_CALL) || - (mPhoneState == AudioSystem::MODE_RINGTONE))) { - - mpClientInterface->suspendOutput(mA2dpOutput); - mA2dpSuspended = true; - } - } -} - - -#endif - -uint32_t AudioPolicyManagerBase::getNewDevice(audio_io_handle_t output, bool fromCache) -{ - uint32_t device = 0; - - AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output); - // check the following by order of priority to request a routing change if necessary: - // 1: we are in call or the strategy phone is active on the hardware output: - // use device for strategy phone - // 2: the strategy sonification is active on the hardware output: - // use device for strategy sonification - // 3: the strategy media is active on the hardware output: - // use device for strategy media - // 4: the strategy DTMF is active on the hardware output: - // use device for strategy DTMF - if (isInCall() || - outputDesc->isUsedByStrategy(STRATEGY_PHONE)) { - device = getDeviceForStrategy(STRATEGY_PHONE, fromCache); - } else if (outputDesc->isUsedByStrategy(STRATEGY_SONIFICATION)) { - device = getDeviceForStrategy(STRATEGY_SONIFICATION, fromCache); - } else if (outputDesc->isUsedByStrategy(STRATEGY_MEDIA)) { - device = getDeviceForStrategy(STRATEGY_MEDIA, fromCache); - } else if (outputDesc->isUsedByStrategy(STRATEGY_DTMF)) { - device = getDeviceForStrategy(STRATEGY_DTMF, fromCache); - } - - LOGV("getNewDevice() selected device %x", device); - return device; -} - -uint32_t AudioPolicyManagerBase::getStrategyForStream(AudioSystem::stream_type stream) { - return (uint32_t)getStrategy(stream); -} - -uint32_t AudioPolicyManagerBase::getDevicesForStream(AudioSystem::stream_type stream) { - uint32_t devices; - // By checking the range of stream before calling getStrategy, we avoid - // getStrategy's behavior for invalid streams. getStrategy would do a LOGE - // and then return STRATEGY_MEDIA, but we want to return the empty set. - if (stream < (AudioSystem::stream_type) 0 || stream >= AudioSystem::NUM_STREAM_TYPES) { - devices = 0; - } else { - AudioPolicyManagerBase::routing_strategy strategy = getStrategy(stream); - devices = getDeviceForStrategy(strategy, true); - } - return devices; -} - -AudioPolicyManagerBase::routing_strategy AudioPolicyManagerBase::getStrategy( - AudioSystem::stream_type stream) { - // stream to strategy mapping - switch (stream) { - case AudioSystem::VOICE_CALL: - case AudioSystem::BLUETOOTH_SCO: - return STRATEGY_PHONE; - case AudioSystem::RING: - case AudioSystem::NOTIFICATION: - case AudioSystem::ALARM: - case AudioSystem::ENFORCED_AUDIBLE: - return STRATEGY_SONIFICATION; - case AudioSystem::DTMF: - return STRATEGY_DTMF; - default: - LOGE("unknown stream type"); - case AudioSystem::SYSTEM: - // NOTE: SYSTEM stream uses MEDIA strategy because muting music and switching outputs - // while key clicks are played produces a poor result - case AudioSystem::TTS: - case AudioSystem::MUSIC: - return STRATEGY_MEDIA; - } -} - -uint32_t AudioPolicyManagerBase::getDeviceForStrategy(routing_strategy strategy, bool fromCache) -{ - uint32_t device = 0; - - if (fromCache) { - LOGV("getDeviceForStrategy() from cache strategy %d, device %x", strategy, mDeviceForStrategy[strategy]); - return mDeviceForStrategy[strategy]; - } - - switch (strategy) { - case STRATEGY_DTMF: - if (!isInCall()) { - // when off call, DTMF strategy follows the same rules as MEDIA strategy - device = getDeviceForStrategy(STRATEGY_MEDIA, false); - break; - } - // when in call, DTMF and PHONE strategies follow the same rules - // FALL THROUGH - - case STRATEGY_PHONE: - // for phone strategy, we first consider the forced use and then the available devices by order - // of priority - switch (mForceUse[AudioSystem::FOR_COMMUNICATION]) { - case AudioSystem::FORCE_BT_SCO: - if (!isInCall() || strategy != STRATEGY_DTMF) { - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT; - if (device) break; - } - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_HEADSET; - if (device) break; - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO; - if (device) break; - // if SCO device is requested but no SCO device is available, fall back to default case - // FALL THROUGH - - default: // FORCE_NONE - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE; - if (device) break; - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET; - if (device) break; -#ifdef WITH_A2DP - // when not in a phone call, phone strategy should route STREAM_VOICE_CALL to A2DP - if (!isInCall() && !mA2dpSuspended) { - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP; - if (device) break; - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES; - if (device) break; - } -#endif - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_AUX_DIGITAL; - if (device) break; - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET; - if (device) break; - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET; - if (device) break; - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_EARPIECE; - if (device == 0) { - LOGE("getDeviceForStrategy() earpiece device not found"); - } - break; - - case AudioSystem::FORCE_SPEAKER: -#ifdef WITH_A2DP - // when not in a phone call, phone strategy should route STREAM_VOICE_CALL to - // A2DP speaker when forcing to speaker output - if (!isInCall() && !mA2dpSuspended) { - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER; - if (device) break; - } -#endif - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_AUX_DIGITAL; - if (device) break; - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET; - if (device) break; - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET; - if (device) break; - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER; - if (device == 0) { - LOGE("getDeviceForStrategy() speaker device not found"); - } - break; - } - break; - - case STRATEGY_SONIFICATION: - - // If incall, just select the STRATEGY_PHONE device: The rest of the behavior is handled by - // handleIncallSonification(). - if (isInCall()) { - device = getDeviceForStrategy(STRATEGY_PHONE, false); - break; - } - device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER; - if (device == 0) { - LOGE("getDeviceForStrategy() speaker device not found"); - } - // The second device used for sonification is the same as the device used by media strategy - // FALL THROUGH - - case STRATEGY_MEDIA: { - uint32_t device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE; - if (device2 == 0) { - device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET; - } -#ifdef WITH_A2DP - if ((mA2dpOutput != 0) && !mA2dpSuspended && - (strategy != STRATEGY_SONIFICATION || a2dpUsedForSonification())) { - if (device2 == 0) { - device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP; - } - if (device2 == 0) { - device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES; - } - if (device2 == 0) { - device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER; - } - } -#endif - if (device2 == 0) { - device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_AUX_DIGITAL; - } - if (device2 == 0) { - device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET; - } - if (device2 == 0) { - device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET; - } - if (device2 == 0) { - device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_SPEAKER; - } - - // device is DEVICE_OUT_SPEAKER if we come from case STRATEGY_SONIFICATION, 0 otherwise - device |= device2; - if (device == 0) { - LOGE("getDeviceForStrategy() speaker device not found"); - } - } break; - - default: - LOGW("getDeviceForStrategy() unknown strategy: %d", strategy); - break; - } - - LOGV("getDeviceForStrategy() strategy %d, device %x", strategy, device); - return device; -} - -void AudioPolicyManagerBase::updateDeviceForStrategy() -{ - for (int i = 0; i < NUM_STRATEGIES; i++) { - mDeviceForStrategy[i] = getDeviceForStrategy((routing_strategy)i, false); - } -} - -void AudioPolicyManagerBase::setOutputDevice(audio_io_handle_t output, uint32_t device, bool force, int delayMs) -{ - LOGV("setOutputDevice() output %d device %x delayMs %d", output, device, delayMs); - AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output); - - - if (outputDesc->isDuplicated()) { - setOutputDevice(outputDesc->mOutput1->mId, device, force, delayMs); - setOutputDevice(outputDesc->mOutput2->mId, device, force, delayMs); - return; - } -#ifdef WITH_A2DP - // filter devices according to output selected - if (output == mA2dpOutput) { - device &= AudioSystem::DEVICE_OUT_ALL_A2DP; - } else { - device &= ~AudioSystem::DEVICE_OUT_ALL_A2DP; - } -#endif - - uint32_t prevDevice = (uint32_t)outputDesc->device(); - // Do not change the routing if: - // - the requestede device is 0 - // - the requested device is the same as current device and force is not specified. - // Doing this check here allows the caller to call setOutputDevice() without conditions - if ((device == 0 || device == prevDevice) && !force) { - LOGV("setOutputDevice() setting same device %x or null device for output %d", device, output); - return; - } - - outputDesc->mDevice = device; - // mute media streams if both speaker and headset are selected - if (output == mHardwareOutput && AudioSystem::popCount(device) == 2) { - setStrategyMute(STRATEGY_MEDIA, true, output); - // wait for the PCM output buffers to empty before proceeding with the rest of the command - usleep(outputDesc->mLatency*2*1000); - } - - // do the routing - AudioParameter param = AudioParameter(); - param.addInt(String8(AudioParameter::keyRouting), (int)device); - mpClientInterface->setParameters(mHardwareOutput, param.toString(), delayMs); - // update stream volumes according to new device - applyStreamVolumes(output, device, delayMs); - - // if changing from a combined headset + speaker route, unmute media streams - if (output == mHardwareOutput && AudioSystem::popCount(prevDevice) == 2) { - setStrategyMute(STRATEGY_MEDIA, false, output, delayMs); - } -} - -uint32_t AudioPolicyManagerBase::getDeviceForInputSource(int inputSource) -{ - uint32_t device; - - switch(inputSource) { - case AUDIO_SOURCE_DEFAULT: - case AUDIO_SOURCE_MIC: - case AUDIO_SOURCE_VOICE_RECOGNITION: - case AUDIO_SOURCE_VOICE_COMMUNICATION: - if (mForceUse[AudioSystem::FOR_RECORD] == AudioSystem::FORCE_BT_SCO && - mAvailableInputDevices & AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET) { - device = AudioSystem::DEVICE_IN_BLUETOOTH_SCO_HEADSET; - } else if (mAvailableInputDevices & AudioSystem::DEVICE_IN_WIRED_HEADSET) { - device = AudioSystem::DEVICE_IN_WIRED_HEADSET; - } else { - device = AudioSystem::DEVICE_IN_BUILTIN_MIC; - } - break; - case AUDIO_SOURCE_CAMCORDER: - if (hasBackMicrophone()) { - device = AudioSystem::DEVICE_IN_BACK_MIC; - } else { - device = AudioSystem::DEVICE_IN_BUILTIN_MIC; - } - break; - case AUDIO_SOURCE_VOICE_UPLINK: - case AUDIO_SOURCE_VOICE_DOWNLINK: - case AUDIO_SOURCE_VOICE_CALL: - device = AudioSystem::DEVICE_IN_VOICE_CALL; - break; - default: - LOGW("getInput() invalid input source %d", inputSource); - device = 0; - break; - } - LOGV("getDeviceForInputSource()input source %d, device %08x", inputSource, device); - return device; -} - -audio_io_handle_t AudioPolicyManagerBase::getActiveInput() -{ - for (size_t i = 0; i < mInputs.size(); i++) { - if (mInputs.valueAt(i)->mRefCount > 0) { - return mInputs.keyAt(i); - } - } - return 0; -} - -float AudioPolicyManagerBase::volIndexToAmpl(uint32_t device, const StreamDescriptor& streamDesc, - int indexInUi) { - // the volume index in the UI is relative to the min and max volume indices for this stream type - int nbSteps = 1 + streamDesc.mVolIndex[StreamDescriptor::VOLMAX] - - streamDesc.mVolIndex[StreamDescriptor::VOLMIN]; - int volIdx = (nbSteps * (indexInUi - streamDesc.mIndexMin)) / - (streamDesc.mIndexMax - streamDesc.mIndexMin); - - // find what part of the curve this index volume belongs to, or if it's out of bounds - int segment = 0; - if (volIdx < streamDesc.mVolIndex[StreamDescriptor::VOLMIN]) { // out of bounds - return 0.0f; - } else if (volIdx < streamDesc.mVolIndex[StreamDescriptor::VOLKNEE1]) { - segment = 0; - } else if (volIdx < streamDesc.mVolIndex[StreamDescriptor::VOLKNEE2]) { - segment = 1; - } else if (volIdx <= streamDesc.mVolIndex[StreamDescriptor::VOLMAX]) { - segment = 2; - } else { // out of bounds - return 1.0f; - } - - // linear interpolation in the attenuation table in dB - float decibels = streamDesc.mVolDbAtt[segment] + - ((float)(volIdx - streamDesc.mVolIndex[segment])) * - ( (streamDesc.mVolDbAtt[segment+1] - streamDesc.mVolDbAtt[segment]) / - ((float)(streamDesc.mVolIndex[segment+1] - streamDesc.mVolIndex[segment])) ); - - float amplification = exp( decibels * 0.115129f); // exp( dB * ln(10) / 20 ) - - LOGV("VOLUME vol index=[%d %d %d], dB=[%.1f %.1f %.1f] ampl=%.5f", - streamDesc.mVolIndex[segment], volIdx, streamDesc.mVolIndex[segment+1], - streamDesc.mVolDbAtt[segment], decibels, streamDesc.mVolDbAtt[segment+1], - amplification); - - return amplification; -} - -void AudioPolicyManagerBase::initializeVolumeCurves() { - // initialize the volume curves to a (-49.5 - 0 dB) attenuation in 0.5dB steps - for (int i=0 ; i< AudioSystem::NUM_STREAM_TYPES ; i++) { - mStreams[i].mVolIndex[StreamDescriptor::VOLMIN] = 1; - mStreams[i].mVolDbAtt[StreamDescriptor::VOLMIN] = -49.5f; - mStreams[i].mVolIndex[StreamDescriptor::VOLKNEE1] = 33; - mStreams[i].mVolDbAtt[StreamDescriptor::VOLKNEE1] = -33.5f; - mStreams[i].mVolIndex[StreamDescriptor::VOLKNEE2] = 66; - mStreams[i].mVolDbAtt[StreamDescriptor::VOLKNEE2] = -17.0f; - // here we use 100 steps to avoid rounding errors - // when computing the volume in volIndexToAmpl() - mStreams[i].mVolIndex[StreamDescriptor::VOLMAX] = 100; - mStreams[i].mVolDbAtt[StreamDescriptor::VOLMAX] = 0.0f; - } - - // Modification for music: more attenuation for lower volumes, finer steps at high volumes - mStreams[AudioSystem::MUSIC].mVolIndex[StreamDescriptor::VOLMIN] = 1; - mStreams[AudioSystem::MUSIC].mVolDbAtt[StreamDescriptor::VOLMIN] = -58.0f; - mStreams[AudioSystem::MUSIC].mVolIndex[StreamDescriptor::VOLKNEE1] = 20; - mStreams[AudioSystem::MUSIC].mVolDbAtt[StreamDescriptor::VOLKNEE1] = -40.0f; - mStreams[AudioSystem::MUSIC].mVolIndex[StreamDescriptor::VOLKNEE2] = 60; - mStreams[AudioSystem::MUSIC].mVolDbAtt[StreamDescriptor::VOLKNEE2] = -17.0f; - mStreams[AudioSystem::MUSIC].mVolIndex[StreamDescriptor::VOLMAX] = 100; - mStreams[AudioSystem::MUSIC].mVolDbAtt[StreamDescriptor::VOLMAX] = 0.0f; -} - -float AudioPolicyManagerBase::computeVolume(int stream, int index, audio_io_handle_t output, uint32_t device) -{ - float volume = 1.0; - AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output); - StreamDescriptor &streamDesc = mStreams[stream]; - - if (device == 0) { - device = outputDesc->device(); - } - - // if volume is not 0 (not muted), force media volume to max on digital output - if (stream == AudioSystem::MUSIC && - index != mStreams[stream].mIndexMin && - device == AudioSystem::DEVICE_OUT_AUX_DIGITAL) { - return 1.0; - } - - volume = volIndexToAmpl(device, streamDesc, index); - - // if a headset is connected, apply the following rules to ring tones and notifications - // to avoid sound level bursts in user's ears: - // - always attenuate ring tones and notifications volume by 6dB - // - if music is playing, always limit the volume to current music volume, - // with a minimum threshold at -36dB so that notification is always perceived. - if ((device & - (AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP | - AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | - AudioSystem::DEVICE_OUT_WIRED_HEADSET | - AudioSystem::DEVICE_OUT_WIRED_HEADPHONE)) && - ((getStrategy((AudioSystem::stream_type)stream) == STRATEGY_SONIFICATION) || - (stream == AudioSystem::SYSTEM)) && - streamDesc.mCanBeMuted) { - volume *= SONIFICATION_HEADSET_VOLUME_FACTOR; - // when the phone is ringing we must consider that music could have been paused just before - // by the music application and behave as if music was active if the last music track was - // just stopped - if (outputDesc->mRefCount[AudioSystem::MUSIC] || mLimitRingtoneVolume) { - float musicVol = computeVolume(AudioSystem::MUSIC, mStreams[AudioSystem::MUSIC].mIndexCur, output, device); - float minVol = (musicVol > SONIFICATION_HEADSET_VOLUME_MIN) ? musicVol : SONIFICATION_HEADSET_VOLUME_MIN; - if (volume > minVol) { - volume = minVol; - LOGV("computeVolume limiting volume to %f musicVol %f", minVol, musicVol); - } - } - } - - return volume; -} - -status_t AudioPolicyManagerBase::checkAndSetVolume(int stream, int index, audio_io_handle_t output, uint32_t device, int delayMs, bool force) -{ - - // do not change actual stream volume if the stream is muted - if (mOutputs.valueFor(output)->mMuteCount[stream] != 0) { - LOGV("checkAndSetVolume() stream %d muted count %d", stream, mOutputs.valueFor(output)->mMuteCount[stream]); - return NO_ERROR; - } - - // do not change in call volume if bluetooth is connected and vice versa - if ((stream == AudioSystem::VOICE_CALL && mForceUse[AudioSystem::FOR_COMMUNICATION] == AudioSystem::FORCE_BT_SCO) || - (stream == AudioSystem::BLUETOOTH_SCO && mForceUse[AudioSystem::FOR_COMMUNICATION] != AudioSystem::FORCE_BT_SCO)) { - LOGV("checkAndSetVolume() cannot set stream %d volume with force use = %d for comm", - stream, mForceUse[AudioSystem::FOR_COMMUNICATION]); - return INVALID_OPERATION; - } - - float volume = computeVolume(stream, index, output, device); - // We actually change the volume if: - // - the float value returned by computeVolume() changed - // - the force flag is set - if (volume != mOutputs.valueFor(output)->mCurVolume[stream] || - force) { - mOutputs.valueFor(output)->mCurVolume[stream] = volume; - LOGV("setStreamVolume() for output %d stream %d, volume %f, delay %d", output, stream, volume, delayMs); - if (stream == AudioSystem::VOICE_CALL || - stream == AudioSystem::DTMF || - stream == AudioSystem::BLUETOOTH_SCO) { - // offset value to reflect actual hardware volume that never reaches 0 - // 1% corresponds roughly to first step in VOICE_CALL stream volume setting (see AudioService.java) - volume = 0.01 + 0.99 * volume; - // Force VOICE_CALL to track BLUETOOTH_SCO stream volume when bluetooth audio is - // enabled - if (stream == AudioSystem::BLUETOOTH_SCO) { - mpClientInterface->setStreamVolume(AudioSystem::VOICE_CALL, volume, output, delayMs); - } - } - - mpClientInterface->setStreamVolume((AudioSystem::stream_type)stream, volume, output, delayMs); - } - - if (stream == AudioSystem::VOICE_CALL || - stream == AudioSystem::BLUETOOTH_SCO) { - float voiceVolume; - // Force voice volume to max for bluetooth SCO as volume is managed by the headset - if (stream == AudioSystem::VOICE_CALL) { - voiceVolume = (float)index/(float)mStreams[stream].mIndexMax; - } else { - voiceVolume = 1.0; - } - - if (voiceVolume != mLastVoiceVolume && output == mHardwareOutput) { - mpClientInterface->setVoiceVolume(voiceVolume, delayMs); - mLastVoiceVolume = voiceVolume; - } - } - - return NO_ERROR; -} - -void AudioPolicyManagerBase::applyStreamVolumes(audio_io_handle_t output, uint32_t device, int delayMs, bool force) -{ - LOGV("applyStreamVolumes() for output %d and device %x", output, device); - - for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) { - checkAndSetVolume(stream, mStreams[stream].mIndexCur, output, device, delayMs, force); - } -} - -void AudioPolicyManagerBase::setStrategyMute(routing_strategy strategy, bool on, audio_io_handle_t output, int delayMs) -{ - LOGV("setStrategyMute() strategy %d, mute %d, output %d", strategy, on, output); - for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) { - if (getStrategy((AudioSystem::stream_type)stream) == strategy) { - setStreamMute(stream, on, output, delayMs); - } - } -} - -void AudioPolicyManagerBase::setStreamMute(int stream, bool on, audio_io_handle_t output, int delayMs) -{ - StreamDescriptor &streamDesc = mStreams[stream]; - AudioOutputDescriptor *outputDesc = mOutputs.valueFor(output); - - LOGV("setStreamMute() stream %d, mute %d, output %d, mMuteCount %d", stream, on, output, outputDesc->mMuteCount[stream]); - - if (on) { - if (outputDesc->mMuteCount[stream] == 0) { - if (streamDesc.mCanBeMuted) { - checkAndSetVolume(stream, 0, output, outputDesc->device(), delayMs); - } - } - // increment mMuteCount after calling checkAndSetVolume() so that volume change is not ignored - outputDesc->mMuteCount[stream]++; - } else { - if (outputDesc->mMuteCount[stream] == 0) { - LOGW("setStreamMute() unmuting non muted stream!"); - return; - } - if (--outputDesc->mMuteCount[stream] == 0) { - checkAndSetVolume(stream, streamDesc.mIndexCur, output, outputDesc->device(), delayMs); - } - } -} - -void AudioPolicyManagerBase::handleIncallSonification(int stream, bool starting, bool stateChange) -{ - // if the stream pertains to sonification strategy and we are in call we must - // mute the stream if it is low visibility. If it is high visibility, we must play a tone - // in the device used for phone strategy and play the tone if the selected device does not - // interfere with the device used for phone strategy - // if stateChange is true, we are called from setPhoneState() and we must mute or unmute as - // many times as there are active tracks on the output - - if (getStrategy((AudioSystem::stream_type)stream) == STRATEGY_SONIFICATION) { - AudioOutputDescriptor *outputDesc = mOutputs.valueFor(mHardwareOutput); - LOGV("handleIncallSonification() stream %d starting %d device %x stateChange %d", - stream, starting, outputDesc->mDevice, stateChange); - if (outputDesc->mRefCount[stream]) { - int muteCount = 1; - if (stateChange) { - muteCount = outputDesc->mRefCount[stream]; - } - if (AudioSystem::isLowVisibility((AudioSystem::stream_type)stream)) { - LOGV("handleIncallSonification() low visibility, muteCount %d", muteCount); - for (int i = 0; i < muteCount; i++) { - setStreamMute(stream, starting, mHardwareOutput); - } - } else { - LOGV("handleIncallSonification() high visibility"); - if (outputDesc->device() & getDeviceForStrategy(STRATEGY_PHONE)) { - LOGV("handleIncallSonification() high visibility muted, muteCount %d", muteCount); - for (int i = 0; i < muteCount; i++) { - setStreamMute(stream, starting, mHardwareOutput); - } - } - if (starting) { - mpClientInterface->startTone(ToneGenerator::TONE_SUP_CALL_WAITING, AudioSystem::VOICE_CALL); - } else { - mpClientInterface->stopTone(); - } - } - } - } -} - -bool AudioPolicyManagerBase::isInCall() -{ - return isStateInCall(mPhoneState); -} - -bool AudioPolicyManagerBase::isStateInCall(int state) { - return ((state == AudioSystem::MODE_IN_CALL) || - (state == AudioSystem::MODE_IN_COMMUNICATION)); -} - -bool AudioPolicyManagerBase::needsDirectOuput(AudioSystem::stream_type stream, - uint32_t samplingRate, - uint32_t format, - uint32_t channels, - AudioSystem::output_flags flags, - uint32_t device) -{ - return ((flags & AudioSystem::OUTPUT_FLAG_DIRECT) || - (format !=0 && !AudioSystem::isLinearPCM(format))); -} - -uint32_t AudioPolicyManagerBase::getMaxEffectsCpuLoad() -{ - return MAX_EFFECTS_CPU_LOAD; -} - -uint32_t AudioPolicyManagerBase::getMaxEffectsMemory() -{ - return MAX_EFFECTS_MEMORY; -} - -// --- AudioOutputDescriptor class implementation - -AudioPolicyManagerBase::AudioOutputDescriptor::AudioOutputDescriptor() - : mId(0), mSamplingRate(0), mFormat(0), mChannels(0), mLatency(0), - mFlags((AudioSystem::output_flags)0), mDevice(0), mOutput1(0), mOutput2(0) -{ - // clear usage count for all stream types - for (int i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) { - mRefCount[i] = 0; - mCurVolume[i] = -1.0; - mMuteCount[i] = 0; - mStopTime[i] = 0; - } -} - -uint32_t AudioPolicyManagerBase::AudioOutputDescriptor::device() -{ - uint32_t device = 0; - if (isDuplicated()) { - device = mOutput1->mDevice | mOutput2->mDevice; - } else { - device = mDevice; - } - return device; -} - -void AudioPolicyManagerBase::AudioOutputDescriptor::changeRefCount(AudioSystem::stream_type stream, int delta) -{ - // forward usage count change to attached outputs - if (isDuplicated()) { - mOutput1->changeRefCount(stream, delta); - mOutput2->changeRefCount(stream, delta); - } - if ((delta + (int)mRefCount[stream]) < 0) { - LOGW("changeRefCount() invalid delta %d for stream %d, refCount %d", delta, stream, mRefCount[stream]); - mRefCount[stream] = 0; - return; - } - mRefCount[stream] += delta; - LOGV("changeRefCount() stream %d, count %d", stream, mRefCount[stream]); -} - -uint32_t AudioPolicyManagerBase::AudioOutputDescriptor::refCount() -{ - uint32_t refcount = 0; - for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) { - refcount += mRefCount[i]; - } - return refcount; -} - -uint32_t AudioPolicyManagerBase::AudioOutputDescriptor::strategyRefCount(routing_strategy strategy) -{ - uint32_t refCount = 0; - for (int i = 0; i < (int)AudioSystem::NUM_STREAM_TYPES; i++) { - if (getStrategy((AudioSystem::stream_type)i) == strategy) { - refCount += mRefCount[i]; - } - } - return refCount; -} - -status_t AudioPolicyManagerBase::AudioOutputDescriptor::dump(int fd) -{ - const size_t SIZE = 256; - char buffer[SIZE]; - String8 result; - - snprintf(buffer, SIZE, " Sampling rate: %d\n", mSamplingRate); - result.append(buffer); - snprintf(buffer, SIZE, " Format: %d\n", mFormat); - result.append(buffer); - snprintf(buffer, SIZE, " Channels: %08x\n", mChannels); - result.append(buffer); - snprintf(buffer, SIZE, " Latency: %d\n", mLatency); - result.append(buffer); - snprintf(buffer, SIZE, " Flags %08x\n", mFlags); - result.append(buffer); - snprintf(buffer, SIZE, " Devices %08x\n", device()); - result.append(buffer); - snprintf(buffer, SIZE, " Stream volume refCount muteCount\n"); - result.append(buffer); - for (int i = 0; i < AudioSystem::NUM_STREAM_TYPES; i++) { - snprintf(buffer, SIZE, " %02d %.03f %02d %02d\n", i, mCurVolume[i], mRefCount[i], mMuteCount[i]); - result.append(buffer); - } - write(fd, result.string(), result.size()); - - return NO_ERROR; -} - -// --- AudioInputDescriptor class implementation - -AudioPolicyManagerBase::AudioInputDescriptor::AudioInputDescriptor() - : mSamplingRate(0), mFormat(0), mChannels(0), - mAcoustics((AudioSystem::audio_in_acoustics)0), mDevice(0), mRefCount(0) -{ -} - -status_t AudioPolicyManagerBase::AudioInputDescriptor::dump(int fd) -{ - const size_t SIZE = 256; - char buffer[SIZE]; - String8 result; - - snprintf(buffer, SIZE, " Sampling rate: %d\n", mSamplingRate); - result.append(buffer); - snprintf(buffer, SIZE, " Format: %d\n", mFormat); - result.append(buffer); - snprintf(buffer, SIZE, " Channels: %08x\n", mChannels); - result.append(buffer); - snprintf(buffer, SIZE, " Acoustics %08x\n", mAcoustics); - result.append(buffer); - snprintf(buffer, SIZE, " Devices %08x\n", mDevice); - result.append(buffer); - snprintf(buffer, SIZE, " Ref Count %d\n", mRefCount); - result.append(buffer); - write(fd, result.string(), result.size()); - - return NO_ERROR; -} - -// --- StreamDescriptor class implementation - -void AudioPolicyManagerBase::StreamDescriptor::dump(char* buffer, size_t size) -{ - snprintf(buffer, size, " %02d %02d %02d %d\n", - mIndexMin, - mIndexMax, - mIndexCur, - mCanBeMuted); -} - -// --- EffectDescriptor class implementation - -status_t AudioPolicyManagerBase::EffectDescriptor::dump(int fd) -{ - const size_t SIZE = 256; - char buffer[SIZE]; - String8 result; - - snprintf(buffer, SIZE, " Output: %d\n", mOutput); - result.append(buffer); - snprintf(buffer, SIZE, " Strategy: %d\n", mStrategy); - result.append(buffer); - snprintf(buffer, SIZE, " Session: %d\n", mSession); - result.append(buffer); - snprintf(buffer, SIZE, " Name: %s\n", mDesc.name); - result.append(buffer); - write(fd, result.string(), result.size()); - - return NO_ERROR; -} - - - -}; // namespace android diff --git a/services/audioflinger/AudioPolicyService.cpp b/services/audioflinger/AudioPolicyService.cpp index b614c48..eebc1b3 100644 --- a/services/audioflinger/AudioPolicyService.cpp +++ b/services/audioflinger/AudioPolicyService.cpp @@ -30,11 +30,15 @@ #include <utils/String16.h> #include <utils/threads.h> #include "AudioPolicyService.h" -#include <hardware_legacy/AudioPolicyManagerBase.h> #include <cutils/properties.h> #include <dlfcn.h> #include <hardware_legacy/power.h> +#include <hardware/hardware.h> +#include <hardware/audio.h> +#include <hardware/audio_policy.h> +#include <hardware/audio_policy_hal.h> + // ---------------------------------------------------------------------------- // the sim build doesn't have gettid @@ -44,7 +48,6 @@ namespace android { - static const char *kDeadlockedString = "AudioPolicyService may be deadlocked\n"; static const char *kCmdDeadlockedString = "AudioPolicyService command thread may be deadlocked\n"; @@ -61,12 +64,19 @@ static bool checkPermission() { return ok; } +namespace { + extern struct audio_policy_service_ops aps_ops; +}; + // ---------------------------------------------------------------------------- AudioPolicyService::AudioPolicyService() - : BnAudioPolicyService() , mpPolicyManager(NULL) + : BnAudioPolicyService() , mpAudioPolicyDev(NULL) , mpAudioPolicy(NULL) { char value[PROPERTY_VALUE_MAX]; + const struct hw_module_t *module; + int forced_val; + int rc; Mutex::Autolock _l(mLock); @@ -75,33 +85,32 @@ AudioPolicyService::AudioPolicyService() // start audio commands thread mAudioCommandThread = new AudioCommandThread(String8("ApmCommandThread")); -#if (defined GENERIC_AUDIO) || (defined AUDIO_POLICY_TEST) - mpPolicyManager = new AudioPolicyManagerBase(this); - LOGV("build for GENERIC_AUDIO - using generic audio policy"); -#else - // if running in emulation - use the emulator driver - if (property_get("ro.kernel.qemu", value, 0)) { - LOGV("Running in emulation - using generic audio policy"); - mpPolicyManager = new AudioPolicyManagerBase(this); - } - else { - LOGV("Using hardware specific audio policy"); - mpPolicyManager = createAudioPolicyManager(this); - } -#endif + /* instantiate the audio policy manager */ + rc = hw_get_module(AUDIO_POLICY_HARDWARE_MODULE_ID, &module); + if (rc) + return; - if ((mpPolicyManager != NULL) && (mpPolicyManager->initCheck() != NO_ERROR)) { - delete mpPolicyManager; - mpPolicyManager = NULL; - } + rc = audio_policy_dev_open(module, &mpAudioPolicyDev); + LOGE_IF(rc, "couldn't open audio policy device (%s)", strerror(-rc)); + if (rc) + return; - if (mpPolicyManager == NULL) { - LOGE("Could not create AudioPolicyManager"); - } else { - // load properties - property_get("ro.camera.sound.forced", value, "0"); - mpPolicyManager->setSystemProperty("ro.camera.sound.forced", value); - } + rc = mpAudioPolicyDev->create_audio_policy(mpAudioPolicyDev, &aps_ops, this, + &mpAudioPolicy); + LOGE_IF(rc, "couldn't create audio policy (%s)", strerror(-rc)); + if (rc) + return; + + rc = mpAudioPolicy->init_check(mpAudioPolicy); + LOGE_IF(rc, "couldn't init_check the audio policy (%s)", strerror(-rc)); + if (rc) + return; + + property_get("ro.camera.sound.forced", value, "0"); + forced_val = strtol(value, NULL, 0); + mpAudioPolicy->set_can_mute_enforced_audible(mpAudioPolicy, !forced_val); + + LOGI("Loaded audio policy from %s (%s)", module->name, module->id); } AudioPolicyService::~AudioPolicyService() @@ -111,57 +120,59 @@ AudioPolicyService::~AudioPolicyService() mAudioCommandThread->exit(); mAudioCommandThread.clear(); - if (mpPolicyManager) { - delete mpPolicyManager; - } + if (mpAudioPolicy && mpAudioPolicyDev) + mpAudioPolicyDev->destroy_audio_policy(mpAudioPolicyDev, mpAudioPolicy); + if (mpAudioPolicyDev) + audio_policy_dev_close(mpAudioPolicyDev); } - -status_t AudioPolicyService::setDeviceConnectionState(AudioSystem::audio_devices device, - AudioSystem::device_connection_state state, +status_t AudioPolicyService::setDeviceConnectionState(audio_devices_t device, + audio_policy_dev_state_t state, const char *device_address) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return NO_INIT; } if (!checkPermission()) { return PERMISSION_DENIED; } - if (!AudioSystem::isOutputDevice(device) && !AudioSystem::isInputDevice(device)) { + if (!audio_is_output_device(device) && !audio_is_input_device(device)) { return BAD_VALUE; } - if (state != AudioSystem::DEVICE_STATE_AVAILABLE && - state != AudioSystem::DEVICE_STATE_UNAVAILABLE) { + if (state != AUDIO_POLICY_DEVICE_STATE_AVAILABLE && + state != AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE) { return BAD_VALUE; } LOGV("setDeviceConnectionState() tid %d", gettid()); Mutex::Autolock _l(mLock); - return mpPolicyManager->setDeviceConnectionState(device, state, device_address); + return mpAudioPolicy->set_device_connection_state(mpAudioPolicy, device, + state, device_address); } -AudioSystem::device_connection_state AudioPolicyService::getDeviceConnectionState( - AudioSystem::audio_devices device, +audio_policy_dev_state_t AudioPolicyService::getDeviceConnectionState( + audio_devices_t device, const char *device_address) { - if (mpPolicyManager == NULL) { - return AudioSystem::DEVICE_STATE_UNAVAILABLE; + if (mpAudioPolicy == NULL) { + return AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE; } if (!checkPermission()) { - return AudioSystem::DEVICE_STATE_UNAVAILABLE; + return AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE; } - return mpPolicyManager->getDeviceConnectionState(device, device_address); + return mpAudioPolicy->get_device_connection_state(mpAudioPolicy, device, + device_address); } status_t AudioPolicyService::setPhoneState(int state) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return NO_INIT; } if (!checkPermission()) { return PERMISSION_DENIED; } - if (state < 0 || state >= AudioSystem::NUM_MODES) { + if (state < 0 || state >= AUDIO_MODE_CNT) { return BAD_VALUE; } @@ -171,215 +182,215 @@ status_t AudioPolicyService::setPhoneState(int state) AudioSystem::setMode(state); Mutex::Autolock _l(mLock); - mpPolicyManager->setPhoneState(state); + mpAudioPolicy->set_phone_state(mpAudioPolicy, state); return NO_ERROR; } status_t AudioPolicyService::setRingerMode(uint32_t mode, uint32_t mask) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return NO_INIT; } if (!checkPermission()) { return PERMISSION_DENIED; } - mpPolicyManager->setRingerMode(mode, mask); + mpAudioPolicy->set_ringer_mode(mpAudioPolicy, mode, mask); return NO_ERROR; } -status_t AudioPolicyService::setForceUse(AudioSystem::force_use usage, - AudioSystem::forced_config config) +status_t AudioPolicyService::setForceUse(audio_policy_force_use_t usage, + audio_policy_forced_cfg_t config) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return NO_INIT; } if (!checkPermission()) { return PERMISSION_DENIED; } - if (usage < 0 || usage >= AudioSystem::NUM_FORCE_USE) { + if (usage < 0 || usage >= AUDIO_POLICY_FORCE_USE_CNT) { return BAD_VALUE; } - if (config < 0 || config >= AudioSystem::NUM_FORCE_CONFIG) { + if (config < 0 || config >= AUDIO_POLICY_FORCE_CFG_CNT) { return BAD_VALUE; } LOGV("setForceUse() tid %d", gettid()); Mutex::Autolock _l(mLock); - mpPolicyManager->setForceUse(usage, config); + mpAudioPolicy->set_force_use(mpAudioPolicy, usage, config); return NO_ERROR; } -AudioSystem::forced_config AudioPolicyService::getForceUse(AudioSystem::force_use usage) +audio_policy_forced_cfg_t AudioPolicyService::getForceUse(audio_policy_force_use_t usage) { - if (mpPolicyManager == NULL) { - return AudioSystem::FORCE_NONE; + if (mpAudioPolicy == NULL) { + return AUDIO_POLICY_FORCE_NONE; } if (!checkPermission()) { - return AudioSystem::FORCE_NONE; + return AUDIO_POLICY_FORCE_NONE; } - if (usage < 0 || usage >= AudioSystem::NUM_FORCE_USE) { - return AudioSystem::FORCE_NONE; + if (usage < 0 || usage >= AUDIO_POLICY_FORCE_USE_CNT) { + return AUDIO_POLICY_FORCE_NONE; } - return mpPolicyManager->getForceUse(usage); + return mpAudioPolicy->get_force_use(mpAudioPolicy, usage); } -audio_io_handle_t AudioPolicyService::getOutput(AudioSystem::stream_type stream, +audio_io_handle_t AudioPolicyService::getOutput(audio_stream_type_t stream, uint32_t samplingRate, uint32_t format, uint32_t channels, - AudioSystem::output_flags flags) + audio_policy_output_flags_t flags) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return 0; } LOGV("getOutput() tid %d", gettid()); Mutex::Autolock _l(mLock); - return mpPolicyManager->getOutput(stream, samplingRate, format, channels, flags); + return mpAudioPolicy->get_output(mpAudioPolicy, stream, samplingRate, format, channels, flags); } status_t AudioPolicyService::startOutput(audio_io_handle_t output, - AudioSystem::stream_type stream, + audio_stream_type_t stream, int session) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return NO_INIT; } LOGV("startOutput() tid %d", gettid()); Mutex::Autolock _l(mLock); - return mpPolicyManager->startOutput(output, stream, session); + return mpAudioPolicy->start_output(mpAudioPolicy, output, stream, session); } status_t AudioPolicyService::stopOutput(audio_io_handle_t output, - AudioSystem::stream_type stream, + audio_stream_type_t stream, int session) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return NO_INIT; } LOGV("stopOutput() tid %d", gettid()); Mutex::Autolock _l(mLock); - return mpPolicyManager->stopOutput(output, stream, session); + return mpAudioPolicy->stop_output(mpAudioPolicy, output, stream, session); } void AudioPolicyService::releaseOutput(audio_io_handle_t output) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return; } LOGV("releaseOutput() tid %d", gettid()); Mutex::Autolock _l(mLock); - mpPolicyManager->releaseOutput(output); + mpAudioPolicy->release_output(mpAudioPolicy, output); } audio_io_handle_t AudioPolicyService::getInput(int inputSource, uint32_t samplingRate, uint32_t format, uint32_t channels, - AudioSystem::audio_in_acoustics acoustics) + audio_in_acoustics_t acoustics) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return 0; } Mutex::Autolock _l(mLock); - return mpPolicyManager->getInput(inputSource, samplingRate, format, channels, acoustics); + return mpAudioPolicy->get_input(mpAudioPolicy, inputSource, samplingRate, format, channels, acoustics); } status_t AudioPolicyService::startInput(audio_io_handle_t input) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return NO_INIT; } Mutex::Autolock _l(mLock); - return mpPolicyManager->startInput(input); + return mpAudioPolicy->start_input(mpAudioPolicy, input); } status_t AudioPolicyService::stopInput(audio_io_handle_t input) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return NO_INIT; } Mutex::Autolock _l(mLock); - return mpPolicyManager->stopInput(input); + return mpAudioPolicy->stop_input(mpAudioPolicy, input); } void AudioPolicyService::releaseInput(audio_io_handle_t input) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return; } Mutex::Autolock _l(mLock); - mpPolicyManager->releaseInput(input); + mpAudioPolicy->release_input(mpAudioPolicy, input); } -status_t AudioPolicyService::initStreamVolume(AudioSystem::stream_type stream, +status_t AudioPolicyService::initStreamVolume(audio_stream_type_t stream, int indexMin, int indexMax) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return NO_INIT; } if (!checkPermission()) { return PERMISSION_DENIED; } - if (stream < 0 || stream >= AudioSystem::NUM_STREAM_TYPES) { + if (stream < 0 || stream >= AUDIO_STREAM_CNT) { return BAD_VALUE; } - mpPolicyManager->initStreamVolume(stream, indexMin, indexMax); + mpAudioPolicy->init_stream_volume(mpAudioPolicy, stream, indexMin, indexMax); return NO_ERROR; } -status_t AudioPolicyService::setStreamVolumeIndex(AudioSystem::stream_type stream, int index) +status_t AudioPolicyService::setStreamVolumeIndex(audio_stream_type_t stream, int index) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return NO_INIT; } if (!checkPermission()) { return PERMISSION_DENIED; } - if (stream < 0 || stream >= AudioSystem::NUM_STREAM_TYPES) { + if (stream < 0 || stream >= AUDIO_STREAM_CNT) { return BAD_VALUE; } - return mpPolicyManager->setStreamVolumeIndex(stream, index); + return mpAudioPolicy->set_stream_volume_index(mpAudioPolicy, stream, index); } -status_t AudioPolicyService::getStreamVolumeIndex(AudioSystem::stream_type stream, int *index) +status_t AudioPolicyService::getStreamVolumeIndex(audio_stream_type_t stream, int *index) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return NO_INIT; } if (!checkPermission()) { return PERMISSION_DENIED; } - if (stream < 0 || stream >= AudioSystem::NUM_STREAM_TYPES) { + if (stream < 0 || stream >= AUDIO_STREAM_CNT) { return BAD_VALUE; } - return mpPolicyManager->getStreamVolumeIndex(stream, index); + return mpAudioPolicy->get_stream_volume_index(mpAudioPolicy, stream, index); } -uint32_t AudioPolicyService::getStrategyForStream(AudioSystem::stream_type stream) +uint32_t AudioPolicyService::getStrategyForStream(audio_stream_type_t stream) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return 0; } - return mpPolicyManager->getStrategyForStream(stream); + return mpAudioPolicy->get_strategy_for_stream(mpAudioPolicy, stream); } -uint32_t AudioPolicyService::getDevicesForStream(AudioSystem::stream_type stream) +uint32_t AudioPolicyService::getDevicesForStream(audio_stream_type_t stream) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return 0; } - return mpPolicyManager->getDevicesForStream(stream); + return mpAudioPolicy->get_devices_for_stream(mpAudioPolicy, stream); } audio_io_handle_t AudioPolicyService::getOutputForEffect(effect_descriptor_t *desc) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return NO_INIT; } Mutex::Autolock _l(mLock); - return mpPolicyManager->getOutputForEffect(desc); + return mpAudioPolicy->get_output_for_effect(mpAudioPolicy, desc); } status_t AudioPolicyService::registerEffect(effect_descriptor_t *desc, @@ -388,27 +399,27 @@ status_t AudioPolicyService::registerEffect(effect_descriptor_t *desc, int session, int id) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return NO_INIT; } - return mpPolicyManager->registerEffect(desc, output, strategy, session, id); + return mpAudioPolicy->register_effect(mpAudioPolicy, desc, output, strategy, session, id); } status_t AudioPolicyService::unregisterEffect(int id) { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return NO_INIT; } - return mpPolicyManager->unregisterEffect(id); + return mpAudioPolicy->unregister_effect(mpAudioPolicy, id); } bool AudioPolicyService::isStreamActive(int stream, uint32_t inPastMs) const { - if (mpPolicyManager == NULL) { + if (mpAudioPolicy == NULL) { return 0; } Mutex::Autolock _l(mLock); - return mpPolicyManager->isStreamActive(stream, inPastMs); + return mpAudioPolicy->is_stream_active(mpAudioPolicy, stream, inPastMs); } void AudioPolicyService::binderDied(const wp<IBinder>& who) { @@ -435,7 +446,7 @@ status_t AudioPolicyService::dumpInternals(int fd) char buffer[SIZE]; String8 result; - snprintf(buffer, SIZE, "PolicyManager Interface: %p\n", mpPolicyManager); + snprintf(buffer, SIZE, "PolicyManager Interface: %p\n", mpAudioPolicy); result.append(buffer); snprintf(buffer, SIZE, "Command Thread: %p\n", mAudioCommandThread.get()); result.append(buffer); @@ -465,8 +476,8 @@ status_t AudioPolicyService::dump(int fd, const Vector<String16>& args) mTonePlaybackThread->dump(fd); } - if (mpPolicyManager) { - mpPolicyManager->dump(fd); + if (mpAudioPolicy) { + mpAudioPolicy->dump(mpAudioPolicy, fd); } if (locked) mLock.unlock(); @@ -495,154 +506,6 @@ status_t AudioPolicyService::onTransact( } -// ---------------------------------------------------------------------------- -// AudioPolicyClientInterface implementation -// ---------------------------------------------------------------------------- - - -audio_io_handle_t AudioPolicyService::openOutput(uint32_t *pDevices, - uint32_t *pSamplingRate, - uint32_t *pFormat, - uint32_t *pChannels, - uint32_t *pLatencyMs, - AudioSystem::output_flags flags) -{ - sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == 0) { - LOGW("openOutput() could not get AudioFlinger"); - return 0; - } - - return af->openOutput(pDevices, - pSamplingRate, - (uint32_t *)pFormat, - pChannels, - pLatencyMs, - flags); -} - -audio_io_handle_t AudioPolicyService::openDuplicateOutput(audio_io_handle_t output1, - audio_io_handle_t output2) -{ - sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == 0) { - LOGW("openDuplicateOutput() could not get AudioFlinger"); - return 0; - } - return af->openDuplicateOutput(output1, output2); -} - -status_t AudioPolicyService::closeOutput(audio_io_handle_t output) -{ - sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == 0) return PERMISSION_DENIED; - - return af->closeOutput(output); -} - - -status_t AudioPolicyService::suspendOutput(audio_io_handle_t output) -{ - sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == 0) { - LOGW("suspendOutput() could not get AudioFlinger"); - return PERMISSION_DENIED; - } - - return af->suspendOutput(output); -} - -status_t AudioPolicyService::restoreOutput(audio_io_handle_t output) -{ - sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == 0) { - LOGW("restoreOutput() could not get AudioFlinger"); - return PERMISSION_DENIED; - } - - return af->restoreOutput(output); -} - -audio_io_handle_t AudioPolicyService::openInput(uint32_t *pDevices, - uint32_t *pSamplingRate, - uint32_t *pFormat, - uint32_t *pChannels, - uint32_t acoustics) -{ - sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == 0) { - LOGW("openInput() could not get AudioFlinger"); - return 0; - } - - return af->openInput(pDevices, pSamplingRate, (uint32_t *)pFormat, pChannels, acoustics); -} - -status_t AudioPolicyService::closeInput(audio_io_handle_t input) -{ - sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == 0) return PERMISSION_DENIED; - - return af->closeInput(input); -} - -status_t AudioPolicyService::setStreamVolume(AudioSystem::stream_type stream, - float volume, - audio_io_handle_t output, - int delayMs) -{ - return mAudioCommandThread->volumeCommand((int)stream, volume, (int)output, delayMs); -} - -status_t AudioPolicyService::setStreamOutput(AudioSystem::stream_type stream, - audio_io_handle_t output) -{ - sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == 0) return PERMISSION_DENIED; - - return af->setStreamOutput(stream, output); -} - -status_t AudioPolicyService::moveEffects(int session, audio_io_handle_t srcOutput, - audio_io_handle_t dstOutput) -{ - sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); - if (af == 0) return PERMISSION_DENIED; - - return af->moveEffects(session, (int)srcOutput, (int)dstOutput); -} - -void AudioPolicyService::setParameters(audio_io_handle_t ioHandle, - const String8& keyValuePairs, - int delayMs) -{ - mAudioCommandThread->parametersCommand((int)ioHandle, keyValuePairs, delayMs); -} - -String8 AudioPolicyService::getParameters(audio_io_handle_t ioHandle, const String8& keys) -{ - String8 result = AudioSystem::getParameters(ioHandle, keys); - return result; -} - -status_t AudioPolicyService::startTone(ToneGenerator::tone_type tone, - AudioSystem::stream_type stream) -{ - mTonePlaybackThread->startToneCommand(tone, stream); - return NO_ERROR; -} - -status_t AudioPolicyService::stopTone() -{ - mTonePlaybackThread->stopToneCommand(); - return NO_ERROR; -} - -status_t AudioPolicyService::setVoiceVolume(float volume, int delayMs) -{ - return mAudioCommandThread->voiceVolumeCommand(volume, delayMs); -} - // ----------- AudioPolicyService::AudioCommandThread implementation ---------- AudioPolicyService::AudioCommandThread::AudioCommandThread(String8 name) @@ -859,7 +722,7 @@ status_t AudioPolicyService::AudioCommandThread::volumeCommand(int stream, } status_t AudioPolicyService::AudioCommandThread::parametersCommand(int ioHandle, - const String8& keyValuePairs, + const char *keyValuePairs, int delayMs) { status_t status = NO_ERROR; @@ -868,7 +731,7 @@ status_t AudioPolicyService::AudioCommandThread::parametersCommand(int ioHandle, command->mCommand = SET_PARAMETERS; ParametersData *data = new ParametersData(); data->mIO = ioHandle; - data->mKeyValuePairs = keyValuePairs; + data->mKeyValuePairs = String8(keyValuePairs); command->mParam = data; if (delayMs == 0) { command->mWaitStatus = true; @@ -878,7 +741,7 @@ status_t AudioPolicyService::AudioCommandThread::parametersCommand(int ioHandle, Mutex::Autolock _l(mLock); insertCommand_l(command, delayMs); LOGV("AudioCommandThread() adding set parameter string %s, io %d ,delay %d", - keyValuePairs.string(), ioHandle, delayMs); + keyValuePairs, ioHandle, delayMs); mWaitWorkCV.signal(); if (command->mWaitStatus) { command->mCond.wait(mLock); @@ -1023,4 +886,226 @@ void AudioPolicyService::AudioCommandThread::AudioCommand::dump(char* buffer, si mParam); } +/******* helpers for the service_ops callbacks defined below *********/ +void AudioPolicyService::setParameters(audio_io_handle_t ioHandle, + const char *keyValuePairs, + int delayMs) +{ + mAudioCommandThread->parametersCommand((int)ioHandle, keyValuePairs, + delayMs); +} + +int AudioPolicyService::setStreamVolume(audio_stream_type_t stream, + float volume, + audio_io_handle_t output, + int delayMs) +{ + return (int)mAudioCommandThread->volumeCommand((int)stream, volume, + (int)output, delayMs); +} + +int AudioPolicyService::startTone(audio_policy_tone_t tone, + audio_stream_type_t stream) +{ + if (tone != AUDIO_POLICY_TONE_IN_CALL_NOTIFICATION) + LOGE("startTone: illegal tone requested (%d)", tone); + if (stream != AUDIO_STREAM_VOICE_CALL) + LOGE("startTone: illegal stream (%d) requested for tone %d", stream, + tone); + mTonePlaybackThread->startToneCommand(ToneGenerator::TONE_SUP_CALL_WAITING, + AUDIO_STREAM_VOICE_CALL); + return 0; +} + +int AudioPolicyService::stopTone() +{ + mTonePlaybackThread->stopToneCommand(); + return 0; +} + +int AudioPolicyService::setVoiceVolume(float volume, int delayMs) +{ + return (int)mAudioCommandThread->voiceVolumeCommand(volume, delayMs); +} + +/* implementation of the interface to the policy manager */ +extern "C" { + +static audio_io_handle_t aps_open_output(void *service, + uint32_t *pDevices, + uint32_t *pSamplingRate, + uint32_t *pFormat, + uint32_t *pChannels, + uint32_t *pLatencyMs, + audio_policy_output_flags_t flags) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == NULL) { + LOGW("%s: could not get AudioFlinger", __func__); + return 0; + } + + return af->openOutput(pDevices, pSamplingRate, pFormat, pChannels, + pLatencyMs, flags); +} + +static audio_io_handle_t aps_open_dup_output(void *service, + audio_io_handle_t output1, + audio_io_handle_t output2) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == NULL) { + LOGW("%s: could not get AudioFlinger", __func__); + return 0; + } + return af->openDuplicateOutput(output1, output2); +} + +static int aps_close_output(void *service, audio_io_handle_t output) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == NULL) + return PERMISSION_DENIED; + + return af->closeOutput(output); +} + +static int aps_suspend_output(void *service, audio_io_handle_t output) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == NULL) { + LOGW("%s: could not get AudioFlinger", __func__); + return PERMISSION_DENIED; + } + + return af->suspendOutput(output); +} + +static int aps_restore_output(void *service, audio_io_handle_t output) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == NULL) { + LOGW("%s: could not get AudioFlinger", __func__); + return PERMISSION_DENIED; + } + + return af->restoreOutput(output); +} + +static audio_io_handle_t aps_open_input(void *service, + uint32_t *pDevices, + uint32_t *pSamplingRate, + uint32_t *pFormat, + uint32_t *pChannels, + uint32_t acoustics) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == NULL) { + LOGW("%s: could not get AudioFlinger", __func__); + return 0; + } + + return af->openInput(pDevices, pSamplingRate, pFormat, pChannels, + acoustics); +} + +static int aps_close_input(void *service, audio_io_handle_t input) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == NULL) + return PERMISSION_DENIED; + + return af->closeInput(input); +} + +static int aps_set_stream_output(void *service, audio_stream_type_t stream, + audio_io_handle_t output) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == NULL) + return PERMISSION_DENIED; + + return af->setStreamOutput(stream, output); +} + +static int aps_move_effects(void *service, int session, + audio_io_handle_t src_output, + audio_io_handle_t dst_output) +{ + sp<IAudioFlinger> af = AudioSystem::get_audio_flinger(); + if (af == NULL) + return PERMISSION_DENIED; + + return af->moveEffects(session, (int)src_output, (int)dst_output); +} + +static char * aps_get_parameters(void *service, audio_io_handle_t io_handle, + const char *keys) +{ + String8 result = AudioSystem::getParameters(io_handle, String8(keys)); + return strdup(result.string()); +} + +static void aps_set_parameters(void *service, audio_io_handle_t io_handle, + const char *kv_pairs, int delay_ms) +{ + AudioPolicyService *audioPolicyService = (AudioPolicyService *)service; + + audioPolicyService->setParameters(io_handle, kv_pairs, delay_ms); +} + +static int aps_set_stream_volume(void *service, audio_stream_type_t stream, + float volume, audio_io_handle_t output, + int delay_ms) +{ + AudioPolicyService *audioPolicyService = (AudioPolicyService *)service; + + return audioPolicyService->setStreamVolume(stream, volume, output, + delay_ms); +} + +static int aps_start_tone(void *service, audio_policy_tone_t tone, + audio_stream_type_t stream) +{ + AudioPolicyService *audioPolicyService = (AudioPolicyService *)service; + + return audioPolicyService->startTone(tone, stream); +} + +static int aps_stop_tone(void *service) +{ + AudioPolicyService *audioPolicyService = (AudioPolicyService *)service; + + return audioPolicyService->stopTone(); +} + +static int aps_set_voice_volume(void *service, float volume, int delay_ms) +{ + AudioPolicyService *audioPolicyService = (AudioPolicyService *)service; + + return audioPolicyService->setVoiceVolume(volume, delay_ms); +} + +}; // extern "C" + +namespace { + struct audio_policy_service_ops aps_ops = { + open_output : aps_open_output, + open_duplicate_output : aps_open_dup_output, + close_output : aps_close_output, + suspend_output : aps_suspend_output, + restore_output : aps_restore_output, + open_input : aps_open_input, + close_input : aps_close_input, + set_stream_volume : aps_set_stream_volume, + set_stream_output : aps_set_stream_output, + set_parameters : aps_set_parameters, + get_parameters : aps_get_parameters, + start_tone : aps_start_tone, + stop_tone : aps_stop_tone, + set_voice_volume : aps_set_voice_volume, + move_effects : aps_move_effects, + }; +}; // namespace <unnamed> + }; // namespace android diff --git a/services/audioflinger/AudioPolicyService.h b/services/audioflinger/AudioPolicyService.h index faad893..01e592b 100644 --- a/services/audioflinger/AudioPolicyService.h +++ b/services/audioflinger/AudioPolicyService.h @@ -18,11 +18,14 @@ #define ANDROID_AUDIOPOLICYSERVICE_H #include <media/IAudioPolicyService.h> -#include <hardware_legacy/AudioPolicyInterface.h> #include <media/ToneGenerator.h> #include <utils/Vector.h> #include <binder/BinderService.h> +#include <hardware/audio.h> +#include <hardware/audio_policy.h> +#include <hardware/audio_policy_hal.h> + namespace android { class String8; @@ -32,7 +35,7 @@ class String8; class AudioPolicyService : public BinderService<AudioPolicyService>, public BnAudioPolicyService, - public AudioPolicyClientInterface, +// public AudioPolicyClientInterface, public IBinder::DeathRecipient { friend class BinderService<AudioPolicyService>; @@ -47,46 +50,46 @@ public: // BnAudioPolicyService (see AudioPolicyInterface for method descriptions) // - virtual status_t setDeviceConnectionState(AudioSystem::audio_devices device, - AudioSystem::device_connection_state state, + virtual status_t setDeviceConnectionState(audio_devices_t device, + audio_policy_dev_state_t state, const char *device_address); - virtual AudioSystem::device_connection_state getDeviceConnectionState( - AudioSystem::audio_devices device, + 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 setForceUse(AudioSystem::force_use usage, AudioSystem::forced_config config); - virtual AudioSystem::forced_config getForceUse(AudioSystem::force_use usage); - virtual audio_io_handle_t getOutput(AudioSystem::stream_type stream, + 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 = AudioSystem::FORMAT_DEFAULT, + uint32_t format = AUDIO_FORMAT_DEFAULT, uint32_t channels = 0, - AudioSystem::output_flags flags = - AudioSystem::OUTPUT_FLAG_INDIRECT); + audio_policy_output_flags_t flags = + AUDIO_POLICY_OUTPUT_FLAG_INDIRECT); virtual status_t startOutput(audio_io_handle_t output, - AudioSystem::stream_type stream, + audio_stream_type_t stream, int session = 0); virtual status_t stopOutput(audio_io_handle_t output, - AudioSystem::stream_type stream, + audio_stream_type_t stream, int session = 0); virtual void releaseOutput(audio_io_handle_t output); virtual audio_io_handle_t getInput(int inputSource, uint32_t samplingRate = 0, - uint32_t format = AudioSystem::FORMAT_DEFAULT, + uint32_t format = AUDIO_FORMAT_DEFAULT, uint32_t channels = 0, - AudioSystem::audio_in_acoustics acoustics = - (AudioSystem::audio_in_acoustics)0); + audio_in_acoustics_t acoustics = + (audio_in_acoustics_t)0); virtual status_t startInput(audio_io_handle_t input); virtual status_t stopInput(audio_io_handle_t input); virtual void releaseInput(audio_io_handle_t input); - virtual status_t initStreamVolume(AudioSystem::stream_type stream, + virtual status_t initStreamVolume(audio_stream_type_t stream, int indexMin, int indexMax); - virtual status_t setStreamVolumeIndex(AudioSystem::stream_type stream, int index); - virtual status_t getStreamVolumeIndex(AudioSystem::stream_type stream, int *index); + virtual status_t setStreamVolumeIndex(audio_stream_type_t stream, int index); + virtual status_t getStreamVolumeIndex(audio_stream_type_t stream, int *index); - virtual uint32_t getStrategyForStream(AudioSystem::stream_type stream); - virtual uint32_t getDevicesForStream(AudioSystem::stream_type stream); + virtual uint32_t getStrategyForStream(audio_stream_type_t stream); + virtual uint32_t getDevicesForStream(audio_stream_type_t stream); virtual audio_io_handle_t getOutputForEffect(effect_descriptor_t *desc); virtual status_t registerEffect(effect_descriptor_t *desc, @@ -107,40 +110,21 @@ public: virtual void binderDied(const wp<IBinder>& who); // - // AudioPolicyClientInterface + // Helpers for the struct audio_policy_service_ops implementation. + // This is used by the audio policy manager for certain operations that + // are implemented by the policy service. // - virtual audio_io_handle_t openOutput(uint32_t *pDevices, - uint32_t *pSamplingRate, - uint32_t *pFormat, - uint32_t *pChannels, - uint32_t *pLatencyMs, - AudioSystem::output_flags flags); - virtual audio_io_handle_t openDuplicateOutput(audio_io_handle_t output1, - audio_io_handle_t output2); - virtual status_t closeOutput(audio_io_handle_t output); - virtual status_t suspendOutput(audio_io_handle_t output); - virtual status_t restoreOutput(audio_io_handle_t output); - virtual audio_io_handle_t openInput(uint32_t *pDevices, - uint32_t *pSamplingRate, - uint32_t *pFormat, - uint32_t *pChannels, - uint32_t acoustics); - virtual status_t closeInput(audio_io_handle_t input); - virtual status_t setStreamVolume(AudioSystem::stream_type stream, + virtual void setParameters(audio_io_handle_t ioHandle, + const char *keyValuePairs, + int delayMs); + + virtual status_t setStreamVolume(audio_stream_type_t stream, float volume, audio_io_handle_t output, int delayMs = 0); - virtual status_t setStreamOutput(AudioSystem::stream_type stream, audio_io_handle_t output); - virtual void setParameters(audio_io_handle_t ioHandle, - const String8& keyValuePairs, - int delayMs = 0); - virtual String8 getParameters(audio_io_handle_t ioHandle, const String8& keys); - virtual status_t startTone(ToneGenerator::tone_type tone, AudioSystem::stream_type stream); + virtual status_t startTone(audio_policy_tone_t tone, audio_stream_type_t stream); virtual status_t stopTone(); virtual status_t setVoiceVolume(float volume, int delayMs = 0); - virtual status_t moveEffects(int session, - audio_io_handle_t srcOutput, - audio_io_handle_t dstOutput); private: AudioPolicyService(); @@ -180,7 +164,7 @@ private: void startToneCommand(int type = 0, int stream = 0); void stopToneCommand(); status_t volumeCommand(int stream, float volume, int output, int delayMs = 0); - status_t parametersCommand(int ioHandle, const String8& keyValuePairs, 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); @@ -240,9 +224,11 @@ private: mutable Mutex mLock; // prevents concurrent access to AudioPolicy manager functions changing // device connection state or routing - AudioPolicyInterface* mpPolicyManager; // the platform specific policy manager sp <AudioCommandThread> mAudioCommandThread; // audio commands thread sp <AudioCommandThread> mTonePlaybackThread; // tone playback thread + + struct audio_policy_device *mpAudioPolicyDev; + struct audio_policy *mpAudioPolicy; }; }; // namespace android diff --git a/services/camera/libcameraservice/Android.mk b/services/camera/libcameraservice/Android.mk index b52fc69..e35435e 100644 --- a/services/camera/libcameraservice/Android.mk +++ b/services/camera/libcameraservice/Android.mk @@ -1,38 +1,5 @@ LOCAL_PATH:= $(call my-dir) -# Set USE_CAMERA_STUB if you don't want to use the hardware camera. - -# force these builds to use camera stub only -ifneq ($(filter sooner generic sim,$(TARGET_DEVICE)),) - USE_CAMERA_STUB:=true -endif - -ifeq ($(USE_CAMERA_STUB),) - USE_CAMERA_STUB:=false -endif - -ifeq ($(USE_CAMERA_STUB),true) -# -# libcamerastub -# - -include $(CLEAR_VARS) - -LOCAL_SRC_FILES:= \ - CameraHardwareStub.cpp \ - FakeCamera.cpp - -LOCAL_MODULE:= libcamerastub - -ifeq ($(TARGET_SIMULATOR),true) -LOCAL_CFLAGS += -DSINGLE_PROCESS -endif - -LOCAL_SHARED_LIBRARIES:= libui - -include $(BUILD_STATIC_LIBRARY) -endif # USE_CAMERA_STUB - # # libcameraservice # @@ -49,19 +16,9 @@ LOCAL_SHARED_LIBRARIES:= \ libcutils \ libmedia \ libcamera_client \ - libsurfaceflinger_client \ - libgui + libgui \ + libhardware LOCAL_MODULE:= libcameraservice -ifeq ($(TARGET_SIMULATOR),true) -LOCAL_CFLAGS += -DSINGLE_PROCESS -endif - -ifeq ($(USE_CAMERA_STUB), true) -LOCAL_STATIC_LIBRARIES += libcamerastub -else -LOCAL_SHARED_LIBRARIES += libcamera -endif - include $(BUILD_SHARED_LIBRARY) diff --git a/services/camera/libcameraservice/CameraHardwareInterface.h b/services/camera/libcameraservice/CameraHardwareInterface.h new file mode 100644 index 0000000..f9fa30e --- /dev/null +++ b/services/camera/libcameraservice/CameraHardwareInterface.h @@ -0,0 +1,619 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HARDWARE_CAMERA_HARDWARE_INTERFACE_H +#define ANDROID_HARDWARE_CAMERA_HARDWARE_INTERFACE_H + +#include <binder/IMemory.h> +#include <binder/MemoryBase.h> +#include <binder/MemoryHeapBase.h> +#include <utils/RefBase.h> +#include <surfaceflinger/ISurface.h> +#include <ui/android_native_buffer.h> +#include <ui/GraphicBuffer.h> +#include <camera/Camera.h> +#include <camera/CameraParameters.h> +#include <system/window.h> +#include <hardware/camera.h> + +namespace android { + +typedef void (*notify_callback)(int32_t msgType, + int32_t ext1, + int32_t ext2, + void* user); + +typedef void (*data_callback)(int32_t msgType, + const sp<IMemory> &dataPtr, + void* user); + +typedef void (*data_callback_timestamp)(nsecs_t timestamp, + int32_t msgType, + const sp<IMemory> &dataPtr, + void *user); + +/** + * CameraHardwareInterface.h defines the interface to the + * camera hardware abstraction layer, used for setting and getting + * parameters, live previewing, and taking pictures. + * + * It is a referenced counted interface with RefBase as its base class. + * CameraService calls openCameraHardware() to retrieve a strong pointer to the + * instance of this interface and may be called multiple times. The + * following steps describe a typical sequence: + * + * -# After CameraService calls openCameraHardware(), getParameters() and + * setParameters() are used to initialize the camera instance. + * CameraService calls getPreviewHeap() to establish access to the + * preview heap so it can be registered with SurfaceFlinger for + * efficient display updating while in preview mode. + * -# startPreview() is called. The camera instance then periodically + * sends the message CAMERA_MSG_PREVIEW_FRAME (if enabled) each time + * a new preview frame is available. If data callback code needs to use + * this memory after returning, it must copy the data. + * + * Prior to taking a picture, CameraService calls autofocus(). When auto + * focusing has completed, the camera instance sends a CAMERA_MSG_FOCUS notification, + * which informs the application whether focusing was successful. The camera instance + * only sends this message once and it is up to the application to call autoFocus() + * again if refocusing is desired. + * + * CameraService calls takePicture() to request the camera instance take a + * picture. At this point, if a shutter, postview, raw, and/or compressed callback + * is desired, the corresponding message must be enabled. As with CAMERA_MSG_PREVIEW_FRAME, + * any memory provided in a data callback must be copied if it's needed after returning. + */ + +class CameraHardwareInterface : public virtual RefBase { +public: + CameraHardwareInterface(hw_module_t *module, const char *name) + { + mDevice = 0; + mName = name; + LOGI("Opening camera %s, this %p", name, this); + int rc = module->methods->open(module, name, + (hw_device_t **)&mDevice); + if (rc != OK) + LOGE("Could not open camera %s: %d", name, rc); + initHalPreviewWindow(); + } + + ~CameraHardwareInterface() + { + LOGI("Destroying camera %s", mName.string()); + int rc = mDevice->common.close(&mDevice->common); + if (rc != OK) + LOGE("Could not close camera %s: %d", mName.string(), rc); + } + + /** Set the ANativeWindow to which preview frames are sent */ + status_t setPreviewWindow(const sp<ANativeWindow>& buf) + { + LOGV("%s(%s) buf %p", __FUNCTION__, mName.string(), buf.get()); + + if (mDevice->ops->set_preview_window) { + mPreviewWindow = buf; + mHalPreviewWindow.user = this; + LOGV("%s &mHalPreviewWindow %p mHalPreviewWindow.user %p", __FUNCTION__, + &mHalPreviewWindow, mHalPreviewWindow.user); + return mDevice->ops->set_preview_window(mDevice, + buf.get() ? &mHalPreviewWindow.nw : 0); + } + return INVALID_OPERATION; + } + + /** Set the notification and data callbacks */ + void setCallbacks(notify_callback notify_cb, + data_callback data_cb, + data_callback_timestamp data_cb_timestamp, + void* user) + { + mNotifyCb = notify_cb; + mDataCb = data_cb; + mDataCbTimestamp = data_cb_timestamp; + mCbUser = user; + + LOGV("%s(%s)", __FUNCTION__, mName.string()); + + if (mDevice->ops->set_callbacks) { + mDevice->ops->set_callbacks(mDevice, + __notify_cb, + __data_cb, + __data_cb_timestamp, + __get_memory, + this); + } + } + + /** + * The following three functions all take a msgtype, + * which is a bitmask of the messages defined in + * include/ui/Camera.h + */ + + /** + * Enable a message, or set of messages. + */ + void enableMsgType(int32_t msgType) + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->enable_msg_type) + mDevice->ops->enable_msg_type(mDevice, msgType); + } + + /** + * Disable a message, or a set of messages. + * + * Once received a call to disableMsgType(CAMERA_MSG_VIDEO_FRAME), camera hal + * should not rely on its client to call releaseRecordingFrame() to release + * video recording frames sent out by the cameral hal before and after the + * disableMsgType(CAMERA_MSG_VIDEO_FRAME) call. Camera hal clients must not + * modify/access any video recording frame after calling + * disableMsgType(CAMERA_MSG_VIDEO_FRAME). + */ + void disableMsgType(int32_t msgType) + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->disable_msg_type) + mDevice->ops->disable_msg_type(mDevice, msgType); + } + + /** + * Query whether a message, or a set of messages, is enabled. + * Note that this is operates as an AND, if any of the messages + * queried are off, this will return false. + */ + int msgTypeEnabled(int32_t msgType) + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->msg_type_enabled) + return mDevice->ops->msg_type_enabled(mDevice, msgType); + return false; + } + + /** + * Start preview mode. + */ + status_t startPreview() + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->start_preview) + return mDevice->ops->start_preview(mDevice); + return INVALID_OPERATION; + } + + /** + * Stop a previously started preview. + */ + void stopPreview() + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->stop_preview) + mDevice->ops->stop_preview(mDevice); + } + + /** + * Returns true if preview is enabled. + */ + int previewEnabled() + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->preview_enabled) + return mDevice->ops->preview_enabled(mDevice); + return false; + } + + /** + * Request the camera hal to store meta data or real YUV data in + * the video buffers send out via CAMERA_MSG_VIDEO_FRRAME for a + * recording session. If it is not called, the default camera + * hal behavior is to store real YUV data in the video buffers. + * + * This method should be called before startRecording() in order + * to be effective. + * + * If meta data is stored in the video buffers, it is up to the + * receiver of the video buffers to interpret the contents and + * to find the actual frame data with the help of the meta data + * in the buffer. How this is done is outside of the scope of + * this method. + * + * Some camera hal may not support storing meta data in the video + * buffers, but all camera hal should support storing real YUV data + * in the video buffers. If the camera hal does not support storing + * the meta data in the video buffers when it is requested to do + * do, INVALID_OPERATION must be returned. It is very useful for + * the camera hal to pass meta data rather than the actual frame + * data directly to the video encoder, since the amount of the + * uncompressed frame data can be very large if video size is large. + * + * @param enable if true to instruct the camera hal to store + * meta data in the video buffers; false to instruct + * the camera hal to store real YUV data in the video + * buffers. + * + * @return OK on success. + */ + + status_t storeMetaDataInBuffers(int enable) + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->store_meta_data_in_buffers) + return mDevice->ops->store_meta_data_in_buffers(mDevice, enable); + return enable ? INVALID_OPERATION: OK; + } + + /** + * Start record mode. When a record image is available a CAMERA_MSG_VIDEO_FRAME + * message is sent with the corresponding frame. Every record frame must be released + * by a cameral hal client via releaseRecordingFrame() before the client calls + * disableMsgType(CAMERA_MSG_VIDEO_FRAME). After the client calls + * disableMsgType(CAMERA_MSG_VIDEO_FRAME), it is camera hal's responsibility + * to manage the life-cycle of the video recording frames, and the client must + * not modify/access any video recording frames. + */ + status_t startRecording() + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->start_recording) + return mDevice->ops->start_recording(mDevice); + return INVALID_OPERATION; + } + + /** + * Stop a previously started recording. + */ + void stopRecording() + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->stop_recording) + mDevice->ops->stop_recording(mDevice); + } + + /** + * Returns true if recording is enabled. + */ + int recordingEnabled() + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->recording_enabled) + return mDevice->ops->recording_enabled(mDevice); + return false; + } + + /** + * Release a record frame previously returned by CAMERA_MSG_VIDEO_FRAME. + * + * It is camera hal client's responsibility to release video recording + * frames sent out by the camera hal before the camera hal receives + * a call to disableMsgType(CAMERA_MSG_VIDEO_FRAME). After it receives + * the call to disableMsgType(CAMERA_MSG_VIDEO_FRAME), it is camera hal's + * responsibility of managing the life-cycle of the video recording + * frames. + */ + void releaseRecordingFrame(const sp<IMemory>& mem) + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->release_recording_frame) { + ssize_t offset; + size_t size; + sp<IMemoryHeap> heap = mem->getMemory(&offset, &size); + void *data = ((uint8_t *)heap->base()) + offset; + return mDevice->ops->release_recording_frame(mDevice, data); + } + } + + /** + * Start auto focus, the notification callback routine is called + * with CAMERA_MSG_FOCUS once when focusing is complete. autoFocus() + * will be called again if another auto focus is needed. + */ + status_t autoFocus() + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->auto_focus) + return mDevice->ops->auto_focus(mDevice); + return INVALID_OPERATION; + } + + /** + * Cancels auto-focus function. If the auto-focus is still in progress, + * this function will cancel it. Whether the auto-focus is in progress + * or not, this function will return the focus position to the default. + * If the camera does not support auto-focus, this is a no-op. + */ + status_t cancelAutoFocus() + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->cancel_auto_focus) + return mDevice->ops->cancel_auto_focus(mDevice); + return INVALID_OPERATION; + } + + /** + * Take a picture. + */ + status_t takePicture() + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->take_picture) + return mDevice->ops->take_picture(mDevice); + return INVALID_OPERATION; + } + + /** + * Cancel a picture that was started with takePicture. Calling this + * method when no picture is being taken is a no-op. + */ + status_t cancelPicture() + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->cancel_picture) + return mDevice->ops->cancel_picture(mDevice); + return INVALID_OPERATION; + } + + /** + * Set the camera parameters. This returns BAD_VALUE if any parameter is + * invalid or not supported. */ + status_t setParameters(const CameraParameters ¶ms) + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->set_parameters) + return mDevice->ops->set_parameters(mDevice, + params.flatten().string()); + return INVALID_OPERATION; + } + + /** Return the camera parameters. */ + CameraParameters getParameters() const + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + CameraParameters parms; + if (mDevice->ops->get_parameters) { + char *temp = mDevice->ops->get_parameters(mDevice); + String8 str_parms(temp); + free(temp); + parms.unflatten(str_parms); + } + return parms; + } + + /** + * Send command to camera driver. + */ + status_t sendCommand(int32_t cmd, int32_t arg1, int32_t arg2) + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->send_command) + return mDevice->ops->send_command(mDevice, cmd, arg1, arg2); + return INVALID_OPERATION; + } + + /** + * Release the hardware resources owned by this object. Note that this is + * *not* done in the destructor. + */ + void release() { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->release) + mDevice->ops->release(mDevice); + } + + /** + * Dump state of the camera hardware + */ + status_t dump(int fd, const Vector<String16>& args) const + { + LOGV("%s(%s)", __FUNCTION__, mName.string()); + if (mDevice->ops->dump) + return mDevice->ops->dump(mDevice, fd); + return OK; // It's fine if the HAL doesn't implement dump() + } + +private: + camera_device_t *mDevice; + String8 mName; + + static void __notify_cb(int32_t msg_type, int32_t ext1, + int32_t ext2, void *user) + { + LOGV("%s", __FUNCTION__); + CameraHardwareInterface *__this = + static_cast<CameraHardwareInterface *>(user); + __this->mNotifyCb(msg_type, ext1, ext2, __this->mCbUser); + } + + static void __data_cb(int32_t msg_type, + const camera_memory_t *data, + void *user) + { + LOGV("%s", __FUNCTION__); + CameraHardwareInterface *__this = + static_cast<CameraHardwareInterface *>(user); + sp<CameraHeapMemory> mem(static_cast<CameraHeapMemory *>(data->handle)); + __this->mDataCb(msg_type, mem, __this->mCbUser); + } + + static void __data_cb_timestamp(nsecs_t timestamp, int32_t msg_type, + const camera_memory_t *data, + void *user) + { + LOGV("%s", __FUNCTION__); + CameraHardwareInterface *__this = + static_cast<CameraHardwareInterface *>(user); + // Start refcounting the heap object from here on. When the clients + // drop all references, it will be destroyed (as well as the enclosed + // MemoryHeapBase. + sp<CameraHeapMemory> mem(static_cast<CameraHeapMemory *>(data->handle)); + __this->mDataCbTimestamp(timestamp, msg_type, mem, __this->mCbUser); + } + + // This is a utility class that combines a MemoryHeapBase and a MemoryBase + // in one. Since we tend to use them in a one-to-one relationship, this is + // handy. + + class CameraHeapMemory : public MemoryBase { + public: + CameraHeapMemory(size_t size) : + MemoryBase(new MemoryHeapBase(size), 0, size) + { + handle.data = getHeap()->base(); + handle.size = size; + handle.handle = this; + } + + camera_memory_t handle; + }; + + static camera_memory_t* __get_memory(size_t size, + void *user __attribute__((unused))) + { + // We allocate the object here, but we do not assign it to a strong + // pointer yet. The HAL will pass it back to us via the data callback + // or the data-timestamp callback, and from there on we will wrap it + // within a strong pointer. + + CameraHeapMemory *mem = new CameraHeapMemory(size); + return &mem->handle; + } + + static ANativeWindow *__to_anw(void *user) + { + CameraHardwareInterface *__this = + reinterpret_cast<CameraHardwareInterface *>(user); + return __this->mPreviewWindow.get(); + } +#define anw(n) __to_anw(((struct camera_preview_window *)n)->user) + + static int __dequeue_buffer(struct preview_stream_ops* w, + buffer_handle_t** buffer) + { + int rc; + ANativeWindow *a = anw(w); + ANativeWindowBuffer* anb; + rc = a->dequeueBuffer(a, &anb); + if (!rc) { + rc = a->lockBuffer(a, anb); + if (!rc) + *buffer = &anb->handle; + else + a->cancelBuffer(a, anb); + } + return rc; + } + +#ifndef container_of +#define container_of(ptr, type, member) ({ \ + const typeof(((type *) 0)->member) *__mptr = (ptr); \ + (type *) ((char *) __mptr - (char *)(&((type *)0)->member)); }) +#endif + + static int __enqueue_buffer(struct preview_stream_ops* w, + buffer_handle_t* buffer) + { + ANativeWindow *a = anw(w); + return a->queueBuffer(a, + container_of(buffer, ANativeWindowBuffer, handle)); + } + + static int __cancel_buffer(struct preview_stream_ops* w, + buffer_handle_t* buffer) + { + ANativeWindow *a = anw(w); + return a->cancelBuffer(a, + container_of(buffer, ANativeWindowBuffer, handle)); + } + + static int __set_buffer_count(struct preview_stream_ops* w, int count) + { + ANativeWindow *a = anw(w); + return native_window_set_buffer_count(a, count); + } + + static int __set_buffers_geometry(struct preview_stream_ops* w, + int width, int height, int format) + { + ANativeWindow *a = anw(w); + return native_window_set_buffers_geometry(a, + width, height, format); + } + + static int __set_crop(struct preview_stream_ops *w, + int left, int top, int right, int bottom) + { + ANativeWindow *a = anw(w); + android_native_rect_t crop; + crop.left = left; + crop.top = top; + crop.right = right; + crop.bottom = bottom; + return native_window_set_crop(a, &crop); + } + + static int __set_usage(struct preview_stream_ops* w, int usage) + { + ANativeWindow *a = anw(w); + return native_window_set_usage(a, usage); + } + + static int __set_swap_interval(struct preview_stream_ops *w, int interval) + { + ANativeWindow *a = anw(w); + return a->setSwapInterval(a, interval); + } + + static int __get_min_undequeued_buffer_count( + const struct preview_stream_ops *w, + int *count) + { + ANativeWindow *a = anw(w); + return a->query(a, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, count); + } + + void initHalPreviewWindow() + { + mHalPreviewWindow.nw.cancel_buffer = __cancel_buffer; + mHalPreviewWindow.nw.dequeue_buffer = __dequeue_buffer; + mHalPreviewWindow.nw.enqueue_buffer = __enqueue_buffer; + 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_usage = __set_usage; + mHalPreviewWindow.nw.set_swap_interval = __set_swap_interval; + + mHalPreviewWindow.nw.get_min_undequeued_buffer_count = + __get_min_undequeued_buffer_count; + } + + sp<ANativeWindow> mPreviewWindow; + + struct camera_preview_window { + struct preview_stream_ops nw; + void *user; + }; + + struct camera_preview_window mHalPreviewWindow; + + notify_callback mNotifyCb; + data_callback mDataCb; + data_callback_timestamp mDataCbTimestamp; + void *mCbUser; +}; + +}; // namespace android + +#endif diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp index a09e16b..1e8c30b 100644 --- a/services/camera/libcameraservice/CameraService.cpp +++ b/services/camera/libcameraservice/CameraService.cpp @@ -16,6 +16,7 @@ */ #define LOG_TAG "CameraService" +//#define LOG_NDEBUG 0 #include <stdio.h> #include <sys/types.h> @@ -37,6 +38,7 @@ #include <utils/String16.h> #include "CameraService.h" +#include "CameraHardwareInterface.h" namespace android { @@ -69,22 +71,32 @@ static int getCallingUid() { static CameraService *gCameraService; CameraService::CameraService() -:mSoundRef(0) +:mSoundRef(0), mModule(0) { LOGI("CameraService started (pid=%d)", getpid()); + gCameraService = this; +} - mNumberOfCameras = HAL_getNumberOfCameras(); - if (mNumberOfCameras > MAX_CAMERAS) { - LOGE("Number of cameras(%d) > MAX_CAMERAS(%d).", - mNumberOfCameras, MAX_CAMERAS); - mNumberOfCameras = MAX_CAMERAS; - } - - for (int i = 0; i < mNumberOfCameras; i++) { - setCameraFree(i); +void CameraService::onFirstRef() +{ + BnCameraService::onFirstRef(); + + if (hw_get_module(CAMERA_HARDWARE_MODULE_ID, + (const hw_module_t **)&mModule) < 0) { + LOGE("Could not load camera HAL module"); + mNumberOfCameras = 0; + } + else { + mNumberOfCameras = mModule->get_number_of_cameras(); + if (mNumberOfCameras > MAX_CAMERAS) { + LOGE("Number of cameras(%d) > MAX_CAMERAS(%d).", + mNumberOfCameras, MAX_CAMERAS); + mNumberOfCameras = MAX_CAMERAS; + } + for (int i = 0; i < mNumberOfCameras; i++) { + setCameraFree(i); + } } - - gCameraService = this; } CameraService::~CameraService() { @@ -103,12 +115,19 @@ int32_t CameraService::getNumberOfCameras() { status_t CameraService::getCameraInfo(int cameraId, struct CameraInfo* cameraInfo) { + if (!mModule) { + return NO_INIT; + } + if (cameraId < 0 || cameraId >= mNumberOfCameras) { return BAD_VALUE; } - HAL_getCameraInfo(cameraId, cameraInfo); - return OK; + struct camera_info info; + status_t rc = mModule->get_camera_info(cameraId, &info); + cameraInfo->facing = info.facing; + cameraInfo->orientation = info.orientation; + return rc; } sp<ICamera> CameraService::connect( @@ -116,6 +135,11 @@ sp<ICamera> CameraService::connect( int callingPid = getCallingPid(); LOG1("CameraService::connect E (pid %d, id %d)", callingPid, cameraId); + if (!mModule) { + LOGE("Camera HAL module not loaded"); + return NULL; + } + sp<Client> client; if (cameraId < 0 || cameraId >= mNumberOfCameras) { LOGE("CameraService::connect X (pid %d) rejected (invalid cameraId %d).", @@ -146,15 +170,19 @@ sp<ICamera> CameraService::connect( return NULL; } - sp<CameraHardwareInterface> hardware = HAL_openCameraHardware(cameraId); - if (hardware == NULL) { - LOGE("Fail to open camera hardware (id=%d)", cameraId); + struct camera_info info; + if (mModule->get_camera_info(cameraId, &info) != OK) { + LOGE("Invalid camera id %d", cameraId); return NULL; } - CameraInfo info; - HAL_getCameraInfo(cameraId, &info); - client = new Client(this, cameraClient, hardware, cameraId, info.facing, - callingPid); + + char camera_device_name[10]; + snprintf(camera_device_name, sizeof(camera_device_name), "%d", cameraId); + + client = new Client(this, cameraClient, + new CameraHardwareInterface(&mModule->common, + camera_device_name), + cameraId, info.facing, callingPid); mClient[cameraId] = client; LOG1("CameraService::connect X"); return client; @@ -244,7 +272,7 @@ void CameraService::setCameraFree(int cameraId) { static MediaPlayer* newMediaPlayer(const char *file) { MediaPlayer* mp = new MediaPlayer(); if (mp->setDataSource(file, NULL) == NO_ERROR) { - mp->setAudioStreamType(AudioSystem::ENFORCED_AUDIBLE); + mp->setAudioStreamType(AUDIO_STREAM_ENFORCED_AUDIBLE); mp->prepare(); } else { LOGE("Failed to load CameraService sounds: %s", file); @@ -283,7 +311,7 @@ void CameraService::playSound(sound_kind kind) { // do not play the sound if stream volume is 0 // (typically because ringer mode is silent). int index; - AudioSystem::getStreamVolumeIndex(AudioSystem::ENFORCED_AUDIBLE, &index); + AudioSystem::getStreamVolumeIndex(AUDIO_STREAM_ENFORCED_AUDIBLE, &index); if (index != 0) { player->seekTo(0); player->start(); @@ -320,7 +348,7 @@ CameraService::Client::Client(const sp<CameraService>& cameraService, CAMERA_MSG_FOCUS); // Callback is disabled by default - mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP; + mPreviewCallbackFlag = CAMERA_FRAME_CALLBACK_FLAG_NOOP; mOrientation = getOrientation(0, mCameraFacing == CAMERA_FACING_FRONT); mPlayShutterSound = true; cameraService->setCameraBusy(cameraId); @@ -410,7 +438,7 @@ status_t CameraService::Client::connect(const sp<ICameraClient>& client) { return NO_ERROR; } - mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP; + mPreviewCallbackFlag = CAMERA_FRAME_CALLBACK_FLAG_NOOP; mClientPid = callingPid; mCameraClient = client; @@ -472,15 +500,15 @@ status_t CameraService::Client::setPreviewDisplay(const sp<Surface>& surface) { result = NO_ERROR; // return if no change in surface. - // asBinder() is safe on NULL (returns NULL) - if (getISurface(surface)->asBinder() == mSurface) { + sp<IBinder> binder(surface != 0 ? surface->asBinder() : 0); + if (binder == mSurface) { return result; } if (mSurface != 0) { LOG1("clearing old preview surface %p", mSurface.get()); } - mSurface = getISurface(surface)->asBinder(); + mSurface = binder; mPreviewWindow = surface; // If preview has been already started, register preview @@ -543,7 +571,7 @@ void CameraService::Client::setPreviewCallbackFlag(int callback_flag) { if (checkPidAndHardware() != NO_ERROR) return; mPreviewCallbackFlag = callback_flag; - if (mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ENABLE_MASK) { + if (mPreviewCallbackFlag & CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK) { enableMsgType(CAMERA_MSG_PREVIEW_FRAME); } else { disableMsgType(CAMERA_MSG_PREVIEW_FRAME); @@ -666,20 +694,6 @@ void CameraService::Client::releaseRecordingFrame(const sp<IMemory>& mem) { mHardware->releaseRecordingFrame(mem); } -int32_t CameraService::Client::getNumberOfVideoBuffers() const { - LOG1("getNumberOfVideoBuffers"); - Mutex::Autolock lock(mLock); - if (checkPidAndHardware() != NO_ERROR) return 0; - return mHardware->getNumberOfVideoBuffers(); -} - -sp<IMemory> CameraService::Client::getVideoBuffer(int32_t index) const { - LOG1("getVideoBuffer: %d", index); - Mutex::Autolock lock(mLock); - if (checkPidAndHardware() != NO_ERROR) return 0; - return mHardware->getVideoBuffer(index); -} - status_t CameraService::Client::storeMetaDataInBuffers(bool enabled) { LOG1("storeMetaDataInBuffers: %s", enabled? "true": "false"); @@ -938,7 +952,7 @@ void CameraService::Client::notifyCallback(int32_t msgType, int32_t ext1, switch (msgType) { case CAMERA_MSG_SHUTTER: // ext1 is the dimension of the yuv picture. - client->handleShutter((image_rect_type *)ext1); + client->handleShutter(); break; default: client->handleGenericNotify(msgType, ext1, ext2); @@ -997,9 +1011,7 @@ void CameraService::Client::dataCallbackTimestamp(nsecs_t timestamp, } // snapshot taken callback -// "size" is the width and height of yuv picture for registerBuffer. -// If it is NULL, use the picture size from parameters. -void CameraService::Client::handleShutter(image_rect_type *size) { +void CameraService::Client::handleShutter(void) { if (mPlayShutterSound) { mCameraService->playSound(SOUND_SHUTTER); } @@ -1025,7 +1037,7 @@ void CameraService::Client::handlePreviewData(const sp<IMemory>& mem) { int flags = mPreviewCallbackFlag; // is callback enabled? - if (!(flags & FRAME_CALLBACK_FLAG_ENABLE_MASK)) { + if (!(flags & CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK)) { // If the enable bit is off, the copy-out and one-shot bits are ignored LOG2("frame callback is disabled"); mLock.unlock(); @@ -1036,17 +1048,17 @@ void CameraService::Client::handlePreviewData(const sp<IMemory>& mem) { sp<ICameraClient> c = mCameraClient; // clear callback flags if no client or one-shot mode - if (c == 0 || (mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ONE_SHOT_MASK)) { + if (c == 0 || (mPreviewCallbackFlag & CAMERA_FRAME_CALLBACK_FLAG_ONE_SHOT_MASK)) { LOG2("Disable preview callback"); - mPreviewCallbackFlag &= ~(FRAME_CALLBACK_FLAG_ONE_SHOT_MASK | - FRAME_CALLBACK_FLAG_COPY_OUT_MASK | - FRAME_CALLBACK_FLAG_ENABLE_MASK); + mPreviewCallbackFlag &= ~(CAMERA_FRAME_CALLBACK_FLAG_ONE_SHOT_MASK | + CAMERA_FRAME_CALLBACK_FLAG_COPY_OUT_MASK | + CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK); disableMsgType(CAMERA_MSG_PREVIEW_FRAME); } if (c != 0) { // Is the received frame copied out or not? - if (flags & FRAME_CALLBACK_FLAG_COPY_OUT_MASK) { + if (flags & CAMERA_FRAME_CALLBACK_FLAG_COPY_OUT_MASK) { LOG2("frame is copied"); copyFrameAndPostCopiedFrame(c, heap, offset, size); } else { @@ -1257,12 +1269,4 @@ status_t CameraService::dump(int fd, const Vector<String16>& args) { return NO_ERROR; } -sp<ISurface> CameraService::getISurface(const sp<Surface>& surface) { - if (surface != 0) { - return surface->getISurface(); - } else { - return sp<ISurface>(0); - } -} - }; // namespace android diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h index 1c43b00..5e2d571 100644 --- a/services/camera/libcameraservice/CameraService.h +++ b/services/camera/libcameraservice/CameraService.h @@ -19,9 +19,8 @@ #define ANDROID_SERVERS_CAMERA_CAMERASERVICE_H #include <binder/BinderService.h> - #include <camera/ICameraService.h> -#include <camera/CameraHardwareInterface.h> +#include <hardware/camera.h> /* This needs to be increased if we can have more cameras */ #define MAX_CAMERAS 2 @@ -30,6 +29,7 @@ namespace android { class MemoryHeapBase; class MediaPlayer; +class CameraHardwareInterface; class CameraService : public BinderService<CameraService>, @@ -53,6 +53,7 @@ public: virtual status_t dump(int fd, const Vector<String16>& args); virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags); + virtual void onFirstRef(); enum sound_kind { SOUND_SHUTTER = 0, @@ -79,12 +80,6 @@ private: sp<MediaPlayer> mSoundPlayer[NUM_SOUNDS]; int mSoundRef; // reference count (release all MediaPlayer when 0) - // Used by Client objects to extract the ISurface from a Surface object. - // This is used because making Client a friend class of Surface would - // require including this header in Surface.h since Client is a nested - // class. - static sp<ISurface> getISurface(const sp<Surface>& surface); - class Client : public BnCamera { public: @@ -99,8 +94,6 @@ private: virtual status_t startPreview(); virtual void stopPreview(); virtual bool previewEnabled(); - virtual int32_t getNumberOfVideoBuffers() const; - virtual sp<IMemory> getVideoBuffer(int32_t index) const; virtual status_t storeMetaDataInBuffers(bool enabled); virtual status_t startRecording(); virtual void stopRecording(); @@ -152,7 +145,7 @@ private: // convert client from cookie static sp<Client> getClientFromCookie(void* user); // handlers for messages - void handleShutter(image_rect_type *size); + void handleShutter(void); void handlePreviewData(const sp<IMemory>& mem); void handlePostview(const sp<IMemory>& mem); void handleRawPicture(const sp<IMemory>& mem); @@ -207,6 +200,8 @@ private: // is found to be disabled. It returns true if mLock is grabbed. bool lockIfMessageWanted(int32_t msgType); }; + + camera_module_t *mModule; }; } // namespace android diff --git a/services/camera/tests/CameraServiceTest/Android.mk b/services/camera/tests/CameraServiceTest/Android.mk index cf4e42f..cf7302a 100644 --- a/services/camera/tests/CameraServiceTest/Android.mk +++ b/services/camera/tests/CameraServiceTest/Android.mk @@ -19,7 +19,7 @@ LOCAL_SHARED_LIBRARIES += \ libutils \ libui \ libcamera_client \ - libsurfaceflinger_client + libgui # Disable it because the ISurface interface may change, and before we have a # chance to fix this test, we don't want to break normal builds. diff --git a/services/camera/tests/CameraServiceTest/CameraServiceTest.cpp b/services/camera/tests/CameraServiceTest/CameraServiceTest.cpp index 8a228fd..f86ca47 100644 --- a/services/camera/tests/CameraServiceTest/CameraServiceTest.cpp +++ b/services/camera/tests/CameraServiceTest/CameraServiceTest.cpp @@ -830,10 +830,10 @@ public: ASSERT(c->previewEnabled() == true); sleep(2); c->stopPreview(); - if ((v & FRAME_CALLBACK_FLAG_ENABLE_MASK) == 0) { + if ((v & CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK) == 0) { cc->assertData(CAMERA_MSG_PREVIEW_FRAME, MCameraClient::EQ, 0); } else { - if ((v & FRAME_CALLBACK_FLAG_ONE_SHOT_MASK) == 0) { + if ((v & CAMERA_FRAME_CALLBACK_FLAG_ONE_SHOT_MASK) == 0) { cc->assertData(CAMERA_MSG_PREVIEW_FRAME, MCameraClient::GE, 10); } else { cc->assertData(CAMERA_MSG_PREVIEW_FRAME, MCameraClient::EQ, 1); @@ -849,7 +849,7 @@ public: ASSERT(c->recordingEnabled() == false); sp<MSurface> surface = new MSurface(); ASSERT(c->setPreviewDisplay(surface) == NO_ERROR); - c->setPreviewCallbackFlag(FRAME_CALLBACK_FLAG_ENABLE_MASK); + c->setPreviewCallbackFlag(CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK); cc->setReleaser(c.get()); c->startRecording(); ASSERT(c->recordingEnabled() == true); @@ -870,7 +870,7 @@ public: CameraParameters param(c->getParameters()); param.setPreviewSize(w, h); - c->setPreviewCallbackFlag(FRAME_CALLBACK_FLAG_ENABLE_MASK); + c->setPreviewCallbackFlag(CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK); c->setParameters(param.flatten()); c->startPreview(); diff --git a/services/input/Android.mk b/services/input/Android.mk index d7b61fc..836c081 100644 --- a/services/input/Android.mk +++ b/services/input/Android.mk @@ -22,15 +22,16 @@ LOCAL_SRC_FILES:= \ InputManager.cpp \ InputReader.cpp \ InputWindow.cpp \ - PointerController.cpp + PointerController.cpp \ + SpriteController.cpp LOCAL_SHARED_LIBRARIES := \ libcutils \ libutils \ libhardware \ libhardware_legacy \ - libsurfaceflinger_client \ libskia \ + libgui \ libui LOCAL_C_INCLUDES := \ diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp index 853dda4..ff4b11a 100644 --- a/services/input/EventHub.cpp +++ b/services/input/EventHub.cpp @@ -127,9 +127,11 @@ EventHub::EventHub(void) : mError(NO_INIT), mBuiltInKeyboardId(-1), mNextDeviceId(1), mOpeningDevices(0), mClosingDevices(0), mOpened(false), mNeedToSendFinishedDeviceScan(false), - mInputBufferIndex(0), mInputBufferCount(0), mInputFdIndex(0) { + mInputFdIndex(1) { acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID); + memset(mSwitches, 0, sizeof(mSwitches)); + mNumCpus = sysconf(_SC_NPROCESSORS_ONLN); } EventHub::~EventHub(void) { @@ -445,17 +447,10 @@ EventHub::Device* EventHub::getDeviceLocked(int32_t deviceId) const { return NULL; } -bool EventHub::getEvent(RawEvent* outEvent) { - outEvent->deviceId = 0; - outEvent->type = 0; - outEvent->scanCode = 0; - outEvent->keyCode = 0; - outEvent->flags = 0; - outEvent->value = 0; - outEvent->when = 0; - - // Note that we only allow one caller to getEvent(), so don't need +size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) { + // Note that we only allow one caller to getEvents(), so don't need // to do locking here... only when adding/removing devices. + LOG_ASSERT(bufferSize >= 1); if (!mOpened) { mError = openPlatformInput() ? NO_ERROR : UNKNOWN_ERROR; @@ -463,99 +458,62 @@ bool EventHub::getEvent(RawEvent* outEvent) { mNeedToSendFinishedDeviceScan = true; } + struct input_event readBuffer[bufferSize]; + + RawEvent* event = buffer; + size_t capacity = bufferSize; for (;;) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + // Report any devices that had last been added/removed. - if (mClosingDevices != NULL) { + while (mClosingDevices) { Device* device = mClosingDevices; LOGV("Reporting device closed: id=%d, name=%s\n", device->id, device->path.string()); mClosingDevices = device->next; - if (device->id == mBuiltInKeyboardId) { - outEvent->deviceId = 0; - } else { - outEvent->deviceId = device->id; - } - outEvent->type = DEVICE_REMOVED; - outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); + event->when = now; + event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; + event->type = DEVICE_REMOVED; + event += 1; delete device; mNeedToSendFinishedDeviceScan = true; - return true; + if (--capacity == 0) { + break; + } } - if (mOpeningDevices != NULL) { + while (mOpeningDevices != NULL) { Device* device = mOpeningDevices; LOGV("Reporting device opened: id=%d, name=%s\n", device->id, device->path.string()); mOpeningDevices = device->next; - if (device->id == mBuiltInKeyboardId) { - outEvent->deviceId = 0; - } else { - outEvent->deviceId = device->id; - } - outEvent->type = DEVICE_ADDED; - outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); + event->when = now; + event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; + event->type = DEVICE_ADDED; + event += 1; mNeedToSendFinishedDeviceScan = true; - return true; + if (--capacity == 0) { + break; + } } if (mNeedToSendFinishedDeviceScan) { mNeedToSendFinishedDeviceScan = false; - outEvent->type = FINISHED_DEVICE_SCAN; - outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); - return true; + event->when = now; + event->type = FINISHED_DEVICE_SCAN; + event += 1; + if (--capacity == 0) { + break; + } } // Grab the next input event. + // mInputFdIndex is initially 1 because index 0 is used for inotify. bool deviceWasRemoved = false; - for (;;) { - // Consume buffered input events, if any. - if (mInputBufferIndex < mInputBufferCount) { - const struct input_event& iev = mInputBufferData[mInputBufferIndex++]; - const Device* device = mDevices[mInputFdIndex]; - - LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, v=%d", device->path.string(), - (int) iev.time.tv_sec, (int) iev.time.tv_usec, iev.type, iev.code, iev.value); - if (device->id == mBuiltInKeyboardId) { - outEvent->deviceId = 0; - } else { - outEvent->deviceId = device->id; - } - outEvent->type = iev.type; - outEvent->scanCode = iev.code; - outEvent->flags = 0; - if (iev.type == EV_KEY) { - outEvent->keyCode = AKEYCODE_UNKNOWN; - if (device->keyMap.haveKeyLayout()) { - status_t err = device->keyMap.keyLayoutMap->mapKey(iev.code, - &outEvent->keyCode, &outEvent->flags); - LOGV("iev.code=%d keyCode=%d flags=0x%08x err=%d\n", - iev.code, outEvent->keyCode, outEvent->flags, err); - } - } else { - outEvent->keyCode = iev.code; - } - outEvent->value = iev.value; - - // Use an event timestamp in the same timebase as - // java.lang.System.nanoTime() and android.os.SystemClock.uptimeMillis() - // as expected by the rest of the system. - outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); - return true; - } - - // Finish reading all events from devices identified in previous poll(). - // This code assumes that mInputDeviceIndex is initially 0 and that the - // revents member of pollfd is initialized to 0 when the device is first added. - // Since mFds[0] is used for inotify, we process regular events starting at index 1. - mInputFdIndex += 1; - if (mInputFdIndex >= mFds.size()) { - break; - } - + while (mInputFdIndex < mFds.size()) { const struct pollfd& pfd = mFds[mInputFdIndex]; if (pfd.revents & POLLIN) { - int32_t readSize = read(pfd.fd, mInputBufferData, - sizeof(struct input_event) * INPUT_BUFFER_SIZE); + int32_t readSize = read(pfd.fd, readBuffer, sizeof(struct input_event) * capacity); if (readSize < 0) { if (errno == ENODEV) { deviceWasRemoved = true; @@ -566,11 +524,60 @@ bool EventHub::getEvent(RawEvent* outEvent) { } } else if ((readSize % sizeof(struct input_event)) != 0) { LOGE("could not get event (wrong size: %d)", readSize); + } else if (readSize == 0) { // eof + deviceWasRemoved = true; + break; } else { - mInputBufferCount = size_t(readSize) / sizeof(struct input_event); - mInputBufferIndex = 0; + const Device* device = mDevices[mInputFdIndex]; + int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; + + size_t count = size_t(readSize) / sizeof(struct input_event); + for (size_t i = 0; i < count; i++) { + const struct input_event& iev = readBuffer[i]; + LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, value=%d", + device->path.string(), + (int) iev.time.tv_sec, (int) iev.time.tv_usec, + iev.type, iev.code, iev.value); + +#ifdef HAVE_POSIX_CLOCKS + // Use the time specified in the event instead of the current time + // so that downstream code can get more accurate estimates of + // event dispatch latency from the time the event is enqueued onto + // the evdev client buffer. + // + // The event's timestamp fortuitously uses the same monotonic clock + // time base as the rest of Android. The kernel event device driver + // (drivers/input/evdev.c) obtains timestamps using ktime_get_ts(). + // The systemTime(SYSTEM_TIME_MONOTONIC) function we use everywhere + // calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a + // system call that also queries ktime_get_ts(). + event->when = nsecs_t(iev.time.tv_sec) * 1000000000LL + + nsecs_t(iev.time.tv_usec) * 1000LL; + LOGV("event time %lld, now %lld", event->when, now); +#else + event->when = now; +#endif + event->deviceId = deviceId; + event->type = iev.type; + event->scanCode = iev.code; + event->value = iev.value; + event->keyCode = AKEYCODE_UNKNOWN; + event->flags = 0; + if (iev.type == EV_KEY && device->keyMap.haveKeyLayout()) { + status_t err = device->keyMap.keyLayoutMap->mapKey(iev.code, + &event->keyCode, &event->flags); + LOGV("iev.code=%d keyCode=%d flags=0x%08x err=%d\n", + iev.code, event->keyCode, event->flags, err); + } + event += 1; + } + capacity -= count; + if (capacity == 0) { + break; + } } } + mInputFdIndex += 1; } // Handle the case where a device has been removed but INotify has not yet noticed. @@ -586,10 +593,16 @@ bool EventHub::getEvent(RawEvent* outEvent) { if(mFds[0].revents & POLLIN) { readNotify(mFds[0].fd); mFds.editItemAt(0).revents = 0; + mInputFdIndex = mFds.size(); continue; // report added or removed devices immediately } #endif + // Return now if we have collected any events, otherwise poll. + if (event != buffer) { + break; + } + // Poll for events. Mind the wake lock dance! // We hold a wake lock at all times except during poll(). This works due to some // subtle choreography. When a device driver has pending (unread) events, it acquires @@ -598,22 +611,46 @@ bool EventHub::getEvent(RawEvent* outEvent) { // when this happens, the EventHub holds onto its own user wake lock while the client // is processing events. Thus the system can only sleep if there are no events // pending or currently being processed. + // + // The timeout is advisory only. If the device is asleep, it will not wake just to + // service the timeout. release_wake_lock(WAKE_LOCK_ID); - int pollResult = poll(mFds.editArray(), mFds.size(), -1); + int pollResult = poll(mFds.editArray(), mFds.size(), timeoutMillis); acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID); - if (pollResult <= 0) { + if (pollResult == 0) { + break; // timed out + } + if (pollResult < 0) { + // Sleep after errors to avoid locking up the system. + // Hopefully the error is transient. if (errno != EINTR) { LOGW("poll failed (errno=%d)\n", errno); usleep(100000); } + } else { + // 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); + } } // Prepare to process all of the FDs we just polled. - mInputFdIndex = 0; + mInputFdIndex = 1; } + + // All done, return the number of events we read. + return event - buffer; } /* diff --git a/services/input/EventHub.h b/services/input/EventHub.h index 7053a94..4d26a95 100644 --- a/services/input/EventHub.h +++ b/services/input/EventHub.h @@ -157,6 +157,8 @@ public: // Sent when all added/removed devices from the most recent scan have been reported. // This event is always sent at least once. FINISHED_DEVICE_SCAN = 0x30000000, + + FIRST_SYNTHETIC_EVENT = DEVICE_ADDED, }; virtual uint32_t getDeviceClasses(int32_t deviceId) const = 0; @@ -181,13 +183,18 @@ public: virtual void addExcludedDevice(const char* deviceName) = 0; /* - * Wait for the next event to become available and return it. + * Wait for events to become available and returns them. * After returning, the EventHub holds onto a wake lock until the next call to getEvent. * This ensures that the device will not go to sleep while the event is being processed. * If the device needs to remain awake longer than that, then the caller is responsible * for taking care of it (say, by poking the power manager user activity timer). + * + * The timeout is advisory only. If the device is asleep, it will not wake just to + * service the timeout. + * + * Returns the number of events obtained, or 0 if the timeout expired. */ - virtual bool getEvent(RawEvent* outEvent) = 0; + virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) = 0; /* * Query current input state. @@ -244,7 +251,7 @@ public: virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const; - virtual bool getEvent(RawEvent* outEvent); + virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize); virtual bool hasLed(int32_t deviceId, int32_t led) const; virtual void setLedState(int32_t deviceId, int32_t led, bool on); @@ -331,11 +338,11 @@ private: // device ids that report particular switches. int32_t mSwitches[SW_MAX + 1]; - static const int INPUT_BUFFER_SIZE = 64; - struct input_event mInputBufferData[INPUT_BUFFER_SIZE]; - size_t mInputBufferIndex; - size_t mInputBufferCount; + // The index of the next file descriptor that needs to be read. size_t mInputFdIndex; + + // Set to the number of CPUs. + int32_t mNumCpus; }; }; // namespace android diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp index a94e0e9..5bca7ee 100644 --- a/services/input/InputDispatcher.cpp +++ b/services/input/InputDispatcher.cpp @@ -48,6 +48,9 @@ // Log debug messages about the app switch latency optimization. #define DEBUG_APP_SWITCH 0 +// Log debug messages about hover events. +#define DEBUG_HOVER 0 + #include "InputDispatcher.h" #include <cutils/log.h> @@ -76,6 +79,22 @@ const nsecs_t APP_SWITCH_TIMEOUT = 500 * 1000000LL; // 0.5sec // before considering it stale and dropping it. const nsecs_t STALE_EVENT_TIMEOUT = 10000 * 1000000LL; // 10sec +// Motion samples that are received within this amount of time are simply coalesced +// when batched instead of being appended. This is done because some drivers update +// the location of pointers one at a time instead of all at once. +// For example, when there are 10 fingers down, the input dispatcher may receive 10 +// samples in quick succession with only one finger's location changed in each sample. +// +// This value effectively imposes an upper bound on the touch sampling rate. +// Touch sensors typically have a 50Hz - 200Hz sampling rate, so we expect distinct +// samples to become available 5-20ms apart but individual finger reports can trickle +// in over a period of 2-4ms or so. +// +// Empirical testing shows that a 2ms coalescing interval (500Hz) is not enough, +// a 3ms coalescing interval (333Hz) works well most of the time and doesn't introduce +// significant quantization noise on current hardware. +const nsecs_t MOTION_SAMPLE_COALESCE_INTERVAL = 3 * 1000000LL; // 3ms, 333Hz + static inline nsecs_t now() { return systemTime(SYSTEM_TIME_MONOTONIC); @@ -115,7 +134,9 @@ static bool isValidMotionAction(int32_t action, size_t pointerCount) { case AMOTION_EVENT_ACTION_CANCEL: case AMOTION_EVENT_ACTION_MOVE: case AMOTION_EVENT_ACTION_OUTSIDE: + case AMOTION_EVENT_ACTION_HOVER_ENTER: case AMOTION_EVENT_ACTION_HOVER_MOVE: + case AMOTION_EVENT_ACTION_HOVER_EXIT: case AMOTION_EVENT_ACTION_SCROLL: return true; case AMOTION_EVENT_ACTION_POINTER_DOWN: @@ -189,11 +210,12 @@ InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& polic mPolicy(policy), mPendingEvent(NULL), mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX), mNextUnblockedEvent(NULL), - mDispatchEnabled(true), mDispatchFrozen(false), + mDispatchEnabled(true), mDispatchFrozen(false), mInputFilterEnabled(false), mFocusedWindow(NULL), mFocusedApplication(NULL), mCurrentInputTargetsValid(false), - mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) { + mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE), + mLastHoverWindow(NULL) { mLooper = new Looper(false); mInboundQueue.headSentinel.refCount = -1; @@ -246,15 +268,7 @@ void InputDispatcher::dispatchOnce() { // Wait for callback or timeout or wake. (make sure we round up, not down) nsecs_t currentTime = now(); - int32_t timeoutMillis; - if (nextWakeupTime > currentTime) { - uint64_t timeout = uint64_t(nextWakeupTime - currentTime); - timeout = (timeout + 999999LL) / 1000000LL; - timeoutMillis = timeout > INT_MAX ? -1 : int32_t(timeout); - } else { - timeoutMillis = 0; - } - + int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime); mLooper->pollOnce(timeoutMillis); } @@ -379,7 +393,7 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout, // Now we have an event to dispatch. // All events are eventually dequeued and processed this way, even if we intend to drop them. - assert(mPendingEvent != NULL); + LOG_ASSERT(mPendingEvent != NULL); bool done = false; DropReason dropReason = DROP_REASON_NOT_DROPPED; if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) { @@ -441,7 +455,7 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout, } default: - assert(false); + LOG_ASSERT(false); break; } @@ -568,23 +582,24 @@ void InputDispatcher::dropInboundEventLocked(EventEntry* entry, DropReason dropR reason = "inbound event was dropped because it is stale"; break; default: - assert(false); + LOG_ASSERT(false); return; } switch (entry->type) { - case EventEntry::TYPE_KEY: - synthesizeCancelationEventsForAllConnectionsLocked( - InputState::CANCEL_NON_POINTER_EVENTS, reason); + case EventEntry::TYPE_KEY: { + CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason); + synthesizeCancelationEventsForAllConnectionsLocked(options); break; + } case EventEntry::TYPE_MOTION: { MotionEntry* motionEntry = static_cast<MotionEntry*>(entry); if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) { - synthesizeCancelationEventsForAllConnectionsLocked( - InputState::CANCEL_POINTER_EVENTS, reason); + CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, reason); + synthesizeCancelationEventsForAllConnectionsLocked(options); } else { - synthesizeCancelationEventsForAllConnectionsLocked( - InputState::CANCEL_NON_POINTER_EVENTS, reason); + CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason); + synthesizeCancelationEventsForAllConnectionsLocked(options); } break; } @@ -734,7 +749,7 @@ bool InputDispatcher::dispatchKeyLocked( if (entry->repeatCount == 0 && entry->action == AKEY_EVENT_ACTION_DOWN && (entry->policyFlags & POLICY_FLAG_TRUSTED) - && !entry->isInjected()) { + && (!(entry->policyFlags & POLICY_FLAG_DISABLE_KEY_REPEAT))) { if (mKeyRepeatState.lastKeyEntry && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) { // We have seen two identical key downs in a row which indicates that the device @@ -853,10 +868,11 @@ bool InputDispatcher::dispatchMotionLocked( bool conflictingPointerActions = false; if (! mCurrentInputTargetsValid) { int32_t injectionResult; + const MotionSample* splitBatchAfterSample = NULL; if (isPointerEvent) { // Pointer event. (eg. touchscreen) injectionResult = findTouchedWindowTargetsLocked(currentTime, - entry, nextWakeupTime, &conflictingPointerActions); + entry, nextWakeupTime, &conflictingPointerActions, &splitBatchAfterSample); } else { // Non touch event. (eg. trackball) injectionResult = findFocusedWindowTargetsLocked(currentTime, @@ -873,12 +889,48 @@ bool InputDispatcher::dispatchMotionLocked( addMonitoringTargetsLocked(); commitTargetsLocked(); + + // Unbatch the event if necessary by splitting it into two parts after the + // motion sample indicated by splitBatchAfterSample. + if (splitBatchAfterSample && splitBatchAfterSample->next) { +#if DEBUG_BATCHING + uint32_t originalSampleCount = entry->countSamples(); +#endif + MotionSample* nextSample = splitBatchAfterSample->next; + MotionEntry* nextEntry = mAllocator.obtainMotionEntry(nextSample->eventTime, + entry->deviceId, entry->source, entry->policyFlags, + entry->action, entry->flags, entry->metaState, entry->edgeFlags, + entry->xPrecision, entry->yPrecision, entry->downTime, + entry->pointerCount, entry->pointerIds, nextSample->pointerCoords); + if (nextSample != entry->lastSample) { + nextEntry->firstSample.next = nextSample->next; + nextEntry->lastSample = entry->lastSample; + } + mAllocator.freeMotionSample(nextSample); + + entry->lastSample = const_cast<MotionSample*>(splitBatchAfterSample); + entry->lastSample->next = NULL; + + if (entry->injectionState) { + nextEntry->injectionState = entry->injectionState; + entry->injectionState->refCount += 1; + } + +#if DEBUG_BATCHING + LOGD("Split batch of %d samples into two parts, first part has %d samples, " + "second part has %d samples.", originalSampleCount, + entry->countSamples(), nextEntry->countSamples()); +#endif + + mInboundQueue.enqueueAtHead(nextEntry); + } } // Dispatch the motion. if (conflictingPointerActions) { - synthesizeCancelationEventsForAllConnectionsLocked( - InputState::CANCEL_POINTER_EVENTS, "Conflicting pointer actions."); + CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, + "conflicting pointer actions"); + synthesizeCancelationEventsForAllConnectionsLocked(options); } dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); return true; @@ -934,7 +986,7 @@ void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTi toString(resumeWithAppendedMotionSample)); #endif - assert(eventEntry->dispatchInProgress); // should already have been set to true + LOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true pokeUserActivityLocked(eventEntry); @@ -1044,9 +1096,9 @@ void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout if (connectionIndex >= 0) { sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); if (connection->status == Connection::STATUS_NORMAL) { - synthesizeCancelationEventsForConnectionLocked( - connection, InputState::CANCEL_ALL_EVENTS, + CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS, "application not responding"); + synthesizeCancelationEventsForConnectionLocked(connection, options); } } } @@ -1123,7 +1175,8 @@ int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime, // Success! Output targets. injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; - addWindowTargetLocked(mFocusedWindow, InputTarget::FLAG_FOREGROUND, BitSet32(0)); + addWindowTargetLocked(mFocusedWindow, + InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0)); // Done. Failed: @@ -1140,7 +1193,8 @@ Unresponsive: } int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, - const MotionEntry* entry, nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) { + const MotionEntry* entry, nsecs_t* nextWakeupTime, bool* outConflictingPointerActions, + const MotionSample** outSplitBatchAfterSample) { enum InjectionPermission { INJECTION_PERMISSION_UNKNOWN, INJECTION_PERMISSION_GRANTED, @@ -1183,14 +1237,19 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, // Update the touch state as needed based on the properties of the touch event. int32_t injectionResult = INPUT_EVENT_INJECTION_PENDING; InjectionPermission injectionPermission = INJECTION_PERMISSION_UNKNOWN; + const InputWindow* newHoverWindow = NULL; bool isSplit = mTouchState.split; bool wrongDevice = mTouchState.down && (mTouchState.deviceId != entry->deviceId || mTouchState.source != entry->source); - if (maskedAction == AMOTION_EVENT_ACTION_DOWN - || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE - || maskedAction == AMOTION_EVENT_ACTION_SCROLL) { + bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE + || maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER + || maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT); + bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN + || maskedAction == AMOTION_EVENT_ACTION_SCROLL + || isHoverAction); + if (newGesture) { bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN; if (wrongDevice && !down) { mTempTouchState.copyFrom(mTouchState); @@ -1206,26 +1265,25 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, mTempTouchState.copyFrom(mTouchState); } if (wrongDevice) { -#if DEBUG_INPUT_DISPATCHER_POLICY +#if DEBUG_FOCUS LOGD("Dropping event because a pointer for a different device is already down."); #endif injectionResult = INPUT_EVENT_INJECTION_FAILED; goto Failed; } - if (maskedAction == AMOTION_EVENT_ACTION_DOWN - || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) - || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE - || maskedAction == AMOTION_EVENT_ACTION_SCROLL) { + if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { /* Case 1: New splittable pointer going down, or need target for hover or scroll. */ + const MotionSample* sample = &entry->firstSample; int32_t pointerIndex = getMotionEventActionPointerIndex(action); - int32_t x = int32_t(entry->firstSample.pointerCoords[pointerIndex]. + int32_t x = int32_t(sample->pointerCoords[pointerIndex]. getAxisValue(AMOTION_EVENT_AXIS_X)); - int32_t y = int32_t(entry->firstSample.pointerCoords[pointerIndex]. + int32_t y = int32_t(sample->pointerCoords[pointerIndex]. getAxisValue(AMOTION_EVENT_AXIS_Y)); const InputWindow* newTouchedWindow = NULL; const InputWindow* topErrorWindow = NULL; + bool isTouchModal = false; // Traverse windows from front to back to find touched window and outside targets. size_t numWindows = mWindows.size(); @@ -1241,7 +1299,7 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, if (window->visible) { if (! (flags & InputWindow::FLAG_NOT_TOUCHABLE)) { - bool isTouchModal = (flags & (InputWindow::FLAG_NOT_FOCUSABLE + isTouchModal = (flags & (InputWindow::FLAG_NOT_FOCUSABLE | InputWindow::FLAG_NOT_TOUCH_MODAL)) == 0; if (isTouchModal || window->touchableRegionContainsPoint(x, y)) { if (! screenWasOff || flags & InputWindow::FLAG_TOUCHABLE_WHEN_WAKING) { @@ -1253,7 +1311,7 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, if (maskedAction == AMOTION_EVENT_ACTION_DOWN && (flags & InputWindow::FLAG_WATCH_OUTSIDE_TOUCH)) { - int32_t outsideTargetFlags = InputTarget::FLAG_OUTSIDE; + int32_t outsideTargetFlags = InputTarget::FLAG_DISPATCH_AS_OUTSIDE; if (isWindowObscuredAtPointLocked(window, x, y)) { outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; } @@ -1306,7 +1364,7 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, } // Set target flags. - int32_t targetFlags = InputTarget::FLAG_FOREGROUND; + int32_t targetFlags = InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS; if (isSplit) { targetFlags |= InputTarget::FLAG_SPLIT; } @@ -1314,6 +1372,28 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; } + // Update hover state. + if (isHoverAction) { + newHoverWindow = newTouchedWindow; + + // Ensure all subsequent motion samples are also within the touched window. + // Set *outSplitBatchAfterSample to the sample before the first one that is not + // within the touched window. + if (!isTouchModal) { + while (sample->next) { + if (!newHoverWindow->touchableRegionContainsPoint( + sample->next->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), + sample->next->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y))) { + *outSplitBatchAfterSample = sample; + break; + } + sample = sample->next; + } + } + } else if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) { + newHoverWindow = mLastHoverWindow; + } + // Update the temporary touch state. BitSet32 pointerIds; if (isSplit) { @@ -1326,7 +1406,7 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, // If the pointer is not currently down, then ignore the event. if (! mTempTouchState.down) { -#if DEBUG_INPUT_DISPATCHER_POLICY +#if DEBUG_FOCUS LOGD("Dropping event because the pointer is not down or we previously " "dropped the pointer down event."); #endif @@ -1335,6 +1415,29 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, } } + if (newHoverWindow != mLastHoverWindow) { + // Split the batch here so we send exactly one sample as part of ENTER or EXIT. + *outSplitBatchAfterSample = &entry->firstSample; + + // Let the previous window know that the hover sequence is over. + if (mLastHoverWindow) { +#if DEBUG_HOVER + LOGD("Sending hover exit event to window %s.", mLastHoverWindow->name.string()); +#endif + mTempTouchState.addOrUpdateWindow(mLastHoverWindow, + InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT, BitSet32(0)); + } + + // Let the new window know that the hover sequence is starting. + if (newHoverWindow) { +#if DEBUG_HOVER + LOGD("Sending hover enter event to window %s.", newHoverWindow->name.string()); +#endif + mTempTouchState.addOrUpdateWindow(newHoverWindow, + InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER, BitSet32(0)); + } + } + // Check permission to inject into all touched foreground windows and ensure there // is at least one touched foreground window. { @@ -1351,7 +1454,7 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, } } if (! haveForegroundWindow) { -#if DEBUG_INPUT_DISPATCHER_POLICY +#if DEBUG_FOCUS LOGD("Dropping event because there is no touched foreground window to receive it."); #endif injectionResult = INPUT_EVENT_INJECTION_FAILED; @@ -1368,7 +1471,7 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) { // If the touched window is paused then keep waiting. if (touchedWindow.window->paused) { -#if DEBUG_INPUT_DISPATCHER_POLICY +#if DEBUG_FOCUS LOGD("Waiting because touched window is paused."); #endif injectionResult = handleTargetsNotReadyLocked(currentTime, entry, @@ -1401,7 +1504,9 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, const InputWindow* window = & mWindows[i]; if (window->layoutParamsType == InputWindow::TYPE_WALLPAPER) { mTempTouchState.addOrUpdateWindow(window, - InputTarget::FLAG_WINDOW_IS_OBSCURED, BitSet32(0)); + InputTarget::FLAG_WINDOW_IS_OBSCURED + | InputTarget::FLAG_DISPATCH_AS_IS, + BitSet32(0)); } } } @@ -1416,8 +1521,9 @@ int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, touchedWindow.pointerIds); } - // Drop the outside touch window since we will not care about them in the next iteration. - mTempTouchState.removeOutsideTouchWindows(); + // Drop the outside or hover touch windows since we will not care about them + // in the next iteration. + mTempTouchState.filterNonAsIsTouchWindows(); Failed: // Check injection permission once and for all. @@ -1434,7 +1540,7 @@ Failed: if (!wrongDevice) { if (maskedAction == AMOTION_EVENT_ACTION_UP || maskedAction == AMOTION_EVENT_ACTION_CANCEL - || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) { + || isHoverAction) { // All pointers up or canceled. mTouchState.reset(); } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) { @@ -1471,6 +1577,9 @@ Failed: // Save changes to touch state as-is for all other actions. mTouchState.copyFrom(mTempTouchState); } + + // Update hover state. + mLastHoverWindow = newHoverWindow; } } else { #if DEBUG_FOCUS @@ -1512,9 +1621,10 @@ void InputDispatcher::addMonitoringTargetsLocked() { InputTarget& target = mCurrentInputTargets.editTop(); target.inputChannel = mMonitoringChannels[i]; - target.flags = 0; + target.flags = InputTarget::FLAG_DISPATCH_AS_IS; target.xOffset = 0; target.yOffset = 0; + target.pointerIds.clear(); target.scaleFactor = 1.0f; } } @@ -1628,7 +1738,7 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, // Make sure we are never called for streaming when splitting across multiple windows. bool isSplit = inputTarget->flags & InputTarget::FLAG_SPLIT; - assert(! (resumeWithAppendedMotionSample && isSplit)); + LOG_ASSERT(! (resumeWithAppendedMotionSample && isSplit)); // Skip this event if the connection status is not normal. // We don't want to enqueue additional outbound events if the connection is broken. @@ -1642,7 +1752,7 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, // Split a motion event if needed. if (isSplit) { - assert(eventEntry->type == EventEntry::TYPE_MOTION); + LOG_ASSERT(eventEntry->type == EventEntry::TYPE_MOTION); MotionEntry* originalMotionEntry = static_cast<MotionEntry*>(eventEntry); if (inputTarget->pointerIds.count() != originalMotionEntry->pointerCount) { @@ -1749,10 +1859,36 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, } } + // Enqueue dispatch entries for the requested modes. + enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, + resumeWithAppendedMotionSample, InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT); + enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, + resumeWithAppendedMotionSample, InputTarget::FLAG_DISPATCH_AS_OUTSIDE); + enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, + resumeWithAppendedMotionSample, InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER); + enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, + resumeWithAppendedMotionSample, InputTarget::FLAG_DISPATCH_AS_IS); + + // If the outbound queue was previously empty, start the dispatch cycle going. + if (wasEmpty && !connection->outboundQueue.isEmpty()) { + activateConnectionLocked(connection.get()); + startDispatchCycleLocked(currentTime, connection); + } +} + +void InputDispatcher::enqueueDispatchEntryLocked( + const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget, + bool resumeWithAppendedMotionSample, int32_t dispatchMode) { + int32_t inputTargetFlags = inputTarget->flags; + if (!(inputTargetFlags & dispatchMode)) { + return; + } + inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode; + // This is a new event. // Enqueue a new dispatch entry onto the outbound queue for this connection. DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry, // increments ref - inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset, + inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset, inputTarget->scaleFactor); if (dispatchEntry->hasForegroundTarget()) { incrementPendingForegroundDispatchesLocked(eventEntry); @@ -1774,12 +1910,6 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, // Enqueue the dispatch entry. connection->outboundQueue.enqueueAtTail(dispatchEntry); - - // If the outbound queue was previously empty, start the dispatch cycle going. - if (wasEmpty) { - activateConnectionLocked(connection.get()); - startDispatchCycleLocked(currentTime, connection); - } } void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, @@ -1789,21 +1919,18 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, connection->getInputChannelName()); #endif - assert(connection->status == Connection::STATUS_NORMAL); - assert(! connection->outboundQueue.isEmpty()); + LOG_ASSERT(connection->status == Connection::STATUS_NORMAL); + LOG_ASSERT(! connection->outboundQueue.isEmpty()); DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next; - assert(! dispatchEntry->inProgress); + LOG_ASSERT(! dispatchEntry->inProgress); // Mark the dispatch entry as in progress. dispatchEntry->inProgress = true; - // Update the connection's input state. - EventEntry* eventEntry = dispatchEntry->eventEntry; - connection->inputState.trackEvent(eventEntry); - // Publish the event. status_t status; + EventEntry* eventEntry = dispatchEntry->eventEntry; switch (eventEntry->type) { case EventEntry::TYPE_KEY: { KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry); @@ -1812,6 +1939,9 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, int32_t action = keyEntry->action; int32_t flags = keyEntry->flags; + // Update the connection's input state. + connection->inputState.trackKey(keyEntry, action); + // Publish the key event. status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->source, action, flags, keyEntry->keyCode, keyEntry->scanCode, @@ -1833,8 +1963,12 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, // Apply target flags. int32_t action = motionEntry->action; int32_t flags = motionEntry->flags; - if (dispatchEntry->targetFlags & InputTarget::FLAG_OUTSIDE) { + if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) { action = AMOTION_EVENT_ACTION_OUTSIDE; + } else if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT) { + action = AMOTION_EVENT_ACTION_HOVER_EXIT; + } else if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER) { + action = AMOTION_EVENT_ACTION_HOVER_ENTER; } if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) { flags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; @@ -1871,6 +2005,9 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, scaleFactor = 1.0f; } + // Update the connection's input state. + connection->inputState.trackMotion(motionEntry, action); + // Publish the motion event and the first motion sample. status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId, motionEntry->source, action, flags, motionEntry->edgeFlags, motionEntry->metaState, @@ -1885,44 +2022,47 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, return; } - // Append additional motion samples. - MotionSample* nextMotionSample = firstMotionSample->next; - for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) { - if ((motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) != 0 && scaleFactor != 1.0f) { - for (size_t i = 0; i < motionEntry->pointerCount; i++) { - scaledCoords[i] = nextMotionSample->pointerCoords[i]; - scaledCoords[i].scale(scaleFactor); + if (action == AMOTION_EVENT_ACTION_MOVE + || action == AMOTION_EVENT_ACTION_HOVER_MOVE) { + // Append additional motion samples. + MotionSample* nextMotionSample = firstMotionSample->next; + for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) { + if ((motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) != 0 && scaleFactor != 1.0f) { + for (size_t i = 0; i < motionEntry->pointerCount; i++) { + scaledCoords[i] = nextMotionSample->pointerCoords[i]; + scaledCoords[i].scale(scaleFactor); + } + } else { + usingCoords = nextMotionSample->pointerCoords; } - } else { - usingCoords = nextMotionSample->pointerCoords; - } - status = connection->inputPublisher.appendMotionSample( - nextMotionSample->eventTime, usingCoords); - if (status == NO_MEMORY) { + status = connection->inputPublisher.appendMotionSample( + nextMotionSample->eventTime, usingCoords); + if (status == NO_MEMORY) { #if DEBUG_DISPATCH_CYCLE LOGD("channel '%s' ~ Shared memory buffer full. Some motion samples will " "be sent in the next dispatch cycle.", connection->getInputChannelName()); #endif - break; - } - if (status != OK) { - LOGE("channel '%s' ~ Could not append motion sample " - "for a reason other than out of memory, status=%d", - connection->getInputChannelName(), status); - abortBrokenDispatchCycleLocked(currentTime, connection); - return; + break; + } + if (status != OK) { + LOGE("channel '%s' ~ Could not append motion sample " + "for a reason other than out of memory, status=%d", + connection->getInputChannelName(), status); + abortBrokenDispatchCycleLocked(currentTime, connection); + return; + } } - } - // Remember the next motion sample that we could not dispatch, in case we ran out - // of space in the shared memory buffer. - dispatchEntry->tailMotionSample = nextMotionSample; + // Remember the next motion sample that we could not dispatch, in case we ran out + // of space in the shared memory buffer. + dispatchEntry->tailMotionSample = nextMotionSample; + } break; } default: { - assert(false); + LOG_ASSERT(false); } } @@ -2088,26 +2228,24 @@ int InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data } void InputDispatcher::synthesizeCancelationEventsForAllConnectionsLocked( - InputState::CancelationOptions options, const char* reason) { + const CancelationOptions& options) { for (size_t i = 0; i < mConnectionsByReceiveFd.size(); i++) { synthesizeCancelationEventsForConnectionLocked( - mConnectionsByReceiveFd.valueAt(i), options, reason); + mConnectionsByReceiveFd.valueAt(i), options); } } void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked( - const sp<InputChannel>& channel, InputState::CancelationOptions options, - const char* reason) { + const sp<InputChannel>& channel, const CancelationOptions& options) { ssize_t index = getConnectionIndexLocked(channel); if (index >= 0) { synthesizeCancelationEventsForConnectionLocked( - mConnectionsByReceiveFd.valueAt(index), options, reason); + mConnectionsByReceiveFd.valueAt(index), options); } } void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( - const sp<Connection>& connection, InputState::CancelationOptions options, - const char* reason) { + const sp<Connection>& connection, const CancelationOptions& options) { nsecs_t currentTime = now(); mTempCancelationEvents.clear(); @@ -2118,8 +2256,9 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( && connection->status != Connection::STATUS_BROKEN) { #if DEBUG_OUTBOUND_EVENT_DETAILS LOGD("channel '%s' ~ Synthesized %d cancelation events to bring channel back in sync " - "with reality: %s, options=%d.", - connection->getInputChannelName(), mTempCancelationEvents.size(), reason, options); + "with reality: %s, mode=%d.", + connection->getInputChannelName(), mTempCancelationEvents.size(), + options.reason, options.mode); #endif for (size_t i = 0; i < mTempCancelationEvents.size(); i++) { EventEntry* cancelationEventEntry = mTempCancelationEvents.itemAt(i); @@ -2163,7 +2302,7 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( InputDispatcher::MotionEntry* InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds) { - assert(pointerIds.value != 0); + LOG_ASSERT(pointerIds.value != 0); uint32_t splitPointerIndexMap[MAX_POINTERS]; int32_t splitPointerIds[MAX_POINTERS]; @@ -2178,8 +2317,8 @@ InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet if (pointerIds.hasBit(pointerId)) { splitPointerIndexMap[splitPointerCount] = originalPointerIndex; splitPointerIds[splitPointerCount] = pointerId; - splitPointerCoords[splitPointerCount] = - originalMotionEntry->firstSample.pointerCoords[originalPointerIndex]; + splitPointerCoords[splitPointerCount].copyFrom( + originalMotionEntry->firstSample.pointerCoords[originalPointerIndex]); splitPointerCount += 1; } } @@ -2242,14 +2381,19 @@ InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet for (uint32_t splitPointerIndex = 0; splitPointerIndex < splitPointerCount; splitPointerIndex++) { uint32_t originalPointerIndex = splitPointerIndexMap[splitPointerIndex]; - splitPointerCoords[splitPointerIndex] = - originalMotionSample->pointerCoords[originalPointerIndex]; + splitPointerCoords[splitPointerIndex].copyFrom( + originalMotionSample->pointerCoords[originalPointerIndex]); } mAllocator.appendMotionSample(splitMotionEntry, originalMotionSample->eventTime, splitPointerCoords); } + if (originalMotionEntry->injectionState) { + splitMotionEntry->injectionState = originalMotionEntry->injectionState; + splitMotionEntry->injectionState->refCount += 1; + } + return splitMotionEntry; } @@ -2318,7 +2462,18 @@ void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t so bool needWake; { // acquire lock - AutoMutex _l(mLock); + mLock.lock(); + + if (mInputFilterEnabled) { + mLock.unlock(); + + policyFlags |= POLICY_FLAG_FILTERED; + if (!mPolicy->filterInputEvent(&event, policyFlags)) { + return; // event was consumed by the filter + } + + mLock.lock(); + } int32_t repeatCount = 0; KeyEntry* newEntry = mAllocator.obtainKeyEntry(eventTime, @@ -2326,6 +2481,7 @@ void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t so metaState, repeatCount, downTime); needWake = enqueueInboundEventLocked(newEntry); + mLock.unlock(); } // release lock if (needWake) { @@ -2368,7 +2524,23 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t bool needWake; { // acquire lock - AutoMutex _l(mLock); + mLock.lock(); + + if (mInputFilterEnabled) { + mLock.unlock(); + + MotionEvent event; + event.initialize(deviceId, source, action, flags, edgeFlags, metaState, 0, 0, + xPrecision, yPrecision, downTime, eventTime, + pointerCount, pointerIds, pointerCoords); + + policyFlags |= POLICY_FLAG_FILTERED; + if (!mPolicy->filterInputEvent(&event, policyFlags)) { + return; // event was consumed by the filter + } + + mLock.lock(); + } // Attempt batching and streaming of move events. if (action == AMOTION_EVENT_ACTION_MOVE @@ -2392,24 +2564,43 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t continue; } - if (motionEntry->action != action - || motionEntry->pointerCount != pointerCount - || motionEntry->isInjected()) { + if (!motionEntry->canAppendSamples(action, pointerCount, pointerIds)) { // Last motion event in the queue for this device and source is // not compatible for appending new samples. Stop here. goto NoBatchingOrStreaming; } - // The last motion event is a move and is compatible for appending. // Do the batching magic. - mAllocator.appendMotionSample(motionEntry, eventTime, pointerCoords); -#if DEBUG_BATCHING - LOGD("Appended motion sample onto batch for most recent " - "motion event for this device in the inbound queue."); -#endif + batchMotionLocked(motionEntry, eventTime, metaState, pointerCoords, + "most recent motion event for this device and source in the inbound queue"); + mLock.unlock(); return; // done! } + // BATCHING ONTO PENDING EVENT CASE + // + // Try to append a move sample to the currently pending event, if there is one. + // We can do this as long as we are still waiting to find the targets for the + // event. Once the targets are locked-in we can only do streaming. + if (mPendingEvent + && (!mPendingEvent->dispatchInProgress || !mCurrentInputTargetsValid) + && mPendingEvent->type == EventEntry::TYPE_MOTION) { + MotionEntry* motionEntry = static_cast<MotionEntry*>(mPendingEvent); + if (motionEntry->deviceId == deviceId && motionEntry->source == source) { + if (!motionEntry->canAppendSamples(action, pointerCount, pointerIds)) { + // Pending motion event is for this device and source but it is + // not compatible for appending new samples. Stop here. + goto NoBatchingOrStreaming; + } + + // Do the batching magic. + batchMotionLocked(motionEntry, eventTime, metaState, pointerCoords, + "pending motion event"); + mLock.unlock(); + return; // done! + } + } + // STREAMING CASE // // There is no pending motion event (of any kind) for this device in the inbound queue. @@ -2459,12 +2650,35 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t continue; } + if (action == AMOTION_EVENT_ACTION_HOVER_MOVE) { + if (!mLastHoverWindow) { +#if DEBUG_BATCHING + LOGD("Not streaming hover move because there is no " + "last hovered window."); +#endif + goto NoBatchingOrStreaming; + } + + const InputWindow* hoverWindow = findTouchedWindowAtLocked( + pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), + pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y)); + if (mLastHoverWindow != hoverWindow) { +#if DEBUG_BATCHING + LOGD("Not streaming hover move because the last hovered window " + "is '%s' but the currently hovered window is '%s'.", + mLastHoverWindow->name.string(), + hoverWindow ? hoverWindow->name.string() : "<null>"); +#endif + goto NoBatchingOrStreaming; + } + } + // Hurray! This foreground target is currently dispatching a move event // that we can stream onto. Append the motion sample and resume dispatch. mAllocator.appendMotionSample(motionEntry, eventTime, pointerCoords); #if DEBUG_BATCHING LOGD("Appended motion sample onto batch for most recently dispatched " - "motion event for this device in the outbound queues. " + "motion event for this device and source in the outbound queues. " "Attempting to stream the motion sample."); #endif nsecs_t currentTime = now(); @@ -2472,6 +2686,7 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t true /*resumeWithAppendedMotionSample*/); runCommandsLockedInterruptible(); + mLock.unlock(); return; // done! } } @@ -2486,6 +2701,7 @@ NoBatchingOrStreaming:; pointerCount, pointerIds, pointerCoords); needWake = enqueueInboundEventLocked(newEntry); + mLock.unlock(); } // release lock if (needWake) { @@ -2493,6 +2709,36 @@ NoBatchingOrStreaming:; } } +void InputDispatcher::batchMotionLocked(MotionEntry* entry, nsecs_t eventTime, + int32_t metaState, const PointerCoords* pointerCoords, const char* eventDescription) { + // Combine meta states. + entry->metaState |= metaState; + + // Coalesce this sample if not enough time has elapsed since the last sample was + // initially appended to the batch. + MotionSample* lastSample = entry->lastSample; + long interval = eventTime - lastSample->eventTimeBeforeCoalescing; + if (interval <= MOTION_SAMPLE_COALESCE_INTERVAL) { + uint32_t pointerCount = entry->pointerCount; + for (uint32_t i = 0; i < pointerCount; i++) { + lastSample->pointerCoords[i].copyFrom(pointerCoords[i]); + } + lastSample->eventTime = eventTime; +#if DEBUG_BATCHING + LOGD("Coalesced motion into last sample of batch for %s, events were %0.3f ms apart", + eventDescription, interval * 0.000001f); +#endif + return; + } + + // Append the sample. + mAllocator.appendMotionSample(entry, eventTime, pointerCoords); +#if DEBUG_BATCHING + LOGD("Appended motion sample onto batch for %s, events were %0.3f ms apart", + eventDescription, interval * 0.000001f); +#endif +} + void InputDispatcher::notifySwitch(nsecs_t when, int32_t switchCode, int32_t switchValue, uint32_t policyFlags) { #if DEBUG_INBOUND_EVENT_DETAILS @@ -2505,16 +2751,17 @@ void InputDispatcher::notifySwitch(nsecs_t when, int32_t switchCode, int32_t swi } int32_t InputDispatcher::injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) { + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, + uint32_t policyFlags) { #if DEBUG_INBOUND_EVENT_DETAILS LOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, " - "syncMode=%d, timeoutMillis=%d", - event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis); + "syncMode=%d, timeoutMillis=%d, policyFlags=0x%08x", + event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis, policyFlags); #endif nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis); - uint32_t policyFlags = POLICY_FLAG_INJECTED; + policyFlags |= POLICY_FLAG_INJECTED; if (hasInjectionPermission(injectorPid, injectorUid)) { policyFlags |= POLICY_FLAG_TRUSTED; } @@ -2533,7 +2780,9 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event, policyFlags |= POLICY_FLAG_VIRTUAL; } - mPolicy->interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags); + if (!(policyFlags & POLICY_FLAG_FILTERED)) { + mPolicy->interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags); + } if (policyFlags & POLICY_FLAG_WOKE_HERE) { flags |= AKEY_EVENT_FLAG_WOKE_HERE; @@ -2557,8 +2806,10 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event, return INPUT_EVENT_INJECTION_FAILED; } - nsecs_t eventTime = motionEvent->getEventTime(); - mPolicy->interceptMotionBeforeQueueing(eventTime, /*byref*/ policyFlags); + if (!(policyFlags & POLICY_FLAG_FILTERED)) { + nsecs_t eventTime = motionEvent->getEventTime(); + mPolicy->interceptMotionBeforeQueueing(eventTime, /*byref*/ policyFlags); + } mLock.lock(); const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes(); @@ -2673,7 +2924,8 @@ void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t inject injectionResult, injectionState->injectorPid, injectionState->injectorUid); #endif - if (injectionState->injectionIsAsync) { + if (injectionState->injectionIsAsync + && !(entry->policyFlags & POLICY_FLAG_FILTERED)) { // Log the outcome since the injector did not wait for the injection result. switch (injectionResult) { case INPUT_EVENT_INJECTION_SUCCEEDED: @@ -2737,6 +2989,11 @@ void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) { oldFocusedWindowChannel = mFocusedWindow->inputChannel; mFocusedWindow = NULL; } + sp<InputChannel> oldLastHoverWindowChannel; + if (mLastHoverWindow) { + oldLastHoverWindowChannel = mLastHoverWindow->inputChannel; + mLastHoverWindow = NULL; + } mWindows.clear(); @@ -2759,8 +3016,9 @@ void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) { LOGD("Focus left window: %s", oldFocusedWindowChannel->getName().string()); #endif - synthesizeCancelationEventsForInputChannelLocked(oldFocusedWindowChannel, - InputState::CANCEL_NON_POINTER_EVENTS, "focus left window"); + CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, + "focus left window"); + synthesizeCancelationEventsForInputChannelLocked(oldFocusedWindowChannel, options); oldFocusedWindowChannel.clear(); } } @@ -2781,12 +3039,19 @@ void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) { #if DEBUG_FOCUS LOGD("Touched window was removed: %s", touchedWindow.channel->getName().string()); #endif - synthesizeCancelationEventsForInputChannelLocked(touchedWindow.channel, - InputState::CANCEL_POINTER_EVENTS, "touched window was removed"); + CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, + "touched window was removed"); + synthesizeCancelationEventsForInputChannelLocked(touchedWindow.channel, options); mTouchState.windows.removeAt(i); } } + // Recover the last hovered window. + if (oldLastHoverWindowChannel != NULL) { + mLastHoverWindow = getWindowLocked(oldLastHoverWindowChannel); + oldLastHoverWindowChannel.clear(); + } + #if DEBUG_FOCUS //logDispatchStateLocked(); #endif @@ -2862,6 +3127,26 @@ void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) { } } +void InputDispatcher::setInputFilterEnabled(bool enabled) { +#if DEBUG_FOCUS + LOGD("setInputFilterEnabled: enabled=%d", enabled); +#endif + + { // acquire lock + AutoMutex _l(mLock); + + if (mInputFilterEnabled == enabled) { + return; + } + + mInputFilterEnabled = enabled; + resetAndDropEverythingLocked("input filter is being enabled or disabled"); + } // release lock + + // Wake up poll loop since there might be work to do to drop everything. + mLooper->wake(); +} + bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel, const sp<InputChannel>& toChannel) { #if DEBUG_FOCUS @@ -2896,7 +3181,8 @@ bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel, mTouchState.windows.removeAt(i); int32_t newTargetFlags = oldTargetFlags - & (InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_SPLIT); + & (InputTarget::FLAG_FOREGROUND + | InputTarget::FLAG_SPLIT | InputTarget::FLAG_DISPATCH_AS_IS); mTouchState.addOrUpdateWindow(toWindow, newTargetFlags, pointerIds); found = true; @@ -2918,9 +3204,9 @@ bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel, sp<Connection> toConnection = mConnectionsByReceiveFd.valueAt(toConnectionIndex); fromConnection->inputState.copyPointerStateTo(toConnection->inputState); - synthesizeCancelationEventsForConnectionLocked(fromConnection, - InputState::CANCEL_POINTER_EVENTS, + CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, "transferring touch focus from this window to another window"); + synthesizeCancelationEventsForConnectionLocked(fromConnection, options); } #if DEBUG_FOCUS @@ -2938,7 +3224,8 @@ void InputDispatcher::resetAndDropEverythingLocked(const char* reason) { LOGD("Resetting and dropping all events (%s).", reason); #endif - synthesizeCancelationEventsForAllConnectionsLocked(InputState::CANCEL_ALL_EVENTS, reason); + CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS, reason); + synthesizeCancelationEventsForAllConnectionsLocked(options); resetKeyRepeatLocked(); releasePendingEventLocked(); @@ -3269,58 +3556,49 @@ void InputDispatcher::doDispatchCycleFinishedLockedInterruptible( if (!connection->outboundQueue.isEmpty()) { DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next; if (dispatchEntry->inProgress - && dispatchEntry->hasForegroundTarget() && dispatchEntry->eventEntry->type == EventEntry::TYPE_KEY) { KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry); if (!(keyEntry->flags & AKEY_EVENT_FLAG_FALLBACK)) { - if (handled) { - // If the application handled a non-fallback key, then immediately - // cancel all fallback keys previously dispatched to the application. - // This behavior will prevent chording with fallback keys (so they cannot - // be used as modifiers) but it will ensure that fallback keys do not - // get stuck. This takes care of the case where the application does not handle - // the original DOWN so we generate a fallback DOWN but it does handle - // the original UP in which case we want to send a fallback CANCEL. - synthesizeCancelationEventsForConnectionLocked(connection, - InputState::CANCEL_FALLBACK_EVENTS, - "application handled a non-fallback event, " - "canceling all fallback events"); - connection->originalKeyCodeForFallback = -1; + // Get the fallback key state. + // Clear it out after dispatching the UP. + int32_t originalKeyCode = keyEntry->keyCode; + int32_t fallbackKeyCode = connection->inputState.getFallbackKey(originalKeyCode); + if (keyEntry->action == AKEY_EVENT_ACTION_UP) { + connection->inputState.removeFallbackKey(originalKeyCode); + } + + if (handled || !dispatchEntry->hasForegroundTarget()) { + // If the application handles the original key for which we previously + // generated a fallback or if the window is not a foreground window, + // then cancel the associated fallback key, if any. + if (fallbackKeyCode != -1) { + if (fallbackKeyCode != AKEYCODE_UNKNOWN) { + CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS, + "application handled the original non-fallback key " + "or is no longer a foreground target, " + "canceling previously dispatched fallback key"); + options.keyCode = fallbackKeyCode; + synthesizeCancelationEventsForConnectionLocked(connection, options); + } + connection->inputState.removeFallbackKey(originalKeyCode); + } } else { // If the application did not handle a non-fallback key, first check - // that we are in a good state to handle the fallback key. Then ask - // the policy what to do with it. - if (connection->originalKeyCodeForFallback < 0) { - if (keyEntry->action != AKEY_EVENT_ACTION_DOWN - || keyEntry->repeatCount != 0) { -#if DEBUG_OUTBOUND_EVENT_DETAILS - LOGD("Unhandled key event: Skipping fallback since this " - "is not an initial down. " - "keyCode=%d, action=%d, repeatCount=%d", - keyEntry->keyCode, keyEntry->action, keyEntry->repeatCount); -#endif - goto SkipFallback; - } - - // Start handling the fallback key on DOWN. - connection->originalKeyCodeForFallback = keyEntry->keyCode; - } else { - if (keyEntry->keyCode != connection->originalKeyCodeForFallback) { + // that we are in a good state to perform unhandled key event processing + // Then ask the policy what to do with it. + bool initialDown = keyEntry->action == AKEY_EVENT_ACTION_DOWN + && keyEntry->repeatCount == 0; + if (fallbackKeyCode == -1 && !initialDown) { #if DEBUG_OUTBOUND_EVENT_DETAILS - LOGD("Unhandled key event: Skipping fallback since there is " - "already a different fallback in progress. " - "keyCode=%d, originalKeyCodeForFallback=%d", - keyEntry->keyCode, connection->originalKeyCodeForFallback); + LOGD("Unhandled key event: Skipping unhandled key event processing " + "since this is not an initial down. " + "keyCode=%d, action=%d, repeatCount=%d", + originalKeyCode, keyEntry->action, keyEntry->repeatCount); #endif - goto SkipFallback; - } - - // Finish handling the fallback key on UP. - if (keyEntry->action == AKEY_EVENT_ACTION_UP) { - connection->originalKeyCodeForFallback = -1; - } + goto SkipFallback; } + // Dispatch the unhandled key to the policy. #if DEBUG_OUTBOUND_EVENT_DETAILS LOGD("Unhandled key event: Asking policy to perform fallback action. " "keyCode=%d, action=%d, repeatCount=%d", @@ -3337,10 +3615,70 @@ void InputDispatcher::doDispatchCycleFinishedLockedInterruptible( mLock.lock(); if (connection->status != Connection::STATUS_NORMAL) { + connection->inputState.removeFallbackKey(originalKeyCode); return; } - assert(connection->outboundQueue.headSentinel.next == dispatchEntry); + LOG_ASSERT(connection->outboundQueue.headSentinel.next == dispatchEntry); + + // Latch the fallback keycode for this key on an initial down. + // The fallback keycode cannot change at any other point in the lifecycle. + if (initialDown) { + if (fallback) { + fallbackKeyCode = event.getKeyCode(); + } else { + fallbackKeyCode = AKEYCODE_UNKNOWN; + } + connection->inputState.setFallbackKey(originalKeyCode, fallbackKeyCode); + } + + LOG_ASSERT(fallbackKeyCode != -1); + + // Cancel the fallback key if the policy decides not to send it anymore. + // We will continue to dispatch the key to the policy but we will no + // longer dispatch a fallback key to the application. + if (fallbackKeyCode != AKEYCODE_UNKNOWN + && (!fallback || fallbackKeyCode != event.getKeyCode())) { +#if DEBUG_OUTBOUND_EVENT_DETAILS + if (fallback) { + LOGD("Unhandled key event: Policy requested to send key %d" + "as a fallback for %d, but on the DOWN it had requested " + "to send %d instead. Fallback canceled.", + event.getKeyCode(), originalKeyCode, fallbackKeyCode); + } else { + LOGD("Unhandled key event: Policy did not request fallback for %d," + "but on the DOWN it had requested to send %d. " + "Fallback canceled.", + originalKeyCode, fallbackKeyCode); + } +#endif + + CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS, + "canceling fallback, policy no longer desires it"); + options.keyCode = fallbackKeyCode; + synthesizeCancelationEventsForConnectionLocked(connection, options); + + fallback = false; + fallbackKeyCode = AKEYCODE_UNKNOWN; + if (keyEntry->action != AKEY_EVENT_ACTION_UP) { + connection->inputState.setFallbackKey(originalKeyCode, + fallbackKeyCode); + } + } + +#if DEBUG_OUTBOUND_EVENT_DETAILS + { + String8 msg; + const KeyedVector<int32_t, int32_t>& fallbackKeys = + connection->inputState.getFallbackKeys(); + for (size_t i = 0; i < fallbackKeys.size(); i++) { + msg.appendFormat(", %d->%d", fallbackKeys.keyAt(i), + fallbackKeys.valueAt(i)); + } + LOGD("Unhandled key event: %d currently tracked fallback keys%s.", + fallbackKeys.size(), msg.string()); + } +#endif if (fallback) { // Restart the dispatch cycle using the fallback key. @@ -3348,7 +3686,7 @@ void InputDispatcher::doDispatchCycleFinishedLockedInterruptible( keyEntry->deviceId = event.getDeviceId(); keyEntry->source = event.getSource(); keyEntry->flags = event.getFlags() | AKEY_EVENT_FLAG_FALLBACK; - keyEntry->keyCode = event.getKeyCode(); + keyEntry->keyCode = fallbackKeyCode; keyEntry->scanCode = event.getScanCode(); keyEntry->metaState = event.getMetaState(); keyEntry->repeatCount = event.getRepeatCount(); @@ -3357,13 +3695,17 @@ void InputDispatcher::doDispatchCycleFinishedLockedInterruptible( #if DEBUG_OUTBOUND_EVENT_DETAILS LOGD("Unhandled key event: Dispatching fallback key. " - "fallbackKeyCode=%d, fallbackMetaState=%08x", - keyEntry->keyCode, keyEntry->metaState); + "originalKeyCode=%d, fallbackKeyCode=%d, fallbackMetaState=%08x", + originalKeyCode, fallbackKeyCode, keyEntry->metaState); #endif dispatchEntry->inProgress = false; startDispatchCycleLocked(now(), connection); return; + } else { +#if DEBUG_OUTBOUND_EVENT_DETAILS + LOGD("Unhandled key event: No fallback key."); +#endif } } } @@ -3493,11 +3835,12 @@ InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry(nsec entry->downTime = downTime; entry->pointerCount = pointerCount; entry->firstSample.eventTime = eventTime; + entry->firstSample.eventTimeBeforeCoalescing = eventTime; entry->firstSample.next = NULL; entry->lastSample = & entry->firstSample; for (uint32_t i = 0; i < pointerCount; i++) { entry->pointerIds[i] = pointerIds[i]; - entry->firstSample.pointerCoords[i] = pointerCoords[i]; + entry->firstSample.pointerCoords[i].copyFrom(pointerCoords[i]); } return entry; } @@ -3529,7 +3872,7 @@ void InputDispatcher::Allocator::releaseInjectionState(InjectionState* injection if (injectionState->refCount == 0) { mInjectionStatePool.free(injectionState); } else { - assert(injectionState->refCount > 0); + LOG_ASSERT(injectionState->refCount > 0); } } @@ -3545,7 +3888,7 @@ void InputDispatcher::Allocator::releaseEventEntry(EventEntry* entry) { releaseMotionEntry(static_cast<MotionEntry*>(entry)); break; default: - assert(false); + LOG_ASSERT(false); break; } } @@ -3557,7 +3900,7 @@ void InputDispatcher::Allocator::releaseConfigurationChangedEntry( releaseEventEntryInjectionState(entry); mConfigurationChangeEntryPool.free(entry); } else { - assert(entry->refCount > 0); + LOG_ASSERT(entry->refCount > 0); } } @@ -3567,7 +3910,7 @@ void InputDispatcher::Allocator::releaseKeyEntry(KeyEntry* entry) { releaseEventEntryInjectionState(entry); mKeyEntryPool.free(entry); } else { - assert(entry->refCount > 0); + LOG_ASSERT(entry->refCount > 0); } } @@ -3582,10 +3925,14 @@ void InputDispatcher::Allocator::releaseMotionEntry(MotionEntry* entry) { } mMotionEntryPool.free(entry); } else { - assert(entry->refCount > 0); + LOG_ASSERT(entry->refCount > 0); } } +void InputDispatcher::Allocator::freeMotionSample(MotionSample* sample) { + mMotionSamplePool.free(sample); +} + void InputDispatcher::Allocator::releaseDispatchEntry(DispatchEntry* entry) { releaseEventEntry(entry->eventEntry); mDispatchEntryPool.free(entry); @@ -3599,9 +3946,10 @@ void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry, nsecs_t eventTime, const PointerCoords* pointerCoords) { MotionSample* sample = mMotionSamplePool.alloc(); sample->eventTime = eventTime; + sample->eventTimeBeforeCoalescing = eventTime; uint32_t pointerCount = motionEntry->pointerCount; for (uint32_t i = 0; i < pointerCount; i++) { - sample->pointerCoords[i] = pointerCoords[i]; + sample->pointerCoords[i].copyFrom(pointerCoords[i]); } sample->next = NULL; @@ -3628,6 +3976,21 @@ uint32_t InputDispatcher::MotionEntry::countSamples() const { return count; } +bool InputDispatcher::MotionEntry::canAppendSamples(int32_t action, uint32_t pointerCount, + const int32_t* pointerIds) const { + if (this->action != action + || this->pointerCount != pointerCount + || this->isInjected()) { + return false; + } + for (uint32_t i = 0; i < pointerCount; i++) { + if (this->pointerIds[i] != pointerIds[i]) { + return false; + } + } + return true; +} + // --- InputDispatcher::InputState --- @@ -3641,22 +4004,30 @@ bool InputDispatcher::InputState::isNeutral() const { return mKeyMementos.isEmpty() && mMotionMementos.isEmpty(); } -void InputDispatcher::InputState::trackEvent( - const EventEntry* entry) { +void InputDispatcher::InputState::trackEvent(const EventEntry* entry, int32_t action) { switch (entry->type) { case EventEntry::TYPE_KEY: - trackKey(static_cast<const KeyEntry*>(entry)); + trackKey(static_cast<const KeyEntry*>(entry), action); break; case EventEntry::TYPE_MOTION: - trackMotion(static_cast<const MotionEntry*>(entry)); + trackMotion(static_cast<const MotionEntry*>(entry), action); break; } } -void InputDispatcher::InputState::trackKey( - const KeyEntry* entry) { - int32_t action = entry->action; +void InputDispatcher::InputState::trackKey(const KeyEntry* entry, int32_t action) { + if (action == AKEY_EVENT_ACTION_UP + && (entry->flags & AKEY_EVENT_FLAG_FALLBACK)) { + for (size_t i = 0; i < mFallbackKeys.size(); ) { + if (mFallbackKeys.valueAt(i) == entry->keyCode) { + mFallbackKeys.removeItemsAt(i); + } else { + i += 1; + } + } + } + for (size_t i = 0; i < mKeyMementos.size(); i++) { KeyMemento& memento = mKeyMementos.editItemAt(i); if (memento.deviceId == entry->deviceId @@ -3691,17 +4062,18 @@ Found: } } -void InputDispatcher::InputState::trackMotion( - const MotionEntry* entry) { - int32_t action = entry->action & AMOTION_EVENT_ACTION_MASK; +void InputDispatcher::InputState::trackMotion(const MotionEntry* entry, int32_t action) { + int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK; for (size_t i = 0; i < mMotionMementos.size(); i++) { MotionMemento& memento = mMotionMementos.editItemAt(i); if (memento.deviceId == entry->deviceId && memento.source == entry->source) { - switch (action) { + switch (actionMasked) { case AMOTION_EVENT_ACTION_UP: case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_HOVER_ENTER: case AMOTION_EVENT_ACTION_HOVER_MOVE: + case AMOTION_EVENT_ACTION_HOVER_EXIT: mMotionMementos.removeAt(i); return; @@ -3722,7 +4094,11 @@ void InputDispatcher::InputState::trackMotion( } Found: - if (action == AMOTION_EVENT_ACTION_DOWN) { + switch (actionMasked) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_HOVER_ENTER: + case AMOTION_EVENT_ACTION_HOVER_MOVE: + case AMOTION_EVENT_ACTION_HOVER_EXIT: mMotionMementos.push(); MotionMemento& memento = mMotionMementos.editTop(); memento.deviceId = entry->deviceId; @@ -3731,6 +4107,7 @@ Found: memento.yPrecision = entry->yPrecision; memento.downTime = entry->downTime; memento.setPointers(entry); + memento.hovering = actionMasked != AMOTION_EVENT_ACTION_DOWN; } } @@ -3738,13 +4115,13 @@ void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* pointerCount = entry->pointerCount; for (uint32_t i = 0; i < entry->pointerCount; i++) { pointerIds[i] = entry->pointerIds[i]; - pointerCoords[i] = entry->lastSample->pointerCoords[i]; + pointerCoords[i].copyFrom(entry->lastSample->pointerCoords[i]); } } void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTime, Allocator* allocator, Vector<EventEntry*>& outEvents, - CancelationOptions options) { + const CancelationOptions& options) { for (size_t i = 0; i < mKeyMementos.size(); ) { const KeyMemento& memento = mKeyMementos.itemAt(i); if (shouldCancelKey(memento, options)) { @@ -3763,7 +4140,10 @@ void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTim if (shouldCancelMotion(memento, options)) { outEvents.push(allocator->obtainMotionEntry(currentTime, memento.deviceId, memento.source, 0, - AMOTION_EVENT_ACTION_CANCEL, 0, 0, 0, + memento.hovering + ? AMOTION_EVENT_ACTION_HOVER_EXIT + : AMOTION_EVENT_ACTION_CANCEL, + 0, 0, 0, memento.xPrecision, memento.yPrecision, memento.downTime, memento.pointerCount, memento.pointerIds, memento.pointerCoords)); mMotionMementos.removeAt(i); @@ -3776,6 +4156,7 @@ void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTim void InputDispatcher::InputState::clear() { mKeyMementos.clear(); mMotionMementos.clear(); + mFallbackKeys.clear(); } void InputDispatcher::InputState::copyPointerStateTo(InputState& other) const { @@ -3796,13 +4177,36 @@ void InputDispatcher::InputState::copyPointerStateTo(InputState& other) const { } } +int32_t InputDispatcher::InputState::getFallbackKey(int32_t originalKeyCode) { + ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode); + return index >= 0 ? mFallbackKeys.valueAt(index) : -1; +} + +void InputDispatcher::InputState::setFallbackKey(int32_t originalKeyCode, + int32_t fallbackKeyCode) { + ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode); + if (index >= 0) { + mFallbackKeys.replaceValueAt(index, fallbackKeyCode); + } else { + mFallbackKeys.add(originalKeyCode, fallbackKeyCode); + } +} + +void InputDispatcher::InputState::removeFallbackKey(int32_t originalKeyCode) { + mFallbackKeys.removeItem(originalKeyCode); +} + bool InputDispatcher::InputState::shouldCancelKey(const KeyMemento& memento, - CancelationOptions options) { - switch (options) { - case CANCEL_ALL_EVENTS: - case CANCEL_NON_POINTER_EVENTS: + const CancelationOptions& options) { + if (options.keyCode != -1 && memento.keyCode != options.keyCode) { + return false; + } + + switch (options.mode) { + case CancelationOptions::CANCEL_ALL_EVENTS: + case CancelationOptions::CANCEL_NON_POINTER_EVENTS: return true; - case CANCEL_FALLBACK_EVENTS: + case CancelationOptions::CANCEL_FALLBACK_EVENTS: return memento.flags & AKEY_EVENT_FLAG_FALLBACK; default: return false; @@ -3810,13 +4214,13 @@ bool InputDispatcher::InputState::shouldCancelKey(const KeyMemento& memento, } bool InputDispatcher::InputState::shouldCancelMotion(const MotionMemento& memento, - CancelationOptions options) { - switch (options) { - case CANCEL_ALL_EVENTS: + const CancelationOptions& options) { + switch (options.mode) { + case CancelationOptions::CANCEL_ALL_EVENTS: return true; - case CANCEL_POINTER_EVENTS: + case CancelationOptions::CANCEL_POINTER_EVENTS: return memento.source & AINPUT_SOURCE_CLASS_POINTER; - case CANCEL_NON_POINTER_EVENTS: + case CancelationOptions::CANCEL_NON_POINTER_EVENTS: return !(memento.source & AINPUT_SOURCE_CLASS_POINTER); default: return false; @@ -3830,8 +4234,7 @@ InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel, const sp<InputWindowHandle>& inputWindowHandle) : status(STATUS_NORMAL), inputChannel(inputChannel), inputWindowHandle(inputWindowHandle), inputPublisher(inputChannel), - lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX), - originalKeyCodeForFallback(-1) { + lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX) { } InputDispatcher::Connection::~Connection() { @@ -3929,12 +4332,15 @@ void InputDispatcher::TouchState::addOrUpdateWindow(const InputWindow* window, touchedWindow.channel = window->inputChannel; } -void InputDispatcher::TouchState::removeOutsideTouchWindows() { +void InputDispatcher::TouchState::filterNonAsIsTouchWindows() { for (size_t i = 0 ; i < windows.size(); ) { - if (windows[i].targetFlags & InputTarget::FLAG_OUTSIDE) { - windows.removeAt(i); - } else { + TouchedWindow& window = windows.editItemAt(i); + if (window.targetFlags & InputTarget::FLAG_DISPATCH_AS_IS) { + window.targetFlags &= ~InputTarget::FLAG_DISPATCH_MASK; + window.targetFlags |= InputTarget::FLAG_DISPATCH_AS_IS; i += 1; + } else { + windows.removeAt(i); } } } diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h index dd89328..96ece32 100644 --- a/services/input/InputDispatcher.h +++ b/services/input/InputDispatcher.h @@ -86,20 +86,40 @@ enum { struct InputTarget { enum { /* This flag indicates that the event is being delivered to a foreground application. */ - FLAG_FOREGROUND = 0x01, - - /* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside - * of the area of this target and so should instead be delivered as an - * AMOTION_EVENT_ACTION_OUTSIDE to this target. */ - FLAG_OUTSIDE = 0x02, + FLAG_FOREGROUND = 1 << 0, /* This flag indicates that the target of a MotionEvent is partly or wholly * obscured by another visible window above it. The motion event should be * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */ - FLAG_WINDOW_IS_OBSCURED = 0x04, + FLAG_WINDOW_IS_OBSCURED = 1 << 1, /* This flag indicates that a motion event is being split across multiple windows. */ - FLAG_SPLIT = 0x08, + FLAG_SPLIT = 1 << 2, + + /* This flag indicates that the event should be sent as is. + * Should always be set unless the event is to be transmuted. */ + FLAG_DISPATCH_AS_IS = 1 << 8, + + /* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside + * of the area of this target and so should instead be delivered as an + * AMOTION_EVENT_ACTION_OUTSIDE to this target. */ + FLAG_DISPATCH_AS_OUTSIDE = 1 << 9, + + /* This flag indicates that a hover sequence is starting in the given window. + * The event is transmuted into ACTION_HOVER_ENTER. */ + FLAG_DISPATCH_AS_HOVER_ENTER = 1 << 10, + + /* This flag indicates that a hover event happened outside of a window which handled + * previous hover events, signifying the end of the current hover sequence for that + * window. + * The event is transmuted into ACTION_HOVER_ENTER. */ + FLAG_DISPATCH_AS_HOVER_EXIT = 1 << 11, + + /* Mask for all dispatch modes. */ + FLAG_DISPATCH_MASK = FLAG_DISPATCH_AS_IS + | FLAG_DISPATCH_AS_OUTSIDE + | FLAG_DISPATCH_AS_HOVER_ENTER + | FLAG_DISPATCH_AS_HOVER_EXIT, }; // The input channel to be targeted. @@ -160,6 +180,13 @@ public: */ virtual int32_t getMaxEventsPerSecond() = 0; + /* Filters an input event. + * Return true to dispatch the event unmodified, false to consume the event. + * A filter can also transform and inject events later by passing POLICY_FLAG_FILTERED + * to injectInputEvent. + */ + virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) = 0; + /* Intercepts a key event immediately before queueing it. * The policy can use this method as an opportunity to perform power management functions * and early event preprocessing such as updating policy flags. @@ -250,7 +277,8 @@ public: * This method may be called on any thread (usually by the input manager). */ virtual int32_t injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) = 0; + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, + uint32_t policyFlags) = 0; /* Sets the list of input windows. * @@ -270,6 +298,14 @@ public: */ virtual void setInputDispatchMode(bool enabled, bool frozen) = 0; + /* Sets whether input event filtering is enabled. + * When enabled, incoming input events are sent to the policy's filterInputEvent + * method instead of being dispatched. The filter is expected to use + * injectInputEvent to inject the events it would like to have dispatched. + * It should include POLICY_FLAG_FILTERED in the policy flags during injection. + */ + virtual void setInputFilterEnabled(bool enabled) = 0; + /* Transfers touch focus from the window associated with one channel to the * window associated with the other channel. * @@ -329,11 +365,13 @@ public: int32_t switchCode, int32_t switchValue, uint32_t policyFlags) ; virtual int32_t injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis); + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, + uint32_t policyFlags); virtual void setInputWindows(const Vector<InputWindow>& inputWindows); virtual void setFocusedApplication(const InputApplication* inputApplication); virtual void setInputDispatchMode(bool enabled, bool frozen); + virtual void setInputFilterEnabled(bool enabled); virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel, const sp<InputChannel>& toChannel); @@ -375,7 +413,7 @@ private: bool dispatchInProgress; // initially false, set to true while dispatching - inline bool isInjected() { return injectionState != NULL; } + inline bool isInjected() const { return injectionState != NULL; } }; struct ConfigurationChangedEntry : EventEntry { @@ -405,7 +443,8 @@ private: struct MotionSample { MotionSample* next; - nsecs_t eventTime; + nsecs_t eventTime; // may be updated during coalescing + nsecs_t eventTimeBeforeCoalescing; // not updated during coalescing PointerCoords pointerCoords[MAX_POINTERS]; }; @@ -427,6 +466,10 @@ private: MotionSample* lastSample; uint32_t countSamples() const; + + // Checks whether we can append samples, assuming the device id and source are the same. + bool canAppendSamples(int32_t action, uint32_t pointerCount, + const int32_t* pointerIds) const; }; // Tracks the progress of dispatching a particular event to a particular connection. @@ -572,6 +615,7 @@ private: void releaseConfigurationChangedEntry(ConfigurationChangedEntry* entry); void releaseKeyEntry(KeyEntry* entry); void releaseMotionEntry(MotionEntry* entry); + void freeMotionSample(MotionSample* sample); void releaseDispatchEntry(DispatchEntry* entry); void releaseCommandEntry(CommandEntry* entry); @@ -594,18 +638,32 @@ private: void releaseEventEntryInjectionState(EventEntry* entry); }; - /* Tracks dispatched key and motion event state so that cancelation events can be - * synthesized when events are dropped. */ - class InputState { - public: - // Specifies the sources to cancel. - enum CancelationOptions { + /* Specifies which events are to be canceled and why. */ + struct CancelationOptions { + enum Mode { CANCEL_ALL_EVENTS = 0, CANCEL_POINTER_EVENTS = 1, CANCEL_NON_POINTER_EVENTS = 2, CANCEL_FALLBACK_EVENTS = 3, }; + // The criterion to use to determine which events should be canceled. + Mode mode; + + // Descriptive reason for the cancelation. + const char* reason; + + // The specific keycode of the key event to cancel, or -1 to cancel any key event. + int32_t keyCode; + + CancelationOptions(Mode mode, const char* reason) : + mode(mode), reason(reason), keyCode(-1) { } + }; + + /* Tracks dispatched key and motion event state so that cancelation events can be + * synthesized when events are dropped. */ + class InputState { + public: InputState(); ~InputState(); @@ -613,17 +671,17 @@ private: bool isNeutral() const; // Records tracking information for an event that has just been published. - void trackEvent(const EventEntry* entry); + void trackEvent(const EventEntry* entry, int32_t action); // Records tracking information for a key event that has just been published. - void trackKey(const KeyEntry* entry); + void trackKey(const KeyEntry* entry, int32_t action); // Records tracking information for a motion event that has just been published. - void trackMotion(const MotionEntry* entry); + void trackMotion(const MotionEntry* entry, int32_t action); // Synthesizes cancelation events for the current state and resets the tracked state. void synthesizeCancelationEvents(nsecs_t currentTime, Allocator* allocator, - Vector<EventEntry*>& outEvents, CancelationOptions options); + Vector<EventEntry*>& outEvents, const CancelationOptions& options); // Clears the current state. void clear(); @@ -631,6 +689,21 @@ private: // Copies pointer-related parts of the input state to another instance. void copyPointerStateTo(InputState& other) const; + // Gets the fallback key associated with a keycode. + // Returns -1 if none. + // Returns AKEYCODE_UNKNOWN if we are only dispatching the unhandled key to the policy. + int32_t getFallbackKey(int32_t originalKeyCode); + + // Sets the fallback key for a particular keycode. + void setFallbackKey(int32_t originalKeyCode, int32_t fallbackKeyCode); + + // Removes the fallback key for a particular keycode. + void removeFallbackKey(int32_t originalKeyCode); + + inline const KeyedVector<int32_t, int32_t>& getFallbackKeys() const { + return mFallbackKeys; + } + private: struct KeyMemento { int32_t deviceId; @@ -650,17 +723,19 @@ private: uint32_t pointerCount; int32_t pointerIds[MAX_POINTERS]; PointerCoords pointerCoords[MAX_POINTERS]; + bool hovering; void setPointers(const MotionEntry* entry); }; Vector<KeyMemento> mKeyMementos; Vector<MotionMemento> mMotionMementos; + KeyedVector<int32_t, int32_t> mFallbackKeys; static bool shouldCancelKey(const KeyMemento& memento, - CancelationOptions options); + const CancelationOptions& options); static bool shouldCancelMotion(const MotionMemento& memento, - CancelationOptions options); + const CancelationOptions& options); }; /* Manages the dispatch state associated with a single input channel. */ @@ -687,7 +762,6 @@ private: nsecs_t lastEventTime; // the time when the event was originally captured nsecs_t lastDispatchTime; // the time when the last event was dispatched - int32_t originalKeyCodeForFallback; // original keycode for fallback in progress, -1 if none explicit Connection(const sp<InputChannel>& inputChannel, const sp<InputWindowHandle>& inputWindowHandle); @@ -738,6 +812,11 @@ private: void dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout, nsecs_t keyRepeatDelay, nsecs_t* nextWakeupTime); + // Batches a new sample onto a motion entry. + // Assumes that the we have already checked that we can append samples. + void batchMotionLocked(MotionEntry* entry, nsecs_t eventTime, int32_t metaState, + const PointerCoords* pointerCoords, const char* eventDescription); + // Enqueues an inbound event. Returns true if mLooper->wake() should be called. bool enqueueInboundEventLocked(EventEntry* entry); @@ -817,6 +896,7 @@ private: // Dispatch state. bool mDispatchEnabled; bool mDispatchFrozen; + bool mInputFilterEnabled; Vector<InputWindow> mWindows; @@ -844,7 +924,7 @@ private: void reset(); void copyFrom(const TouchState& other); void addOrUpdateWindow(const InputWindow* window, int32_t targetFlags, BitSet32 pointerIds); - void removeOutsideTouchWindows(); + void filterNonAsIsTouchWindows(); const InputWindow* getFirstForegroundWindow(); }; @@ -887,6 +967,9 @@ private: bool mInputTargetWaitTimeoutExpired; sp<InputApplicationHandle> mInputTargetWaitApplication; + // Contains the last window which received a hover event. + const InputWindow* mLastHoverWindow; + // Finding targets for input events. void resetTargetsLocked(); void commitTargetsLocked(); @@ -901,7 +984,8 @@ private: int32_t findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry* entry, nsecs_t* nextWakeupTime); int32_t findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry* entry, - nsecs_t* nextWakeupTime, bool* outConflictingPointerActions); + nsecs_t* nextWakeupTime, bool* outConflictingPointerActions, + const MotionSample** outSplitBatchAfterSample); void addWindowTargetLocked(const InputWindow* window, int32_t targetFlags, BitSet32 pointerIds); @@ -920,6 +1004,9 @@ private: void prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget, bool resumeWithAppendedMotionSample); + void enqueueDispatchEntryLocked(const sp<Connection>& connection, + EventEntry* eventEntry, const InputTarget* inputTarget, + bool resumeWithAppendedMotionSample, int32_t dispatchMode); void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection); void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection, bool handled); @@ -929,11 +1016,11 @@ private: static int handleReceiveCallback(int receiveFd, int events, void* data); void synthesizeCancelationEventsForAllConnectionsLocked( - InputState::CancelationOptions options, const char* reason); + const CancelationOptions& options); void synthesizeCancelationEventsForInputChannelLocked(const sp<InputChannel>& channel, - InputState::CancelationOptions options, const char* reason); + const CancelationOptions& options); void synthesizeCancelationEventsForConnectionLocked(const sp<Connection>& connection, - InputState::CancelationOptions options, const char* reason); + const CancelationOptions& options); // Splitting motion events across windows. MotionEntry* splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds); diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp index 3029028..98b3526 100644 --- a/services/input/InputReader.cpp +++ b/services/input/InputReader.cpp @@ -33,6 +33,8 @@ // Log debug messages about pointer assignment calculations. #define DEBUG_POINTER_ASSIGNMENT 0 +// Log debug messages about gesture detection. +#define DEBUG_GESTURES 0 #include "InputReader.h" @@ -54,6 +56,67 @@ namespace android { +// --- Constants --- + +// Quiet time between certain gesture transitions. +// Time to allow for all fingers or buttons to settle into a stable state before +// starting a new gesture. +static const nsecs_t QUIET_INTERVAL = 100 * 1000000; // 100 ms + +// The minimum speed that a pointer must travel for us to consider switching the active +// touch pointer to it during a drag. This threshold is set to avoid switching due +// to noise from a finger resting on the touch pad (perhaps just pressing it down). +static const float DRAG_MIN_SWITCH_SPEED = 50.0f; // pixels per second + +// Tap gesture delay time. +// The time between down and up must be less than this to be considered a tap. +static const nsecs_t TAP_INTERVAL = 150 * 1000000; // 150 ms + +// Tap drag gesture delay time. +// The time between up and the next up must be greater than this to be considered a +// drag. Otherwise, the previous tap is finished and a new tap begins. +static const nsecs_t TAP_DRAG_INTERVAL = 150 * 1000000; // 150 ms + +// The distance in pixels that the pointer is allowed to move from initial down +// to up and still be called a tap. +static const float TAP_SLOP = 10.0f; // 10 pixels + +// Time after the first touch points go down to settle on an initial centroid. +// This is intended to be enough time to handle cases where the user puts down two +// fingers at almost but not quite exactly the same time. +static const nsecs_t MULTITOUCH_SETTLE_INTERVAL = 100 * 1000000; // 100ms + +// The transition from PRESS to SWIPE or FREEFORM gesture mode is made when +// both of the pointers are moving at least this fast. +static const float MULTITOUCH_MIN_SPEED = 150.0f; // pixels per second + +// The transition from PRESS to SWIPE gesture mode can only occur when the +// cosine of the angle between the two vectors is greater than or equal to than this value +// which indicates that the vectors are oriented in the same direction. +// When the vectors are oriented in the exactly same direction, the cosine is 1.0. +// (In exactly opposite directions, the cosine is -1.0.) +static const float SWIPE_TRANSITION_ANGLE_COSINE = 0.5f; // cosine of 45 degrees + +// The transition from PRESS to SWIPE gesture mode can only occur when the +// fingers are no more than this far apart relative to the diagonal size of +// the touch pad. For example, a ratio of 0.5 means that the fingers must be +// no more than half the diagonal size of the touch pad apart. +static const float SWIPE_MAX_WIDTH_RATIO = 0.333f; // 1/3 + +// The gesture movement speed factor relative to the size of the display. +// Movement speed applies when the fingers are moving in the same direction. +// Without acceleration, a full swipe of the touch pad diagonal in movement mode +// will cover this portion of the display diagonal. +static const float GESTURE_MOVEMENT_SPEED_RATIO = 0.8f; + +// The gesture zoom speed factor relative to the size of the display. +// Zoom speed applies when the fingers are mostly moving relative to each other +// to execute a scale gesture or similar. +// Without acceleration, a full swipe of the touch pad diagonal in zoom mode +// will cover this portion of the display diagonal. +static const float GESTURE_ZOOM_SPEED_RATIO = 0.3f; + + // --- Static Functions --- template<typename T> @@ -77,8 +140,8 @@ inline static float avg(float x, float y) { return (x + y) / 2; } -inline static float pythag(float x, float y) { - return sqrtf(x * x + y * y); +inline static float distance(float x1, float y1, float x2, float y2) { + return hypotf(x1 - x2, y1 - y2); } inline static int32_t signExtendNybble(int32_t value) { @@ -183,6 +246,33 @@ static int32_t calculateEdgeFlagsUsingPointerBounds( return edgeFlags; } +static void clampPositionUsingPointerBounds( + const sp<PointerControllerInterface>& pointerController, float* x, float* y) { + float minX, minY, maxX, maxY; + if (pointerController->getBounds(&minX, &minY, &maxX, &maxY)) { + if (*x < minX) { + *x = minX; + } else if (*x > maxX) { + *x = maxX; + } + if (*y < minY) { + *y = minY; + } else if (*y > maxY) { + *y = maxY; + } + } +} + +static float calculateCommonVector(float a, float b) { + if (a > 0 && b > 0) { + return a < b ? a : b; + } else if (a < 0 && b < 0) { + return a > b ? a : b; + } else { + return 0; + } +} + // --- InputReader --- @@ -190,7 +280,7 @@ InputReader::InputReader(const sp<EventHubInterface>& eventHub, const sp<InputReaderPolicyInterface>& policy, const sp<InputDispatcherInterface>& dispatcher) : mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher), - mGlobalMetaState(0), mDisableVirtualKeysTimeout(-1) { + mGlobalMetaState(0), mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX) { configureExcludedDevices(); updateGlobalMetaState(); updateInputConfiguration(); @@ -203,35 +293,61 @@ InputReader::~InputReader() { } void InputReader::loopOnce() { - RawEvent rawEvent; - mEventHub->getEvent(& rawEvent); + int32_t timeoutMillis = -1; + if (mNextTimeout != LLONG_MAX) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout); + } + size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); + if (count) { + processEvents(mEventBuffer, count); + } + if (!count || timeoutMillis == 0) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); #if DEBUG_RAW_EVENTS - LOGD("Input event: device=%d type=0x%x scancode=%d keycode=%d value=%d", - rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode, - rawEvent.value); + LOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f); #endif - - process(& rawEvent); + mNextTimeout = LLONG_MAX; + timeoutExpired(now); + } } -void InputReader::process(const RawEvent* rawEvent) { - switch (rawEvent->type) { - case EventHubInterface::DEVICE_ADDED: - addDevice(rawEvent->deviceId); - break; - - case EventHubInterface::DEVICE_REMOVED: - removeDevice(rawEvent->deviceId); - break; - - case EventHubInterface::FINISHED_DEVICE_SCAN: - handleConfigurationChanged(rawEvent->when); - break; - - default: - consumeEvent(rawEvent); - break; +void InputReader::processEvents(const RawEvent* rawEvents, size_t count) { + for (const RawEvent* rawEvent = rawEvents; count;) { + int32_t type = rawEvent->type; + size_t batchSize = 1; + if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) { + int32_t deviceId = rawEvent->deviceId; + while (batchSize < count) { + if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT + || rawEvent[batchSize].deviceId != deviceId) { + break; + } + batchSize += 1; + } +#if DEBUG_RAW_EVENTS + LOGD("BatchSize: %d Count: %d", batchSize, count); +#endif + processEventsForDevice(deviceId, rawEvent, batchSize); + } else { + switch (rawEvent->type) { + case EventHubInterface::DEVICE_ADDED: + addDevice(rawEvent->deviceId); + break; + case EventHubInterface::DEVICE_REMOVED: + removeDevice(rawEvent->deviceId); + break; + case EventHubInterface::FINISHED_DEVICE_SCAN: + handleConfigurationChanged(rawEvent->when); + break; + default: + LOG_ASSERT(false); // can't happen + break; + } + } + count -= batchSize; + rawEvent += batchSize; } } @@ -352,9 +468,8 @@ InputDevice* InputReader::createDevice(int32_t deviceId, const String8& name, ui return device; } -void InputReader::consumeEvent(const RawEvent* rawEvent) { - int32_t deviceId = rawEvent->deviceId; - +void InputReader::processEventsForDevice(int32_t deviceId, + const RawEvent* rawEvents, size_t count) { { // acquire device registry reader lock RWLock::AutoRLock _rl(mDeviceRegistryLock); @@ -370,7 +485,20 @@ void InputReader::consumeEvent(const RawEvent* rawEvent) { return; } - device->process(rawEvent); + device->process(rawEvents, count); + } // release device registry reader lock +} + +void InputReader::timeoutExpired(nsecs_t when) { + { // acquire device registry reader lock + RWLock::AutoRLock _rl(mDeviceRegistryLock); + + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + if (!device->isIgnored()) { + device->timeoutExpired(when); + } + } } // release device registry reader lock } @@ -484,6 +612,12 @@ void InputReader::fadePointer() { } // release device registry reader lock } +void InputReader::requestTimeoutAtTime(nsecs_t when) { + if (when < mNextTimeout) { + mNextTimeout = when; + } +} + void InputReader::getInputConfiguration(InputConfiguration* outConfiguration) { { // acquire state lock AutoMutex _l(mStateLock); @@ -713,11 +847,33 @@ void InputDevice::reset() { } } -void InputDevice::process(const RawEvent* rawEvent) { +void InputDevice::process(const RawEvent* rawEvents, size_t count) { + // Process all of the events in order for each mapper. + // We cannot simply ask each mapper to process them in bulk because mappers may + // have side-effects that must be interleaved. For example, joystick movement events and + // gamepad button presses are handled by different mappers but they should be dispatched + // in the order received. + size_t numMappers = mMappers.size(); + for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) { +#if DEBUG_RAW_EVENTS + LOGD("Input event: device=%d type=0x%04x scancode=0x%04x " + "keycode=0x%04x value=0x%04x flags=0x%08x", + rawEvent->deviceId, rawEvent->type, rawEvent->scanCode, rawEvent->keyCode, + rawEvent->value, rawEvent->flags); +#endif + + for (size_t i = 0; i < numMappers; i++) { + InputMapper* mapper = mMappers[i]; + mapper->process(rawEvent); + } + } +} + +void InputDevice::timeoutExpired(nsecs_t when) { size_t numMappers = mMappers.size(); for (size_t i = 0; i < numMappers; i++) { InputMapper* mapper = mMappers[i]; - mapper->process(rawEvent); + mapper->timeoutExpired(when); } } @@ -812,6 +968,9 @@ void InputMapper::configure() { void InputMapper::reset() { } +void InputMapper::timeoutExpired(nsecs_t when) { +} + int32_t InputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) { return AKEY_STATE_UNKNOWN; } @@ -1268,7 +1427,7 @@ void CursorInputMapper::dumpParameters(String8& dump) { dump.append(INDENT4 "Mode: navigation\n"); break; default: - assert(false); + LOG_ASSERT(false); } dump.appendFormat(INDENT4 "OrientationAware: %s\n", @@ -1443,10 +1602,32 @@ void CursorInputMapper::sync(nsecs_t when) { motionEventEdgeFlags = AMOTION_EVENT_EDGE_FLAG_NONE; + if (mHaveVWheel && (fields & Accumulator::FIELD_REL_WHEEL)) { + vscroll = mAccumulator.relWheel; + } else { + vscroll = 0; + } + if (mHaveHWheel && (fields & Accumulator::FIELD_REL_HWHEEL)) { + hscroll = mAccumulator.relHWheel; + } else { + hscroll = 0; + } + if (mPointerController != NULL) { - mPointerController->move(deltaX, deltaY); - if (buttonsChanged) { - mPointerController->setButtonState(mLocked.buttonState); + if (deltaX != 0 || deltaY != 0 || vscroll != 0 || hscroll != 0 + || buttonsChanged) { + mPointerController->setPresentation( + PointerControllerInterface::PRESENTATION_POINTER); + + if (deltaX != 0 || deltaY != 0) { + mPointerController->move(deltaX, deltaY); + } + + if (buttonsChanged) { + mPointerController->setButtonState(mLocked.buttonState); + } + + mPointerController->unfade(); } float x, y; @@ -1464,20 +1645,6 @@ void CursorInputMapper::sync(nsecs_t when) { } pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f); - - if (mHaveVWheel && (fields & Accumulator::FIELD_REL_WHEEL)) { - vscroll = mAccumulator.relWheel; - } else { - vscroll = 0; - } - if (mHaveHWheel && (fields & Accumulator::FIELD_REL_HWHEEL)) { - hscroll = mAccumulator.relHWheel; - } else { - hscroll = 0; - } - if (hscroll != 0 || vscroll != 0) { - mPointerController->unfade(); - } } // release lock // Moving an external trackball or mouse should wake the device. @@ -1495,8 +1662,15 @@ void CursorInputMapper::sync(nsecs_t when) { motionEventAction, 0, metaState, motionEventEdgeFlags, 1, &pointerId, &pointerCoords, mXPrecision, mYPrecision, downTime); - mAccumulator.clear(); + // Send hover move after UP to tell the application that the mouse is hovering now. + if (motionEventAction == AMOTION_EVENT_ACTION_UP + && mPointerController != NULL) { + getDispatcher()->notifyMotion(when, getDeviceId(), mSource, policyFlags, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE, + 1, &pointerId, &pointerCoords, mXPrecision, mYPrecision, downTime); + } + // Send scroll events. if (vscroll != 0 || hscroll != 0) { pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll); pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll); @@ -1505,6 +1679,8 @@ void CursorInputMapper::sync(nsecs_t when) { AMOTION_EVENT_ACTION_SCROLL, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerId, &pointerCoords, mXPrecision, mYPrecision, downTime); } + + mAccumulator.clear(); } int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) { @@ -1540,7 +1716,7 @@ TouchInputMapper::~TouchInputMapper() { } uint32_t TouchInputMapper::getSources() { - return mTouchSource; + return mTouchSource | mPointerSource; } void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) { @@ -1579,6 +1755,18 @@ void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) { if (mLocked.orientedRanges.haveOrientation) { info->addMotionRange(mLocked.orientedRanges.orientation); } + + if (mPointerController != NULL) { + float minX, minY, maxX, maxY; + if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) { + info->addMotionRange(AMOTION_EVENT_AXIS_X, mPointerSource, + minX, maxX, 0.0f, 0.0f); + info->addMotionRange(AMOTION_EVENT_AXIS_Y, mPointerSource, + minY, maxY, 0.0f, 0.0f); + } + info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mPointerSource, + 0.0f, 1.0f, 0.0f, 0.0f); + } } // release lock } @@ -1608,6 +1796,21 @@ void TouchInputMapper::dump(String8& dump) { dump.appendFormat(INDENT3 "Last Touch:\n"); dump.appendFormat(INDENT4 "Pointer Count: %d\n", mLastTouch.pointerCount); + dump.appendFormat(INDENT4 "Button State: 0x%08x\n", mLastTouch.buttonState); + + if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) { + dump.appendFormat(INDENT3 "Pointer Gesture Detector:\n"); + dump.appendFormat(INDENT4 "XMovementScale: %0.3f\n", + mLocked.pointerGestureXMovementScale); + dump.appendFormat(INDENT4 "YMovementScale: %0.3f\n", + mLocked.pointerGestureYMovementScale); + dump.appendFormat(INDENT4 "XZoomScale: %0.3f\n", + mLocked.pointerGestureXZoomScale); + dump.appendFormat(INDENT4 "YZoomScale: %0.3f\n", + mLocked.pointerGestureYZoomScale); + dump.appendFormat(INDENT4 "MaxSwipeWidth: %f\n", + mLocked.pointerGestureMaxSwipeWidth); + } } // release lock } @@ -1630,6 +1833,8 @@ void TouchInputMapper::initializeLocked() { mLocked.orientedRanges.haveTouchSize = false; mLocked.orientedRanges.haveToolSize = false; mLocked.orientedRanges.haveOrientation = false; + + mPointerGesture.reset(); } void TouchInputMapper::configure() { @@ -1642,12 +1847,18 @@ void TouchInputMapper::configure() { switch (mParameters.deviceType) { case Parameters::DEVICE_TYPE_TOUCH_SCREEN: mTouchSource = AINPUT_SOURCE_TOUCHSCREEN; + mPointerSource = 0; break; case Parameters::DEVICE_TYPE_TOUCH_PAD: mTouchSource = AINPUT_SOURCE_TOUCHPAD; + mPointerSource = 0; + break; + case Parameters::DEVICE_TYPE_POINTER: + mTouchSource = AINPUT_SOURCE_TOUCHPAD; + mPointerSource = AINPUT_SOURCE_MOUSE; break; default: - assert(false); + LOG_ASSERT(false); } // Configure absolute axis information. @@ -1671,14 +1882,30 @@ void TouchInputMapper::configureParameters() { mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents(); mParameters.virtualKeyQuietTime = getPolicy()->getVirtualKeyQuietTime(); + // TODO: Make this configurable. + //mParameters.gestureMode = Parameters::GESTURE_MODE_POINTER; + mParameters.gestureMode = Parameters::GESTURE_MODE_SPOTS; + + if (getEventHub()->hasRelativeAxis(getDeviceId(), REL_X) + || getEventHub()->hasRelativeAxis(getDeviceId(), REL_Y)) { + // The device is a cursor device with a touch pad attached. + // By default don't use the touch pad to move the pointer. + mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD; + } else { + // The device is just a touch pad. + // By default use the touch pad to move the pointer and to perform related gestures. + mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER; + } + String8 deviceTypeString; - mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD; if (getDevice()->getConfiguration().tryGetProperty(String8("touch.deviceType"), deviceTypeString)) { if (deviceTypeString == "touchScreen") { mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN; } else if (deviceTypeString == "touchPad") { mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD; + } else if (deviceTypeString == "pointer") { + mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER; } else { LOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.string()); } @@ -1690,6 +1917,7 @@ void TouchInputMapper::configureParameters() { mParameters.associatedDisplayId = mParameters.orientationAware || mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN + || mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER ? 0 : -1; } @@ -1703,8 +1931,11 @@ void TouchInputMapper::dumpParameters(String8& dump) { case Parameters::DEVICE_TYPE_TOUCH_PAD: dump.append(INDENT4 "DeviceType: touchPad\n"); break; + case Parameters::DEVICE_TYPE_POINTER: + dump.append(INDENT4 "DeviceType: pointer\n"); + break; default: - assert(false); + LOG_ASSERT(false); } dump.appendFormat(INDENT4 "AssociatedDisplayId: %d\n", @@ -1776,6 +2007,11 @@ bool TouchInputMapper::configureSurfaceLocked() { } } + if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER + && mPointerController == NULL) { + mPointerController = getPolicy()->obtainPointerController(getDeviceId()); + } + bool orientationChanged = mLocked.surfaceOrientation != orientation; if (orientationChanged) { mLocked.surfaceOrientation = orientation; @@ -1808,7 +2044,7 @@ bool TouchInputMapper::configureSurfaceLocked() { mLocked.geometricScale = avg(mLocked.xScale, mLocked.yScale); // Size of diagonal axis. - float diagonalSize = pythag(width, height); + float diagonalSize = hypotf(width, height); // TouchMajor and TouchMinor factors. if (mCalibration.touchSizeCalibration != Calibration::TOUCH_SIZE_CALIBRATION_NONE) { @@ -1997,6 +2233,46 @@ bool TouchInputMapper::configureSurfaceLocked() { mLocked.orientedRanges.y.fuzz = mLocked.yScale; break; } + + // Compute pointer gesture detection parameters. + // TODO: These factors should not be hardcoded. + if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) { + int32_t rawWidth = mRawAxes.x.maxValue - mRawAxes.x.minValue + 1; + int32_t rawHeight = mRawAxes.y.maxValue - mRawAxes.y.minValue + 1; + float rawDiagonal = hypotf(rawWidth, rawHeight); + float displayDiagonal = hypotf(mLocked.associatedDisplayWidth, + mLocked.associatedDisplayHeight); + + // Scale movements such that one whole swipe of the touch pad covers a + // given area relative to the diagonal size of the display. + // Assume that the touch pad has a square aspect ratio such that movements in + // X and Y of the same number of raw units cover the same physical distance. + const float scaleFactor = 0.8f; + + mLocked.pointerGestureXMovementScale = GESTURE_MOVEMENT_SPEED_RATIO + * displayDiagonal / rawDiagonal; + mLocked.pointerGestureYMovementScale = mLocked.pointerGestureXMovementScale; + + // Scale zooms to cover a smaller range of the display than movements do. + // This value determines the area around the pointer that is affected by freeform + // pointer gestures. + mLocked.pointerGestureXZoomScale = GESTURE_ZOOM_SPEED_RATIO + * displayDiagonal / rawDiagonal; + mLocked.pointerGestureYZoomScale = mLocked.pointerGestureXZoomScale; + + // Max width between pointers to detect a swipe gesture is more than some fraction + // of the diagonal axis of the touch pad. Touches that are wider than this are + // translated into freeform gestures. + mLocked.pointerGestureMaxSwipeWidth = SWIPE_MAX_WIDTH_RATIO * rawDiagonal; + + // Reset the current pointer gesture. + mPointerGesture.reset(); + + // Remove any current spots. + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerController->clearSpots(); + } + } } return true; @@ -2303,7 +2579,7 @@ void TouchInputMapper::dumpCalibration(String8& dump) { dump.append(INDENT4 "touch.touchSize.calibration: pressure\n"); break; default: - assert(false); + LOG_ASSERT(false); } // Tool Size @@ -2321,7 +2597,7 @@ void TouchInputMapper::dumpCalibration(String8& dump) { dump.append(INDENT4 "touch.toolSize.calibration: area\n"); break; default: - assert(false); + LOG_ASSERT(false); } if (mCalibration.haveToolSizeLinearScale) { @@ -2361,7 +2637,7 @@ void TouchInputMapper::dumpCalibration(String8& dump) { dump.append(INDENT4 "touch.pressure.calibration: amplitude\n"); break; default: - assert(false); + LOG_ASSERT(false); } switch (mCalibration.pressureSource) { @@ -2374,7 +2650,7 @@ void TouchInputMapper::dumpCalibration(String8& dump) { case Calibration::PRESSURE_SOURCE_DEFAULT: break; default: - assert(false); + LOG_ASSERT(false); } if (mCalibration.havePressureScale) { @@ -2391,7 +2667,7 @@ void TouchInputMapper::dumpCalibration(String8& dump) { dump.append(INDENT4 "touch.size.calibration: normalized\n"); break; default: - assert(false); + LOG_ASSERT(false); } // Orientation @@ -2406,7 +2682,7 @@ void TouchInputMapper::dumpCalibration(String8& dump) { dump.append(INDENT4 "touch.orientation.calibration: vector\n"); break; default: - assert(false); + LOG_ASSERT(false); } } @@ -2422,12 +2698,30 @@ void TouchInputMapper::reset() { { // acquire lock AutoMutex _l(mLock); initializeLocked(); + + if (mPointerController != NULL + && mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerController->clearSpots(); + } } // release lock InputMapper::reset(); } void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) { +#if DEBUG_RAW_EVENTS + if (!havePointerIds) { + LOGD("syncTouch: pointerCount=%d, no pointer ids", mCurrentTouch.pointerCount); + } else { + LOGD("syncTouch: pointerCount=%d, up=0x%08x, down=0x%08x, move=0x%08x, " + "last=0x%08x, current=0x%08x", mCurrentTouch.pointerCount, + mLastTouch.idBits.value & ~mCurrentTouch.idBits.value, + mCurrentTouch.idBits.value & ~mLastTouch.idBits.value, + mLastTouch.idBits.value & mCurrentTouch.idBits.value, + mLastTouch.idBits.value, mCurrentTouch.idBits.value); + } +#endif + // Preprocess pointer data. if (mParameters.useBadTouchFilter) { if (applyBadTouchFilter()) { @@ -2441,7 +2735,7 @@ void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) { } } - if (! havePointerIds) { + if (!havePointerIds) { calculatePointerIds(); } @@ -2472,21 +2766,39 @@ void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) { } } - // Process touches and virtual keys. - TouchResult touchResult = consumeOffScreenTouches(when, policyFlags); - if (touchResult == DISPATCH_TOUCH) { - suppressSwipeOntoVirtualKeys(when); - dispatchTouches(when, policyFlags); + TouchResult touchResult; + if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount == 0 + && mLastTouch.buttonState == mCurrentTouch.buttonState) { + // Drop spurious syncs. + touchResult = DROP_STROKE; + } else { + // Process touches and virtual keys. + touchResult = consumeOffScreenTouches(when, policyFlags); + if (touchResult == DISPATCH_TOUCH) { + suppressSwipeOntoVirtualKeys(when); + if (mPointerController != NULL) { + dispatchPointerGestures(when, policyFlags, false /*isTimeout*/); + } + dispatchTouches(when, policyFlags); + } } // Copy current touch to last touch in preparation for the next cycle. + // Keep the button state so we can track edge-triggered button state changes. if (touchResult == DROP_STROKE) { mLastTouch.clear(); + mLastTouch.buttonState = savedTouch->buttonState; } else { mLastTouch.copyFrom(*savedTouch); } } +void TouchInputMapper::timeoutExpired(nsecs_t when) { + if (mPointerController != NULL) { + dispatchPointerGestures(when, 0 /*policyFlags*/, true /*isTimeout*/); + } +} + TouchInputMapper::TouchResult TouchInputMapper::consumeOffScreenTouches( nsecs_t when, uint32_t policyFlags) { int32_t keyEventAction, keyEventFlags; @@ -2629,329 +2941,1330 @@ void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) { return; // nothing to do! } + // Update current touch coordinates. + int32_t edgeFlags; + float xPrecision, yPrecision; + prepareTouches(&edgeFlags, &xPrecision, &yPrecision); + + // Dispatch motions. BitSet32 currentIdBits = mCurrentTouch.idBits; BitSet32 lastIdBits = mLastTouch.idBits; + uint32_t metaState = getContext()->getGlobalMetaState(); if (currentIdBits == lastIdBits) { // No pointer id changes so this is a move event. // The dispatcher takes care of batching moves so we don't have to deal with that here. - int32_t motionEventAction = AMOTION_EVENT_ACTION_MOVE; - dispatchTouch(when, policyFlags, & mCurrentTouch, - currentIdBits, -1, currentPointerCount, motionEventAction); + dispatchMotion(when, policyFlags, mTouchSource, + AMOTION_EVENT_ACTION_MOVE, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE, + mCurrentTouchCoords, mCurrentTouch.idToIndex, currentIdBits, -1, + xPrecision, yPrecision, mDownTime); } else { // There may be pointers going up and pointers going down and pointers moving // all at the same time. - BitSet32 upIdBits(lastIdBits.value & ~ currentIdBits.value); - BitSet32 downIdBits(currentIdBits.value & ~ lastIdBits.value); - BitSet32 activeIdBits(lastIdBits.value); - uint32_t pointerCount = lastPointerCount; - - // Produce an intermediate representation of the touch data that consists of the - // old location of pointers that have just gone up and the new location of pointers that - // have just moved but omits the location of pointers that have just gone down. - TouchData interimTouch; - interimTouch.copyFrom(mLastTouch); - + BitSet32 upIdBits(lastIdBits.value & ~currentIdBits.value); + BitSet32 downIdBits(currentIdBits.value & ~lastIdBits.value); BitSet32 moveIdBits(lastIdBits.value & currentIdBits.value); - bool moveNeeded = false; - while (!moveIdBits.isEmpty()) { - uint32_t moveId = moveIdBits.firstMarkedBit(); - moveIdBits.clearBit(moveId); - - int32_t oldIndex = mLastTouch.idToIndex[moveId]; - int32_t newIndex = mCurrentTouch.idToIndex[moveId]; - if (mLastTouch.pointers[oldIndex] != mCurrentTouch.pointers[newIndex]) { - interimTouch.pointers[oldIndex] = mCurrentTouch.pointers[newIndex]; - moveNeeded = true; - } - } + BitSet32 dispatchedIdBits(lastIdBits.value); + + // Update last coordinates of pointers that have moved so that we observe the new + // pointer positions at the same time as other pointers that have just gone up. + bool moveNeeded = updateMovedPointerCoords( + mCurrentTouchCoords, mCurrentTouch.idToIndex, + mLastTouchCoords, mLastTouch.idToIndex, + moveIdBits); - // Dispatch pointer up events using the interim pointer locations. + // Dispatch pointer up events. while (!upIdBits.isEmpty()) { uint32_t upId = upIdBits.firstMarkedBit(); upIdBits.clearBit(upId); - BitSet32 oldActiveIdBits = activeIdBits; - activeIdBits.clearBit(upId); - int32_t motionEventAction; - if (activeIdBits.isEmpty()) { - motionEventAction = AMOTION_EVENT_ACTION_UP; - } else { - motionEventAction = AMOTION_EVENT_ACTION_POINTER_UP; - } - - dispatchTouch(when, policyFlags, &interimTouch, - oldActiveIdBits, upId, pointerCount, motionEventAction); - pointerCount -= 1; + dispatchMotion(when, policyFlags, mTouchSource, + AMOTION_EVENT_ACTION_POINTER_UP, 0, metaState, 0, + mLastTouchCoords, mLastTouch.idToIndex, dispatchedIdBits, upId, + xPrecision, yPrecision, mDownTime); + dispatchedIdBits.clearBit(upId); } // Dispatch move events if any of the remaining pointers moved from their old locations. // Although applications receive new locations as part of individual pointer up // events, they do not generally handle them except when presented in a move event. if (moveNeeded) { - dispatchTouch(when, policyFlags, &mCurrentTouch, - activeIdBits, -1, pointerCount, AMOTION_EVENT_ACTION_MOVE); + LOG_ASSERT(moveIdBits.value == dispatchedIdBits.value); + dispatchMotion(when, policyFlags, mTouchSource, + AMOTION_EVENT_ACTION_MOVE, 0, metaState, 0, + mCurrentTouchCoords, mCurrentTouch.idToIndex, dispatchedIdBits, -1, + xPrecision, yPrecision, mDownTime); } // Dispatch pointer down events using the new pointer locations. while (!downIdBits.isEmpty()) { uint32_t downId = downIdBits.firstMarkedBit(); downIdBits.clearBit(downId); - BitSet32 oldActiveIdBits = activeIdBits; - activeIdBits.markBit(downId); + dispatchedIdBits.markBit(downId); - int32_t motionEventAction; - if (oldActiveIdBits.isEmpty()) { - motionEventAction = AMOTION_EVENT_ACTION_DOWN; + if (dispatchedIdBits.count() == 1) { + // First pointer is going down. Set down time. mDownTime = when; } else { - motionEventAction = AMOTION_EVENT_ACTION_POINTER_DOWN; + // Only send edge flags with first pointer down. + edgeFlags = AMOTION_EVENT_EDGE_FLAG_NONE; } - pointerCount += 1; - dispatchTouch(when, policyFlags, &mCurrentTouch, - activeIdBits, downId, pointerCount, motionEventAction); + dispatchMotion(when, policyFlags, mTouchSource, + AMOTION_EVENT_ACTION_POINTER_DOWN, 0, metaState, edgeFlags, + mCurrentTouchCoords, mCurrentTouch.idToIndex, dispatchedIdBits, downId, + xPrecision, yPrecision, mDownTime); } } + + // Update state for next time. + for (uint32_t i = 0; i < currentPointerCount; i++) { + mLastTouchCoords[i].copyFrom(mCurrentTouchCoords[i]); + } } -void TouchInputMapper::dispatchTouch(nsecs_t when, uint32_t policyFlags, - TouchData* touch, BitSet32 idBits, uint32_t changedId, uint32_t pointerCount, - int32_t motionEventAction) { - int32_t pointerIds[MAX_POINTERS]; - PointerCoords pointerCoords[MAX_POINTERS]; - int32_t motionEventEdgeFlags = AMOTION_EVENT_EDGE_FLAG_NONE; - float xPrecision, yPrecision; +void TouchInputMapper::prepareTouches(int32_t* outEdgeFlags, + float* outXPrecision, float* outYPrecision) { + uint32_t currentPointerCount = mCurrentTouch.pointerCount; + uint32_t lastPointerCount = mLastTouch.pointerCount; - { // acquire lock - AutoMutex _l(mLock); + AutoMutex _l(mLock); + + // Walk through the the active pointers and map touch screen coordinates (TouchData) into + // display or surface coordinates (PointerCoords) and adjust for display orientation. + for (uint32_t i = 0; i < currentPointerCount; i++) { + const PointerData& in = mCurrentTouch.pointers[i]; + + // ToolMajor and ToolMinor + float toolMajor, toolMinor; + switch (mCalibration.toolSizeCalibration) { + case Calibration::TOOL_SIZE_CALIBRATION_GEOMETRIC: + toolMajor = in.toolMajor * mLocked.geometricScale; + if (mRawAxes.toolMinor.valid) { + toolMinor = in.toolMinor * mLocked.geometricScale; + } else { + toolMinor = toolMajor; + } + break; + case Calibration::TOOL_SIZE_CALIBRATION_LINEAR: + toolMajor = in.toolMajor != 0 + ? in.toolMajor * mLocked.toolSizeLinearScale + mLocked.toolSizeLinearBias + : 0; + if (mRawAxes.toolMinor.valid) { + toolMinor = in.toolMinor != 0 + ? in.toolMinor * mLocked.toolSizeLinearScale + + mLocked.toolSizeLinearBias + : 0; + } else { + toolMinor = toolMajor; + } + break; + case Calibration::TOOL_SIZE_CALIBRATION_AREA: + if (in.toolMajor != 0) { + float diameter = sqrtf(in.toolMajor + * mLocked.toolSizeAreaScale + mLocked.toolSizeAreaBias); + toolMajor = diameter * mLocked.toolSizeLinearScale + mLocked.toolSizeLinearBias; + } else { + toolMajor = 0; + } + toolMinor = toolMajor; + break; + default: + toolMajor = 0; + toolMinor = 0; + break; + } + + if (mCalibration.haveToolSizeIsSummed && mCalibration.toolSizeIsSummed) { + toolMajor /= currentPointerCount; + toolMinor /= currentPointerCount; + } + + // Pressure + float rawPressure; + switch (mCalibration.pressureSource) { + case Calibration::PRESSURE_SOURCE_PRESSURE: + rawPressure = in.pressure; + break; + case Calibration::PRESSURE_SOURCE_TOUCH: + rawPressure = in.touchMajor; + break; + default: + rawPressure = 0; + } + + float pressure; + switch (mCalibration.pressureCalibration) { + case Calibration::PRESSURE_CALIBRATION_PHYSICAL: + case Calibration::PRESSURE_CALIBRATION_AMPLITUDE: + pressure = rawPressure * mLocked.pressureScale; + break; + default: + pressure = 1; + break; + } + + // TouchMajor and TouchMinor + float touchMajor, touchMinor; + switch (mCalibration.touchSizeCalibration) { + case Calibration::TOUCH_SIZE_CALIBRATION_GEOMETRIC: + touchMajor = in.touchMajor * mLocked.geometricScale; + if (mRawAxes.touchMinor.valid) { + touchMinor = in.touchMinor * mLocked.geometricScale; + } else { + touchMinor = touchMajor; + } + break; + case Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE: + touchMajor = toolMajor * pressure; + touchMinor = toolMinor * pressure; + break; + default: + touchMajor = 0; + touchMinor = 0; + break; + } + + if (touchMajor > toolMajor) { + touchMajor = toolMajor; + } + if (touchMinor > toolMinor) { + touchMinor = toolMinor; + } + + // Size + float size; + switch (mCalibration.sizeCalibration) { + case Calibration::SIZE_CALIBRATION_NORMALIZED: { + float rawSize = mRawAxes.toolMinor.valid + ? avg(in.toolMajor, in.toolMinor) + : in.toolMajor; + size = rawSize * mLocked.sizeScale; + break; + } + default: + size = 0; + break; + } + + // Orientation + float orientation; + switch (mCalibration.orientationCalibration) { + case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED: + orientation = in.orientation * mLocked.orientationScale; + break; + case Calibration::ORIENTATION_CALIBRATION_VECTOR: { + int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4); + int32_t c2 = signExtendNybble(in.orientation & 0x0f); + if (c1 != 0 || c2 != 0) { + orientation = atan2f(c1, c2) * 0.5f; + float scale = 1.0f + hypotf(c1, c2) / 16.0f; + touchMajor *= scale; + touchMinor /= scale; + toolMajor *= scale; + toolMinor /= scale; + } else { + orientation = 0; + } + break; + } + default: + orientation = 0; + } + + // X and Y + // Adjust coords for surface orientation. + float x, y; + switch (mLocked.surfaceOrientation) { + case DISPLAY_ORIENTATION_90: + x = float(in.y - mRawAxes.y.minValue) * mLocked.yScale; + y = float(mRawAxes.x.maxValue - in.x) * mLocked.xScale; + orientation -= M_PI_2; + if (orientation < - M_PI_2) { + orientation += M_PI; + } + break; + case DISPLAY_ORIENTATION_180: + x = float(mRawAxes.x.maxValue - in.x) * mLocked.xScale; + y = float(mRawAxes.y.maxValue - in.y) * mLocked.yScale; + break; + case DISPLAY_ORIENTATION_270: + x = float(mRawAxes.y.maxValue - in.y) * mLocked.yScale; + y = float(in.x - mRawAxes.x.minValue) * mLocked.xScale; + orientation += M_PI_2; + if (orientation > M_PI_2) { + orientation -= M_PI; + } + break; + default: + x = float(in.x - mRawAxes.x.minValue) * mLocked.xScale; + y = float(in.y - mRawAxes.y.minValue) * mLocked.yScale; + break; + } + + // Write output coords. + PointerCoords& out = mCurrentTouchCoords[i]; + out.clear(); + out.setAxisValue(AMOTION_EVENT_AXIS_X, x); + out.setAxisValue(AMOTION_EVENT_AXIS_Y, y); + out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure); + out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size); + out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor); + out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, touchMinor); + out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor); + out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor); + out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation); + } + + // Check edge flags by looking only at the first pointer since the flags are + // global to the event. + *outEdgeFlags = AMOTION_EVENT_EDGE_FLAG_NONE; + if (lastPointerCount == 0 && currentPointerCount > 0) { + const PointerData& in = mCurrentTouch.pointers[0]; + + if (in.x <= mRawAxes.x.minValue) { + *outEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_LEFT, + mLocked.surfaceOrientation); + } else if (in.x >= mRawAxes.x.maxValue) { + *outEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_RIGHT, + mLocked.surfaceOrientation); + } + if (in.y <= mRawAxes.y.minValue) { + *outEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_TOP, + mLocked.surfaceOrientation); + } else if (in.y >= mRawAxes.y.maxValue) { + *outEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_BOTTOM, + mLocked.surfaceOrientation); + } + } + + *outXPrecision = mLocked.orientedXPrecision; + *outYPrecision = mLocked.orientedYPrecision; +} - // Walk through the the active pointers and map touch screen coordinates (TouchData) into - // display or surface coordinates (PointerCoords) and adjust for display orientation. - for (uint32_t outIndex = 0; ! idBits.isEmpty(); outIndex++) { +void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, + bool isTimeout) { + // Switch pointer presentation. + mPointerController->setPresentation( + mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS + ? PointerControllerInterface::PRESENTATION_SPOT + : PointerControllerInterface::PRESENTATION_POINTER); + + // Update current gesture coordinates. + bool cancelPreviousGesture, finishPreviousGesture; + bool sendEvents = preparePointerGestures(when, + &cancelPreviousGesture, &finishPreviousGesture, isTimeout); + if (!sendEvents) { + return; + } + + // Show the pointer if needed. + if (mPointerGesture.currentGestureMode != PointerGesture::NEUTRAL + && mPointerGesture.currentGestureMode != PointerGesture::QUIET) { + mPointerController->unfade(); + } + + // Send events! + uint32_t metaState = getContext()->getGlobalMetaState(); + + // Update last coordinates of pointers that have moved so that we observe the new + // pointer positions at the same time as other pointers that have just gone up. + bool down = mPointerGesture.currentGestureMode == PointerGesture::TAP + || mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG + || mPointerGesture.currentGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG + || mPointerGesture.currentGestureMode == PointerGesture::PRESS + || mPointerGesture.currentGestureMode == PointerGesture::SWIPE + || mPointerGesture.currentGestureMode == PointerGesture::FREEFORM; + bool moveNeeded = false; + if (down && !cancelPreviousGesture && !finishPreviousGesture + && !mPointerGesture.lastGestureIdBits.isEmpty() + && !mPointerGesture.currentGestureIdBits.isEmpty()) { + BitSet32 movedGestureIdBits(mPointerGesture.currentGestureIdBits.value + & mPointerGesture.lastGestureIdBits.value); + moveNeeded = updateMovedPointerCoords( + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, + movedGestureIdBits); + } + + // Send motion events for all pointers that went up or were canceled. + BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits); + if (!dispatchedGestureIdBits.isEmpty()) { + if (cancelPreviousGesture) { + dispatchMotion(when, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_CANCEL, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, + dispatchedGestureIdBits, -1, + 0, 0, mPointerGesture.downTime); + + dispatchedGestureIdBits.clear(); + } else { + BitSet32 upGestureIdBits; + if (finishPreviousGesture) { + upGestureIdBits = dispatchedGestureIdBits; + } else { + upGestureIdBits.value = dispatchedGestureIdBits.value + & ~mPointerGesture.currentGestureIdBits.value; + } + while (!upGestureIdBits.isEmpty()) { + uint32_t id = upGestureIdBits.firstMarkedBit(); + upGestureIdBits.clearBit(id); + + dispatchMotion(when, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_POINTER_UP, 0, + metaState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, + dispatchedGestureIdBits, id, + 0, 0, mPointerGesture.downTime); + + dispatchedGestureIdBits.clearBit(id); + } + } + } + + // Send motion events for all pointers that moved. + if (moveNeeded) { + dispatchMotion(when, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_MOVE, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + dispatchedGestureIdBits, -1, + 0, 0, mPointerGesture.downTime); + } + + // Send motion events for all pointers that went down. + if (down) { + BitSet32 downGestureIdBits(mPointerGesture.currentGestureIdBits.value + & ~dispatchedGestureIdBits.value); + while (!downGestureIdBits.isEmpty()) { + uint32_t id = downGestureIdBits.firstMarkedBit(); + downGestureIdBits.clearBit(id); + dispatchedGestureIdBits.markBit(id); + + int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_NONE; + if (dispatchedGestureIdBits.count() == 1) { + // First pointer is going down. Calculate edge flags and set down time. + uint32_t index = mPointerGesture.currentGestureIdToIndex[id]; + const PointerCoords& downCoords = mPointerGesture.currentGestureCoords[index]; + edgeFlags = calculateEdgeFlagsUsingPointerBounds(mPointerController, + downCoords.getAxisValue(AMOTION_EVENT_AXIS_X), + downCoords.getAxisValue(AMOTION_EVENT_AXIS_Y)); + mPointerGesture.downTime = when; + } + + dispatchMotion(when, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_POINTER_DOWN, 0, metaState, edgeFlags, + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + dispatchedGestureIdBits, id, + 0, 0, mPointerGesture.downTime); + } + } + + // Send motion events for hover. + if (mPointerGesture.currentGestureMode == PointerGesture::HOVER) { + dispatchMotion(when, policyFlags, mPointerSource, + AMOTION_EVENT_ACTION_HOVER_MOVE, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE, + mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex, + mPointerGesture.currentGestureIdBits, -1, + 0, 0, mPointerGesture.downTime); + } + + // Update state. + mPointerGesture.lastGestureMode = mPointerGesture.currentGestureMode; + if (!down) { + mPointerGesture.lastGestureIdBits.clear(); + } else { + mPointerGesture.lastGestureIdBits = mPointerGesture.currentGestureIdBits; + for (BitSet32 idBits(mPointerGesture.currentGestureIdBits); !idBits.isEmpty(); ) { uint32_t id = idBits.firstMarkedBit(); idBits.clearBit(id); - uint32_t inIndex = touch->idToIndex[id]; + uint32_t index = mPointerGesture.currentGestureIdToIndex[id]; + mPointerGesture.lastGestureCoords[index].copyFrom( + mPointerGesture.currentGestureCoords[index]); + mPointerGesture.lastGestureIdToIndex[id] = index; + } + } +} - const PointerData& in = touch->pointers[inIndex]; +bool TouchInputMapper::preparePointerGestures(nsecs_t when, + bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout) { + *outCancelPreviousGesture = false; + *outFinishPreviousGesture = false; - // ToolMajor and ToolMinor - float toolMajor, toolMinor; - switch (mCalibration.toolSizeCalibration) { - case Calibration::TOOL_SIZE_CALIBRATION_GEOMETRIC: - toolMajor = in.toolMajor * mLocked.geometricScale; - if (mRawAxes.toolMinor.valid) { - toolMinor = in.toolMinor * mLocked.geometricScale; - } else { - toolMinor = toolMajor; + AutoMutex _l(mLock); + + // Handle TAP timeout. + if (isTimeout) { +#if DEBUG_GESTURES + LOGD("Gestures: Processing timeout"); +#endif + + if (mPointerGesture.lastGestureMode == PointerGesture::TAP) { + if (when <= mPointerGesture.tapUpTime + TAP_DRAG_INTERVAL) { + // The tap/drag timeout has not yet expired. + getContext()->requestTimeoutAtTime(mPointerGesture.tapUpTime + TAP_DRAG_INTERVAL); + } else { + // The tap is finished. +#if DEBUG_GESTURES + LOGD("Gestures: TAP finished"); +#endif + *outFinishPreviousGesture = true; + + mPointerGesture.activeGestureId = -1; + mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL; + mPointerGesture.currentGestureIdBits.clear(); + + mPointerController->setButtonState(0); + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL; + mPointerGesture.spotIdBits.clear(); + moveSpotsLocked(); } - break; - case Calibration::TOOL_SIZE_CALIBRATION_LINEAR: - toolMajor = in.toolMajor != 0 - ? in.toolMajor * mLocked.toolSizeLinearScale + mLocked.toolSizeLinearBias - : 0; - if (mRawAxes.toolMinor.valid) { - toolMinor = in.toolMinor != 0 - ? in.toolMinor * mLocked.toolSizeLinearScale - + mLocked.toolSizeLinearBias - : 0; - } else { - toolMinor = toolMajor; + return true; + } + } + + // We did not handle this timeout. + return false; + } + + // Update the velocity tracker. + { + VelocityTracker::Position positions[MAX_POINTERS]; + uint32_t count = 0; + for (BitSet32 idBits(mCurrentTouch.idBits); !idBits.isEmpty(); count++) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + uint32_t index = mCurrentTouch.idToIndex[id]; + positions[count].x = mCurrentTouch.pointers[index].x + * mLocked.pointerGestureXMovementScale; + positions[count].y = mCurrentTouch.pointers[index].y + * mLocked.pointerGestureYMovementScale; + } + mPointerGesture.velocityTracker.addMovement(when, mCurrentTouch.idBits, positions); + } + + // Pick a new active touch id if needed. + // Choose an arbitrary pointer that just went down, if there is one. + // Otherwise choose an arbitrary remaining pointer. + // This guarantees we always have an active touch id when there is at least one pointer. + // We keep the same active touch id for as long as possible. + bool activeTouchChanged = false; + int32_t lastActiveTouchId = mPointerGesture.activeTouchId; + int32_t activeTouchId = lastActiveTouchId; + if (activeTouchId < 0) { + if (!mCurrentTouch.idBits.isEmpty()) { + activeTouchChanged = true; + activeTouchId = mPointerGesture.activeTouchId = mCurrentTouch.idBits.firstMarkedBit(); + mPointerGesture.firstTouchTime = when; + } + } else if (!mCurrentTouch.idBits.hasBit(activeTouchId)) { + activeTouchChanged = true; + if (!mCurrentTouch.idBits.isEmpty()) { + activeTouchId = mPointerGesture.activeTouchId = mCurrentTouch.idBits.firstMarkedBit(); + } else { + activeTouchId = mPointerGesture.activeTouchId = -1; + } + } + + // Determine whether we are in quiet time. + bool isQuietTime = false; + if (activeTouchId < 0) { + mPointerGesture.resetQuietTime(); + } else { + isQuietTime = when < mPointerGesture.quietTime + QUIET_INTERVAL; + if (!isQuietTime) { + if ((mPointerGesture.lastGestureMode == PointerGesture::PRESS + || mPointerGesture.lastGestureMode == PointerGesture::SWIPE + || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM) + && mCurrentTouch.pointerCount < 2) { + // Enter quiet time when exiting swipe or freeform state. + // This is to prevent accidentally entering the hover state and flinging the + // pointer when finishing a swipe and there is still one pointer left onscreen. + isQuietTime = true; + } else if (mPointerGesture.lastGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG + && mCurrentTouch.pointerCount >= 2 + && !isPointerDown(mCurrentTouch.buttonState)) { + // Enter quiet time when releasing the button and there are still two or more + // fingers down. This may indicate that one finger was used to press the button + // but it has not gone up yet. + isQuietTime = true; + } + if (isQuietTime) { + mPointerGesture.quietTime = when; + } + } + } + + // Switch states based on button and pointer state. + if (isQuietTime) { + // Case 1: Quiet time. (QUIET) +#if DEBUG_GESTURES + LOGD("Gestures: QUIET for next %0.3fms", + (mPointerGesture.quietTime + QUIET_INTERVAL - when) * 0.000001f); +#endif + *outFinishPreviousGesture = true; + + mPointerGesture.activeGestureId = -1; + mPointerGesture.currentGestureMode = PointerGesture::QUIET; + mPointerGesture.currentGestureIdBits.clear(); + + mPointerController->setButtonState(0); + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL; + mPointerGesture.spotIdBits.clear(); + moveSpotsLocked(); + } + } else if (isPointerDown(mCurrentTouch.buttonState)) { + // Case 2: Button is pressed. (BUTTON_CLICK_OR_DRAG) + // The pointer follows the active touch point. + // Emit DOWN, MOVE, UP events at the pointer location. + // + // Only the active touch matters; other fingers are ignored. This policy helps + // to handle the case where the user places a second finger on the touch pad + // to apply the necessary force to depress an integrated button below the surface. + // We don't want the second finger to be delivered to applications. + // + // For this to work well, we need to make sure to track the pointer that is really + // active. If the user first puts one finger down to click then adds another + // finger to drag then the active pointer should switch to the finger that is + // being dragged. +#if DEBUG_GESTURES + LOGD("Gestures: BUTTON_CLICK_OR_DRAG activeTouchId=%d, " + "currentTouchPointerCount=%d", activeTouchId, mCurrentTouch.pointerCount); +#endif + // Reset state when just starting. + if (mPointerGesture.lastGestureMode != PointerGesture::BUTTON_CLICK_OR_DRAG) { + *outFinishPreviousGesture = true; + mPointerGesture.activeGestureId = 0; + } + + // Switch pointers if needed. + // Find the fastest pointer and follow it. + if (activeTouchId >= 0) { + if (mCurrentTouch.pointerCount > 1) { + int32_t bestId = -1; + float bestSpeed = DRAG_MIN_SWITCH_SPEED; + for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) { + uint32_t id = mCurrentTouch.pointers[i].id; + float vx, vy; + if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) { + float speed = hypotf(vx, vy); + if (speed > bestSpeed) { + bestId = id; + bestSpeed = speed; + } + } } - break; - case Calibration::TOOL_SIZE_CALIBRATION_AREA: - if (in.toolMajor != 0) { - float diameter = sqrtf(in.toolMajor - * mLocked.toolSizeAreaScale + mLocked.toolSizeAreaBias); - toolMajor = diameter * mLocked.toolSizeLinearScale + mLocked.toolSizeLinearBias; - } else { - toolMajor = 0; + if (bestId >= 0 && bestId != activeTouchId) { + mPointerGesture.activeTouchId = activeTouchId = bestId; + activeTouchChanged = true; +#if DEBUG_GESTURES + LOGD("Gestures: BUTTON_CLICK_OR_DRAG switched pointers, " + "bestId=%d, bestSpeed=%0.3f", bestId, bestSpeed); +#endif } - toolMinor = toolMajor; - break; - default: - toolMajor = 0; - toolMinor = 0; - break; } - if (mCalibration.haveToolSizeIsSummed && mCalibration.toolSizeIsSummed) { - toolMajor /= pointerCount; - toolMinor /= pointerCount; + if (mLastTouch.idBits.hasBit(activeTouchId)) { + const PointerData& currentPointer = + mCurrentTouch.pointers[mCurrentTouch.idToIndex[activeTouchId]]; + const PointerData& lastPointer = + mLastTouch.pointers[mLastTouch.idToIndex[activeTouchId]]; + float deltaX = (currentPointer.x - lastPointer.x) + * mLocked.pointerGestureXMovementScale; + float deltaY = (currentPointer.y - lastPointer.y) + * mLocked.pointerGestureYMovementScale; + + // Move the pointer using a relative motion. + // When using spots, the click will occur at the position of the anchor + // spot and all other spots will move there. + mPointerController->move(deltaX, deltaY); } + } - // Pressure - float rawPressure; - switch (mCalibration.pressureSource) { - case Calibration::PRESSURE_SOURCE_PRESSURE: - rawPressure = in.pressure; - break; - case Calibration::PRESSURE_SOURCE_TOUCH: - rawPressure = in.touchMajor; - break; - default: - rawPressure = 0; + float x, y; + mPointerController->getPosition(&x, &y); + + mPointerGesture.currentGestureMode = PointerGesture::BUTTON_CLICK_OR_DRAG; + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + + mPointerController->setButtonState(BUTTON_STATE_PRIMARY); + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + if (activeTouchId >= 0) { + // Collapse all spots into one point at the pointer location. + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_BUTTON_DRAG; + mPointerGesture.spotIdBits.clear(); + for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) { + uint32_t id = mCurrentTouch.pointers[i].id; + mPointerGesture.spotIdBits.markBit(id); + mPointerGesture.spotIdToIndex[id] = i; + mPointerGesture.spotCoords[i] = mPointerGesture.currentGestureCoords[0]; + } + } else { + // No fingers. Generate a spot at the pointer location so the + // anchor appears to be pressed. + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_BUTTON_CLICK; + mPointerGesture.spotIdBits.clear(); + mPointerGesture.spotIdBits.markBit(0); + mPointerGesture.spotIdToIndex[0] = 0; + mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0]; } + moveSpotsLocked(); + } + } else if (mCurrentTouch.pointerCount == 0) { + // Case 3. No fingers down and button is not pressed. (NEUTRAL) + *outFinishPreviousGesture = true; + + // Watch for taps coming out of HOVER or TAP_DRAG mode. + bool tapped = false; + if ((mPointerGesture.lastGestureMode == PointerGesture::HOVER + || mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG) + && mLastTouch.pointerCount == 1) { + if (when <= mPointerGesture.tapDownTime + TAP_INTERVAL) { + float x, y; + mPointerController->getPosition(&x, &y); + if (fabs(x - mPointerGesture.tapX) <= TAP_SLOP + && fabs(y - mPointerGesture.tapY) <= TAP_SLOP) { +#if DEBUG_GESTURES + LOGD("Gestures: TAP"); +#endif - float pressure; - switch (mCalibration.pressureCalibration) { - case Calibration::PRESSURE_CALIBRATION_PHYSICAL: - case Calibration::PRESSURE_CALIBRATION_AMPLITUDE: - pressure = rawPressure * mLocked.pressureScale; - break; - default: - pressure = 1; - break; - } + mPointerGesture.tapUpTime = when; + getContext()->requestTimeoutAtTime(when + TAP_DRAG_INTERVAL); + + mPointerGesture.activeGestureId = 0; + mPointerGesture.currentGestureMode = PointerGesture::TAP; + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit( + mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[ + mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue( + AMOTION_EVENT_AXIS_X, mPointerGesture.tapX); + mPointerGesture.currentGestureCoords[0].setAxisValue( + AMOTION_EVENT_AXIS_Y, mPointerGesture.tapY); + mPointerGesture.currentGestureCoords[0].setAxisValue( + AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + + mPointerController->setButtonState(BUTTON_STATE_PRIMARY); + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_TAP; + mPointerGesture.spotIdBits.clear(); + mPointerGesture.spotIdBits.markBit(lastActiveTouchId); + mPointerGesture.spotIdToIndex[lastActiveTouchId] = 0; + mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0]; + moveSpotsLocked(); + } - // TouchMajor and TouchMinor - float touchMajor, touchMinor; - switch (mCalibration.touchSizeCalibration) { - case Calibration::TOUCH_SIZE_CALIBRATION_GEOMETRIC: - touchMajor = in.touchMajor * mLocked.geometricScale; - if (mRawAxes.touchMinor.valid) { - touchMinor = in.touchMinor * mLocked.geometricScale; + tapped = true; } else { - touchMinor = touchMajor; +#if DEBUG_GESTURES + LOGD("Gestures: Not a TAP, deltaX=%f, deltaY=%f", + x - mPointerGesture.tapX, + y - mPointerGesture.tapY); +#endif } - break; - case Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE: - touchMajor = toolMajor * pressure; - touchMinor = toolMinor * pressure; - break; - default: - touchMajor = 0; - touchMinor = 0; - break; + } else { +#if DEBUG_GESTURES + LOGD("Gestures: Not a TAP, %0.3fms since down", + (when - mPointerGesture.tapDownTime) * 0.000001f); +#endif } + } + + if (!tapped) { +#if DEBUG_GESTURES + LOGD("Gestures: NEUTRAL"); +#endif + mPointerGesture.activeGestureId = -1; + mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL; + mPointerGesture.currentGestureIdBits.clear(); + + mPointerController->setButtonState(0); - if (touchMajor > toolMajor) { - touchMajor = toolMajor; + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL; + mPointerGesture.spotIdBits.clear(); + moveSpotsLocked(); } - if (touchMinor > toolMinor) { - touchMinor = toolMinor; + } + } else if (mCurrentTouch.pointerCount == 1) { + // Case 4. Exactly one finger down, button is not pressed. (HOVER or TAP_DRAG) + // The pointer follows the active touch point. + // When in HOVER, emit HOVER_MOVE events at the pointer location. + // When in TAP_DRAG, emit MOVE events at the pointer location. + LOG_ASSERT(activeTouchId >= 0); + + mPointerGesture.currentGestureMode = PointerGesture::HOVER; + if (mPointerGesture.lastGestureMode == PointerGesture::TAP) { + if (when <= mPointerGesture.tapUpTime + TAP_DRAG_INTERVAL) { + float x, y; + mPointerController->getPosition(&x, &y); + if (fabs(x - mPointerGesture.tapX) <= TAP_SLOP + && fabs(y - mPointerGesture.tapY) <= TAP_SLOP) { + mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG; + } else { +#if DEBUG_GESTURES + LOGD("Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f", + x - mPointerGesture.tapX, + y - mPointerGesture.tapY); +#endif + } + } else { +#if DEBUG_GESTURES + LOGD("Gestures: Not a TAP_DRAG, %0.3fms time since up", + (when - mPointerGesture.tapUpTime) * 0.000001f); +#endif } + } else if (mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG) { + mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG; + } - // Size - float size; - switch (mCalibration.sizeCalibration) { - case Calibration::SIZE_CALIBRATION_NORMALIZED: { - float rawSize = mRawAxes.toolMinor.valid - ? avg(in.toolMajor, in.toolMinor) - : in.toolMajor; - size = rawSize * mLocked.sizeScale; - break; + if (mLastTouch.idBits.hasBit(activeTouchId)) { + const PointerData& currentPointer = + mCurrentTouch.pointers[mCurrentTouch.idToIndex[activeTouchId]]; + const PointerData& lastPointer = + mLastTouch.pointers[mLastTouch.idToIndex[activeTouchId]]; + float deltaX = (currentPointer.x - lastPointer.x) + * mLocked.pointerGestureXMovementScale; + float deltaY = (currentPointer.y - lastPointer.y) + * mLocked.pointerGestureYMovementScale; + + // Move the pointer using a relative motion. + // When using spots, the hover or drag will occur at the position of the anchor spot. + mPointerController->move(deltaX, deltaY); + } + + bool down; + if (mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG) { +#if DEBUG_GESTURES + LOGD("Gestures: TAP_DRAG"); +#endif + down = true; + } else { +#if DEBUG_GESTURES + LOGD("Gestures: HOVER"); +#endif + *outFinishPreviousGesture = true; + mPointerGesture.activeGestureId = 0; + down = false; + } + + float x, y; + mPointerController->getPosition(&x, &y); + + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, + down ? 1.0f : 0.0f); + + mPointerController->setButtonState(down ? BUTTON_STATE_PRIMARY : 0); + + if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) { + mPointerGesture.resetTap(); + mPointerGesture.tapDownTime = when; + mPointerGesture.tapX = x; + mPointerGesture.tapY = y; + } + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = down ? PointerControllerInterface::SPOT_GESTURE_DRAG + : PointerControllerInterface::SPOT_GESTURE_HOVER; + mPointerGesture.spotIdBits.clear(); + mPointerGesture.spotIdBits.markBit(activeTouchId); + mPointerGesture.spotIdToIndex[activeTouchId] = 0; + mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0]; + moveSpotsLocked(); + } + } else { + // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM) + // We need to provide feedback for each finger that goes down so we cannot wait + // for the fingers to move before deciding what to do. + // + // The ambiguous case is deciding what to do when there are two fingers down but they + // have not moved enough to determine whether they are part of a drag or part of a + // freeform gesture, or just a press or long-press at the pointer location. + // + // When there are two fingers we start with the PRESS hypothesis and we generate a + // down at the pointer location. + // + // When the two fingers move enough or when additional fingers are added, we make + // a decision to transition into SWIPE or FREEFORM mode accordingly. + LOG_ASSERT(activeTouchId >= 0); + + bool needReference = false; + bool settled = when >= mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL; + if (mPointerGesture.lastGestureMode != PointerGesture::PRESS + && mPointerGesture.lastGestureMode != PointerGesture::SWIPE + && mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) { + *outFinishPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::PRESS; + mPointerGesture.activeGestureId = 0; + + if (settled && mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS + && mLastTouch.idBits.hasBit(mPointerGesture.activeTouchId)) { + // The spot is already visible and has settled, use it as the reference point + // for the gesture. Other spots will be positioned relative to this one. +#if DEBUG_GESTURES + LOGD("Gestures: Using active spot as reference for MULTITOUCH, " + "settle time expired %0.3fms ago", + (when - mPointerGesture.firstTouchTime - MULTITOUCH_SETTLE_INTERVAL) + * 0.000001f); +#endif + const PointerData& d = mLastTouch.pointers[mLastTouch.idToIndex[ + mPointerGesture.activeTouchId]]; + mPointerGesture.referenceTouchX = d.x; + mPointerGesture.referenceTouchY = d.y; + const PointerCoords& c = mPointerGesture.spotCoords[mPointerGesture.spotIdToIndex[ + mPointerGesture.activeTouchId]]; + mPointerGesture.referenceGestureX = c.getAxisValue(AMOTION_EVENT_AXIS_X); + mPointerGesture.referenceGestureY = c.getAxisValue(AMOTION_EVENT_AXIS_Y); + } else { +#if DEBUG_GESTURES + LOGD("Gestures: Using centroid as reference for MULTITOUCH, " + "settle time remaining %0.3fms", + (mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL - when) + * 0.000001f); +#endif + needReference = true; } - default: - size = 0; - break; + } else if (!settled && mCurrentTouch.pointerCount > mLastTouch.pointerCount) { + // Additional pointers have gone down but not yet settled. + // Reset the gesture. +#if DEBUG_GESTURES + LOGD("Gestures: Resetting gesture since additional pointers went down for MULTITOUCH, " + "settle time remaining %0.3fms", + (mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL - when) + * 0.000001f); +#endif + *outCancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::PRESS; + mPointerGesture.activeGestureId = 0; + } else { + // Continue previous gesture. + mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode; + } + + if (needReference) { + // Use the centroid and pointer location as the reference points for the gesture. + mCurrentTouch.getCentroid(&mPointerGesture.referenceTouchX, + &mPointerGesture.referenceTouchY); + mPointerController->getPosition(&mPointerGesture.referenceGestureX, + &mPointerGesture.referenceGestureY); + } + + if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) { + float d; + if (mCurrentTouch.pointerCount > 2) { + // There are more than two pointers, switch to FREEFORM. +#if DEBUG_GESTURES + LOGD("Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2", + mCurrentTouch.pointerCount); +#endif + *outCancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::FREEFORM; + } else if (((d = distance( + mCurrentTouch.pointers[0].x, mCurrentTouch.pointers[0].y, + mCurrentTouch.pointers[1].x, mCurrentTouch.pointers[1].y)) + > mLocked.pointerGestureMaxSwipeWidth)) { + // There are two pointers but they are too far apart, switch to FREEFORM. +#if DEBUG_GESTURES + LOGD("Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f", + d, mLocked.pointerGestureMaxSwipeWidth); +#endif + *outCancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::FREEFORM; + } else { + // There are two pointers. Wait for both pointers to start moving + // before deciding whether this is a SWIPE or FREEFORM gesture. + uint32_t id1 = mCurrentTouch.pointers[0].id; + uint32_t id2 = mCurrentTouch.pointers[1].id; + + float vx1, vy1, vx2, vy2; + mPointerGesture.velocityTracker.getVelocity(id1, &vx1, &vy1); + mPointerGesture.velocityTracker.getVelocity(id2, &vx2, &vy2); + + float speed1 = hypotf(vx1, vy1); + float speed2 = hypotf(vx2, vy2); + if (speed1 >= MULTITOUCH_MIN_SPEED && speed2 >= MULTITOUCH_MIN_SPEED) { + // Calculate the dot product of the velocity vectors. + // When the vectors are oriented in approximately the same direction, + // the angle betweeen them is near zero and the cosine of the angle + // approches 1.0. Recall that dot(v1, v2) = cos(angle) * mag(v1) * mag(v2). + float dot = vx1 * vx2 + vy1 * vy2; + float cosine = dot / (speed1 * speed2); // denominator always > 0 + if (cosine >= SWIPE_TRANSITION_ANGLE_COSINE) { + // Pointers are moving in the same direction. Switch to SWIPE. +#if DEBUG_GESTURES + LOGD("Gestures: PRESS transitioned to SWIPE, " + "speed1 %0.3f >= %0.3f, speed2 %0.3f >= %0.3f, " + "cosine %0.3f >= %0.3f", + speed1, MULTITOUCH_MIN_SPEED, speed2, MULTITOUCH_MIN_SPEED, + cosine, SWIPE_TRANSITION_ANGLE_COSINE); +#endif + mPointerGesture.currentGestureMode = PointerGesture::SWIPE; + } else { + // Pointers are moving in different directions. Switch to FREEFORM. +#if DEBUG_GESTURES + LOGD("Gestures: PRESS transitioned to FREEFORM, " + "speed1 %0.3f >= %0.3f, speed2 %0.3f >= %0.3f, " + "cosine %0.3f < %0.3f", + speed1, MULTITOUCH_MIN_SPEED, speed2, MULTITOUCH_MIN_SPEED, + cosine, SWIPE_TRANSITION_ANGLE_COSINE); +#endif + *outCancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::FREEFORM; + } + } + } + } else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) { + // Switch from SWIPE to FREEFORM if additional pointers go down. + // Cancel previous gesture. + if (mCurrentTouch.pointerCount > 2) { +#if DEBUG_GESTURES + LOGD("Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2", + mCurrentTouch.pointerCount); +#endif + *outCancelPreviousGesture = true; + mPointerGesture.currentGestureMode = PointerGesture::FREEFORM; } + } - // Orientation - float orientation; - switch (mCalibration.orientationCalibration) { - case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED: - orientation = in.orientation * mLocked.orientationScale; - break; - case Calibration::ORIENTATION_CALIBRATION_VECTOR: { - int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4); - int32_t c2 = signExtendNybble(in.orientation & 0x0f); - if (c1 != 0 || c2 != 0) { - orientation = atan2f(c1, c2) * 0.5f; - float scale = 1.0f + pythag(c1, c2) / 16.0f; - touchMajor *= scale; - touchMinor /= scale; - toolMajor *= scale; - toolMinor /= scale; + // Move the reference points based on the overall group motion of the fingers. + // The objective is to calculate a vector delta that is common to the movement + // of all fingers. + BitSet32 commonIdBits(mLastTouch.idBits.value & mCurrentTouch.idBits.value); + if (!commonIdBits.isEmpty()) { + float commonDeltaX = 0, commonDeltaY = 0; + for (BitSet32 idBits(commonIdBits); !idBits.isEmpty(); ) { + bool first = (idBits == commonIdBits); + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + + const PointerData& cpd = mCurrentTouch.pointers[mCurrentTouch.idToIndex[id]]; + const PointerData& lpd = mLastTouch.pointers[mLastTouch.idToIndex[id]]; + float deltaX = cpd.x - lpd.x; + float deltaY = cpd.y - lpd.y; + + if (first) { + commonDeltaX = deltaX; + commonDeltaY = deltaY; } else { - orientation = 0; + commonDeltaX = calculateCommonVector(commonDeltaX, deltaX); + commonDeltaY = calculateCommonVector(commonDeltaY, deltaY); } - break; - } - default: - orientation = 0; } - // X and Y - // Adjust coords for surface orientation. - float x, y; - switch (mLocked.surfaceOrientation) { - case DISPLAY_ORIENTATION_90: - x = float(in.y - mRawAxes.y.minValue) * mLocked.yScale; - y = float(mRawAxes.x.maxValue - in.x) * mLocked.xScale; - orientation -= M_PI_2; - if (orientation < - M_PI_2) { - orientation += M_PI; + mPointerGesture.referenceTouchX += commonDeltaX; + mPointerGesture.referenceTouchY += commonDeltaY; + mPointerGesture.referenceGestureX += + commonDeltaX * mLocked.pointerGestureXMovementScale; + mPointerGesture.referenceGestureY += + commonDeltaY * mLocked.pointerGestureYMovementScale; + clampPositionUsingPointerBounds(mPointerController, + &mPointerGesture.referenceGestureX, + &mPointerGesture.referenceGestureY); + } + + // Report gestures. + if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) { + // PRESS mode. +#if DEBUG_GESTURES + LOGD("Gestures: PRESS activeTouchId=%d," + "activeGestureId=%d, currentTouchPointerCount=%d", + activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount); +#endif + LOG_ASSERT(mPointerGesture.activeGestureId >= 0); + + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, + mPointerGesture.referenceGestureX); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, + mPointerGesture.referenceGestureY); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + + mPointerController->setButtonState(BUTTON_STATE_PRIMARY); + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_PRESS; + } + } else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) { + // SWIPE mode. +#if DEBUG_GESTURES + LOGD("Gestures: SWIPE activeTouchId=%d," + "activeGestureId=%d, currentTouchPointerCount=%d", + activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount); +#endif + LOG_ASSERT(mPointerGesture.activeGestureId >= 0); + + mPointerGesture.currentGestureIdBits.clear(); + mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; + mPointerGesture.currentGestureCoords[0].clear(); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, + mPointerGesture.referenceGestureX); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, + mPointerGesture.referenceGestureY); + mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + + mPointerController->setButtonState(0); // touch is not actually following the pointer + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_SWIPE; + } + } else if (mPointerGesture.currentGestureMode == PointerGesture::FREEFORM) { + // FREEFORM mode. +#if DEBUG_GESTURES + LOGD("Gestures: FREEFORM activeTouchId=%d," + "activeGestureId=%d, currentTouchPointerCount=%d", + activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount); +#endif + LOG_ASSERT(mPointerGesture.activeGestureId >= 0); + + mPointerGesture.currentGestureIdBits.clear(); + + BitSet32 mappedTouchIdBits; + BitSet32 usedGestureIdBits; + if (mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) { + // Initially, assign the active gesture id to the active touch point + // if there is one. No other touch id bits are mapped yet. + if (!*outCancelPreviousGesture) { + mappedTouchIdBits.markBit(activeTouchId); + usedGestureIdBits.markBit(mPointerGesture.activeGestureId); + mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] = + mPointerGesture.activeGestureId; + } else { + mPointerGesture.activeGestureId = -1; } - break; - case DISPLAY_ORIENTATION_180: - x = float(mRawAxes.x.maxValue - in.x) * mLocked.xScale; - y = float(mRawAxes.y.maxValue - in.y) * mLocked.yScale; - break; - case DISPLAY_ORIENTATION_270: - x = float(mRawAxes.y.maxValue - in.y) * mLocked.yScale; - y = float(in.x - mRawAxes.x.minValue) * mLocked.xScale; - orientation += M_PI_2; - if (orientation > M_PI_2) { - orientation -= M_PI; + } else { + // Otherwise, assume we mapped all touches from the previous frame. + // Reuse all mappings that are still applicable. + mappedTouchIdBits.value = mLastTouch.idBits.value & mCurrentTouch.idBits.value; + usedGestureIdBits = mPointerGesture.lastGestureIdBits; + + // Check whether we need to choose a new active gesture id because the + // current went went up. + for (BitSet32 upTouchIdBits(mLastTouch.idBits.value & ~mCurrentTouch.idBits.value); + !upTouchIdBits.isEmpty(); ) { + uint32_t upTouchId = upTouchIdBits.firstMarkedBit(); + upTouchIdBits.clearBit(upTouchId); + uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId]; + if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) { + mPointerGesture.activeGestureId = -1; + break; + } } - break; - default: - x = float(in.x - mRawAxes.x.minValue) * mLocked.xScale; - y = float(in.y - mRawAxes.y.minValue) * mLocked.yScale; - break; } - // Write output coords. - PointerCoords& out = pointerCoords[outIndex]; - out.clear(); - out.setAxisValue(AMOTION_EVENT_AXIS_X, x); - out.setAxisValue(AMOTION_EVENT_AXIS_Y, y); - out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure); - out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size); - out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor); - out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, touchMinor); - out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor); - out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor); - out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation); - - pointerIds[outIndex] = int32_t(id); - - if (id == changedId) { - motionEventAction |= outIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; +#if DEBUG_GESTURES + LOGD("Gestures: FREEFORM follow up " + "mappedTouchIdBits=0x%08x, usedGestureIdBits=0x%08x, " + "activeGestureId=%d", + mappedTouchIdBits.value, usedGestureIdBits.value, + mPointerGesture.activeGestureId); +#endif + + for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) { + uint32_t touchId = mCurrentTouch.pointers[i].id; + uint32_t gestureId; + if (!mappedTouchIdBits.hasBit(touchId)) { + gestureId = usedGestureIdBits.firstUnmarkedBit(); + usedGestureIdBits.markBit(gestureId); + mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId; +#if DEBUG_GESTURES + LOGD("Gestures: FREEFORM " + "new mapping for touch id %d -> gesture id %d", + touchId, gestureId); +#endif + } else { + gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId]; +#if DEBUG_GESTURES + LOGD("Gestures: FREEFORM " + "existing mapping for touch id %d -> gesture id %d", + touchId, gestureId); +#endif + } + mPointerGesture.currentGestureIdBits.markBit(gestureId); + mPointerGesture.currentGestureIdToIndex[gestureId] = i; + + float x = (mCurrentTouch.pointers[i].x - mPointerGesture.referenceTouchX) + * mLocked.pointerGestureXZoomScale + mPointerGesture.referenceGestureX; + float y = (mCurrentTouch.pointers[i].y - mPointerGesture.referenceTouchY) + * mLocked.pointerGestureYZoomScale + mPointerGesture.referenceGestureY; + + mPointerGesture.currentGestureCoords[i].clear(); + mPointerGesture.currentGestureCoords[i].setAxisValue( + AMOTION_EVENT_AXIS_X, x); + mPointerGesture.currentGestureCoords[i].setAxisValue( + AMOTION_EVENT_AXIS_Y, y); + mPointerGesture.currentGestureCoords[i].setAxisValue( + AMOTION_EVENT_AXIS_PRESSURE, 1.0f); + } + + if (mPointerGesture.activeGestureId < 0) { + mPointerGesture.activeGestureId = + mPointerGesture.currentGestureIdBits.firstMarkedBit(); +#if DEBUG_GESTURES + LOGD("Gestures: FREEFORM new " + "activeGestureId=%d", mPointerGesture.activeGestureId); +#endif } - } - // Check edge flags by looking only at the first pointer since the flags are - // global to the event. - if (motionEventAction == AMOTION_EVENT_ACTION_DOWN) { - uint32_t inIndex = touch->idToIndex[pointerIds[0]]; - const PointerData& in = touch->pointers[inIndex]; + mPointerController->setButtonState(0); // touch is not actually following the pointer - if (in.x <= mRawAxes.x.minValue) { - motionEventEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_LEFT, - mLocked.surfaceOrientation); - } else if (in.x >= mRawAxes.x.maxValue) { - motionEventEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_RIGHT, - mLocked.surfaceOrientation); + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_FREEFORM; } - if (in.y <= mRawAxes.y.minValue) { - motionEventEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_TOP, - mLocked.surfaceOrientation); - } else if (in.y >= mRawAxes.y.maxValue) { - motionEventEdgeFlags |= rotateEdgeFlag(AMOTION_EVENT_EDGE_FLAG_BOTTOM, - mLocked.surfaceOrientation); + } + + // Update spot locations for PRESS, SWIPE and FREEFORM. + // We use the same calculation as we do to calculate the gesture pointers + // for FREEFORM so that the spots smoothly track gestures. + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotIdBits.clear(); + for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) { + uint32_t id = mCurrentTouch.pointers[i].id; + mPointerGesture.spotIdBits.markBit(id); + mPointerGesture.spotIdToIndex[id] = i; + + float x = (mCurrentTouch.pointers[i].x - mPointerGesture.referenceTouchX) + * mLocked.pointerGestureXZoomScale + mPointerGesture.referenceGestureX; + float y = (mCurrentTouch.pointers[i].y - mPointerGesture.referenceTouchY) + * mLocked.pointerGestureYZoomScale + mPointerGesture.referenceGestureY; + + mPointerGesture.spotCoords[i].clear(); + mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, x); + mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, y); + mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f); } + moveSpotsLocked(); } + } - xPrecision = mLocked.orientedXPrecision; - yPrecision = mLocked.orientedYPrecision; - } // release lock +#if DEBUG_GESTURES + LOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, " + "currentGestureMode=%d, currentGestureIdBits=0x%08x, " + "lastGestureMode=%d, lastGestureIdBits=0x%08x", + toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture), + mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value, + mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value); + for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty(); ) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + uint32_t index = mPointerGesture.currentGestureIdToIndex[id]; + const PointerCoords& coords = mPointerGesture.currentGestureCoords[index]; + LOGD(" currentGesture[%d]: index=%d, x=%0.3f, y=%0.3f, pressure=%0.3f", + id, index, coords.getAxisValue(AMOTION_EVENT_AXIS_X), + coords.getAxisValue(AMOTION_EVENT_AXIS_Y), + coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + } + for (BitSet32 idBits = mPointerGesture.lastGestureIdBits; !idBits.isEmpty(); ) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + uint32_t index = mPointerGesture.lastGestureIdToIndex[id]; + const PointerCoords& coords = mPointerGesture.lastGestureCoords[index]; + LOGD(" lastGesture[%d]: index=%d, x=%0.3f, y=%0.3f, pressure=%0.3f", + id, index, coords.getAxisValue(AMOTION_EVENT_AXIS_X), + coords.getAxisValue(AMOTION_EVENT_AXIS_Y), + coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); + } +#endif + return true; +} - getDispatcher()->notifyMotion(when, getDeviceId(), mTouchSource, policyFlags, - motionEventAction, 0, getContext()->getGlobalMetaState(), motionEventEdgeFlags, - pointerCount, pointerIds, pointerCoords, - xPrecision, yPrecision, mDownTime); +void TouchInputMapper::moveSpotsLocked() { + mPointerController->setSpots(mPointerGesture.spotGesture, + mPointerGesture.spotCoords, mPointerGesture.spotIdToIndex, mPointerGesture.spotIdBits); +} + +void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source, + int32_t action, int32_t flags, uint32_t metaState, int32_t edgeFlags, + const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits, + int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime) { + PointerCoords pointerCoords[MAX_POINTERS]; + int32_t pointerIds[MAX_POINTERS]; + uint32_t pointerCount = 0; + while (!idBits.isEmpty()) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + uint32_t index = idToIndex[id]; + pointerIds[pointerCount] = id; + pointerCoords[pointerCount].copyFrom(coords[index]); + + if (changedId >= 0 && id == uint32_t(changedId)) { + action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + } + + pointerCount += 1; + } + + LOG_ASSERT(pointerCount != 0); + + if (changedId >= 0 && pointerCount == 1) { + // Replace initial down and final up action. + // We can compare the action without masking off the changed pointer index + // because we know the index is 0. + if (action == AMOTION_EVENT_ACTION_POINTER_DOWN) { + action = AMOTION_EVENT_ACTION_DOWN; + } else if (action == AMOTION_EVENT_ACTION_POINTER_UP) { + action = AMOTION_EVENT_ACTION_UP; + } else { + // Can't happen. + LOG_ASSERT(false); + } + } + + getDispatcher()->notifyMotion(when, getDeviceId(), source, policyFlags, + action, flags, metaState, edgeFlags, + pointerCount, pointerIds, pointerCoords, xPrecision, yPrecision, downTime); +} + +bool TouchInputMapper::updateMovedPointerCoords( + const PointerCoords* inCoords, const uint32_t* inIdToIndex, + PointerCoords* outCoords, const uint32_t* outIdToIndex, BitSet32 idBits) const { + bool changed = false; + while (!idBits.isEmpty()) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + + uint32_t inIndex = inIdToIndex[id]; + uint32_t outIndex = outIdToIndex[id]; + const PointerCoords& curInCoords = inCoords[inIndex]; + PointerCoords& curOutCoords = outCoords[outIndex]; + + if (curInCoords != curOutCoords) { + curOutCoords.copyFrom(curInCoords); + changed = true; + } + } + return changed; +} + +void TouchInputMapper::fadePointer() { + { // acquire lock + AutoMutex _l(mLock); + if (mPointerController != NULL) { + mPointerController->fade(); + } + } // release lock } bool TouchInputMapper::isPointInsideSurfaceLocked(int32_t x, int32_t y) { @@ -3080,7 +4393,7 @@ void TouchInputMapper::calculatePointerIds() { // Previous iterations consumed the root element of the heap. // Pop root element off of the heap (sift down). heapSize -= 1; - assert(heapSize > 0); + LOG_ASSERT(heapSize > 0); // Sift down. heap[0] = heap[heapSize]; @@ -3592,6 +4905,7 @@ void SingleTouchInputMapper::initialize() { mY = 0; mPressure = 0; // default to 0 for devices that don't report pressure mToolWidth = 0; // default to 0 for devices that don't report tool width + mButtonState = 0; } void SingleTouchInputMapper::reset() { @@ -3611,6 +4925,19 @@ void SingleTouchInputMapper::process(const RawEvent* rawEvent) { // not have received valid position information yet. This logic assumes that // BTN_TOUCH is always followed by SYN_REPORT as part of a complete packet. break; + default: + if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) { + uint32_t buttonState = getButtonStateForScanCode(rawEvent->scanCode); + if (buttonState) { + if (rawEvent->value) { + mAccumulator.buttonDown |= buttonState; + } else { + mAccumulator.buttonUp |= buttonState; + } + mAccumulator.fields |= Accumulator::FIELD_BUTTONS; + } + } + break; } break; @@ -3671,6 +4998,10 @@ void SingleTouchInputMapper::sync(nsecs_t when) { mToolWidth = mAccumulator.absToolWidth; } + if (fields & Accumulator::FIELD_BUTTONS) { + mButtonState = (mButtonState | mAccumulator.buttonDown) & ~mAccumulator.buttonUp; + } + mCurrentTouch.clear(); if (mDown) { @@ -3686,6 +5017,7 @@ void SingleTouchInputMapper::sync(nsecs_t when) { mCurrentTouch.pointers[0].orientation = 0; mCurrentTouch.idToIndex[0] = 0; mCurrentTouch.idBits.markBit(0); + mCurrentTouch.buttonState = mButtonState; } syncTouch(when, true); @@ -3715,6 +5047,7 @@ MultiTouchInputMapper::~MultiTouchInputMapper() { void MultiTouchInputMapper::initialize() { mAccumulator.clear(); + mButtonState = 0; } void MultiTouchInputMapper::reset() { @@ -3725,6 +5058,20 @@ void MultiTouchInputMapper::reset() { void MultiTouchInputMapper::process(const RawEvent* rawEvent) { switch (rawEvent->type) { + case EV_KEY: { + if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) { + uint32_t buttonState = getButtonStateForScanCode(rawEvent->scanCode); + if (buttonState) { + if (rawEvent->value) { + mAccumulator.buttonDown |= buttonState; + } else { + mAccumulator.buttonUp |= buttonState; + } + } + } + break; + } + case EV_ABS: { uint32_t pointerIndex = mAccumulator.pointerCount; Accumulator::Pointer* pointer = & mAccumulator.pointers[pointerIndex]; @@ -3902,6 +5249,9 @@ void MultiTouchInputMapper::sync(nsecs_t when) { mCurrentTouch.pointerCount = outCount; + mButtonState = (mButtonState | mAccumulator.buttonDown) & ~mAccumulator.buttonUp; + mCurrentTouch.buttonState = mButtonState; + syncTouch(when, havePointerIds); mAccumulator.clear(); diff --git a/services/input/InputReader.h b/services/input/InputReader.h index 68002ca..0485617 100644 --- a/services/input/InputReader.h +++ b/services/input/InputReader.h @@ -159,6 +159,8 @@ public: virtual void fadePointer() = 0; + virtual void requestTimeoutAtTime(nsecs_t when) = 0; + virtual InputReaderPolicyInterface* getPolicy() = 0; virtual InputDispatcherInterface* getDispatcher() = 0; virtual EventHubInterface* getEventHub() = 0; @@ -214,6 +216,10 @@ private: virtual InputDispatcherInterface* getDispatcher() { return mDispatcher.get(); } virtual EventHubInterface* getEventHub() { return mEventHub.get(); } + // The event queue. + static const int EVENT_BUFFER_SIZE = 256; + RawEvent mEventBuffer[EVENT_BUFFER_SIZE]; + // This reader/writer lock guards the list of input devices. // The writer lock must be held whenever the list of input devices is modified // and then promptly released. @@ -226,15 +232,15 @@ private: KeyedVector<int32_t, InputDevice*> mDevices; // low-level input event decoding and device management - void process(const RawEvent* rawEvent); + void processEvents(const RawEvent* rawEvents, size_t count); void addDevice(int32_t deviceId); void removeDevice(int32_t deviceId); - void configureExcludedDevices(); - - void consumeEvent(const RawEvent* rawEvent); + void processEventsForDevice(int32_t deviceId, const RawEvent* rawEvents, size_t count); + void timeoutExpired(nsecs_t when); void handleConfigurationChanged(nsecs_t when); + void configureExcludedDevices(); // state management for all devices Mutex mStateLock; @@ -248,11 +254,14 @@ private: InputConfiguration mInputConfiguration; void updateInputConfiguration(); - nsecs_t mDisableVirtualKeysTimeout; + nsecs_t mDisableVirtualKeysTimeout; // only accessed by reader thread virtual void disableVirtualKeysUntil(nsecs_t time); virtual bool shouldDropVirtualKey(nsecs_t now, InputDevice* device, int32_t keyCode, int32_t scanCode); + nsecs_t mNextTimeout; // only accessed by reader thread + virtual void requestTimeoutAtTime(nsecs_t when); + // state queries typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code); int32_t getState(int32_t deviceId, uint32_t sourceMask, int32_t code, @@ -295,7 +304,8 @@ public: void addMapper(InputMapper* mapper); void configure(); void reset(); - void process(const RawEvent* rawEvent); + void process(const RawEvent* rawEvents, size_t count); + void timeoutExpired(nsecs_t when); void getDeviceInfo(InputDeviceInfo* outDeviceInfo); int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); @@ -352,6 +362,7 @@ public: virtual void configure(); virtual void reset(); virtual void process(const RawEvent* rawEvent) = 0; + virtual void timeoutExpired(nsecs_t when); virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode); virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode); @@ -558,6 +569,9 @@ public: virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags); + virtual void fadePointer(); + virtual void timeoutExpired(nsecs_t when); + protected: Mutex mLock; @@ -611,10 +625,12 @@ protected: PointerData pointers[MAX_POINTERS]; BitSet32 idBits; uint32_t idToIndex[MAX_POINTER_ID + 1]; + uint32_t buttonState; void copyFrom(const TouchData& other) { pointerCount = other.pointerCount; idBits = other.idBits; + buttonState = other.buttonState; for (uint32_t i = 0; i < pointerCount; i++) { pointers[i] = other.pointers[i]; @@ -627,17 +643,34 @@ protected: inline void clear() { pointerCount = 0; idBits.clear(); + buttonState = 0; + } + + void getCentroid(float* outX, float* outY) { + float x = 0, y = 0; + if (pointerCount != 0) { + for (uint32_t i = 0; i < pointerCount; i++) { + x += pointers[i].x; + y += pointers[i].y; + } + x /= pointerCount; + y /= pointerCount; + } + *outX = x; + *outY = y; } }; // Input sources supported by the device. uint32_t mTouchSource; // sources when reporting touch data + uint32_t mPointerSource; // sources when reporting pointer gestures // Immutable configuration parameters. struct Parameters { enum DeviceType { DEVICE_TYPE_TOUCH_SCREEN, DEVICE_TYPE_TOUCH_PAD, + DEVICE_TYPE_POINTER, }; DeviceType deviceType; @@ -648,6 +681,12 @@ protected: bool useJumpyTouchFilter; bool useAveragingTouchFilter; nsecs_t virtualKeyQuietTime; + + enum GestureMode { + GESTURE_MODE_POINTER, + GESTURE_MODE_SPOTS, + }; + GestureMode gestureMode; } mParameters; // Immutable calibration parameters in parsed form. @@ -735,11 +774,17 @@ protected: // Current and previous touch sample data. TouchData mCurrentTouch; + PointerCoords mCurrentTouchCoords[MAX_POINTERS]; + TouchData mLastTouch; + PointerCoords mLastTouchCoords[MAX_POINTERS]; // The time the primary pointer last went down. nsecs_t mDownTime; + // The pointer controller, or null if the device is not a pointer. + sp<PointerControllerInterface> mPointerController; + struct LockedState { Vector<VirtualKey> virtualKeys; @@ -804,6 +849,17 @@ protected: int32_t keyCode; int32_t scanCode; } currentVirtualKey; + + // Scale factor for gesture based pointer movements. + float pointerGestureXMovementScale; + float pointerGestureYMovementScale; + + // Scale factor for gesture based zooming and other freeform motions. + float pointerGestureXZoomScale; + float pointerGestureYZoomScale; + + // The maximum swipe width. + float pointerGestureMaxSwipeWidth; } mLocked; virtual void configureParameters(); @@ -869,13 +925,158 @@ private: uint64_t distance : 48; // squared distance }; + struct PointerGesture { + enum Mode { + // No fingers, button is not pressed. + // Nothing happening. + NEUTRAL, + + // No fingers, button is not pressed. + // Tap detected. + // Emits DOWN and UP events at the pointer location. + TAP, + + // Exactly one finger dragging following a tap. + // Pointer follows the active finger. + // Emits DOWN, MOVE and UP events at the pointer location. + TAP_DRAG, + + // Button is pressed. + // Pointer follows the active finger if there is one. Other fingers are ignored. + // Emits DOWN, MOVE and UP events at the pointer location. + BUTTON_CLICK_OR_DRAG, + + // Exactly one finger, button is not pressed. + // Pointer follows the active finger. + // Emits HOVER_MOVE events at the pointer location. + HOVER, + + // Exactly two fingers but neither have moved enough to clearly indicate + // whether a swipe or freeform gesture was intended. We consider the + // pointer to be pressed so this enables clicking or long-pressing on buttons. + // Pointer does not move. + // Emits DOWN, MOVE and UP events with a single stationary pointer coordinate. + PRESS, + + // Exactly two fingers moving in the same direction, button is not pressed. + // Pointer does not move. + // Emits DOWN, MOVE and UP events with a single pointer coordinate that + // follows the midpoint between both fingers. + SWIPE, + + // Two or more fingers moving in arbitrary directions, button is not pressed. + // Pointer does not move. + // Emits DOWN, POINTER_DOWN, MOVE, POINTER_UP and UP events that follow + // each finger individually relative to the initial centroid of the finger. + FREEFORM, + + // Waiting for quiet time to end before starting the next gesture. + QUIET, + }; + + // Time the first finger went down. + nsecs_t firstTouchTime; + + // The active pointer id from the raw touch data. + int32_t activeTouchId; // -1 if none + + // The active pointer id from the gesture last delivered to the application. + int32_t activeGestureId; // -1 if none + + // Pointer coords and ids for the current and previous pointer gesture. + Mode currentGestureMode; + BitSet32 currentGestureIdBits; + uint32_t currentGestureIdToIndex[MAX_POINTER_ID + 1]; + PointerCoords currentGestureCoords[MAX_POINTERS]; + + Mode lastGestureMode; + BitSet32 lastGestureIdBits; + uint32_t lastGestureIdToIndex[MAX_POINTER_ID + 1]; + PointerCoords lastGestureCoords[MAX_POINTERS]; + + // Pointer coords and ids for the current spots. + PointerControllerInterface::SpotGesture spotGesture; + BitSet32 spotIdBits; // same set of ids as touch ids + uint32_t spotIdToIndex[MAX_POINTER_ID + 1]; + PointerCoords spotCoords[MAX_POINTERS]; + + // Time the pointer gesture last went down. + nsecs_t downTime; + + // Time when the pointer went down for a TAP. + nsecs_t tapDownTime; + + // Time when the pointer went up for a TAP. + nsecs_t tapUpTime; + + // Location of initial tap. + float tapX, tapY; + + // Time we started waiting for quiescence. + nsecs_t quietTime; + + // Reference points for multitouch gestures. + float referenceTouchX; // reference touch X/Y coordinates in surface units + float referenceTouchY; + float referenceGestureX; // reference gesture X/Y coordinates in pixels + float referenceGestureY; + + // Describes how touch ids are mapped to gesture ids for freeform gestures. + uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1]; + + // A velocity tracker for determining whether to switch active pointers during drags. + VelocityTracker velocityTracker; + + void reset() { + firstTouchTime = LLONG_MIN; + activeTouchId = -1; + activeGestureId = -1; + currentGestureMode = NEUTRAL; + currentGestureIdBits.clear(); + lastGestureMode = NEUTRAL; + lastGestureIdBits.clear(); + spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL; + spotIdBits.clear(); + downTime = 0; + velocityTracker.clear(); + resetTap(); + resetQuietTime(); + } + + void resetTap() { + tapDownTime = LLONG_MIN; + tapUpTime = LLONG_MIN; + } + + void resetQuietTime() { + quietTime = LLONG_MIN; + } + } mPointerGesture; + void initializeLocked(); TouchResult consumeOffScreenTouches(nsecs_t when, uint32_t policyFlags); void dispatchTouches(nsecs_t when, uint32_t policyFlags); - void dispatchTouch(nsecs_t when, uint32_t policyFlags, TouchData* touch, - BitSet32 idBits, uint32_t changedId, uint32_t pointerCount, - int32_t motionEventAction); + void prepareTouches(int32_t* outEdgeFlags, float* outXPrecision, float* outYPrecision); + void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, bool isTimeout); + bool preparePointerGestures(nsecs_t when, + bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout); + void moveSpotsLocked(); + + // Dispatches a motion event. + // If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the + // method will take care of setting the index and transmuting the action to DOWN or UP + // it is the first / last pointer to go down / up. + void dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source, + int32_t action, int32_t flags, uint32_t metaState, int32_t edgeFlags, + const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits, + int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime); + + // Updates pointer coords for pointers with specified ids that have moved. + // Returns true if any of them changed. + bool updateMovedPointerCoords(const PointerCoords* inCoords, const uint32_t* inIdToIndex, + PointerCoords* outCoords, const uint32_t* outIdToIndex, BitSet32 idBits) const; + void suppressSwipeOntoVirtualKeys(nsecs_t when); bool isPointInsideSurfaceLocked(int32_t x, int32_t y); @@ -907,6 +1108,7 @@ private: FIELD_ABS_Y = 4, FIELD_ABS_PRESSURE = 8, FIELD_ABS_TOOL_WIDTH = 16, + FIELD_BUTTONS = 32, }; uint32_t fields; @@ -917,8 +1119,13 @@ private: int32_t absPressure; int32_t absToolWidth; + uint32_t buttonDown; + uint32_t buttonUp; + inline void clear() { fields = 0; + buttonDown = 0; + buttonUp = 0; } } mAccumulator; @@ -927,6 +1134,7 @@ private: int32_t mY; int32_t mPressure; int32_t mToolWidth; + uint32_t mButtonState; void initialize(); @@ -978,12 +1186,20 @@ private: } } pointers[MAX_POINTERS + 1]; // + 1 to remove the need for extra range checks + // Bitfield of buttons that went down or up. + uint32_t buttonDown; + uint32_t buttonUp; + inline void clear() { pointerCount = 0; pointers[0].clear(); + buttonDown = 0; + buttonUp = 0; } } mAccumulator; + uint32_t mButtonState; + void initialize(); void sync(nsecs_t when); diff --git a/services/input/InputWindow.h b/services/input/InputWindow.h index 72f1773..cde7294 100644 --- a/services/input/InputWindow.h +++ b/services/input/InputWindow.h @@ -114,10 +114,12 @@ struct InputWindow { TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11, TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12, TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13, - TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+14, + TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14, TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15, TYPE_DRAG = FIRST_SYSTEM_WINDOW+16, - TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+17, + TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17, + TYPE_POINTER = FIRST_SYSTEM_WINDOW+18, + TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19, LAST_SYSTEM_WINDOW = 2999, }; diff --git a/services/input/PointerController.cpp b/services/input/PointerController.cpp index a4ee295..ffef720 100644 --- a/services/input/PointerController.cpp +++ b/services/input/PointerController.cpp @@ -36,59 +36,63 @@ namespace android { // --- PointerController --- // Time to wait before starting the fade when the pointer is inactive. -static const nsecs_t INACTIVITY_FADE_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds -static const nsecs_t INACTIVITY_FADE_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds +static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds +static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds + +// Time to wait between animation frames. +static const nsecs_t ANIMATION_FRAME_INTERVAL = 1000000000LL / 60; + +// Time to spend fading out the spot completely. +static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms // Time to spend fading out the pointer completely. -static const nsecs_t FADE_DURATION = 500 * 1000000LL; // 500 ms +static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms -// Time to wait between frames. -static const nsecs_t FADE_FRAME_INTERVAL = 1000000000LL / 60; -// Amount to subtract from alpha per frame. -static const float FADE_DECAY_PER_FRAME = float(FADE_FRAME_INTERVAL) / FADE_DURATION; +// --- PointerController --- +PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, + const sp<Looper>& looper, const sp<SpriteController>& spriteController) : + mPolicy(policy), mLooper(looper), mSpriteController(spriteController) { + mHandler = new WeakMessageHandler(this); -PointerController::PointerController(const sp<Looper>& looper, int32_t pointerLayer) : - mLooper(looper), mPointerLayer(pointerLayer) { AutoMutex _l(mLock); + mLocked.animationPending = false; + mLocked.displayWidth = -1; mLocked.displayHeight = -1; mLocked.displayOrientation = DISPLAY_ORIENTATION_0; - mLocked.pointerX = 0; - mLocked.pointerY = 0; - mLocked.buttonState = 0; + mLocked.presentation = PRESENTATION_POINTER; + mLocked.presentationChanged = false; - mLocked.iconBitmap = NULL; - mLocked.iconHotSpotX = 0; - mLocked.iconHotSpotY = 0; + mLocked.inactivityTimeout = INACTIVITY_TIMEOUT_NORMAL; - mLocked.fadeAlpha = 1; - mLocked.inactivityFadeDelay = INACTIVITY_FADE_DELAY_NORMAL; + mLocked.pointerIsFading = true; // keep the pointer initially faded + mLocked.pointerX = 0; + mLocked.pointerY = 0; + mLocked.pointerAlpha = 0.0f; + mLocked.pointerSprite = mSpriteController->createSprite(); + mLocked.pointerIconChanged = false; - mLocked.wantVisible = false; - mLocked.visible = false; - mLocked.drawn = false; + mLocked.buttonState = 0; - mHandler = new WeakMessageHandler(this); + loadResources(); } PointerController::~PointerController() { mLooper->removeMessages(mHandler); - if (mSurfaceControl != NULL) { - mSurfaceControl->clear(); - mSurfaceControl.clear(); - } + AutoMutex _l(mLock); - if (mSurfaceComposerClient != NULL) { - mSurfaceComposerClient->dispose(); - mSurfaceComposerClient.clear(); - } + mLocked.pointerSprite.clear(); - delete mLocked.iconBitmap; + for (size_t i = 0; i < mLocked.spots.size(); i++) { + delete mLocked.spots.itemAt(i); + } + mLocked.spots.clear(); + mLocked.recycledSprites.clear(); } bool PointerController::getBounds(float* outMinX, float* outMinY, @@ -141,8 +145,6 @@ void PointerController::setButtonState(uint32_t buttonState) { if (mLocked.buttonState != buttonState) { mLocked.buttonState = buttonState; - unfadeBeforeUpdateLocked(); - updateLocked(); } } @@ -178,8 +180,7 @@ void PointerController::setPositionLocked(float x, float y) { } else { mLocked.pointerY = y; } - unfadeBeforeUpdateLocked(); - updateLocked(); + updatePointerLocked(); } } @@ -193,95 +194,104 @@ void PointerController::getPosition(float* outX, float* outY) const { void PointerController::fade() { AutoMutex _l(mLock); - startFadeLocked(); + sendImmediateInactivityTimeoutLocked(); } void PointerController::unfade() { AutoMutex _l(mLock); - if (unfadeBeforeUpdateLocked()) { - updateLocked(); + // Always reset the inactivity timer. + resetInactivityTimeoutLocked(); + + // Unfade immediately if needed. + if (mLocked.pointerIsFading) { + mLocked.pointerIsFading = false; + mLocked.pointerAlpha = 1.0f; + updatePointerLocked(); } } -void PointerController::setInactivityFadeDelay(InactivityFadeDelay inactivityFadeDelay) { +void PointerController::setPresentation(Presentation presentation) { AutoMutex _l(mLock); - if (mLocked.inactivityFadeDelay != inactivityFadeDelay) { - mLocked.inactivityFadeDelay = inactivityFadeDelay; - startInactivityFadeDelayLocked(); - } -} - -void PointerController::updateLocked() { - bool wantVisibleAndHavePointerIcon = mLocked.wantVisible && mLocked.iconBitmap; + if (mLocked.presentation != presentation) { + mLocked.presentation = presentation; + mLocked.presentationChanged = true; - if (wantVisibleAndHavePointerIcon) { - // Want the pointer to be visible. - // Ensure the surface is created and drawn. - if (!createSurfaceIfNeededLocked() || !drawPointerIfNeededLocked()) { - return; - } - } else { - // Don't want the pointer to be visible. - // If it is not visible then we are done. - if (mSurfaceControl == NULL || !mLocked.visible) { - return; + if (presentation != PRESENTATION_SPOT) { + fadeOutAndReleaseAllSpotsLocked(); } + + updatePointerLocked(); } +} - status_t status = mSurfaceComposerClient->openTransaction(); - if (status) { - LOGE("Error opening surface transaction to update pointer surface."); - return; +void PointerController::setSpots(SpotGesture spotGesture, + const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits) { +#if DEBUG_POINTER_UPDATES + LOGD("setSpots: spotGesture=%d", spotGesture); + for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); + const PointerCoords& c = spotCoords[spotIdToIndex[id]]; + LOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f", id, + c.getAxisValue(AMOTION_EVENT_AXIS_X), + c.getAxisValue(AMOTION_EVENT_AXIS_Y), + c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)); } +#endif - if (wantVisibleAndHavePointerIcon) { - status = mSurfaceControl->setPosition( - mLocked.pointerX - mLocked.iconHotSpotX, - mLocked.pointerY - mLocked.iconHotSpotY); - if (status) { - LOGE("Error %d moving pointer surface.", status); - goto CloseTransaction; - } + AutoMutex _l(mLock); - status = mSurfaceControl->setAlpha(mLocked.fadeAlpha); - if (status) { - LOGE("Error %d setting pointer surface alpha.", status); - goto CloseTransaction; - } + mSpriteController->openTransaction(); - if (!mLocked.visible) { - status = mSurfaceControl->setLayer(mPointerLayer); - if (status) { - LOGE("Error %d setting pointer surface layer.", status); - goto CloseTransaction; - } + // Add or move spots for fingers that are down. + for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) { + uint32_t id = idBits.firstMarkedBit(); + idBits.clearBit(id); - status = mSurfaceControl->show(mPointerLayer); - if (status) { - LOGE("Error %d showing pointer surface.", status); - goto CloseTransaction; - } + const PointerCoords& c = spotCoords[spotIdToIndex[id]]; + const SpriteIcon& icon = c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) > 0 + ? mResources.spotTouch : mResources.spotHover; + float x = c.getAxisValue(AMOTION_EVENT_AXIS_X); + float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y); - mLocked.visible = true; + Spot* spot = getSpotLocked(id); + if (!spot) { + spot = createAndAddSpotLocked(id); } - } else { - if (mLocked.visible) { - status = mSurfaceControl->hide(); - if (status) { - LOGE("Error %d hiding pointer surface.", status); - goto CloseTransaction; - } - mLocked.visible = false; + spot->updateSprite(&icon, x, y); + } + + // Remove spots for fingers that went up. + for (size_t i = 0; i < mLocked.spots.size(); i++) { + Spot* spot = mLocked.spots.itemAt(i); + if (spot->id != Spot::INVALID_ID + && !spotIdBits.hasBit(spot->id)) { + fadeOutAndReleaseSpotLocked(spot); } } -CloseTransaction: - status = mSurfaceComposerClient->closeTransaction(); - if (status) { - LOGE("Error closing surface transaction to update pointer surface."); + mSpriteController->closeTransaction(); +} + +void PointerController::clearSpots() { +#if DEBUG_POINTER_UPDATES + LOGD("clearSpots"); +#endif + + AutoMutex _l(mLock); + + fadeOutAndReleaseAllSpotsLocked(); +} + +void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout) { + AutoMutex _l(mLock); + + if (mLocked.inactivityTimeout != inactivityTimeout) { + mLocked.inactivityTimeout = inactivityTimeout; + resetInactivityTimeoutLocked(); } } @@ -301,7 +311,8 @@ void PointerController::setDisplaySize(int32_t width, int32_t height) { mLocked.pointerY = 0; } - updateLocked(); + fadeOutAndReleaseAllSpotsLocked(); + updatePointerLocked(); } } @@ -339,7 +350,7 @@ void PointerController::setDisplayOrientation(int32_t orientation) { case DISPLAY_ORIENTATION_90: temp = x; x = y; - y = mLocked.displayWidth - x; + y = mLocked.displayWidth - temp; break; case DISPLAY_ORIENTATION_180: x = mLocked.displayWidth - x; @@ -358,173 +369,217 @@ void PointerController::setDisplayOrientation(int32_t orientation) { mLocked.pointerY = y - 0.5f; mLocked.displayOrientation = orientation; - updateLocked(); + updatePointerLocked(); } } -void PointerController::setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY) { +void PointerController::setPointerIcon(const SpriteIcon& icon) { AutoMutex _l(mLock); - if (mLocked.iconBitmap) { - delete mLocked.iconBitmap; - mLocked.iconBitmap = NULL; - } - - if (bitmap) { - mLocked.iconBitmap = new SkBitmap(); - bitmap->copyTo(mLocked.iconBitmap, SkBitmap::kARGB_8888_Config); - } + mLocked.pointerIcon = icon.copy(); + mLocked.pointerIconChanged = true; - mLocked.iconHotSpotX = hotSpotX; - mLocked.iconHotSpotY = hotSpotY; - mLocked.drawn = false; + updatePointerLocked(); } -bool PointerController::createSurfaceIfNeededLocked() { - if (!mLocked.iconBitmap) { - // If we don't have a pointer icon, then no point allocating a surface now. - return false; +void PointerController::handleMessage(const Message& message) { + switch (message.what) { + case MSG_ANIMATE: + doAnimate(); + break; + case MSG_INACTIVITY_TIMEOUT: + doInactivityTimeout(); + break; } +} - if (mSurfaceComposerClient == NULL) { - mSurfaceComposerClient = new SurfaceComposerClient(); +void PointerController::doAnimate() { + AutoMutex _l(mLock); + + bool keepAnimating = false; + mLocked.animationPending = false; + nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime; + + // Animate pointer fade. + if (mLocked.pointerIsFading) { + mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION; + if (mLocked.pointerAlpha <= 0) { + mLocked.pointerAlpha = 0; + } else { + keepAnimating = true; + } + updatePointerLocked(); } - if (mSurfaceControl == NULL) { - mSurfaceControl = mSurfaceComposerClient->createSurface(getpid(), - String8("Pointer Icon"), 0, - mLocked.iconBitmap->width(), mLocked.iconBitmap->height(), - PIXEL_FORMAT_RGBA_8888); - if (mSurfaceControl == NULL) { - LOGE("Error creating pointer surface."); - return false; + // Animate spots that are fading out and being removed. + for (size_t i = 0; i < mLocked.spots.size(); i++) { + Spot* spot = mLocked.spots.itemAt(i); + if (spot->id == Spot::INVALID_ID) { + spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION; + if (spot->alpha <= 0) { + mLocked.spots.removeAt(i--); + releaseSpotLocked(spot); + } else { + spot->sprite->setAlpha(spot->alpha); + keepAnimating = true; + } } } - return true; + + if (keepAnimating) { + startAnimationLocked(); + } } -bool PointerController::drawPointerIfNeededLocked() { - if (!mLocked.drawn) { - if (!mLocked.iconBitmap) { - return false; - } +void PointerController::doInactivityTimeout() { + AutoMutex _l(mLock); - if (!resizeSurfaceLocked(mLocked.iconBitmap->width(), mLocked.iconBitmap->height())) { - return false; - } + if (!mLocked.pointerIsFading) { + mLocked.pointerIsFading = true; + startAnimationLocked(); + } +} - sp<Surface> surface = mSurfaceControl->getSurface(); +void PointerController::startAnimationLocked() { + if (!mLocked.animationPending) { + mLocked.animationPending = true; + mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC); + mLooper->sendMessageDelayed(ANIMATION_FRAME_INTERVAL, mHandler, Message(MSG_ANIMATE)); + } +} - Surface::SurfaceInfo surfaceInfo; - status_t status = surface->lock(&surfaceInfo); - if (status) { - LOGE("Error %d locking pointer surface before drawing.", status); - return false; - } +void PointerController::resetInactivityTimeoutLocked() { + mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT); - SkBitmap surfaceBitmap; - ssize_t bpr = surfaceInfo.s * bytesPerPixel(surfaceInfo.format); - surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config, surfaceInfo.w, surfaceInfo.h, bpr); - surfaceBitmap.setPixels(surfaceInfo.bits); + nsecs_t timeout = mLocked.inactivityTimeout == INACTIVITY_TIMEOUT_SHORT + ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL; + mLooper->sendMessageDelayed(timeout, mHandler, MSG_INACTIVITY_TIMEOUT); +} - SkCanvas surfaceCanvas; - surfaceCanvas.setBitmapDevice(surfaceBitmap); +void PointerController::sendImmediateInactivityTimeoutLocked() { + mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT); + mLooper->sendMessage(mHandler, MSG_INACTIVITY_TIMEOUT); +} - SkPaint paint; - paint.setXfermodeMode(SkXfermode::kSrc_Mode); - surfaceCanvas.drawBitmap(*mLocked.iconBitmap, 0, 0, &paint); +void PointerController::updatePointerLocked() { + mSpriteController->openTransaction(); - status = surface->unlockAndPost(); - if (status) { - LOGE("Error %d unlocking pointer surface after drawing.", status); - return false; - } + mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER); + mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY); + + if (mLocked.pointerAlpha > 0) { + mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha); + mLocked.pointerSprite->setVisible(true); + } else { + mLocked.pointerSprite->setVisible(false); } - mLocked.drawn = true; - return true; + if (mLocked.pointerIconChanged || mLocked.presentationChanged) { + mLocked.pointerSprite->setIcon(mLocked.presentation == PRESENTATION_POINTER + ? mLocked.pointerIcon : mResources.spotAnchor); + mLocked.pointerIconChanged = false; + mLocked.presentationChanged = false; + } + + mSpriteController->closeTransaction(); } -bool PointerController::resizeSurfaceLocked(int32_t width, int32_t height) { - status_t status = mSurfaceComposerClient->openTransaction(); - if (status) { - LOGE("Error opening surface transaction to resize pointer surface."); - return false; +PointerController::Spot* PointerController::getSpotLocked(uint32_t id) { + for (size_t i = 0; i < mLocked.spots.size(); i++) { + Spot* spot = mLocked.spots.itemAt(i); + if (spot->id == id) { + return spot; + } } + return NULL; +} - status = mSurfaceControl->setSize(width, height); - if (status) { - LOGE("Error %d setting pointer surface size.", status); - return false; +PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id) { + // Remove spots until we have fewer than MAX_SPOTS remaining. + while (mLocked.spots.size() >= MAX_SPOTS) { + Spot* spot = removeFirstFadingSpotLocked(); + if (!spot) { + spot = mLocked.spots.itemAt(0); + mLocked.spots.removeAt(0); + } + releaseSpotLocked(spot); } - status = mSurfaceComposerClient->closeTransaction(); - if (status) { - LOGE("Error closing surface transaction to resize pointer surface."); - return false; + // Obtain a sprite from the recycled pool. + sp<Sprite> sprite; + if (! mLocked.recycledSprites.isEmpty()) { + sprite = mLocked.recycledSprites.top(); + mLocked.recycledSprites.pop(); + } else { + sprite = mSpriteController->createSprite(); } - return true; + // Return the new spot. + Spot* spot = new Spot(id, sprite); + mLocked.spots.push(spot); + return spot; } -void PointerController::handleMessage(const Message& message) { - switch (message.what) { - case MSG_FADE_STEP: { - AutoMutex _l(mLock); - fadeStepLocked(); - break; - } +PointerController::Spot* PointerController::removeFirstFadingSpotLocked() { + for (size_t i = 0; i < mLocked.spots.size(); i++) { + Spot* spot = mLocked.spots.itemAt(i); + if (spot->id == Spot::INVALID_ID) { + mLocked.spots.removeAt(i); + return spot; + } } + return NULL; } -bool PointerController::unfadeBeforeUpdateLocked() { - sendFadeStepMessageDelayedLocked(getInactivityFadeDelayTimeLocked()); +void PointerController::releaseSpotLocked(Spot* spot) { + spot->sprite->clearIcon(); - if (isFadingLocked()) { - mLocked.wantVisible = true; - mLocked.fadeAlpha = 1; - return true; // update required to effect the unfade + if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) { + mLocked.recycledSprites.push(spot->sprite); } - return false; // update not required -} -void PointerController::startFadeLocked() { - if (!isFadingLocked()) { - sendFadeStepMessageDelayedLocked(0); - } + delete spot; } -void PointerController::startInactivityFadeDelayLocked() { - if (!isFadingLocked()) { - sendFadeStepMessageDelayedLocked(getInactivityFadeDelayTimeLocked()); +void PointerController::fadeOutAndReleaseSpotLocked(Spot* spot) { + if (spot->id != Spot::INVALID_ID) { + spot->id = Spot::INVALID_ID; + startAnimationLocked(); } } -void PointerController::fadeStepLocked() { - if (mLocked.wantVisible) { - mLocked.fadeAlpha -= FADE_DECAY_PER_FRAME; - if (mLocked.fadeAlpha < 0) { - mLocked.fadeAlpha = 0; - mLocked.wantVisible = false; - } else { - sendFadeStepMessageDelayedLocked(FADE_FRAME_INTERVAL); - } - updateLocked(); +void PointerController::fadeOutAndReleaseAllSpotsLocked() { + for (size_t i = 0; i < mLocked.spots.size(); i++) { + Spot* spot = mLocked.spots.itemAt(i); + fadeOutAndReleaseSpotLocked(spot); } } -bool PointerController::isFadingLocked() { - return !mLocked.wantVisible || mLocked.fadeAlpha != 1; +void PointerController::loadResources() { + mPolicy->loadPointerResources(&mResources); } -nsecs_t PointerController::getInactivityFadeDelayTimeLocked() { - return mLocked.inactivityFadeDelay == INACTIVITY_FADE_DELAY_SHORT - ? INACTIVITY_FADE_DELAY_TIME_SHORT : INACTIVITY_FADE_DELAY_TIME_NORMAL; -} -void PointerController::sendFadeStepMessageDelayedLocked(nsecs_t delayTime) { - mLooper->removeMessages(mHandler, MSG_FADE_STEP); - mLooper->sendMessageDelayed(delayTime, mHandler, Message(MSG_FADE_STEP)); +// --- PointerController::Spot --- + +void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y) { + sprite->setLayer(Sprite::BASE_LAYER_SPOT + id); + sprite->setAlpha(alpha); + sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale)); + sprite->setPosition(x, y); + + this->x = x; + this->y = y; + + if (icon != lastIcon) { + lastIcon = icon; + if (icon) { + sprite->setIcon(*icon); + sprite->setVisible(true); + } else { + sprite->setVisible(false); + } + } } } // namespace android diff --git a/services/input/PointerController.h b/services/input/PointerController.h index e1dab5c..b9184ac 100644 --- a/services/input/PointerController.h +++ b/services/input/PointerController.h @@ -17,22 +17,23 @@ #ifndef _UI_POINTER_CONTROLLER_H #define _UI_POINTER_CONTROLLER_H +#include "SpriteController.h" + #include <ui/DisplayInfo.h> #include <ui/Input.h> #include <utils/RefBase.h> #include <utils/Looper.h> #include <utils/String8.h> -#include <surfaceflinger/Surface.h> -#include <surfaceflinger/SurfaceComposerClient.h> -#include <surfaceflinger/ISurfaceComposer.h> - #include <SkBitmap.h> namespace android { /** - * Interface for tracking a single (mouse) pointer. + * Interface for tracking a mouse / touch pad pointer and touch pad spots. + * + * The spots are sprites on screen that visually represent the positions of + * fingers * * The pointer controller is responsible for providing synchronization and for tracking * display orientation changes if needed. @@ -66,8 +67,98 @@ public: /* Fades the pointer out now. */ virtual void fade() = 0; - /* Makes the pointer visible if it has faded out. */ + /* Makes the pointer visible if it has faded out. + * The pointer never unfades itself automatically. This method must be called + * by the client whenever the pointer is moved or a button is pressed and it + * wants to ensure that the pointer becomes visible again. */ virtual void unfade() = 0; + + enum Presentation { + // Show the mouse pointer. + PRESENTATION_POINTER, + // Show spots and a spot anchor in place of the mouse pointer. + PRESENTATION_SPOT, + }; + + /* Sets the mode of the pointer controller. */ + virtual void setPresentation(Presentation presentation) = 0; + + // Describes the current gesture. + enum SpotGesture { + // No gesture. + // Do not display any spots. + SPOT_GESTURE_NEUTRAL, + // Tap at current location. + // Briefly display one spot at the tapped location. + SPOT_GESTURE_TAP, + // Drag at current location. + // Display spot at pressed location. + SPOT_GESTURE_DRAG, + // Button pressed but no finger is down. + // Display spot at pressed location. + SPOT_GESTURE_BUTTON_CLICK, + // Button pressed and a finger is down. + // Display spot at pressed location. + SPOT_GESTURE_BUTTON_DRAG, + // One finger down and hovering. + // Display spot at the hovered location. + SPOT_GESTURE_HOVER, + // Two fingers down but not sure in which direction they are moving so we consider + // it a press at the pointer location. + // Display two spots near the pointer location. + SPOT_GESTURE_PRESS, + // Two fingers down and moving in same direction. + // Display two spots near the pointer location. + SPOT_GESTURE_SWIPE, + // Two or more fingers down and moving in arbitrary directions. + // Display two or more spots near the pointer location, one for each finger. + SPOT_GESTURE_FREEFORM, + }; + + /* Sets the spots for the current gesture. + * The spots are not subject to the inactivity timeout like the pointer + * itself it since they are expected to remain visible for so long as + * the fingers are on the touch pad. + * + * The values of the AMOTION_EVENT_AXIS_PRESSURE axis is significant. + * For spotCoords, pressure != 0 indicates that the spot's location is being + * pressed (not hovering). + */ + virtual void setSpots(SpotGesture spotGesture, + const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, + BitSet32 spotIdBits) = 0; + + /* Removes all spots. */ + virtual void clearSpots() = 0; +}; + + +/* + * Pointer resources. + */ +struct PointerResources { + SpriteIcon spotHover; + SpriteIcon spotTouch; + SpriteIcon spotAnchor; +}; + + +/* + * Pointer controller policy interface. + * + * The pointer controller policy is used by the pointer controller to interact with + * the Window Manager and other system components. + * + * The actual implementation is partially supported by callbacks into the DVM + * via JNI. This interface is also mocked in the unit tests. + */ +class PointerControllerPolicyInterface : public virtual RefBase { +protected: + PointerControllerPolicyInterface() { } + virtual ~PointerControllerPolicyInterface() { } + +public: + virtual void loadPointerResources(PointerResources* outResources) = 0; }; @@ -81,12 +172,13 @@ protected: virtual ~PointerController(); public: - enum InactivityFadeDelay { - INACTIVITY_FADE_DELAY_NORMAL = 0, - INACTIVITY_FADE_DELAY_SHORT = 1, + enum InactivityTimeout { + INACTIVITY_TIMEOUT_NORMAL = 0, + INACTIVITY_TIMEOUT_SHORT = 1, }; - PointerController(const sp<Looper>& looper, int32_t pointerLayer); + PointerController(const sp<PointerControllerPolicyInterface>& policy, + const sp<Looper>& looper, const sp<SpriteController>& spriteController); virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const; @@ -98,61 +190,101 @@ public: virtual void fade(); virtual void unfade(); + virtual void setPresentation(Presentation presentation); + virtual void setSpots(SpotGesture spotGesture, + const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits); + virtual void clearSpots(); + void setDisplaySize(int32_t width, int32_t height); void setDisplayOrientation(int32_t orientation); - void setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY); - void setInactivityFadeDelay(InactivityFadeDelay inactivityFadeDelay); + void setPointerIcon(const SpriteIcon& icon); + void setInactivityTimeout(InactivityTimeout inactivityTimeout); private: + static const size_t MAX_RECYCLED_SPRITES = 12; + static const size_t MAX_SPOTS = 12; + enum { - MSG_FADE_STEP = 0, + MSG_ANIMATE, + MSG_INACTIVITY_TIMEOUT, + }; + + struct Spot { + static const uint32_t INVALID_ID = 0xffffffff; + + uint32_t id; + sp<Sprite> sprite; + float alpha; + float scale; + float x, y; + + inline Spot(uint32_t id, const sp<Sprite>& sprite) + : id(id), sprite(sprite), alpha(1.0f), scale(1.0f), + x(0.0f), y(0.0f), lastIcon(NULL) { } + + void updateSprite(const SpriteIcon* icon, float x, float y); + + private: + const SpriteIcon* lastIcon; }; mutable Mutex mLock; + sp<PointerControllerPolicyInterface> mPolicy; sp<Looper> mLooper; - int32_t mPointerLayer; - sp<SurfaceComposerClient> mSurfaceComposerClient; - sp<SurfaceControl> mSurfaceControl; + sp<SpriteController> mSpriteController; + sp<WeakMessageHandler> mHandler; + + PointerResources mResources; struct Locked { + bool animationPending; + nsecs_t animationTime; + int32_t displayWidth; int32_t displayHeight; int32_t displayOrientation; + InactivityTimeout inactivityTimeout; + + Presentation presentation; + bool presentationChanged; + + bool pointerIsFading; float pointerX; float pointerY; - uint32_t buttonState; + float pointerAlpha; + sp<Sprite> pointerSprite; + SpriteIcon pointerIcon; + bool pointerIconChanged; - SkBitmap* iconBitmap; - float iconHotSpotX; - float iconHotSpotY; - - float fadeAlpha; - InactivityFadeDelay inactivityFadeDelay; + uint32_t buttonState; - bool wantVisible; - bool visible; - bool drawn; + Vector<Spot*> spots; + Vector<sp<Sprite> > recycledSprites; } mLocked; - sp<WeakMessageHandler> mHandler; - bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const; void setPositionLocked(float x, float y); - void updateLocked(); - bool createSurfaceIfNeededLocked(); - bool drawPointerIfNeededLocked(); - bool resizeSurfaceLocked(int32_t width, int32_t height); void handleMessage(const Message& message); - bool unfadeBeforeUpdateLocked(); - void startFadeLocked(); - void startInactivityFadeDelayLocked(); - void fadeStepLocked(); - bool isFadingLocked(); - nsecs_t getInactivityFadeDelayTimeLocked(); - void sendFadeStepMessageDelayedLocked(nsecs_t delayTime); + void doAnimate(); + void doInactivityTimeout(); + + void startAnimationLocked(); + + void resetInactivityTimeoutLocked(); + void sendImmediateInactivityTimeoutLocked(); + void updatePointerLocked(); + + Spot* getSpotLocked(uint32_t id); + Spot* createAndAddSpotLocked(uint32_t id); + Spot* removeFirstFadingSpotLocked(); + void releaseSpotLocked(Spot* spot); + void fadeOutAndReleaseSpotLocked(Spot* spot); + void fadeOutAndReleaseAllSpotsLocked(); + + void loadResources(); }; } // namespace android diff --git a/services/input/SpriteController.cpp b/services/input/SpriteController.cpp new file mode 100644 index 0000000..08cc75e --- /dev/null +++ b/services/input/SpriteController.cpp @@ -0,0 +1,488 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "Sprites" + +//#define LOG_NDEBUG 0 + +#include "SpriteController.h" + +#include <cutils/log.h> +#include <utils/String8.h> + +#include <SkBitmap.h> +#include <SkCanvas.h> +#include <SkColor.h> +#include <SkPaint.h> +#include <SkXfermode.h> + +namespace android { + +// --- SpriteController --- + +SpriteController::SpriteController(const sp<Looper>& looper, int32_t overlayLayer) : + mLooper(looper), mOverlayLayer(overlayLayer) { + mHandler = new WeakMessageHandler(this); + + mLocked.transactionNestingCount = 0; + mLocked.deferredSpriteUpdate = false; +} + +SpriteController::~SpriteController() { + mLooper->removeMessages(mHandler); + + if (mSurfaceComposerClient != NULL) { + mSurfaceComposerClient->dispose(); + mSurfaceComposerClient.clear(); + } +} + +sp<Sprite> SpriteController::createSprite() { + return new SpriteImpl(this); +} + +void SpriteController::openTransaction() { + AutoMutex _l(mLock); + + mLocked.transactionNestingCount += 1; +} + +void SpriteController::closeTransaction() { + AutoMutex _l(mLock); + + LOG_ALWAYS_FATAL_IF(mLocked.transactionNestingCount == 0, + "Sprite closeTransaction() called but there is no open sprite transaction"); + + mLocked.transactionNestingCount -= 1; + if (mLocked.transactionNestingCount == 0 && mLocked.deferredSpriteUpdate) { + mLocked.deferredSpriteUpdate = false; + mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES)); + } +} + +void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) { + bool wasEmpty = mLocked.invalidatedSprites.isEmpty(); + mLocked.invalidatedSprites.push(sprite); + if (wasEmpty) { + if (mLocked.transactionNestingCount != 0) { + mLocked.deferredSpriteUpdate = true; + } else { + mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES)); + } + } +} + +void SpriteController::disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl) { + bool wasEmpty = mLocked.disposedSurfaces.isEmpty(); + mLocked.disposedSurfaces.push(surfaceControl); + if (wasEmpty) { + mLooper->sendMessage(mHandler, Message(MSG_DISPOSE_SURFACES)); + } +} + +void SpriteController::handleMessage(const Message& message) { + switch (message.what) { + case MSG_UPDATE_SPRITES: + doUpdateSprites(); + break; + case MSG_DISPOSE_SURFACES: + doDisposeSurfaces(); + break; + } +} + +void SpriteController::doUpdateSprites() { + // Collect information about sprite updates. + // Each sprite update record includes a reference to its associated sprite so we can + // be certain the sprites will not be deleted while this function runs. Sprites + // may invalidate themselves again during this time but we will handle those changes + // in the next iteration. + Vector<SpriteUpdate> updates; + size_t numSprites; + { // acquire lock + AutoMutex _l(mLock); + + numSprites = mLocked.invalidatedSprites.size(); + for (size_t i = 0; i < numSprites; i++) { + const sp<SpriteImpl>& sprite = mLocked.invalidatedSprites.itemAt(i); + + updates.push(SpriteUpdate(sprite, sprite->getStateLocked())); + sprite->resetDirtyLocked(); + } + mLocked.invalidatedSprites.clear(); + } // release lock + + // Create missing surfaces. + bool surfaceChanged = false; + for (size_t i = 0; i < numSprites; i++) { + SpriteUpdate& update = updates.editItemAt(i); + + if (update.state.surfaceControl == NULL && update.state.wantSurfaceVisible()) { + update.state.surfaceWidth = update.state.icon.bitmap.width(); + update.state.surfaceHeight = update.state.icon.bitmap.height(); + update.state.surfaceDrawn = false; + update.state.surfaceVisible = false; + update.state.surfaceControl = obtainSurface( + update.state.surfaceWidth, update.state.surfaceHeight); + if (update.state.surfaceControl != NULL) { + update.surfaceChanged = surfaceChanged = true; + } + } + } + + // Resize sprites if needed, inside a global transaction. + bool haveGlobalTransaction = false; + for (size_t i = 0; i < numSprites; i++) { + SpriteUpdate& update = updates.editItemAt(i); + + if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) { + int32_t desiredWidth = update.state.icon.bitmap.width(); + int32_t desiredHeight = update.state.icon.bitmap.height(); + if (update.state.surfaceWidth < desiredWidth + || update.state.surfaceHeight < desiredHeight) { + if (!haveGlobalTransaction) { + SurfaceComposerClient::openGlobalTransaction(); + haveGlobalTransaction = true; + } + + status_t status = update.state.surfaceControl->setSize(desiredWidth, desiredHeight); + if (status) { + LOGE("Error %d resizing sprite surface from %dx%d to %dx%d", + status, update.state.surfaceWidth, update.state.surfaceHeight, + desiredWidth, desiredHeight); + } else { + update.state.surfaceWidth = desiredWidth; + update.state.surfaceHeight = desiredHeight; + update.state.surfaceDrawn = false; + update.surfaceChanged = surfaceChanged = true; + + if (update.state.surfaceVisible) { + status = update.state.surfaceControl->hide(); + if (status) { + LOGE("Error %d hiding sprite surface after resize.", status); + } else { + update.state.surfaceVisible = false; + } + } + } + } + } + } + if (haveGlobalTransaction) { + SurfaceComposerClient::closeGlobalTransaction(); + } + + // Redraw sprites if needed. + for (size_t i = 0; i < numSprites; i++) { + SpriteUpdate& update = updates.editItemAt(i); + + if ((update.state.dirty & DIRTY_BITMAP) && update.state.surfaceDrawn) { + update.state.surfaceDrawn = false; + update.surfaceChanged = surfaceChanged = true; + } + + if (update.state.surfaceControl != NULL && !update.state.surfaceDrawn + && update.state.wantSurfaceVisible()) { + sp<Surface> surface = update.state.surfaceControl->getSurface(); + Surface::SurfaceInfo surfaceInfo; + status_t status = surface->lock(&surfaceInfo); + if (status) { + LOGE("Error %d locking sprite surface before drawing.", status); + } else { + SkBitmap surfaceBitmap; + ssize_t bpr = surfaceInfo.s * bytesPerPixel(surfaceInfo.format); + surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config, + surfaceInfo.w, surfaceInfo.h, bpr); + surfaceBitmap.setPixels(surfaceInfo.bits); + + SkCanvas surfaceCanvas; + surfaceCanvas.setBitmapDevice(surfaceBitmap); + + SkPaint paint; + paint.setXfermodeMode(SkXfermode::kSrc_Mode); + surfaceCanvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint); + + if (surfaceInfo.w > uint32_t(update.state.icon.bitmap.width())) { + paint.setColor(0); // transparent fill color + surfaceCanvas.drawRectCoords(update.state.icon.bitmap.width(), 0, + surfaceInfo.w, update.state.icon.bitmap.height(), paint); + } + if (surfaceInfo.h > uint32_t(update.state.icon.bitmap.height())) { + paint.setColor(0); // transparent fill color + surfaceCanvas.drawRectCoords(0, update.state.icon.bitmap.height(), + surfaceInfo.w, surfaceInfo.h, paint); + } + + status = surface->unlockAndPost(); + if (status) { + LOGE("Error %d unlocking and posting sprite surface after drawing.", status); + } else { + update.state.surfaceDrawn = true; + update.surfaceChanged = surfaceChanged = true; + } + } + } + } + + // Set sprite surface properties and make them visible. + bool haveTransaction = false; + for (size_t i = 0; i < numSprites; i++) { + SpriteUpdate& update = updates.editItemAt(i); + + bool wantSurfaceVisibleAndDrawn = update.state.wantSurfaceVisible() + && update.state.surfaceDrawn; + bool becomingVisible = wantSurfaceVisibleAndDrawn && !update.state.surfaceVisible; + bool becomingHidden = !wantSurfaceVisibleAndDrawn && update.state.surfaceVisible; + if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden + || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA + | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER + | DIRTY_VISIBILITY | DIRTY_HOTSPOT))))) { + status_t status; + if (!haveTransaction) { + status = mSurfaceComposerClient->openTransaction(); + if (status) { + LOGE("Error %d opening transation to update sprite surface.", status); + break; + } + haveTransaction = true; + } + + if (wantSurfaceVisibleAndDrawn + && (becomingVisible || (update.state.dirty & DIRTY_ALPHA))) { + status = update.state.surfaceControl->setAlpha(update.state.alpha); + if (status) { + LOGE("Error %d setting sprite surface alpha.", status); + } + } + + if (wantSurfaceVisibleAndDrawn + && (becomingVisible || (update.state.dirty & (DIRTY_POSITION + | DIRTY_HOTSPOT)))) { + status = update.state.surfaceControl->setPosition( + update.state.positionX - update.state.icon.hotSpotX, + update.state.positionY - update.state.icon.hotSpotY); + if (status) { + LOGE("Error %d setting sprite surface position.", status); + } + } + + if (wantSurfaceVisibleAndDrawn + && (becomingVisible + || (update.state.dirty & DIRTY_TRANSFORMATION_MATRIX))) { + status = update.state.surfaceControl->setMatrix( + update.state.transformationMatrix.dsdx, + update.state.transformationMatrix.dtdx, + update.state.transformationMatrix.dsdy, + update.state.transformationMatrix.dtdy); + if (status) { + LOGE("Error %d setting sprite surface transformation matrix.", status); + } + } + + int32_t surfaceLayer = mOverlayLayer + update.state.layer; + if (wantSurfaceVisibleAndDrawn + && (becomingVisible || (update.state.dirty & DIRTY_LAYER))) { + status = update.state.surfaceControl->setLayer(surfaceLayer); + if (status) { + LOGE("Error %d setting sprite surface layer.", status); + } + } + + if (becomingVisible) { + status = update.state.surfaceControl->show(surfaceLayer); + if (status) { + LOGE("Error %d showing sprite surface.", status); + } else { + update.state.surfaceVisible = true; + update.surfaceChanged = surfaceChanged = true; + } + } else if (becomingHidden) { + status = update.state.surfaceControl->hide(); + if (status) { + LOGE("Error %d hiding sprite surface.", status); + } else { + update.state.surfaceVisible = false; + update.surfaceChanged = surfaceChanged = true; + } + } + } + } + + if (haveTransaction) { + status_t status = mSurfaceComposerClient->closeTransaction(); + if (status) { + LOGE("Error %d closing transaction to update sprite surface.", status); + } + } + + // If any surfaces were changed, write back the new surface properties to the sprites. + if (surfaceChanged) { // acquire lock + AutoMutex _l(mLock); + + for (size_t i = 0; i < numSprites; i++) { + const SpriteUpdate& update = updates.itemAt(i); + + if (update.surfaceChanged) { + update.sprite->setSurfaceLocked(update.state.surfaceControl, + update.state.surfaceWidth, update.state.surfaceHeight, + update.state.surfaceDrawn, update.state.surfaceVisible); + } + } + } // release lock + + // Clear the sprite update vector outside the lock. It is very important that + // we do not clear sprite references inside the lock since we could be releasing + // the last remaining reference to the sprite here which would result in the + // sprite being deleted and the lock being reacquired by the sprite destructor + // while already held. + updates.clear(); +} + +void SpriteController::doDisposeSurfaces() { + // Collect disposed surfaces. + Vector<sp<SurfaceControl> > disposedSurfaces; + { // acquire lock + AutoMutex _l(mLock); + + disposedSurfaces = mLocked.disposedSurfaces; + mLocked.disposedSurfaces.clear(); + } // release lock + + // Release the last reference to each surface outside of the lock. + // We don't want the surfaces to be deleted while we are holding our lock. + disposedSurfaces.clear(); +} + +void SpriteController::ensureSurfaceComposerClient() { + if (mSurfaceComposerClient == NULL) { + mSurfaceComposerClient = new SurfaceComposerClient(); + } +} + +sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height) { + ensureSurfaceComposerClient(); + + sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface( + String8("Sprite"), 0, width, height, PIXEL_FORMAT_RGBA_8888); + if (surfaceControl == NULL || !surfaceControl->isValid() + || !surfaceControl->getSurface()->isValid()) { + LOGE("Error creating sprite surface."); + return NULL; + } + return surfaceControl; +} + + +// --- SpriteController::SpriteImpl --- + +SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) : + mController(controller) { +} + +SpriteController::SpriteImpl::~SpriteImpl() { + AutoMutex _m(mController->mLock); + + // Let the controller take care of deleting the last reference to sprite + // surfaces so that we do not block the caller on an IPC here. + if (mLocked.state.surfaceControl != NULL) { + mController->disposeSurfaceLocked(mLocked.state.surfaceControl); + mLocked.state.surfaceControl.clear(); + } +} + +void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) { + AutoMutex _l(mController->mLock); + + uint32_t dirty; + if (icon.isValid()) { + icon.bitmap.copyTo(&mLocked.state.icon.bitmap, SkBitmap::kARGB_8888_Config); + + if (!mLocked.state.icon.isValid() + || mLocked.state.icon.hotSpotX != icon.hotSpotX + || mLocked.state.icon.hotSpotY != icon.hotSpotY) { + mLocked.state.icon.hotSpotX = icon.hotSpotX; + mLocked.state.icon.hotSpotY = icon.hotSpotY; + dirty = DIRTY_BITMAP | DIRTY_HOTSPOT; + } else { + dirty = DIRTY_BITMAP; + } + } else if (mLocked.state.icon.isValid()) { + mLocked.state.icon.bitmap.reset(); + dirty = DIRTY_BITMAP | DIRTY_HOTSPOT; + } else { + return; // setting to invalid icon and already invalid so nothing to do + } + + invalidateLocked(dirty); +} + +void SpriteController::SpriteImpl::setVisible(bool visible) { + AutoMutex _l(mController->mLock); + + if (mLocked.state.visible != visible) { + mLocked.state.visible = visible; + invalidateLocked(DIRTY_VISIBILITY); + } +} + +void SpriteController::SpriteImpl::setPosition(float x, float y) { + AutoMutex _l(mController->mLock); + + if (mLocked.state.positionX != x || mLocked.state.positionY != y) { + mLocked.state.positionX = x; + mLocked.state.positionY = y; + invalidateLocked(DIRTY_POSITION); + } +} + +void SpriteController::SpriteImpl::setLayer(int32_t layer) { + AutoMutex _l(mController->mLock); + + if (mLocked.state.layer != layer) { + mLocked.state.layer = layer; + invalidateLocked(DIRTY_LAYER); + } +} + +void SpriteController::SpriteImpl::setAlpha(float alpha) { + AutoMutex _l(mController->mLock); + + if (mLocked.state.alpha != alpha) { + mLocked.state.alpha = alpha; + invalidateLocked(DIRTY_ALPHA); + } +} + +void SpriteController::SpriteImpl::setTransformationMatrix( + const SpriteTransformationMatrix& matrix) { + AutoMutex _l(mController->mLock); + + if (mLocked.state.transformationMatrix != matrix) { + mLocked.state.transformationMatrix = matrix; + invalidateLocked(DIRTY_TRANSFORMATION_MATRIX); + } +} + +void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) { + bool wasDirty = mLocked.state.dirty; + mLocked.state.dirty |= dirty; + + if (!wasDirty) { + mController->invalidateSpriteLocked(this); + } +} + +} // namespace android diff --git a/services/input/SpriteController.h b/services/input/SpriteController.h new file mode 100644 index 0000000..50ae8a5 --- /dev/null +++ b/services/input/SpriteController.h @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _UI_SPRITES_H +#define _UI_SPRITES_H + +#include <utils/RefBase.h> +#include <utils/Looper.h> + +#include <surfaceflinger/Surface.h> +#include <surfaceflinger/SurfaceComposerClient.h> +#include <surfaceflinger/ISurfaceComposer.h> + +#include <SkBitmap.h> + +namespace android { + +/* + * Transformation matrix for a sprite. + */ +struct SpriteTransformationMatrix { + inline SpriteTransformationMatrix() : dsdx(1.0f), dtdx(0.0f), dsdy(0.0f), dtdy(1.0f) { } + inline SpriteTransformationMatrix(float dsdx, float dtdx, float dsdy, float dtdy) : + dsdx(dsdx), dtdx(dtdx), dsdy(dsdy), dtdy(dtdy) { } + + float dsdx; + float dtdx; + float dsdy; + float dtdy; + + inline bool operator== (const SpriteTransformationMatrix& other) { + return dsdx == other.dsdx + && dtdx == other.dtdx + && dsdy == other.dsdy + && dtdy == other.dtdy; + } + + inline bool operator!= (const SpriteTransformationMatrix& other) { + return !(*this == other); + } +}; + +/* + * Icon that a sprite displays, including its hotspot. + */ +struct SpriteIcon { + inline SpriteIcon() : hotSpotX(0), hotSpotY(0) { } + inline SpriteIcon(const SkBitmap& bitmap, float hotSpotX, float hotSpotY) : + bitmap(bitmap), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { } + + SkBitmap bitmap; + float hotSpotX; + float hotSpotY; + + inline SpriteIcon copy() const { + SkBitmap bitmapCopy; + bitmap.copyTo(&bitmapCopy, SkBitmap::kARGB_8888_Config); + return SpriteIcon(bitmapCopy, hotSpotX, hotSpotY); + } + + inline void reset() { + bitmap.reset(); + hotSpotX = 0; + hotSpotY = 0; + } + + inline bool isValid() const { + return !bitmap.isNull() && !bitmap.empty(); + } +}; + +/* + * A sprite is a simple graphical object that is displayed on-screen above other layers. + * The basic sprite class is an interface. + * The implementation is provided by the sprite controller. + */ +class Sprite : public RefBase { +protected: + Sprite() { } + virtual ~Sprite() { } + +public: + enum { + // The base layer for pointer sprites. + BASE_LAYER_POINTER = 0, // reserve space for 1 pointer + + // The base layer for spot sprites. + BASE_LAYER_SPOT = 1, // reserve space for MAX_POINTER_ID spots + }; + + /* Sets the bitmap that is drawn by the sprite. + * The sprite retains a copy of the bitmap for subsequent rendering. */ + virtual void setIcon(const SpriteIcon& icon) = 0; + + inline void clearIcon() { + setIcon(SpriteIcon()); + } + + /* Sets whether the sprite is visible. */ + virtual void setVisible(bool visible) = 0; + + /* Sets the sprite position on screen, relative to the sprite's hot spot. */ + virtual void setPosition(float x, float y) = 0; + + /* Sets the layer of the sprite, relative to the system sprite overlay layer. + * Layer 0 is the overlay layer, > 0 appear above this layer. */ + virtual void setLayer(int32_t layer) = 0; + + /* Sets the sprite alpha blend ratio between 0.0 and 1.0. */ + virtual void setAlpha(float alpha) = 0; + + /* Sets the sprite transformation matrix. */ + virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0; +}; + +/* + * Displays sprites on the screen. + * + * This interface is used by PointerController and SpotController to draw pointers or + * spot representations of fingers. It is not intended for general purpose use + * by other components. + * + * All sprite position updates and rendering is performed asynchronously. + * + * Clients are responsible for animating sprites by periodically updating their properties. + */ +class SpriteController : public MessageHandler { +protected: + virtual ~SpriteController(); + +public: + SpriteController(const sp<Looper>& looper, int32_t overlayLayer); + + /* Creates a new sprite, initially invisible. */ + sp<Sprite> createSprite(); + + /* Opens or closes a transaction to perform a batch of sprite updates as part of + * a single operation such as setPosition and setAlpha. It is not necessary to + * open a transaction when updating a single property. + * Calls to openTransaction() nest and must be matched by an equal number + * of calls to closeTransaction(). */ + void openTransaction(); + void closeTransaction(); + +private: + enum { + MSG_UPDATE_SPRITES, + MSG_DISPOSE_SURFACES, + }; + + enum { + DIRTY_BITMAP = 1 << 0, + DIRTY_ALPHA = 1 << 1, + DIRTY_POSITION = 1 << 2, + DIRTY_TRANSFORMATION_MATRIX = 1 << 3, + DIRTY_LAYER = 1 << 4, + DIRTY_VISIBILITY = 1 << 5, + DIRTY_HOTSPOT = 1 << 6, + }; + + /* Describes the state of a sprite. + * This structure is designed so that it can be copied during updates so that + * surfaces can be resized and redrawn without blocking the client by holding a lock + * on the sprites for a long time. + * Note that the SkBitmap holds a reference to a shared (and immutable) pixel ref. */ + struct SpriteState { + inline SpriteState() : + dirty(0), visible(false), + positionX(0), positionY(0), layer(0), alpha(1.0f), + surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) { + } + + uint32_t dirty; + + SpriteIcon icon; + bool visible; + float positionX; + float positionY; + int32_t layer; + float alpha; + SpriteTransformationMatrix transformationMatrix; + + sp<SurfaceControl> surfaceControl; + int32_t surfaceWidth; + int32_t surfaceHeight; + bool surfaceDrawn; + bool surfaceVisible; + + inline bool wantSurfaceVisible() const { + return visible && alpha > 0.0f && icon.isValid(); + } + }; + + /* Client interface for a sprite. + * Requests acquire a lock on the controller, update local state and request the + * controller to invalidate the sprite. + * The real heavy lifting of creating, resizing and redrawing surfaces happens + * asynchronously with no locks held except in short critical section to copy + * the sprite state before the work and update the sprite surface control afterwards. + */ + class SpriteImpl : public Sprite { + protected: + virtual ~SpriteImpl(); + + public: + SpriteImpl(const sp<SpriteController> controller); + + virtual void setIcon(const SpriteIcon& icon); + virtual void setVisible(bool visible); + virtual void setPosition(float x, float y); + virtual void setLayer(int32_t layer); + virtual void setAlpha(float alpha); + virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix); + + inline const SpriteState& getStateLocked() const { + return mLocked.state; + } + + inline void resetDirtyLocked() { + mLocked.state.dirty = 0; + } + + inline void setSurfaceLocked(const sp<SurfaceControl>& surfaceControl, + int32_t width, int32_t height, bool drawn, bool visible) { + mLocked.state.surfaceControl = surfaceControl; + mLocked.state.surfaceWidth = width; + mLocked.state.surfaceHeight = height; + mLocked.state.surfaceDrawn = drawn; + mLocked.state.surfaceVisible = visible; + } + + private: + sp<SpriteController> mController; + + struct Locked { + SpriteState state; + } mLocked; // guarded by mController->mLock + + void invalidateLocked(uint32_t dirty); + }; + + /* Stores temporary information collected during the sprite update cycle. */ + struct SpriteUpdate { + inline SpriteUpdate() : surfaceChanged(false) { } + inline SpriteUpdate(const sp<SpriteImpl> sprite, const SpriteState& state) : + sprite(sprite), state(state), surfaceChanged(false) { + } + + sp<SpriteImpl> sprite; + SpriteState state; + bool surfaceChanged; + }; + + mutable Mutex mLock; + + sp<Looper> mLooper; + const int32_t mOverlayLayer; + sp<WeakMessageHandler> mHandler; + + sp<SurfaceComposerClient> mSurfaceComposerClient; + + struct Locked { + Vector<sp<SpriteImpl> > invalidatedSprites; + Vector<sp<SurfaceControl> > disposedSurfaces; + uint32_t transactionNestingCount; + bool deferredSpriteUpdate; + } mLocked; // guarded by mLock + + void invalidateSpriteLocked(const sp<SpriteImpl>& sprite); + void disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl); + + void handleMessage(const Message& message); + void doUpdateSprites(); + void doDisposeSurfaces(); + + void ensureSurfaceComposerClient(); + sp<SurfaceControl> obtainSurface(int32_t width, int32_t height); +}; + +} // namespace android + +#endif // _UI_SPRITES_H diff --git a/services/input/tests/Android.mk b/services/input/tests/Android.mk index 799eb76..cabbccb 100644 --- a/services/input/tests/Android.mk +++ b/services/input/tests/Android.mk @@ -15,7 +15,6 @@ shared_libraries := \ libhardware \ libhardware_legacy \ libui \ - libsurfaceflinger_client \ libskia \ libstlport \ libinput diff --git a/services/input/tests/InputDispatcher_test.cpp b/services/input/tests/InputDispatcher_test.cpp index 2f846c4..3650da0 100644 --- a/services/input/tests/InputDispatcher_test.cpp +++ b/services/input/tests/InputDispatcher_test.cpp @@ -67,6 +67,10 @@ private: return 60; } + virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) { + return true; + } + virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) { } @@ -124,7 +128,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { /*action*/ -1, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject key events with undefined action."; // Rejects ACTION_MULTIPLE since it is not supported despite being defined in the API. @@ -132,7 +136,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) { AKEY_EVENT_ACTION_MULTIPLE, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject key events with ACTION_MULTIPLE."; } @@ -150,7 +154,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with undefined action."; // Rejects pointer down with invalid index. @@ -160,7 +164,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer down index too large."; event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, @@ -169,7 +173,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer down index too small."; // Rejects pointer up with invalid index. @@ -179,7 +183,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer up index too large."; event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, @@ -188,7 +192,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer up index too small."; // Rejects motion events with invalid number of pointers. @@ -197,7 +201,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 0, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with 0 pointers."; event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, @@ -205,7 +209,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ MAX_POINTERS + 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with more than MAX_POINTERS pointers."; // Rejects motion events with invalid pointer ids. @@ -215,7 +219,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer ids less than 0."; pointerIds[0] = MAX_POINTER_ID + 1; @@ -224,7 +228,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 1, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with pointer ids greater than MAX_POINTER_ID."; // Rejects motion events with duplicate pointer ids. @@ -235,7 +239,7 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) { ARBITRARY_TIME, ARBITRARY_TIME, /*pointerCount*/ 2, pointerIds, pointerCoords); ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event, - INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0)) + INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0)) << "Should reject motion events with duplicate pointer ids."; } diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp index 32982c4..54bb9d7 100644 --- a/services/input/tests/InputReader_test.cpp +++ b/services/input/tests/InputReader_test.cpp @@ -97,6 +97,16 @@ private: virtual void unfade() { } + + virtual void setPresentation(Presentation presentation) { + } + + virtual void setSpots(SpotGesture spotGesture, + const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits) { + } + + virtual void clearSpots() { + } }; @@ -369,7 +379,8 @@ private: } virtual int32_t injectInputEvent(const InputEvent* event, - int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) { + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, + uint32_t policyFlags) { ADD_FAILURE() << "Should never be called by input reader."; return INPUT_EVENT_INJECTION_FAILED; } @@ -386,6 +397,10 @@ private: ADD_FAILURE() << "Should never be called by input reader."; } + virtual void setInputFilterEnabled(bool enabled) { + ADD_FAILURE() << "Should never be called by input reader."; + } + virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel, const sp<InputChannel>& toChannel) { ADD_FAILURE() << "Should never be called by input reader."; @@ -622,14 +637,14 @@ private: mExcludedDevices.add(String8(deviceName)); } - virtual bool getEvent(RawEvent* outEvent) { + virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) { if (mEvents.empty()) { - return false; + return 0; } - *outEvent = *mEvents.begin(); + *buffer = *mEvents.begin(); mEvents.erase(mEvents.begin()); - return true; + return 1; } virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const { @@ -780,6 +795,9 @@ private: virtual void fadePointer() { } + + virtual void requestTimeoutAtTime(nsecs_t when) { + } }; @@ -1442,7 +1460,7 @@ TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRe // Event handling. RawEvent event; - mDevice->process(&event); + mDevice->process(&event, 1); ASSERT_NO_FATAL_FAILURE(mapper1->assertProcessWasCalled()); ASSERT_NO_FATAL_FAILURE(mapper2->assertProcessWasCalled()); @@ -2460,8 +2478,18 @@ void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper* mapper) { } -TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecified_ReturnsTouchPad) { +TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNotACursor_ReturnsPointer) { + SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + prepareAxes(POSITION); + addMapperAndConfigure(mapper); + + ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper->getSources()); +} + +TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndIsACursor_ReturnsTouchPad) { SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice); + mFakeEventHub->addRelativeAxis(DEVICE_ID, REL_X); + mFakeEventHub->addRelativeAxis(DEVICE_ID, REL_Y); prepareAxes(POSITION); addMapperAndConfigure(mapper); diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index a4a95a0..c03b994 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -541,7 +541,9 @@ class AppWidgetService extends IAppWidgetService.Stub IRemoteViewsFactory.Stub.asInterface(service); try { cb.onDestroy(intent); - } catch (Exception e) { + } catch (RemoteException e) { + e.printStackTrace(); + } catch (RuntimeException e) { e.printStackTrace(); } mContext.unbindService(this); diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index ea38fbb..a334dbb 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -23,10 +23,14 @@ import android.app.IActivityManager; import android.app.IApplicationThread; import android.app.IBackupAgent; import android.app.PendingIntent; +import android.app.backup.BackupDataOutput; +import android.app.backup.FullBackup; import android.app.backup.RestoreSet; import android.app.backup.IBackupManager; +import android.app.backup.IFullBackupRestoreObserver; import android.app.backup.IRestoreObserver; import android.app.backup.IRestoreSession; +import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -60,6 +64,9 @@ import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; +import android.util.StringBuilderPrinter; + +import libcore.io.Libcore; import com.android.internal.backup.BackupConstants; import com.android.internal.backup.IBackupTransport; @@ -81,10 +88,15 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; class BackupManagerService extends IBackupManager.Stub { private static final String TAG = "BackupManagerService"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = true; + + // Name and current contents version of the full-backup manifest file + static final String BACKUP_MANIFEST_FILENAME = "_manifest"; + static final int BACKUP_MANIFEST_VERSION = 1; // How often we perform a backup pass. Privileged external callers can // trigger an immediate pass. @@ -108,14 +120,20 @@ class BackupManagerService extends IBackupManager.Stub { private static final int MSG_RUN_GET_RESTORE_SETS = 6; private static final int MSG_TIMEOUT = 7; private static final int MSG_RESTORE_TIMEOUT = 8; + private static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9; + private static final int MSG_RUN_FULL_RESTORE = 10; // Timeout interval for deciding that a bind or clear-data has taken too long static final long TIMEOUT_INTERVAL = 10 * 1000; // Timeout intervals for agent backup & restore operations static final long TIMEOUT_BACKUP_INTERVAL = 30 * 1000; + static final long TIMEOUT_FULL_BACKUP_INTERVAL = 5 * 60 * 1000; static final long TIMEOUT_RESTORE_INTERVAL = 60 * 1000; + // User confirmation timeout for a full backup/restore operation + static final long TIMEOUT_FULL_CONFIRMATION = 30 * 1000; + private Context mContext; private PackageManager mPackageManager; IPackageManager mPackageManagerBinder; @@ -138,15 +156,13 @@ class BackupManagerService extends IBackupManager.Stub { // set of backup services that have pending changes class BackupRequest { public ApplicationInfo appInfo; - public boolean fullBackup; - BackupRequest(ApplicationInfo app, boolean isFull) { + BackupRequest(ApplicationInfo app) { appInfo = app; - fullBackup = isFull; } public String toString() { - return "BackupRequest{app=" + appInfo + " full=" + fullBackup + "}"; + return "BackupRequest{app=" + appInfo + "}"; } } // Backups that we haven't started yet. Keys are package names. @@ -232,6 +248,38 @@ class BackupManagerService extends IBackupManager.Stub { } } + class FullParams { + public ParcelFileDescriptor fd; + public final AtomicBoolean latch; + public IFullBackupRestoreObserver observer; + + FullParams() { + latch = new AtomicBoolean(false); + } + } + + class FullBackupParams extends FullParams { + public boolean includeApks; + public boolean includeShared; + public boolean allApps; + public String[] packages; + + FullBackupParams(ParcelFileDescriptor output, boolean saveApks, boolean saveShared, + boolean doAllApps, String[] pkgList) { + fd = output; + includeApks = saveApks; + includeShared = saveShared; + allApps = doAllApps; + packages = pkgList; + } + } + + class FullRestoreParams extends FullParams { + FullRestoreParams(ParcelFileDescriptor input) { + fd = input; + } + } + // Bookkeeping of in-flight operations for timeout etc. purposes. The operation // token is the index of the entry in the pending-operations list. static final int OP_PENDING = 0; @@ -242,6 +290,8 @@ class BackupManagerService extends IBackupManager.Stub { final Object mCurrentOpLock = new Object(); final Random mTokenGenerator = new Random(); + final SparseArray<FullParams> mFullConfirmations = new SparseArray<FullParams>(); + // Where we keep our journal files and other bookkeeping File mBaseStateDir; File mDataDir; @@ -264,6 +314,17 @@ class BackupManagerService extends IBackupManager.Stub { static final String INIT_SENTINEL_FILE_NAME = "_need_init_"; HashSet<String> mPendingInits = new HashSet<String>(); // transport names + // Utility: build a new random integer token + int generateToken() { + int token; + do { + synchronized (mTokenGenerator) { + token = mTokenGenerator.nextInt(); + } + } while (token < 0); + return token; + } + // ----- Asynchronous backup/restore handler thread ----- private class BackupHandler extends Handler { @@ -321,7 +382,13 @@ class BackupManagerService extends IBackupManager.Stub { } case MSG_RUN_FULL_BACKUP: + { + FullBackupParams params = (FullBackupParams)msg.obj; + (new PerformFullBackupTask(params.fd, params.observer, params.includeApks, + params.includeShared, params.allApps, params.packages, + params.latch)).run(); break; + } case MSG_RUN_RESTORE: { @@ -416,6 +483,34 @@ class BackupManagerService extends IBackupManager.Stub { } } } + + case MSG_FULL_CONFIRMATION_TIMEOUT: + { + synchronized (mFullConfirmations) { + FullParams params = mFullConfirmations.get(msg.arg1); + if (params != null) { + Slog.i(TAG, "Full backup/restore timed out waiting for user confirmation"); + + // Release the waiter; timeout == completion + signalFullBackupRestoreCompletion(params); + + // Remove the token from the set + mFullConfirmations.delete(msg.arg1); + + // Report a timeout to the observer, if any + if (params.observer != null) { + try { + params.observer.onTimeout(); + } catch (RemoteException e) { + /* don't care if the app has gone away */ + } + } + } else { + Slog.d(TAG, "couldn't find params for token " + msg.arg1); + } + } + break; + } } } } @@ -760,15 +855,15 @@ class BackupManagerService extends IBackupManager.Stub { sf.delete(); } } + } - // Enqueue a new backup of every participant - int N = mBackupParticipants.size(); - for (int i=0; i<N; i++) { - int uid = mBackupParticipants.keyAt(i); - HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i); - for (ApplicationInfo app: participants) { - dataChangedImpl(app.packageName); - } + // Enqueue a new backup of every participant + int N = mBackupParticipants.size(); + for (int i=0; i<N; i++) { + int uid = mBackupParticipants.keyAt(i); + HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i); + for (ApplicationInfo app: participants) { + dataChangedImpl(app.packageName); } } } @@ -1253,9 +1348,11 @@ class BackupManagerService extends IBackupManager.Stub { void prepareOperationTimeout(int token, long interval) { if (DEBUG) Slog.v(TAG, "starting timeout: token=" + Integer.toHexString(token) + " interval=" + interval); - mCurrentOperations.put(token, OP_PENDING); - Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0); - mBackupHandler.sendMessageDelayed(msg, interval); + synchronized (mCurrentOpLock) { + mCurrentOperations.put(token, OP_PENDING); + Message msg = mBackupHandler.obtainMessage(MSG_TIMEOUT, token, 0); + mBackupHandler.sendMessageDelayed(msg, interval); + } } // ----- Back up a set of applications via a worker thread ----- @@ -1313,7 +1410,7 @@ class BackupManagerService extends IBackupManager.Stub { if (status == BackupConstants.TRANSPORT_OK) { PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent( mPackageManager, allAgentPackages()); - BackupRequest pmRequest = new BackupRequest(new ApplicationInfo(), false); + BackupRequest pmRequest = new BackupRequest(new ApplicationInfo()); pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL; status = processOneBackup(pmRequest, IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport); @@ -1407,12 +1504,10 @@ class BackupManagerService extends IBackupManager.Stub { } IBackupAgent agent = null; - int mode = (request.fullBackup) - ? IApplicationThread.BACKUP_MODE_FULL - : IApplicationThread.BACKUP_MODE_INCREMENTAL; try { mWakelock.setWorkSource(new WorkSource(request.appInfo.uid)); - agent = bindToAgentSynchronous(request.appInfo, mode); + agent = bindToAgentSynchronous(request.appInfo, + IApplicationThread.BACKUP_MODE_INCREMENTAL); if (agent != null) { int result = processOneBackup(request, agent, transport); if (result != BackupConstants.TRANSPORT_OK) return result; @@ -1446,7 +1541,7 @@ class BackupManagerService extends IBackupManager.Stub { ParcelFileDescriptor newState = null; PackageInfo packInfo; - int token = mTokenGenerator.nextInt(); + final int token = generateToken(); try { // Look up the package info & signatures. This is first so that if it // throws an exception, there's no file setup yet that would need to @@ -1461,12 +1556,11 @@ class BackupManagerService extends IBackupManager.Stub { } // In a full backup, we pass a null ParcelFileDescriptor as - // the saved-state "file" - if (!request.fullBackup) { - savedState = ParcelFileDescriptor.open(savedStateName, - ParcelFileDescriptor.MODE_READ_ONLY | - ParcelFileDescriptor.MODE_CREATE); // Make an empty file if necessary - } + // the saved-state "file". This is by definition an incremental, + // so we build a saved state file to pass. + savedState = ParcelFileDescriptor.open(savedStateName, + ParcelFileDescriptor.MODE_READ_ONLY | + ParcelFileDescriptor.MODE_CREATE); // Make an empty file if necessary backupData = ParcelFileDescriptor.open(backupDataName, ParcelFileDescriptor.MODE_READ_WRITE | @@ -1480,7 +1574,8 @@ class BackupManagerService extends IBackupManager.Stub { // Initiate the target's backup pass prepareOperationTimeout(token, TIMEOUT_BACKUP_INTERVAL); - agent.doBackup(savedState, backupData, newState, token, mBackupManagerBinder); + agent.doBackup(savedState, backupData, newState, false, + token, mBackupManagerBinder); boolean success = waitUntilOperationComplete(token); if (!success) { @@ -1552,6 +1647,224 @@ class BackupManagerService extends IBackupManager.Stub { } + // ----- Full backup to a file/socket ----- + + class PerformFullBackupTask implements Runnable { + ParcelFileDescriptor mOutputFile; + IFullBackupRestoreObserver mObserver; + boolean mIncludeApks; + boolean mIncludeShared; + boolean mAllApps; + String[] mPackages; + AtomicBoolean mLatchObject; + File mFilesDir; + File mManifestFile; + + PerformFullBackupTask(ParcelFileDescriptor fd, IFullBackupRestoreObserver observer, + boolean includeApks, boolean includeShared, + boolean doAllApps, String[] packages, AtomicBoolean latch) { + mOutputFile = fd; + mObserver = observer; + mIncludeApks = includeApks; + mIncludeShared = includeShared; + mAllApps = doAllApps; + mPackages = packages; + mLatchObject = latch; + + mFilesDir = new File("/data/system"); + mManifestFile = new File(mFilesDir, BACKUP_MANIFEST_FILENAME); + } + + @Override + public void run() { + final List<PackageInfo> packagesToBackup; + + sendStartBackup(); + + // doAllApps supersedes the package set if any + if (mAllApps) { + packagesToBackup = mPackageManager.getInstalledPackages( + PackageManager.GET_SIGNATURES); + } else { + packagesToBackup = new ArrayList<PackageInfo>(); + for (String pkgName : mPackages) { + try { + packagesToBackup.add(mPackageManager.getPackageInfo(pkgName, + PackageManager.GET_SIGNATURES)); + } catch (NameNotFoundException e) { + Slog.w(TAG, "Unknown package " + pkgName + ", skipping"); + } + } + } + + // Now back up the app data via the agent mechanism + PackageInfo pkg = null; + try { + int N = packagesToBackup.size(); + for (int i = 0; i < N; i++) { + pkg = packagesToBackup.get(i); + + Slog.d(TAG, "Binding to full backup agent : " + pkg.packageName); + + IBackupAgent agent = bindToAgentSynchronous(pkg.applicationInfo, + IApplicationThread.BACKUP_MODE_FULL); + if (agent != null) { + try { + ApplicationInfo app = mPackageManager.getApplicationInfo( + pkg.packageName, 0); + boolean sendApk = mIncludeApks + && ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0) + && ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 || + (app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); + + sendOnBackupPackage(pkg.packageName); + + { + BackupDataOutput output = new BackupDataOutput( + mOutputFile.getFileDescriptor()); + + if (DEBUG) Slog.d(TAG, "Writing manifest for " + pkg.packageName); + writeAppManifest(pkg, mManifestFile, sendApk); + FullBackup.backupToTar(pkg.packageName, null, null, + mFilesDir.getAbsolutePath(), + mManifestFile.getAbsolutePath(), + output); + } + + if (DEBUG) Slog.d(TAG, "Calling doBackup()"); + final int token = generateToken(); + prepareOperationTimeout(token, TIMEOUT_FULL_BACKUP_INTERVAL); + agent.doBackup(null, mOutputFile, null, sendApk, + token, mBackupManagerBinder); + boolean success = waitUntilOperationComplete(token); + if (!success) { + Slog.d(TAG, "Full backup failed on package " + pkg.packageName); + } else { + if (DEBUG) Slog.d(TAG, "Full backup success: " + pkg.packageName); + } + } catch (NameNotFoundException e) { + Slog.e(TAG, "Package exists but not app info; skipping: " + + pkg.packageName); + } catch (IOException e) { + Slog.e(TAG, "Error backing up " + pkg.packageName, e); + } + } else { + Slog.w(TAG, "Unable to bind to full agent for " + pkg.packageName); + } + tearDown(pkg); + } + } catch (RemoteException e) { + Slog.e(TAG, "App died during full backup"); + } finally { + if (pkg != null) { + tearDown(pkg); + } + try { + mOutputFile.close(); + } catch (IOException e) { + /* nothing we can do about this */ + } + synchronized (mCurrentOpLock) { + mCurrentOperations.clear(); + } + synchronized (mLatchObject) { + mLatchObject.set(true); + mLatchObject.notifyAll(); + } + sendEndBackup(); + mWakelock.release(); + if (DEBUG) Slog.d(TAG, "Full backup pass complete."); + } + } + + private void writeAppManifest(PackageInfo pkg, File manifestFile, boolean withApk) + throws IOException { + // Manifest format. All data are strings ending in LF: + // BACKUP_MANIFEST_VERSION, currently 1 + // + // Version 1: + // package name + // package's versionCode + // boolean: "1" if archive includes .apk, "0" otherwise + // number of signatures == N + // N*: signature byte array in ascii format per Signature.toCharsString() + StringBuilder builder = new StringBuilder(4096); + StringBuilderPrinter printer = new StringBuilderPrinter(builder); + + printer.println(Integer.toString(BACKUP_MANIFEST_VERSION)); + printer.println(pkg.packageName); + printer.println(Integer.toString(pkg.versionCode)); + printer.println(withApk ? "1" : "0"); + if (pkg.signatures == null) { + printer.println("0"); + } else { + printer.println(Integer.toString(pkg.signatures.length)); + for (Signature sig : pkg.signatures) { + printer.println(sig.toCharsString()); + } + } + + FileOutputStream outstream = new FileOutputStream(manifestFile); + Libcore.os.ftruncate(outstream.getFD(), 0); + outstream.write(builder.toString().getBytes()); + outstream.close(); + } + + private void tearDown(PackageInfo pkg) { + final ApplicationInfo app = pkg.applicationInfo; + try { + // unbind and tidy up even on timeout or failure, just in case + mActivityManager.unbindBackupAgent(app); + + // The agent was running with a stub Application object, so shut it down + if (app.uid != Process.SYSTEM_UID) { + if (DEBUG) Slog.d(TAG, "Backup complete, killing host process"); + mActivityManager.killApplicationProcess(app.processName, app.uid); + } else { + if (DEBUG) Slog.d(TAG, "Not killing after restore: " + app.processName); + } + } catch (RemoteException e) { + Slog.d(TAG, "Lost app trying to shut down"); + } + } + + // wrappers for observer use + void sendStartBackup() { + if (mObserver != null) { + try { + mObserver.onStartBackup(); + } catch (RemoteException e) { + Slog.w(TAG, "full backup observer went away: startBackup"); + mObserver = null; + } + } + } + + void sendOnBackupPackage(String name) { + if (mObserver != null) { + try { + // TODO: use a more user-friendly name string + mObserver.onBackupPackage(name); + } catch (RemoteException e) { + Slog.w(TAG, "full backup observer went away: backupPackage"); + mObserver = null; + } + } + } + + void sendEndBackup() { + if (mObserver != null) { + try { + mObserver.onEndBackup(); + } catch (RemoteException e) { + Slog.w(TAG, "full backup observer went away: endBackup"); + mObserver = null; + } + } + } + } + + // ----- Restore handling ----- private boolean signaturesMatch(Signature[] storedSigs, PackageInfo target) { @@ -1893,7 +2206,7 @@ class BackupManagerService extends IBackupManager.Stub { ParcelFileDescriptor backupData = null; ParcelFileDescriptor newState = null; - int token = mTokenGenerator.nextInt(); + final int token = generateToken(); try { // Run the transport's restore pass backupData = ParcelFileDescriptor.open(backupDataName, @@ -1957,7 +2270,9 @@ class BackupManagerService extends IBackupManager.Stub { try { if (backupData != null) backupData.close(); } catch (IOException e) {} try { if (newState != null) newState.close(); } catch (IOException e) {} backupData = newState = null; - mCurrentOperations.delete(token); + synchronized (mCurrentOperations) { + mCurrentOperations.delete(token); + } // If we know a priori that we'll need to perform a full post-restore backup // pass, clear the new state file data. This means we're discarding work that @@ -2092,7 +2407,7 @@ class BackupManagerService extends IBackupManager.Stub { if (app.packageName.equals(packageName)) { // Add the caller to the set of pending backups. If there is // one already there, then overwrite it, but no harm done. - BackupRequest req = new BackupRequest(app, false); + BackupRequest req = new BackupRequest(app); if (mPendingBackups.put(app.packageName, req) == null) { // Journal this request in case of crash. The put() // operation returned null when this package was not already @@ -2239,10 +2554,141 @@ class BackupManagerService extends IBackupManager.Stub { } } + // Run a *full* backup pass for the given package, writing the resulting data stream + // to the supplied file descriptor. This method is synchronous and does not return + // to the caller until the backup has been completed. + public void fullBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeShared, + boolean doAllApps, String[] pkgList) { + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup"); + + // Validate + if (!doAllApps) { + if (!includeShared) { + // If we're backing up shared data (sdcard or equivalent), then we can run + // without any supplied app names. Otherwise, we'd be doing no work, so + // report the error. + if (pkgList == null || pkgList.length == 0) { + throw new IllegalArgumentException( + "Backup requested but neither shared nor any apps named"); + } + } + } + + if (DEBUG) Slog.v(TAG, "Requesting full backup: apks=" + includeApks + + " shared=" + includeShared + " all=" + doAllApps + + " pkgs=" + pkgList); + + long oldId = Binder.clearCallingIdentity(); + try { + FullBackupParams params = new FullBackupParams(fd, includeApks, includeShared, + doAllApps, pkgList); + final int token = generateToken(); + synchronized (mFullConfirmations) { + mFullConfirmations.put(token, params); + } + + // start up the confirmation UI, making sure the screen lights up + if (DEBUG) Slog.d(TAG, "Starting confirmation UI, token=" + token); + try { + Intent confIntent = new Intent(FullBackup.FULL_BACKUP_INTENT_ACTION); + confIntent.setClassName("com.android.backupconfirm", + "com.android.backupconfirm.BackupRestoreConfirmation"); + confIntent.putExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, token); + confIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(confIntent); + } catch (ActivityNotFoundException e) { + Slog.e(TAG, "Unable to launch full backup confirmation", e); + mFullConfirmations.delete(token); + return; + } + mPowerManager.userActivity(SystemClock.uptimeMillis(), false); + + // start the confirmation countdown + if (DEBUG) Slog.d(TAG, "Posting conf timeout msg after " + + TIMEOUT_FULL_CONFIRMATION + " millis"); + Message msg = mBackupHandler.obtainMessage(MSG_FULL_CONFIRMATION_TIMEOUT, + token, 0, params); + mBackupHandler.sendMessageDelayed(msg, TIMEOUT_FULL_CONFIRMATION); + + // wait for the backup to be performed + if (DEBUG) Slog.d(TAG, "Waiting for full backup completion..."); + waitForCompletion(params); + if (DEBUG) Slog.d(TAG, "...Full backup operation complete!"); + } finally { + Binder.restoreCallingIdentity(oldId); + try { + fd.close(); + } catch (IOException e) { + // just eat it + } + } + } + + void waitForCompletion(FullParams params) { + synchronized (params.latch) { + while (params.latch.get() == false) { + try { + params.latch.wait(); + } catch (InterruptedException e) { /* never interrupted */ } + } + } + } + + void signalFullBackupRestoreCompletion(FullParams params) { + synchronized (params.latch) { + params.latch.set(true); + params.latch.notifyAll(); + } + } + + // Confirm that the previously-requested full backup/restore operation can proceed. This + // is used to require a user-facing disclosure about the operation. + public void acknowledgeFullBackupOrRestore(int token, boolean allow, + IFullBackupRestoreObserver observer) { + if (DEBUG) Slog.d(TAG, "acknowledgeFullBackupOrRestore : token=" + token + + " allow=" + allow); + + // TODO: possibly require not just this signature-only permission, but even + // require that the specific designated confirmation-UI app uid is the caller? + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "fullBackup"); + + long oldId = Binder.clearCallingIdentity(); + try { + + FullParams params; + synchronized (mFullConfirmations) { + params = mFullConfirmations.get(token); + if (params != null) { + mBackupHandler.removeMessages(MSG_FULL_CONFIRMATION_TIMEOUT, params); + mFullConfirmations.delete(token); + + if (allow) { + params.observer = observer; + final int verb = params instanceof FullBackupParams + ? MSG_RUN_FULL_BACKUP + : MSG_RUN_FULL_RESTORE; + + mWakelock.acquire(); + Message msg = mBackupHandler.obtainMessage(verb, params); + mBackupHandler.sendMessage(msg); + } else { + Slog.w(TAG, "User rejected full backup/restore operation"); + // indicate completion without having actually transferred any data + signalFullBackupRestoreCompletion(params); + } + } else { + Slog.w(TAG, "Attempted to ack full backup/restore with invalid token"); + } + } + } finally { + Binder.restoreCallingIdentity(oldId); + } + } + // Enable/disable the backup service public void setBackupEnabled(boolean enable) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, - "setBackupEnabled"); + "setBackupEnabled"); Slog.i(TAG, "Backup enabled => " + enable); diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java index 47599c8..1aff9a2 100644 --- a/services/java/com/android/server/BatteryService.java +++ b/services/java/com/android/server/BatteryService.java @@ -390,7 +390,7 @@ class BatteryService extends Binder { intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryTechnology); intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger); - if (true) { + if (false) { Slog.d(TAG, "level:" + mBatteryLevel + " scale:" + BATTERY_SCALE + " status:" + mBatteryStatus + " health:" + mBatteryHealth + " present:" + mBatteryPresent + diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 6780b03..db831c7 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -41,6 +41,7 @@ import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; +import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; import android.os.PowerManager; @@ -425,7 +426,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - mTethering = new Tethering(mContext, mHandler.getLooper()); + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService nmService = INetworkManagementService.Stub.asInterface(b); + + mTethering = new Tethering(mContext, nmService, mHandler.getLooper()); mTetheringConfigValid = (((mNetTrackers[ConnectivityManager.TYPE_MOBILE_DUN] != null) || !mTethering.isDunRequired()) && (mTethering.getTetherableUsbRegexs().length != 0 || @@ -1381,13 +1385,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { addDefaultRoute(mNetTrackers[netType]); } else { // many radios add a default route even when we don't want one. - // remove the default interface unless we need it for our active network + // remove the default route unless we need it for our active network if (mActiveDefaultNetwork != -1) { - LinkProperties linkProperties = + LinkProperties defaultLinkProperties = mNetTrackers[mActiveDefaultNetwork].getLinkProperties(); LinkProperties newLinkProperties = mNetTrackers[netType].getLinkProperties(); - String defaultIface = linkProperties.getInterfaceName(); + String defaultIface = defaultLinkProperties.getInterfaceName(); if (defaultIface != null && !defaultIface.equals(newLinkProperties.getInterfaceName())) { removeDefaultRoute(mNetTrackers[netType]); diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java index 0fba7c3..d34087f 100644 --- a/services/java/com/android/server/DeviceStorageMonitorService.java +++ b/services/java/com/android/server/DeviceStorageMonitorService.java @@ -34,7 +34,6 @@ import android.os.StatFs; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; -import android.util.Config; import android.util.EventLog; import android.util.Slog; @@ -56,10 +55,10 @@ import android.util.Slog; * settings parameter with a default value of 2MB), the free memory is * logged to the event log. */ -class DeviceStorageMonitorService extends Binder { +public class DeviceStorageMonitorService extends Binder { private static final String TAG = "DeviceStorageMonitorService"; private static final boolean DEBUG = false; - private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; + private static final boolean localLOGV = false; private static final int DEVICE_MEMORY_WHAT = 1; private static final int MONITOR_INTERVAL = 1; //in minutes private static final int LOW_MEMORY_NOTIFICATION_ID = 1; @@ -99,7 +98,7 @@ class DeviceStorageMonitorService extends Binder { /** * This string is used for ServiceManager access to this class. */ - static final String SERVICE = "devicestoragemonitor"; + public static final String SERVICE = "devicestoragemonitor"; /** * Handler that checks the amount of disk space on the device and sends a @@ -398,4 +397,24 @@ class DeviceStorageMonitorService extends Binder { // force an early check postCheckMemoryMsg(true, 0); } + + /** + * Callable from other things in the system service to obtain the low memory + * threshold. + * + * @return low memory threshold in bytes + */ + public long getMemoryLowThreshold() { + return mMemLowThreshold; + } + + /** + * Callable from other things in the system process to check whether memory + * is low. + * + * @return true is memory is low + */ + public boolean isMemoryLow() { + return mLowMemFlag; + } } diff --git a/services/java/com/android/server/EntropyService.java b/services/java/com/android/server/EntropyService.java index 0f1fc78..788a2f5 100644 --- a/services/java/com/android/server/EntropyService.java +++ b/services/java/com/android/server/EntropyService.java @@ -96,7 +96,7 @@ public class EntropyService extends Binder { private void loadInitialEntropy() { try { - RandomBlock.fromFile(entropyFile).toFile(randomDevice); + RandomBlock.fromFile(entropyFile).toFile(randomDevice, false); } catch (IOException e) { Slog.w(TAG, "unable to load initial entropy (first boot?)", e); } @@ -104,7 +104,7 @@ public class EntropyService extends Binder { private void writeEntropy() { try { - RandomBlock.fromFile(randomDevice).toFile(entropyFile); + RandomBlock.fromFile(randomDevice).toFile(entropyFile, true); } catch (IOException e) { Slog.w(TAG, "unable to write entropy", e); } diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 1455764..7028772 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -1399,6 +1399,24 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + public InputMethodSubtype getLastInputMethodSubtype() { + synchronized (mMethodMap) { + final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); + // TODO: Handle the case of the last IME with no subtypes + if (lastIme == null || TextUtils.isEmpty(lastIme.first) + || TextUtils.isEmpty(lastIme.second)) return null; + final InputMethodInfo lastImi = mMethodMap.get(lastIme.first); + if (lastImi == null) return null; + try { + final int lastSubtypeHash = Integer.valueOf(lastIme.second); + return lastImi.getSubtypeAt(getSubtypeIdFromHashCode( + lastImi, lastSubtypeHash)); + } catch (NumberFormatException e) { + return null; + } + } + } + private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) { synchronized (mMethodMap) { if (token == null) { diff --git a/services/java/com/android/server/IntentResolver.java b/services/java/com/android/server/IntentResolver.java index b78389b..1d3e3ac 100644 --- a/services/java/com/android/server/IntentResolver.java +++ b/services/java/com/android/server/IntentResolver.java @@ -35,7 +35,6 @@ import android.util.Slog; import android.util.LogPrinter; import android.util.Printer; -import android.util.Config; import android.content.Intent; import android.content.IntentFilter; @@ -45,7 +44,7 @@ import android.content.IntentFilter; public class IntentResolver<F extends IntentFilter, R extends Object> { final private static String TAG = "IntentResolver"; final private static boolean DEBUG = false; - final private static boolean localLOGV = DEBUG || Config.LOGV; + final private static boolean localLOGV = DEBUG || false; public void addFilter(F f) { if (localLOGV) { diff --git a/services/java/com/android/server/LightsService.java b/services/java/com/android/server/LightsService.java index 21f2bcf..1e95f3e 100644 --- a/services/java/com/android/server/LightsService.java +++ b/services/java/com/android/server/LightsService.java @@ -148,7 +148,6 @@ public class LightsService { fis.close(); return (result != '0'); } catch (Exception e) { - Slog.e(TAG, "getFlashlightEnabled failed", e); return false; } } @@ -168,7 +167,7 @@ public class LightsService { fos.write(bytes); fos.close(); } catch (Exception e) { - Slog.e(TAG, "setFlashlightEnabled failed", e); + // fail silently } } }; diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index 6c2f8d1..c18ccc8 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -18,6 +18,7 @@ package com.android.server; import com.android.internal.app.IMediaContainerService; import com.android.server.am.ActivityManagerService; +import com.android.server.pm.PackageManagerService; import android.Manifest; import android.content.BroadcastReceiver; @@ -87,6 +88,9 @@ class MountService extends IMountService.Stub implements INativeDaemonConnectorC private static final String VOLD_TAG = "VoldConnector"; + /** Maximum number of ASEC containers allowed to be mounted. */ + private static final int MAX_CONTAINERS = 250; + /* * Internal vold volume state constants */ @@ -510,7 +514,6 @@ class MountService extends IMountService.Stub implements INativeDaemonConnectorC } } }; - private final class MountServiceBinderListener implements IBinder.DeathRecipient { final IMountServiceListener mListener; @@ -1112,8 +1115,7 @@ class MountService extends IMountService.Stub implements INativeDaemonConnectorC * amount of containers we'd ever expect to have. This keeps an * "asec list" from blocking a thread repeatedly. */ - mConnector = new NativeDaemonConnector(this, "vold", - PackageManagerService.MAX_CONTAINERS * 2, VOLD_TAG); + mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG); mReady = false; Thread thread = new Thread(mConnector, VOLD_TAG); thread.start(); diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index 44f5df2..d931350 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.content.pm.PackageManager; +import android.net.NetworkStats; import android.net.Uri; import android.net.InterfaceConfiguration; import android.net.INetworkManagementEventObserver; @@ -32,6 +33,8 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; import android.os.INetworkManagementService; import android.os.Handler; +import android.os.RemoteException; +import android.os.SystemClock; import android.os.SystemProperties; import android.text.TextUtils; import android.util.Log; @@ -44,13 +47,21 @@ import android.content.ContentResolver; import android.database.ContentObserver; import java.io.File; +import java.io.FileInputStream; import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.RandomAccessFile; +import java.io.Reader; import java.lang.IllegalStateException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.concurrent.CountDownLatch; +import libcore.io.IoUtils; + /** * @hide */ @@ -716,12 +727,47 @@ class NetworkManagementService extends INetworkManagementService.Stub { return -1; } - public long getInterfaceRxCounter(String iface) { - return getInterfaceCounter(iface, true); + /** {@inheritDoc} */ + public NetworkStats getNetworkStatsSummary() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + + final String[] ifaces = listInterfaces(); + final NetworkStats.Builder stats = new NetworkStats.Builder( + SystemClock.elapsedRealtime(), ifaces.length); + + for (String iface : ifaces) { + final long rx = getInterfaceCounter(iface, true); + final long tx = getInterfaceCounter(iface, false); + stats.addEntry(iface, NetworkStats.UID_ALL, rx, tx); + } + + return stats.build(); } - public long getInterfaceTxCounter(String iface) { - return getInterfaceCounter(iface, false); + /** {@inheritDoc} */ + public NetworkStats getNetworkStatsDetail() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService"); + + final File procPath = new File("/proc/uid_stat"); + final String[] knownUids = procPath.list(); + final NetworkStats.Builder stats = new NetworkStats.Builder( + SystemClock.elapsedRealtime(), knownUids.length); + + // TODO: kernel module will provide interface-level stats in future + // TODO: migrate these stats to come across netd in bulk, instead of all + // these individual file reads. + for (String uid : knownUids) { + final File uidPath = new File(procPath, uid); + final int rx = readSingleIntFromFile(new File(uidPath, "tcp_rcv")); + final int tx = readSingleIntFromFile(new File(uidPath, "tcp_snd")); + + final int uidInt = Integer.parseInt(uid); + stats.addEntry(NetworkStats.IFACE_ALL, uidInt, rx, tx); + } + + return stats.build(); } public void setInterfaceThrottle(String iface, int rxKbps, int txKbps) { @@ -782,4 +828,24 @@ class NetworkManagementService extends INetworkManagementService.Stub { public int getInterfaceTxThrottle(String iface) { return getInterfaceThrottle(iface, false); } + + /** + * Utility method to read a single plain-text {@link Integer} from the given + * {@link File}, usually from a {@code /proc/} filesystem. + */ + private static int readSingleIntFromFile(File file) { + RandomAccessFile f = null; + try { + f = new RandomAccessFile(file, "r"); + byte[] buffer = new byte[(int) f.length()]; + f.readFully(buffer); + return Integer.parseInt(new String(buffer).trim()); + } catch (NumberFormatException e) { + return -1; + } catch (IOException e) { + return -1; + } finally { + IoUtils.closeQuietly(f); + } + } } diff --git a/services/java/com/android/server/ProcessStats.java b/services/java/com/android/server/ProcessStats.java index 1a12a84..f693ed1 100644 --- a/services/java/com/android/server/ProcessStats.java +++ b/services/java/com/android/server/ProcessStats.java @@ -20,7 +20,6 @@ import static android.os.Process.*; import android.os.Process; import android.os.SystemClock; -import android.util.Config; import android.util.Slog; import java.io.File; @@ -35,7 +34,7 @@ import java.util.StringTokenizer; public class ProcessStats { private static final String TAG = "ProcessStats"; private static final boolean DEBUG = false; - private static final boolean localLOGV = DEBUG || Config.LOGV; + private static final boolean localLOGV = DEBUG || false; private static final int[] PROCESS_STATS_FORMAT = new int[] { PROC_SPACE_TERM, diff --git a/services/java/com/android/server/RandomBlock.java b/services/java/com/android/server/RandomBlock.java index cc22bd9..e5d7301 100644 --- a/services/java/com/android/server/RandomBlock.java +++ b/services/java/com/android/server/RandomBlock.java @@ -62,11 +62,11 @@ class RandomBlock { return retval; } - void toFile(String filename) throws IOException { + void toFile(String filename, boolean sync) throws IOException { if (DEBUG) Slog.v(TAG, "writing to file " + filename); RandomAccessFile out = null; try { - out = new RandomAccessFile(filename, "rws"); + out = new RandomAccessFile(filename, sync ? "rws" : "rw"); toDataOut(out); truncateIfPossible(out); } finally { diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java index 8df8177..1d2072c 100644 --- a/services/java/com/android/server/StatusBarManagerService.java +++ b/services/java/com/android/server/StatusBarManagerService.java @@ -122,6 +122,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub // ================================================================================ // From IStatusBarService // ================================================================================ + public void userActivity() { + if (mBar != null) try { + mBar.userActivity(); + } catch (RemoteException ex) {} + } public void expand() { enforceExpandStatusBar(); diff --git a/services/java/com/android/server/SystemBackupAgent.java b/services/java/com/android/server/SystemBackupAgent.java index 80b0174..54555bb 100644 --- a/services/java/com/android/server/SystemBackupAgent.java +++ b/services/java/com/android/server/SystemBackupAgent.java @@ -20,6 +20,7 @@ package com.android.server; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.app.backup.BackupAgentHelper; +import android.app.backup.FullBackup; import android.app.backup.WallpaperBackupHelper; import android.content.Context; import android.os.ParcelFileDescriptor; @@ -37,12 +38,19 @@ public class SystemBackupAgent extends BackupAgentHelper { private static final String TAG = "SystemBackupAgent"; // These paths must match what the WallpaperManagerService uses - private static final String WALLPAPER_IMAGE = "/data/data/com.android.settings/files/wallpaper"; - private static final String WALLPAPER_INFO = "/data/system/wallpaper_info.xml"; + private static final String WALLPAPER_IMAGE_DIR = "/data/data/com.android.settings/files"; + private static final String WALLPAPER_IMAGE = WALLPAPER_IMAGE_DIR + "/wallpaper"; + private static final String WALLPAPER_INFO_DIR = "/data/system"; + private static final String WALLPAPER_INFO = WALLPAPER_INFO_DIR + "/wallpaper_info.xml"; @Override public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException { + if (oldState == null) { + runFullBackup(data); + return; + } + // We only back up the data under the current "wallpaper" schema with metadata WallpaperManagerService wallpaper = (WallpaperManagerService)ServiceManager.getService( Context.WALLPAPER_SERVICE); @@ -57,6 +65,14 @@ public class SystemBackupAgent extends BackupAgentHelper { super.onBackup(oldState, data, newState); } + private void runFullBackup(BackupDataOutput output) { + // Back up the data files directly + FullBackup.backupToTar(getPackageName(), null, null, + WALLPAPER_IMAGE_DIR, WALLPAPER_IMAGE, output); + FullBackup.backupToTar(getPackageName(), null, null, + WALLPAPER_INFO_DIR, WALLPAPER_INFO, output); + } + @Override public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index d160963..cd8915d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -16,7 +16,9 @@ package com.android.server; +import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.am.ActivityManagerService; +import com.android.server.pm.PackageManagerService; import com.android.server.usb.UsbService; import com.android.server.wm.WindowManagerService; import com.android.internal.app.ShutdownThread; diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java index eb14180..948118f 100644 --- a/services/java/com/android/server/TelephonyRegistry.java +++ b/services/java/com/android/server/TelephonyRegistry.java @@ -279,7 +279,6 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (!checkNotifyPermission("notifyServiceState()")){ return; } - Slog.i(TAG, "notifyServiceState: " + state); synchronized (mRecords) { mServiceState = state; for (Record r : mRecords) { diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java index d841cb3..510ff62 100644 --- a/services/java/com/android/server/ThrottleService.java +++ b/services/java/com/android/server/ThrottleService.java @@ -16,6 +16,9 @@ package com.android.server; +import com.android.internal.R; +import com.android.internal.telephony.TelephonyProperties; + import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; @@ -30,7 +33,7 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.net.INetworkManagementEventObserver; import android.net.IThrottleManager; -import android.net.SntpClient; +import android.net.NetworkStats; import android.net.ThrottleManager; import android.os.Binder; import android.os.Environment; @@ -47,10 +50,9 @@ import android.os.SystemProperties; import android.provider.Settings; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.NtpTrustedTime; import android.util.Slog; - -import com.android.internal.R; -import com.android.internal.telephony.TelephonyProperties; +import android.util.TrustedTime; import java.io.BufferedWriter; import java.io.File; @@ -60,11 +62,11 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.Calendar; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; import java.util.GregorianCalendar; import java.util.Properties; import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; // TODO - add comments - reference the ThrottleManager for public API public class ThrottleService extends IThrottleManager.Stub { @@ -84,6 +86,11 @@ public class ThrottleService extends IThrottleManager.Stub { private static final int TESTING_RESET_PERIOD_SEC = 60 * 10; private static final long TESTING_THRESHOLD = 1 * 1024 * 1024; + private static final long MAX_NTP_CACHE_AGE = 24 * 60 * 60 * 1000; + private static final long MAX_NTP_FETCH_WAIT = 20 * 1000; + + private long mMaxNtpCacheAge = MAX_NTP_CACHE_AGE; + private int mPolicyPollPeriodSec; private AtomicLong mPolicyThreshold; private AtomicInteger mPolicyThrottleValue; @@ -121,10 +128,24 @@ public class ThrottleService extends IThrottleManager.Stub { private static final int THROTTLE_INDEX_UNTHROTTLED = 0; private static final String PROPERTIES_FILE = "/etc/gps.conf"; - private String mNtpServer; - private boolean mNtpActive; + + private Intent mPollStickyBroadcast; + + private TrustedTime mTime; + + private static INetworkManagementService getNetworkManagementService() { + final IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + return INetworkManagementService.Stub.asInterface(b); + } public ThrottleService(Context context) { + // TODO: move to using cached NtpTrustedTime + this(context, getNetworkManagementService(), new NtpTrustedTime(), + context.getResources().getString(R.string.config_datause_iface)); + } + + public ThrottleService(Context context, INetworkManagementService nmService, TrustedTime time, + String iface) { if (VDBG) Slog.v(TAG, "Starting ThrottleService"); mContext = context; @@ -132,17 +153,15 @@ public class ThrottleService extends IThrottleManager.Stub { mPolicyThrottleValue = new AtomicInteger(); mThrottleIndex = new AtomicInteger(); - mNtpActive = false; - - mIface = mContext.getResources().getString(R.string.config_datause_iface); + mIface = iface; mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); Intent pollIntent = new Intent(ACTION_POLL, null); mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0); Intent resetIntent = new Intent(ACTION_RESET, null); mPendingResetIntent = PendingIntent.getBroadcast(mContext, RESET_REQUEST, resetIntent, 0); - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - mNMService = INetworkManagementService.Stub.asInterface(b); + mNMService = nmService; + mTime = time; mNotificationManager = (NotificationManager)mContext.getSystemService( Context.NOTIFICATION_SERVICE); @@ -189,7 +208,7 @@ public class ThrottleService extends IThrottleManager.Stub { mMsg = msg; } - void observe(Context context) { + void register(Context context) { ContentResolver resolver = context.getContentResolver(); resolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.THROTTLE_POLLING_SEC), false, this); @@ -207,6 +226,11 @@ public class ThrottleService extends IThrottleManager.Stub { Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC), false, this); } + void unregister(Context context) { + final ContentResolver resolver = context.getContentResolver(); + resolver.unregisterContentObserver(this); + } + @Override public void onChange(boolean selfChange) { mHandler.obtainMessage(mMsg).sendToTarget(); @@ -220,7 +244,9 @@ public class ThrottleService extends IThrottleManager.Stub { } private long ntpToWallTime(long ntpTime) { - long bestNow = getBestTime(true); // do it quickly + // get time quickly without worrying about trusted state + long bestNow = mTime.hasCache() ? mTime.currentTimeMillis() + : System.currentTimeMillis(); long localNow = System.currentTimeMillis(); return localNow + (ntpTime - bestNow); } @@ -300,7 +326,7 @@ public class ThrottleService extends IThrottleManager.Stub { new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - mHandler.obtainMessage(EVENT_POLL_ALARM).sendToTarget(); + dispatchPoll(); } }, new IntentFilter(ACTION_POLL)); @@ -308,7 +334,7 @@ public class ThrottleService extends IThrottleManager.Stub { new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget(); + dispatchReset(); } }, new IntentFilter(ACTION_RESET)); @@ -318,7 +344,10 @@ public class ThrottleService extends IThrottleManager.Stub { File file = new File(PROPERTIES_FILE); stream = new FileInputStream(file); properties.load(stream); - mNtpServer = properties.getProperty("NTP_SERVER", null); + final String ntpServer = properties.getProperty("NTP_SERVER", null); + if (mTime instanceof NtpTrustedTime) { + ((NtpTrustedTime) mTime).setNtpServer(ntpServer, MAX_NTP_FETCH_WAIT); + } } catch (IOException e) { Slog.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE); } finally { @@ -343,9 +372,33 @@ public class ThrottleService extends IThrottleManager.Stub { } mSettingsObserver = new SettingsObserver(mHandler, EVENT_POLICY_CHANGED); - mSettingsObserver.observe(mContext); + mSettingsObserver.register(mContext); + } + + void shutdown() { + // TODO: eventually connect with ShutdownThread to persist stats during + // graceful shutdown. + + if (mThread != null) { + mThread.quit(); + } + + if (mSettingsObserver != null) { + mSettingsObserver.unregister(mContext); + } + + if (mPollStickyBroadcast != null) { + mContext.removeStickyBroadcast(mPollStickyBroadcast); + } } + void dispatchPoll() { + mHandler.obtainMessage(EVENT_POLL_ALARM).sendToTarget(); + } + + void dispatchReset() { + mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget(); + } private static final int EVENT_REBOOT_RECOVERY = 0; private static final int EVENT_POLICY_CHANGED = 1; @@ -440,15 +493,17 @@ public class ThrottleService extends IThrottleManager.Stub { mPolicyNotificationsAllowedMask = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.THROTTLE_NOTIFICATION_TYPE, defaultNotificationType); - mMaxNtpCacheAgeSec = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC, MAX_NTP_CACHE_AGE_SEC); + final int maxNtpCacheAgeSec = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC, + (int) (MAX_NTP_CACHE_AGE / 1000)); + mMaxNtpCacheAge = maxNtpCacheAgeSec * 1000; if (VDBG || (mPolicyThreshold.get() != 0)) { Slog.d(TAG, "onPolicyChanged testing=" + testing +", period=" + mPolicyPollPeriodSec + ", threshold=" + mPolicyThreshold.get() + ", value=" + mPolicyThrottleValue.get() + ", resetDay=" + mPolicyResetDay + - ", noteType=" + mPolicyNotificationsAllowedMask + ", maxNtpCacheAge=" + - mMaxNtpCacheAgeSec); + ", noteType=" + mPolicyNotificationsAllowedMask + ", mMaxNtpCacheAge=" + + mMaxNtpCacheAge); } // force updates @@ -464,15 +519,30 @@ public class ThrottleService extends IThrottleManager.Stub { private void onPollAlarm() { long now = SystemClock.elapsedRealtime(); - long next = now + mPolicyPollPeriodSec*1000; + long next = now + mPolicyPollPeriodSec * 1000; - checkForAuthoritativeTime(); + // when trusted cache outdated, try refreshing + if (mTime.getCacheAge() > mMaxNtpCacheAge) { + if (mTime.forceRefresh()) { + if (VDBG) Slog.d(TAG, "updated trusted time, reseting alarm"); + dispatchReset(); + } + } long incRead = 0; long incWrite = 0; try { - incRead = mNMService.getInterfaceRxCounter(mIface) - mLastRead; - incWrite = mNMService.getInterfaceTxCounter(mIface) - mLastWrite; + final NetworkStats stats = mNMService.getNetworkStatsSummary(); + final int index = stats.findIndex(mIface, NetworkStats.UID_ALL); + + if (index != -1) { + incRead = stats.rx[index] - mLastRead; + incWrite = stats.tx[index] - mLastWrite; + } else { + // missing iface, assume stats are 0 + Slog.w(TAG, "unable to find stats for iface " + mIface); + } + // handle iface resets - on some device the 3g iface comes and goes and gets // totals reset to 0. Deal with it if ((incRead < 0) || (incWrite < 0)) { @@ -509,6 +579,7 @@ public class ThrottleService extends IThrottleManager.Stub { broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_START, getPeriodStartTime(mIface)); broadcast.putExtra(ThrottleManager.EXTRA_CYCLE_END, getResetTime(mIface)); mContext.sendStickyBroadcast(broadcast); + mPollStickyBroadcast = broadcast; mAlarmManager.cancel(mPendingPollIntent); mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent); @@ -538,7 +609,8 @@ public class ThrottleService extends IThrottleManager.Stub { // have we spoken with an ntp server yet? // this is controversial, but we'd rather err towards not throttling - if ((mNtpServer != null) && !mNtpActive) { + if (!mTime.hasCache()) { + Slog.w(TAG, "missing trusted time, skipping throttle check"); return; } @@ -697,9 +769,15 @@ public class ThrottleService extends IThrottleManager.Stub { " bytes read and " + mRecorder.getPeriodTx(0) + " written"); } - long now = getBestTime(false); + // when trusted cache outdated, try refreshing + if (mTime.getCacheAge() > mMaxNtpCacheAge) { + mTime.forceRefresh(); + } - if (mNtpActive || (mNtpServer == null)) { + // as long as we have a trusted time cache, we always reset alarms, + // even if the refresh above failed. + if (mTime.hasCache()) { + final long now = mTime.currentTimeMillis(); Calendar end = calculatePeriodEnd(now); Calendar start = calculatePeriodStart(end); @@ -714,56 +792,11 @@ public class ThrottleService extends IThrottleManager.Stub { SystemClock.elapsedRealtime() + offset, mPendingResetIntent); } else { - if (VDBG) Slog.d(TAG, "no authoritative time - not resetting period"); + if (VDBG) Slog.d(TAG, "no trusted time, not resetting period"); } } } - private void checkForAuthoritativeTime() { - if (mNtpActive || (mNtpServer == null)) return; - - // will try to get the ntp time and switch to it if found. - // will also cache the time so we don't fetch it repeatedly. - getBestTime(false); - } - - private static final int MAX_NTP_CACHE_AGE_SEC = 60 * 60 * 24; // 1 day - private static final int MAX_NTP_FETCH_WAIT = 20 * 1000; - private int mMaxNtpCacheAgeSec = MAX_NTP_CACHE_AGE_SEC; - private long cachedNtp; - private long cachedNtpTimestamp; - - // if the request is tied to UI and ANR's are a danger, request a fast result - // the regular polling should have updated the cached time recently using the - // slower method (!fast) - private long getBestTime(boolean fast) { - if (mNtpServer != null) { - if (mNtpActive) { - long ntpAge = SystemClock.elapsedRealtime() - cachedNtpTimestamp; - if (ntpAge < mMaxNtpCacheAgeSec * 1000 || fast) { - if (VDBG) Slog.v(TAG, "using cached time"); - return cachedNtp + ntpAge; - } - } - SntpClient client = new SntpClient(); - if (client.requestTime(mNtpServer, MAX_NTP_FETCH_WAIT)) { - cachedNtp = client.getNtpTime(); - cachedNtpTimestamp = SystemClock.elapsedRealtime(); - if (!mNtpActive) { - mNtpActive = true; - if (VDBG) Slog.d(TAG, "found Authoritative time - reseting alarm"); - mHandler.obtainMessage(EVENT_RESET_ALARM).sendToTarget(); - } - if (VDBG) Slog.v(TAG, "using Authoritative time: " + cachedNtp); - return cachedNtp; - } - } - long time = System.currentTimeMillis(); - if (VDBG) Slog.v(TAG, "using User time: " + time); - mNtpActive = false; - return time; - } - // records bytecount data for a given time and accumulates it into larger time windows // for logging and other purposes // @@ -929,7 +962,7 @@ public class ThrottleService extends IThrottleManager.Stub { private void checkAndDeleteLRUDataFile(File dir) { File[] files = dir.listFiles(); - if (files.length <= MAX_SIMS_SUPPORTED) return; + if (files == null || files.length <= MAX_SIMS_SUPPORTED) return; if (DBG) Slog.d(TAG, "Too many data files"); do { File oldest = null; @@ -949,9 +982,11 @@ public class ThrottleService extends IThrottleManager.Stub { File newest = null; File[] files = dir.listFiles(); - for (File f : files) { - if ((newest == null) || (newest.lastModified() < f.lastModified())) { - newest = f; + if (files != null) { + for (File f : files) { + if ((newest == null) || (newest.lastModified() < f.lastModified())) { + newest = f; + } } } if (newest == null) { @@ -1126,7 +1161,7 @@ public class ThrottleService extends IThrottleManager.Stub { " seconds."); pw.println("Polling every " + mPolicyPollPeriodSec + " seconds"); pw.println("Current Throttle Index is " + mThrottleIndex.get()); - pw.println("Max NTP Cache Age is " + mMaxNtpCacheAgeSec); + pw.println("mMaxNtpCacheAge=" + mMaxNtpCacheAge); for (int i = 0; i < mRecorder.getPeriodCount(); i++) { pw.println(" Period[" + i + "] - read:" + mRecorder.getPeriodRx(i) + ", written:" + diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java index b1a6a9a..c129b97 100644 --- a/services/java/com/android/server/WallpaperManagerService.java +++ b/services/java/com/android/server/WallpaperManagerService.java @@ -49,6 +49,7 @@ import android.service.wallpaper.IWallpaperService; import android.service.wallpaper.WallpaperService; import android.util.Slog; import android.util.Xml; +import android.view.Display; import android.view.IWindowManager; import android.view.WindowManager; @@ -726,6 +727,17 @@ class WallpaperManagerService extends IWallpaperManager.Stub { mHeight = -1; mName = ""; } + + // We always want to have some reasonable width hint. + WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); + Display d = wm.getDefaultDisplay(); + int baseSize = d.getMaximumSizeDimension(); + if (mWidth < baseSize) { + mWidth = baseSize; + } + if (mHeight < baseSize) { + mHeight = baseSize; + } } // Called by SystemBackupAgent after files are restored to disk. diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java index 2a25c2a..2d3ac00 100644 --- a/services/java/com/android/server/Watchdog.java +++ b/services/java/com/android/server/Watchdog.java @@ -33,7 +33,6 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; -import android.util.Config; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -45,7 +44,7 @@ import java.util.Calendar; /** This class calls its monitor every minute. Killing this process if they don't return **/ public class Watchdog extends Thread { static final String TAG = "Watchdog"; - static final boolean localLOGV = false || Config.LOGV; + static final boolean localLOGV = false || false; // Set this to true to use debug default values. static final boolean DB = false; diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 0000237..a2d10df 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -235,6 +235,15 @@ public class WifiService extends IWifiManager.Stub { } break; } + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: { + if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) { + Slog.d(TAG, "Send failed, client connection lost"); + } else { + Slog.d(TAG, "Client connection lost with reason: " + msg.arg1); + } + mClients.remove((AsyncChannel) msg.obj); + break; + } case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: { AsyncChannel ac = new AsyncChannel(); ac.connect(mContext, this, msg.replyTo); @@ -928,7 +937,7 @@ public class WifiService extends IWifiManager.Stub { evaluateTrafficStatsPolling(); mWifiStateMachine.enableRssiPolling(true); if (mBackgroundScanSupported) { - mWifiStateMachine.enableBackgroundScan(false); + mWifiStateMachine.enableBackgroundScanCommand(false); } mWifiStateMachine.enableAllNetworks(); updateWifiState(); @@ -940,7 +949,7 @@ public class WifiService extends IWifiManager.Stub { evaluateTrafficStatsPolling(); mWifiStateMachine.enableRssiPolling(false); if (mBackgroundScanSupported) { - mWifiStateMachine.enableBackgroundScan(true); + mWifiStateMachine.enableBackgroundScanCommand(true); } /* * Set a timer to put Wi-Fi to sleep, but only if the screen is off diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java index 94531bb..6ef6963 100644 --- a/services/java/com/android/server/WifiWatchdogService.java +++ b/services/java/com/android/server/WifiWatchdogService.java @@ -28,12 +28,12 @@ import android.net.NetworkInfo; import android.net.wifi.ScanResult; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; +import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.provider.Settings; import android.text.TextUtils; -import android.util.Config; import android.util.Slog; import java.io.IOException; @@ -73,8 +73,8 @@ import java.util.Random; */ public class WifiWatchdogService { private static final String TAG = "WifiWatchdogService"; - private static final boolean V = false || Config.LOGV; - private static final boolean D = true || Config.LOGD; + private static final boolean V = false || false; + private static final boolean D = true || false; private Context mContext; private ContentResolver mContentResolver; @@ -509,6 +509,7 @@ public class WifiWatchdogService { // This access point does not require a watchdog, so queue idle on the main thread mHandler.idle(); } + mHandler.checkWalledGarden(ssid); } /** @@ -1003,6 +1004,8 @@ public class WifiWatchdogService { * The object will be an {@link AccessPoint}. */ static final int ACTION_BACKGROUND_CHECK_AP = 3; + /** Check whether the connection is a walled garden */ + static final int ACTION_CHECK_WALLED_GARDEN = 4; /** * Go to sleep for the current network. We are conservative with making @@ -1023,7 +1026,16 @@ public class WifiWatchdogService { static final int MESSAGE_DISCONNECTED = 104; /** Performs a hard-reset on the watchdog state. */ static final int MESSAGE_RESET = 105; - + + /* Walled garden detection */ + private String mLastSsid; + private long mLastTime; + private final long MIN_WALLED_GARDEN_TEST_INTERVAL = 15 * 60 * 1000; //15 minutes + + void checkWalledGarden(String ssid) { + sendMessage(obtainMessage(ACTION_CHECK_WALLED_GARDEN, ssid)); + } + void checkAp(AccessPoint ap) { removeAllActions(); sendMessage(obtainMessage(ACTION_CHECK_AP, ap)); @@ -1085,6 +1097,9 @@ public class WifiWatchdogService { case ACTION_BACKGROUND_CHECK_AP: handleBackgroundCheckAp((AccessPoint) msg.obj); break; + case ACTION_CHECK_WALLED_GARDEN: + handleWalledGardenCheck((String) msg.obj); + break; case MESSAGE_SLEEP: handleSleep((String) msg.obj); break; @@ -1102,6 +1117,43 @@ public class WifiWatchdogService { break; } } + + private boolean isWalledGardenConnection() { + //One way to detect a walled garden is to see if multiple DNS queries + //resolve to the same IP address + try { + String host1 = "www.google.com"; + String host2 = "www.android.com"; + String address1 = InetAddress.getByName(host1).getHostAddress(); + String address2 = InetAddress.getByName(host2).getHostAddress(); + if (address1.equals(address2)) return true; + } catch (UnknownHostException e) { + return false; + } + return false; + } + + private void handleWalledGardenCheck(String ssid) { + long currentTime = System.currentTimeMillis(); + //Avoid a walled garden test on the same network if one was already done + //within MIN_WALLED_GARDEN_TEST_INTERVAL. This will handle scenarios where + //there are frequent network disconnections + if (ssid.equals(mLastSsid) && + (currentTime - mLastTime) < MIN_WALLED_GARDEN_TEST_INTERVAL) { + return; + } + + mLastTime = currentTime; + mLastSsid = ssid; + + if (isWalledGardenConnection()) { + Uri uri = Uri.parse("http://www.google.com"); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | + Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + } + } } /** @@ -1264,7 +1316,7 @@ public class WifiWatchdogService { return false; } catch (Exception e) { - if (V || Config.LOGD) { + if (V || false) { Slog.d(TAG, "DnsPinger.isReachable got an unknown exception", e); } return false; diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java new file mode 100644 index 0000000..8ba0a0b --- /dev/null +++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -0,0 +1,111 @@ +/* + * 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.accessibility; + +import com.android.server.wm.InputFilter; + +import android.content.Context; +import android.util.Slog; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.MotionEvent; +import android.view.WindowManagerPolicy; + +/** + * Input filter for accessibility. + * + * Currently just a stub but will eventually implement touch exploration, etc. + */ +public class AccessibilityInputFilter extends InputFilter { + private static final String TAG = "AccessibilityInputFilter"; + private static final boolean DEBUG = false; + + private final Context mContext; + + /** + * This is an interface for explorers that take a {@link MotionEvent} + * stream and perform touch exploration of the screen content. + */ + public interface Explorer { + /** + * Handles a {@link MotionEvent}. + * + * @param event The event to handle. + * @param policyFlags The policy flags associated with the event. + */ + public void onMotionEvent(MotionEvent event, int policyFlags); + + /** + * Requests that the explorer clears its internal state. + * + * @param event The last received event. + * @param policyFlags The policy flags associated with the event. + */ + public void clear(MotionEvent event, int policyFlags); + } + + private TouchExplorer mTouchExplorer; + private int mTouchscreenSourceDeviceId; + + public AccessibilityInputFilter(Context context) { + super(context.getMainLooper()); + mContext = context; + } + + @Override + public void onInstalled() { + if (DEBUG) { + Slog.d(TAG, "Accessibility input filter installed."); + } + super.onInstalled(); + } + + @Override + public void onUninstalled() { + if (DEBUG) { + Slog.d(TAG, "Accessibility input filter uninstalled."); + } + super.onUninstalled(); + } + + @Override + public void onInputEvent(InputEvent event, int policyFlags) { + if (DEBUG) { + Slog.d(TAG, "Received event: " + event + ", policyFlags=0x" + + Integer.toHexString(policyFlags)); + } + if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) { + MotionEvent motionEvent = (MotionEvent) event; + int deviceId = event.getDeviceId(); + if (mTouchscreenSourceDeviceId != deviceId) { + mTouchscreenSourceDeviceId = deviceId; + if (mTouchExplorer != null) { + mTouchExplorer.clear(motionEvent, policyFlags); + } else { + mTouchExplorer = new TouchExplorer(this, mContext); + } + } + if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) != 0) { + mTouchExplorer.onMotionEvent(motionEvent, policyFlags); + } else { + mTouchExplorer.clear(motionEvent, policyFlags); + } + } else { + super.onInputEvent(event, policyFlags); + } + } +} diff --git a/services/java/com/android/server/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index 04ae490..d96369b 100644 --- a/services/java/com/android/server/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -14,11 +14,12 @@ ** limitations under the License. */ -package com.android.server; +package com.android.server.accessibility; import com.android.internal.content.PackageMonitor; import com.android.internal.os.HandlerCaller; import com.android.internal.os.HandlerCaller.SomeArgs; +import com.android.server.wm.WindowManagerService; import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; @@ -43,10 +44,10 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; +import android.os.ServiceManager; import android.provider.Settings; import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; -import android.util.Config; import android.util.Slog; import android.util.SparseArray; import android.view.accessibility.AccessibilityEvent; @@ -55,6 +56,7 @@ import android.view.accessibility.IAccessibilityManagerClient; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -73,6 +75,8 @@ import java.util.Set; public class AccessibilityManagerService extends IAccessibilityManager.Stub implements HandlerCaller.Callback { + private static final boolean DEBUG = false; + private static final String LOG_TAG = "AccessibilityManagerService"; private static int sIdCounter = 0; @@ -101,11 +105,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(':'); + private final SparseArray<List<ServiceInfo>> mFeedbackTypeToEnabledServicesMap = + new SparseArray<List<ServiceInfo>>(); + private PackageManager mPackageManager; private int mHandledFeedbackTypes = 0; private boolean mIsEnabled; + private AccessibilityInputFilter mInputFilter; /** * Handler for delayed event dispatch. @@ -131,7 +139,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * * @param context A {@link Context} instance. */ - AccessibilityManagerService(Context context) { + public AccessibilityManagerService(Context context) { mContext = context; mPackageManager = mContext.getPackageManager(); mCaller = new HandlerCaller(context, this); @@ -296,6 +304,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + public List<ServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) { + synchronized (mLock) { + List<ServiceInfo> enabledServices = mFeedbackTypeToEnabledServicesMap.get(feedbackType); + if (enabledServices == null) { + return Collections.emptyList(); + } + return enabledServices; + } + } + public void interrupt() { synchronized (mLock) { for (int i = 0, count = mServices.size(); i < count; i++) { @@ -335,6 +353,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } service.mNotificationTimeout = info.notificationTimeout; service.mIsDefault = (info.flags & AccessibilityServiceInfo.DEFAULT) != 0; + + updateStateOnEnabledService(service); } return; default: @@ -445,7 +465,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub try { listener.onAccessibilityEvent(event); - if (Config.DEBUG) { + if (DEBUG) { Slog.i(LOG_TAG, "Event " + event + " sent to " + listener); } } catch (RemoteException re) { @@ -465,10 +485,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * @return True if the service was removed, false otherwise. */ private boolean removeDeadServiceLocked(Service service) { - if (Config.DEBUG) { + if (DEBUG) { Slog.i(LOG_TAG, "Dead service " + service.mService + " removed"); } mHandler.removeMessages(service.mId); + updateStateOnDisabledService(service); return mServices.remove(service); } @@ -589,7 +610,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (isEnabled) { if (enabledServices.contains(componentName)) { if (service == null) { - service = new Service(componentName); + service = new Service(componentName, intalledService); } service.bind(); } else if (!enabledServices.contains(componentName)) { @@ -621,6 +642,68 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } /** + * Sets the input filter state. If the filter is in enabled it is registered + * in the window manager, otherwise the filter is removed from the latter. + * + * @param enabled Whether the input filter is enabled. + */ + private void setInputFilterEnabledLocked(boolean enabled) { + WindowManagerService wm = (WindowManagerService)ServiceManager.getService( + Context.WINDOW_SERVICE); + if (wm != null) { + if (enabled) { + if (mInputFilter == null) { + mInputFilter = new AccessibilityInputFilter(mContext); + } + wm.setInputFilter(mInputFilter); + } else { + wm.setInputFilter(null); + } + } + } + + /** + * Updates the set of enabled services for a given feedback type and + * if more than one of them provides spoken feedback enables touch + * exploration. + * + * @param service An enable service. + */ + private void updateStateOnEnabledService(Service service) { + int feedbackType = service.mFeedbackType; + List<ServiceInfo> enabledServices = mFeedbackTypeToEnabledServicesMap.get(feedbackType); + if (enabledServices == null) { + enabledServices = new ArrayList<ServiceInfo>(); + mFeedbackTypeToEnabledServicesMap.put(feedbackType, enabledServices); + } + enabledServices.add(service.mServiceInfo); + + // We enable touch exploration if at least one + // enabled service provides spoken feedback. + if (enabledServices.size() > 0 + && service.mFeedbackType == AccessibilityServiceInfo.FEEDBACK_SPOKEN) { + updateClientsLocked(); + setInputFilterEnabledLocked(true); + } + } + + private void updateStateOnDisabledService(Service service) { + List<ServiceInfo> enabledServices = + mFeedbackTypeToEnabledServicesMap.get(service.mFeedbackType); + if (enabledServices == null) { + return; + } + enabledServices.remove(service.mServiceInfo); + // We disable touch exploration if no + // enabled service provides spoken feedback. + if (enabledServices.isEmpty() + && service.mFeedbackType == AccessibilityServiceInfo.FEEDBACK_SPOKEN) { + updateClientsLocked(); + setInputFilterEnabledLocked(false); + } + } + + /** * This class represents an accessibility service. It stores all per service * data required for the service management, provides API for starting/stopping the * service and is responsible for adding/removing the service in the data structures @@ -631,6 +714,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub class Service extends IAccessibilityServiceConnection.Stub implements ServiceConnection { int mId = 0; + ServiceInfo mServiceInfo; + IBinder mService; IEventListener mServiceInterface; @@ -655,9 +740,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<AccessibilityEvent>(); - Service(ComponentName componentName) { + Service(ComponentName componentName, ServiceInfo serviceInfo) { mId = sIdCounter++; mComponentName = componentName; + mServiceInfo = serviceInfo; mIntent = new Intent().setComponent(mComponentName); mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, com.android.internal.R.string.accessibility_binding_label); @@ -689,6 +775,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mContext.unbindService(this); mComponentNameToServiceMap.remove(mComponentName); mServices.remove(this); + updateStateOnDisabledService(this); return true; } return false; diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java new file mode 100644 index 0000000..4ba6060 --- /dev/null +++ b/services/java/com/android/server/accessibility/TouchExplorer.java @@ -0,0 +1,1540 @@ +/* + ** Copyright 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.accessibility; + +import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END; +import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START; + +import com.android.server.accessibility.AccessibilityInputFilter.Explorer; +import com.android.server.wm.InputFilter; + +import android.content.Context; +import android.os.Handler; +import android.os.SystemClock; +import android.util.Slog; +import android.util.SparseArray; +import android.view.MotionEvent; +import android.view.ViewConfiguration; +import android.view.WindowManagerPolicy; +import android.view.MotionEvent.PointerCoords; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; + +import java.util.Arrays; + +/** + * This class is a strategy for performing touch exploration. It + * transforms the motion event stream by modifying, adding, replacing, + * and consuming certain events. The interaction model is: + * + * <ol> + * <li>1. One finger moving around performs touch exploration.</li> + * <li>2. Two close fingers moving in the same direction perform a drag.</li> + * <li>3. Multi-finger gestures are delivered to view hierarchy.</li> + * <li>4. Pointers that have not moved more than a specified distance after they + * went down are considered inactive.</li> + * <li>5. Two fingers moving too far from each other or in different directions + * are considered a multi-finger gesture.</li> + * <li>6. Tapping on the last touch explored location within given time and + * distance slop performs a click.</li> + * <li>7. Tapping and holding for a while on the last touch explored location within + * given time and distance slop performs a long press.</li> + * <ol> + * + * @hide + */ +public class TouchExplorer implements Explorer { + private static final boolean DEBUG = false; + + // Tag for logging received events. + private static final String LOG_TAG_RECEIVED = "TouchExplorer-RECEIVED"; + // Tag for logging injected events. + private static final String LOG_TAG_INJECTED = "TouchExplorer-INJECTED"; + // Tag for logging the current state. + private static final String LOG_TAG_STATE = "TouchExplorer-STATE"; + + // States this explorer can be in. + private static final int STATE_TOUCH_EXPLORING = 0x00000001; + private static final int STATE_DRAGGING = 0x00000002; + private static final int STATE_DELEGATING = 0x00000004; + + // Human readable symbolic names for the states of the explorer. + private static final SparseArray<String> sStateSymbolicNames = new SparseArray<String>(); + static { + SparseArray<String> symbolicNames = sStateSymbolicNames; + symbolicNames.append(STATE_TOUCH_EXPLORING, "STATE_TOUCH_EXPLORING"); + symbolicNames.append(STATE_DRAGGING, "STATE_DRAGING"); + symbolicNames.append(STATE_DELEGATING, "STATE_DELEGATING"); + } + + // Invalid pointer ID. + private static final int INVALID_POINTER_ID = -1; + + // The coefficient by which to multiply + // ViewConfiguration.#getScaledTouchExplorationTapSlop() + // to compute #mDraggingDistance. + private static final int COEFFICIENT_DRAGGING_DISTANCE = 2; + + // The time slop in milliseconds for activating an item after it has + // been touch explored. Tapping on an item within this slop will perform + // a click and tapping and holding down a long press. + private static final long ACTIVATION_TIME_SLOP = 2000; + + // This constant captures the current implementation detail that + // pointer IDs are between 0 and 31 inclusive (subject to change). + // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h) + private static final int MAX_POINTER_COUNT = 32; + + // The minimum of the cosine between the vectors of two moving + // pointers so they can be considered moving in the same direction. + private static final float MIN_ANGLE_COS = 0.866025404f; // cos(pi/6) + + // The delay for sending a hover enter event. + private static final long DELAY_SEND_HOVER_MOVE = 200; + + // Temporary array for storing pointer IDs. + private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT]; + + // Temporary array for mapping new to old pointer IDs while filtering inactive pointers. + private final int [] mTempNewToOldPointerIndexMap = new int[MAX_POINTER_COUNT]; + + // Temporary array for storing PointerCoords + private final PointerCoords[] mTempPointerCoords= new PointerCoords[MAX_POINTER_COUNT]; + + // The maximal distance between two pointers so they are + // considered to be performing a drag operation. + private final float mDraggingDistance; + + // The distance from the last touch explored location tapping within + // which would perform a click and tapping and holding a long press. + private final int mTouchExplorationTapSlop; + + // Context handle for accessing resources. + private final Context mContext; + + // The InputFilter this tracker is associated with i.e. the filter + // which delegates event processing to this touch explorer. + private final InputFilter mInputFilter; + + // Helper class for tracking pointers on the screen, for example which + // pointers are down, which are active, etc. + private final PointerTracker mPointerTracker; + + // Handle to the accessibility manager for firing accessibility events + // announcing touch exploration gesture start and end. + private final AccessibilityManager mAccessibilityManager; + + // The last event that was received while performing touch exploration. + private MotionEvent mLastTouchExploreEvent; + + // The current state of the touch explorer. + private int mCurrentState = STATE_TOUCH_EXPLORING; + + // Flag whether a touch exploration gesture is in progress. + private boolean mTouchExploreGestureInProgress; + + // The ID of the pointer used for dragging. + private int mDraggingPointerId; + + // Handler for performing asynchronous operations. + private final Handler mHandler; + + // Command for delayed sending of a hover event. + private final SendHoverDelayed mSendHoverDelayed; + + /** + * Creates a new instance. + * + * @param inputFilter The input filter associated with this explorer. + * @param context A context handle for accessing resources. + */ + public TouchExplorer(InputFilter inputFilter, Context context) { + mInputFilter = inputFilter; + mTouchExplorationTapSlop = + ViewConfiguration.get(context).getScaledTouchExplorationTapSlop(); + mDraggingDistance = mTouchExplorationTapSlop * COEFFICIENT_DRAGGING_DISTANCE; + mPointerTracker = new PointerTracker(context); + mContext = context; + mHandler = new Handler(context.getMainLooper()); + mSendHoverDelayed = new SendHoverDelayed(); + mAccessibilityManager = AccessibilityManager.getInstance(context); + + // Populate the temporary array with PointerCorrds to be reused. + for (int i = 0, count = mTempPointerCoords.length; i < count; i++) { + mTempPointerCoords[i] = new PointerCoords(); + } + } + + public void clear(MotionEvent event, int policyFlags) { + sendUpForInjectedDownPointers(event, policyFlags); + clear(); + } + + /** + * {@inheritDoc} + */ + public void onMotionEvent(MotionEvent event, int policyFlags) { + if (DEBUG) { + Slog.d(LOG_TAG_RECEIVED, "Received event: " + event + ", policyFlags=0x" + + Integer.toHexString(policyFlags)); + Slog.d(LOG_TAG_STATE, sStateSymbolicNames.get(mCurrentState)); + } + + // Keep track of the pointers's state. + mPointerTracker.onReceivedMotionEvent(event); + + switch(mCurrentState) { + case STATE_TOUCH_EXPLORING: { + handleMotionEventStateTouchExploring(event, policyFlags); + } break; + case STATE_DRAGGING: { + handleMotionEventStateDragging(event, policyFlags); + } break; + case STATE_DELEGATING: { + handleMotionEventStateDelegating(event, policyFlags); + } break; + default: { + throw new IllegalStateException("Illegal state: " + mCurrentState); + } + } + } + + /** + * Handles a motion event in touch exploring state. + * + * @param event The event to be handled. + * @param policyFlags The policy flags associated with the event. + */ + private void handleMotionEventStateTouchExploring(MotionEvent event, int policyFlags) { + PointerTracker pointerTracker = mPointerTracker; + final int activePointerCount = pointerTracker.getActivePointerCount(); + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + // Send a hover for every finger down so the user gets feedback + // where she is currently touching. + mSendHoverDelayed.forceSendAndRemove(); + mSendHoverDelayed.post(event, MotionEvent.ACTION_HOVER_ENTER, 0, policyFlags, + DELAY_SEND_HOVER_MOVE); + } break; + case MotionEvent.ACTION_POINTER_DOWN: { + switch (activePointerCount) { + case 0: { + throw new IllegalStateException("The must always be one active pointer in" + + "touch exploring state!"); + } + case 1: { + // Schedule a hover event which will lead to firing an + // accessibility event from the hovered view. + mSendHoverDelayed.remove(); + final int pointerId = pointerTracker.getPrimaryActivePointerId(); + final int pointerIndex = event.findPointerIndex(pointerId); + final int lastAction = pointerTracker.getLastInjectedHoverAction(); + // If a schedules hover enter for another pointer is delivered we send move. + final int action = (lastAction == MotionEvent.ACTION_HOVER_ENTER) + ? MotionEvent.ACTION_HOVER_MOVE + : MotionEvent.ACTION_HOVER_ENTER; + mSendHoverDelayed.post(event, action, pointerIndex, policyFlags, + DELAY_SEND_HOVER_MOVE); + + if (mLastTouchExploreEvent == null) { + break; + } + + // If more pointers down on the screen since the last touch + // exploration we discard the last cached touch explore event. + if (event.getPointerCount() != mLastTouchExploreEvent.getPointerCount()) { + mLastTouchExploreEvent = null; + } + } break; + default: { + /* do nothing - let the code for ACTION_MOVE decide what to do */ + } break; + } + } break; + case MotionEvent.ACTION_MOVE: { + switch (activePointerCount) { + case 0: { + /* do nothing - no active pointers so we swallow the event */ + } break; + case 1: { + final int pointerId = pointerTracker.getPrimaryActivePointerId(); + final int pointerIndex = event.findPointerIndex(pointerId); + + // Detect touch exploration gesture start by having one active pointer + // that moved more than a given distance. + if (!mTouchExploreGestureInProgress) { + final float deltaX = pointerTracker.getReceivedPointerDownX(pointerId) + - event.getX(pointerIndex); + final float deltaY = pointerTracker.getReceivedPointerDownY(pointerId) + - event.getY(pointerIndex); + final double moveDelta = Math.hypot(deltaX, deltaY); + + if (moveDelta > mTouchExplorationTapSlop) { + mTouchExploreGestureInProgress = true; + sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START); + // Make sure the scheduled down/move event is sent. + mSendHoverDelayed.forceSendAndRemove(); + sendHoverEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIndex, + policyFlags); + } + } else { + // Touch exploration gesture in progress so send a hover event. + sendHoverEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIndex, + policyFlags); + } + + // Detect long press on the last touch explored position. + if (!mTouchExploreGestureInProgress && mLastTouchExploreEvent != null) { + + // If the down was not in the time slop => nothing else to do. + final long pointerDownTime = + pointerTracker.getReceivedPointerDownTime(pointerId); + final long lastExploreTime = mLastTouchExploreEvent.getEventTime(); + final long deltaTimeExplore = pointerDownTime - lastExploreTime; + if (deltaTimeExplore > ACTIVATION_TIME_SLOP) { + mLastTouchExploreEvent = null; + break; + } + + // If the pointer moved more than the tap slop => nothing else to do. + final float deltaX = mLastTouchExploreEvent.getX(pointerIndex) + - event.getX(pointerIndex); + final float deltaY = mLastTouchExploreEvent.getY(pointerIndex) + - event.getY(pointerIndex); + final float moveDelta = (float) Math.hypot(deltaX, deltaY); + if (moveDelta > mTouchExplorationTapSlop) { + mLastTouchExploreEvent = null; + break; + } + + // If down for long enough we get a long press. + final long deltaTimeMove = event.getEventTime() - pointerDownTime; + if (deltaTimeMove > ViewConfiguration.getLongPressTimeout()) { + mCurrentState = STATE_DELEGATING; + // Make sure the scheduled hover exit is delivered. + mSendHoverDelayed.forceSendAndRemove(); + sendDownForAllActiveNotInjectedPointers(event, policyFlags); + sendMotionEvent(event, policyFlags); + mTouchExploreGestureInProgress = false; + mLastTouchExploreEvent = null; + } + } + } break; + case 2: { + // Make sure the scheduled hover enter is delivered. + mSendHoverDelayed.forceSendAndRemove(); + // We want to no longer hover over the location so subsequent + // touch at the same spot will generate a hover enter. + final int pointerId = pointerTracker.getPrimaryActivePointerId(); + final int pointerIndex = event.findPointerIndex(pointerId); + sendHoverEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIndex, + policyFlags); + + if (isDraggingGesture(event)) { + // Two pointers moving in the same direction within + // a given distance perform a drag. + mCurrentState = STATE_DRAGGING; + if (mTouchExploreGestureInProgress) { + sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END); + mTouchExploreGestureInProgress = false; + } + mDraggingPointerId = pointerTracker.getPrimaryActivePointerId(); + sendDragEvent(event, MotionEvent.ACTION_DOWN, policyFlags); + } else { + // Two pointers moving arbitrary are delegated to the view hierarchy. + mCurrentState = STATE_DELEGATING; + if (mTouchExploreGestureInProgress) { + sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END); + mTouchExploreGestureInProgress = false; + } + sendDownForAllActiveNotInjectedPointers(event, policyFlags); + } + } break; + default: { + // Make sure the scheduled hover enter is delivered. + mSendHoverDelayed.forceSendAndRemove(); + // We want to no longer hover over the location so subsequent + // touch at the same spot will generate a hover enter. + final int pointerId = pointerTracker.getPrimaryActivePointerId(); + final int pointerIndex = event.findPointerIndex(pointerId); + sendHoverEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIndex, + policyFlags); + + // More than two pointers are delegated to the view hierarchy. + mCurrentState = STATE_DELEGATING; + mSendHoverDelayed.remove(); + if (mTouchExploreGestureInProgress) { + sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END); + mTouchExploreGestureInProgress = false; + } + sendDownForAllActiveNotInjectedPointers(event, policyFlags); + } + } + } break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: { + switch (activePointerCount) { + case 0: { + // If the pointer that went up was not active we have nothing to do. + if (!pointerTracker.wasLastReceivedUpPointerActive()) { + break; + } + + // If touch exploring announce the end of the gesture. + if (mTouchExploreGestureInProgress) { + sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END); + mTouchExploreGestureInProgress = false; + } + + // Detect whether to activate i.e. click on the last explored location. + if (mLastTouchExploreEvent != null) { + final int pointerId = pointerTracker.getLastReceivedUpPointerId(); + + // If the down was not in the time slop => nothing else to do. + final long eventTime = + pointerTracker.getLastReceivedUpPointerDownTime(); + final long exploreTime = mLastTouchExploreEvent.getEventTime(); + final long deltaTime = eventTime - exploreTime; + if (deltaTime > ACTIVATION_TIME_SLOP) { + mSendHoverDelayed.forceSendAndRemove(); + scheduleHoverExit(event, policyFlags); + mLastTouchExploreEvent = MotionEvent.obtain(event); + break; + } + + // If the pointer moved more than the tap slop => nothing else to do. + final int pointerIndex = event.findPointerIndex(pointerId); + final float deltaX = pointerTracker.getLastReceivedUpPointerDownX() + - event.getX(pointerIndex); + final float deltaY = pointerTracker.getLastReceivedUpPointerDownY() + - event.getY(pointerIndex); + final float deltaMove = (float) Math.hypot(deltaX, deltaY); + if (deltaMove > mTouchExplorationTapSlop) { + mSendHoverDelayed.forceSendAndRemove(); + scheduleHoverExit(event, policyFlags); + mLastTouchExploreEvent = MotionEvent.obtain(event); + break; + } + + // All preconditions are met, so click the last explored location. + mSendHoverDelayed.forceSendAndRemove(); + sendActionDownAndUp(mLastTouchExploreEvent, policyFlags); + mLastTouchExploreEvent = null; + } else { + mSendHoverDelayed.forceSendAndRemove(); + scheduleHoverExit(event, policyFlags); + mLastTouchExploreEvent = MotionEvent.obtain(event); + } + } break; + } + } break; + case MotionEvent.ACTION_CANCEL: { + final int lastAction = pointerTracker.getLastInjectedHoverAction(); + if (lastAction != MotionEvent.ACTION_HOVER_EXIT) { + final int pointerId = pointerTracker.getPrimaryActivePointerId(); + final int pointerIndex = event.findPointerIndex(pointerId); + sendHoverEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIndex, + policyFlags); + } + clear(); + } break; + } + } + + /** + * Handles a motion event in dragging state. + * + * @param event The event to be handled. + * @param policyFlags The policy flags associated with the event. + */ + private void handleMotionEventStateDragging(MotionEvent event, int policyFlags) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + throw new IllegalStateException("Dragging state can be reached only if two " + + "pointers are already down"); + } + case MotionEvent.ACTION_POINTER_DOWN: { + // We are in dragging state so we have two pointers and another one + // goes down => delegate the three pointers to the view hierarchy + mCurrentState = STATE_DELEGATING; + sendDragEvent(event, MotionEvent.ACTION_UP, policyFlags); + sendDownForAllActiveNotInjectedPointers(event, policyFlags); + } break; + case MotionEvent.ACTION_MOVE: { + final int activePointerCount = mPointerTracker.getActivePointerCount(); + switch (activePointerCount) { + case 2: { + if (isDraggingGesture(event)) { + // If still dragging send a drag event. + sendDragEvent(event, MotionEvent.ACTION_MOVE, policyFlags); + } else { + // The two pointers are moving either in different directions or + // no close enough => delegate the gesture to the view hierarchy. + mCurrentState = STATE_DELEGATING; + // Send an event to the end of the drag gesture. + sendDragEvent(event, MotionEvent.ACTION_UP, policyFlags); + // Deliver all active pointers to the view hierarchy. + sendDownForAllActiveNotInjectedPointers(event, policyFlags); + } + } break; + default: { + mCurrentState = STATE_DELEGATING; + // Send an event to the end of the drag gesture. + sendDragEvent(event, MotionEvent.ACTION_UP, policyFlags); + // Deliver all active pointers to the view hierarchy. + sendDownForAllActiveNotInjectedPointers(event, policyFlags); + } + } + } break; + case MotionEvent.ACTION_POINTER_UP: { + mCurrentState = STATE_TOUCH_EXPLORING; + // Send an event to the end of the drag gesture. + sendDragEvent(event, MotionEvent.ACTION_UP, policyFlags); + } break; + case MotionEvent.ACTION_CANCEL: { + clear(); + } break; + } + } + + /** + * Handles a motion event in delegating state. + * + * @param event The event to be handled. + * @param policyFlags The policy flags associated with the event. + */ + public void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + throw new IllegalStateException("Delegating state can only be reached if " + + "there is at least one pointer down!"); + } + case MotionEvent.ACTION_UP: { + mCurrentState = STATE_TOUCH_EXPLORING; + } break; + case MotionEvent.ACTION_MOVE: { + // Check whether some other pointer became active because they have moved + // a given distance and if such exist send them to the view hierarchy + final int notInjectedCount = mPointerTracker.getNotInjectedActivePointerCount(); + if (notInjectedCount > 0) { + sendDownForAllActiveNotInjectedPointers(event, policyFlags); + } + } break; + case MotionEvent.ACTION_POINTER_UP: { + // No active pointers => go to initial state. + if (mPointerTracker.getActivePointerCount() == 0) { + mCurrentState = STATE_TOUCH_EXPLORING; + } + } break; + case MotionEvent.ACTION_CANCEL: { + clear(); + } break; + } + // Deliver the event striping out inactive pointers. + sendMotionEventStripInactivePointers(event, policyFlags); + } + + /** + * Schedules a hover up event so subsequent poking on the same location after + * the scheduled delay will perform exploration. + * + * @param prototype The prototype from which to create the injected events. + * @param policyFlags The policy flags associated with the event. + */ + private void scheduleHoverExit(MotionEvent prototype, + int policyFlags) { + final int pointerId = mPointerTracker.getLastReceivedUpPointerId(); + final int pointerIndex = prototype.findPointerIndex(pointerId); + // We want to no longer hover over the location so subsequent + // touch at the same spot will generate a hover enter. + mSendHoverDelayed.post(prototype, MotionEvent.ACTION_HOVER_EXIT, + pointerIndex, policyFlags, ACTIVATION_TIME_SLOP); + } + + /** + * Sends down events to the view hierarchy for all active pointers which are + * not already being delivered i.e. pointers that are not yet injected. + * + * @param prototype The prototype from which to create the injected events. + * @param policyFlags The policy flags associated with the event. + */ + private void sendDownForAllActiveNotInjectedPointers(MotionEvent prototype, int policyFlags) { + PointerCoords[] pointerCoords = mTempPointerCoords; + PointerTracker pointerTracker = mPointerTracker; + int[] pointerIds = mTempPointerIds; + int pointerDataIndex = 0; + + final int pinterCount = prototype.getPointerCount(); + for (int i = 0; i < pinterCount; i++) { + final int pointerId = prototype.getPointerId(i); + + // Skip inactive pointers. + if (!pointerTracker.isActivePointer(pointerId)) { + continue; + } + // Skip already delivered pointers. + if (pointerTracker.isInjectedPointerDown(pointerId)) { + continue; + } + + // Populate and inject an event for the current pointer. + pointerIds[pointerDataIndex] = pointerId; + prototype.getPointerCoords(i, pointerCoords[pointerDataIndex]); + + final long downTime = pointerTracker.getLastInjectedDownEventTime(); + final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, pointerDataIndex); + final int pointerCount = pointerDataIndex + 1; + final long pointerDownTime = SystemClock.uptimeMillis(); + + MotionEvent event = MotionEvent.obtain(downTime, pointerDownTime, + action, pointerCount, pointerIds, pointerCoords, prototype.getMetaState(), + prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(), + prototype.getEdgeFlags(), prototype.getSource(), prototype.getFlags()); + sendMotionEvent(event, policyFlags); + event.recycle(); + + pointerDataIndex++; + } + } + + /** + * Sends up events to the view hierarchy for all active pointers which are + * already being delivered i.e. pointers that are injected. + * + * @param prototype The prototype from which to create the injected events. + * @param policyFlags The policy flags associated with the event. + */ + private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) { + PointerTracker pointerTracker = mPointerTracker; + PointerCoords[] pointerCoords = mTempPointerCoords; + int[] pointerIds = mTempPointerIds; + int pointerDataIndex = 0; + + final int pointerCount = prototype.getPointerCount(); + for (int i = 0; i < pointerCount; i++) { + final int pointerId = prototype.getPointerId(i); + + // Skip non injected down pointers. + if (!pointerTracker.isInjectedPointerDown(pointerId)) { + continue; + } + + // Populate and inject event. + pointerIds[pointerDataIndex] = pointerId; + prototype.getPointerCoords(i, pointerCoords[pointerDataIndex]); + + final long downTime = pointerTracker.getLastInjectedDownEventTime(); + final int action = computeInjectionAction(MotionEvent.ACTION_UP, pointerDataIndex); + final int newPointerCount = pointerDataIndex + 1; + final long eventTime = SystemClock.uptimeMillis(); + + MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, + newPointerCount, pointerIds, pointerCoords, prototype.getMetaState(), + prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(), + prototype.getEdgeFlags(), prototype.getSource(), prototype.getFlags()); + + sendMotionEvent(event, policyFlags); + event.recycle(); + + pointerDataIndex++; + } + } + + /** + * Sends a motion event by first stripping the inactive pointers. + * + * @param prototype The prototype from which to create the injected event. + * @param policyFlags The policy flags associated with the event. + */ + private void sendMotionEventStripInactivePointers(MotionEvent prototype, int policyFlags) { + PointerTracker pointerTracker = mPointerTracker; + + // All pointers active therefore we just inject the event as is. + if (prototype.getPointerCount() == pointerTracker.getActivePointerCount()) { + sendMotionEvent(prototype, policyFlags); + return; + } + + // No active pointers and the one that just went up was not + // active, therefore we have nothing to do. + if (pointerTracker.getActivePointerCount() == 0 + && !pointerTracker.wasLastReceivedUpPointerActive()) { + return; + } + + // Filter out inactive pointers from the event and inject it. + PointerCoords[] pointerCoords = mTempPointerCoords; + int[] pointerIds = mTempPointerIds; + int [] newToOldPointerIndexMap = mTempNewToOldPointerIndexMap; + int newPointerIndex = 0; + int actionIndex = prototype.getActionIndex(); + + final int oldPointerCount = prototype.getPointerCount(); + for (int oldPointerIndex = 0; oldPointerIndex < oldPointerCount; oldPointerIndex++) { + final int pointerId = prototype.getPointerId(oldPointerIndex); + + // If the pointer is inactive or the pointer that just went up + // was inactive we strip the pointer data from the event. + if (!pointerTracker.isActiveOrWasLastActiveUpPointer(pointerId)) { + if (oldPointerIndex <= prototype.getActionIndex()) { + actionIndex--; + } + continue; + } + + newToOldPointerIndexMap[newPointerIndex] = oldPointerIndex; + pointerIds[newPointerIndex] = pointerId; + prototype.getPointerCoords(oldPointerIndex, pointerCoords[newPointerIndex]); + + newPointerIndex++; + } + + // If we skipped all pointers => nothing to do. + if (newPointerIndex == 0) { + return; + } + + // Populate and inject the event. + final long downTime = pointerTracker.getLastInjectedDownEventTime(); + final int action = computeInjectionAction(prototype.getActionMasked(), actionIndex); + final int newPointerCount = newPointerIndex; + MotionEvent prunedEvent = MotionEvent.obtain(downTime, prototype.getEventTime(), action, + newPointerCount, pointerIds, pointerCoords, prototype.getMetaState(), + prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(), + prototype.getEdgeFlags(), prototype.getSource(),prototype.getFlags()); + + // Add the filtered history. + final int historySize = prototype.getHistorySize(); + for (int historyIndex = 0; historyIndex < historySize; historyIndex++) { + for (int pointerIndex = 0; pointerIndex < newPointerCount; pointerIndex++) { + final int oldPointerIndex = newToOldPointerIndexMap[pointerIndex]; + prototype.getPointerCoords(oldPointerIndex, pointerCoords[pointerIndex]); + } + final long historicalTime = prototype.getHistoricalEventTime(historyIndex); + prunedEvent.addBatch(historicalTime, pointerCoords, 0); + } + + sendMotionEvent(prunedEvent, policyFlags); + prunedEvent.recycle(); + } + + /** + * Sends a dragging event from a two pointer event. The two pointers are + * merged into one and delivered to the view hierarchy. Through the entire + * drag gesture the pointer id delivered to the view hierarchy is the same. + * + * @param prototype The prototype from which to create the injected event. + * @param action The dragging action that is to be injected. + * @param policyFlags The policy flags associated with the event. + */ + private void sendDragEvent(MotionEvent prototype, int action, int policyFlags) { + PointerCoords[] pointerCoords = mTempPointerCoords; + int[] pointerIds = mTempPointerIds; + final int pointerId = mDraggingPointerId; + final int pointerIndex = prototype.findPointerIndex(pointerId); + + // Populate the event with the date of the dragging pointer and inject it. + pointerIds[0] = pointerId; + prototype.getPointerCoords(pointerIndex, pointerCoords[0]); + + MotionEvent event = MotionEvent.obtain(prototype.getDownTime(), + prototype.getEventTime(), action, 1, pointerIds, pointerCoords, + prototype.getMetaState(), prototype.getXPrecision(), prototype.getYPrecision(), + prototype.getDeviceId(), prototype.getEdgeFlags(), prototype.getSource(), + prototype.getFlags()); + + sendMotionEvent(event, policyFlags); + event.recycle(); + } + + /** + * Sends an up and down events. + * + * @param prototype The prototype from which to create the injected events. + * @param policyFlags The policy flags associated with the event. + */ + private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) { + PointerCoords[] pointerCoords = mTempPointerCoords; + int[] pointerIds = mTempPointerIds; + final int pointerId = mPointerTracker.getLastReceivedUpPointerId(); + final int pointerIndex = prototype.findPointerIndex(pointerId); + + // Send down. + pointerIds[0] = pointerId; + prototype.getPointerCoords(pointerIndex, pointerCoords[0]); + + final long downTime = SystemClock.uptimeMillis(); + + MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, + 1, mTempPointerIds, mTempPointerCoords, prototype.getMetaState(), + prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(), + prototype.getEdgeFlags(), prototype.getSource(), prototype.getFlags()); + + // Clone the down event before recycling it. + MotionEvent upEvent = MotionEvent.obtain(downEvent); + + sendMotionEvent(downEvent, policyFlags); + downEvent.recycle(); + + // Send up. + upEvent.setAction(MotionEvent.ACTION_UP); + sendMotionEvent(upEvent, policyFlags); + upEvent.recycle(); + } + + /** + * Sends a hover event. + * + * @param prototype The prototype from which to create the injected event. + * @param action The hover action. + * @param pointerIndex The action pointer index. + * @param policyFlags The policy flags associated with the event. + */ + private void sendHoverEvent(MotionEvent prototype, int action, int pointerIndex, int + policyFlags) { + PointerCoords[] pointerCoords = mTempPointerCoords; + int[] pointerIds = mTempPointerIds; + + // Keep only data relevant to a hover event. + pointerIds[0] = prototype.getPointerId(pointerIndex); + pointerCoords[0].clear(); + pointerCoords[0].x = prototype.getX(pointerIndex); + pointerCoords[0].y = prototype.getY(pointerIndex); + + final long downTime = mPointerTracker.getLastInjectedDownEventTime(); + + // Populate and inject a hover event. + MotionEvent hoverEvent = MotionEvent.obtain(downTime, prototype.getEventTime(), action, + 1, pointerIds, pointerCoords, 0, 0, 0, prototype.getDeviceId(), 0, + prototype.getSource(), 0); + + sendMotionEvent(hoverEvent, policyFlags); + hoverEvent.recycle(); + } + + /** + * Computes the action for an injected event based on a masked action + * and a pointer index. + * + * @param actionMasked The masked action. + * @param pointerIndex The index of the pointer which has changed. + * @return The action to be used for injection. + */ + private int computeInjectionAction(int actionMasked, int pointerIndex) { + switch (actionMasked) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: { + PointerTracker pointerTracker = mPointerTracker; + // Compute the action based on how many down pointers are injected. + if (pointerTracker.getInjectedPointerDownCount() == 0) { + return MotionEvent.ACTION_DOWN; + } else { + return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + | MotionEvent.ACTION_POINTER_DOWN; + } + } + case MotionEvent.ACTION_POINTER_UP: { + PointerTracker pointerTracker = mPointerTracker; + // Compute the action based on how many down pointers are injected. + if (pointerTracker.getInjectedPointerDownCount() == 1) { + return MotionEvent.ACTION_UP; + } else { + return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) + | MotionEvent.ACTION_POINTER_UP; + } + } + default: + return actionMasked; + } + } + + /** + * Determines whether a two pointer gesture is a dragging one. + * + * @param event The event with the pointer data. + * @return True if the gesture is a dragging one. + */ + private boolean isDraggingGesture(MotionEvent event) { + PointerTracker pointerTracker = mPointerTracker; + int[] pointerIds = mTempPointerIds; + pointerTracker.populateActivePointerIds(pointerIds); + + final int firstPtrIndex = event.findPointerIndex(pointerIds[0]); + final int secondPtrIndex = event.findPointerIndex(pointerIds[1]); + + final float firstPtrX = event.getX(firstPtrIndex); + final float firstPtrY = event.getY(firstPtrIndex); + final float secondPtrX = event.getX(secondPtrIndex); + final float secondPtrY = event.getY(secondPtrIndex); + + // Check if the pointers are close enough. + final float deltaX = firstPtrX - secondPtrX; + final float deltaY = firstPtrY - secondPtrY; + final float deltaMove = (float) Math.hypot(deltaX, deltaY); + if (deltaMove > mDraggingDistance) { + return false; + } + + // Check if the pointers are moving in the same direction. + final float firstDeltaX = + firstPtrX - pointerTracker.getReceivedPointerDownX(firstPtrIndex); + final float firstDeltaY = + firstPtrY - pointerTracker.getReceivedPointerDownY(firstPtrIndex); + final float firstMagnitude = + (float) Math.sqrt(firstDeltaX * firstDeltaX + firstDeltaY * firstDeltaY); + final float firstXNormalized = + (firstMagnitude > 0) ? firstDeltaX / firstMagnitude : firstDeltaX; + final float firstYNormalized = + (firstMagnitude > 0) ? firstDeltaY / firstMagnitude : firstDeltaY; + + final float secondDeltaX = + secondPtrX - pointerTracker.getReceivedPointerDownX(secondPtrIndex); + final float secondDeltaY = + secondPtrY - pointerTracker.getReceivedPointerDownY(secondPtrIndex); + final float secondMagnitude = + (float) Math.sqrt(secondDeltaX * secondDeltaX + secondDeltaY * secondDeltaY); + final float secondXNormalized = + (secondMagnitude > 0) ? secondDeltaX / secondMagnitude : secondDeltaX; + final float secondYNormalized = + (secondMagnitude > 0) ? secondDeltaY / secondMagnitude : secondDeltaY; + + final float angleCos = + firstXNormalized * secondXNormalized + firstYNormalized * secondYNormalized; + + if (angleCos < MIN_ANGLE_COS) { + return false; + } + + return true; + } + + /** + * Sends an event announcing the start/end of a touch exploration gesture. + * + * @param eventType The type of the event to send. + */ + private void sendAccessibilityEvent(int eventType) { + AccessibilityEvent event = AccessibilityEvent.obtain(eventType); + event.setPackageName(mContext.getPackageName()); + event.setClassName(getClass().getName()); + mAccessibilityManager.sendAccessibilityEvent(event); + } + + /** + * Sends a motion event to the input filter for injection. + * + * @param event The event to send. + * @param policyFlags The policy flags associated with the event. + */ + private void sendMotionEvent(MotionEvent event, int policyFlags) { + if (DEBUG) { + Slog.d(LOG_TAG_INJECTED, "Injecting event: " + event + ", policyFlags=0x" + + Integer.toHexString(policyFlags)); + } + // Make sure that the user will see the event. + policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER; + mPointerTracker.onInjectedMotionEvent(event); + mInputFilter.sendInputEvent(event, policyFlags); + } + + /** + * Clears the internal state of this explorer. + */ + private void clear() { + mSendHoverDelayed.remove(); + mPointerTracker.clear(); + mLastTouchExploreEvent = null; + mCurrentState = STATE_TOUCH_EXPLORING; + mTouchExploreGestureInProgress = false; + mDraggingPointerId = INVALID_POINTER_ID; + } + + /** + * Helper class for tracking pointers and more specifically which of + * them are currently down, which are active, and which are delivered + * to the view hierarchy. The enclosing {@link TouchExplorer} uses the + * pointer state reported by this class to perform touch exploration. + * <p> + * The main purpose of this class is to allow the touch explorer to + * disregard pointers put down by accident by the user and not being + * involved in the interaction. For example, a blind user grabs the + * device with her left hand such that she touches the screen and she + * uses her right hand's index finger to explore the screen content. + * In this scenario the touches generated by the left hand are to be + * ignored. + */ + class PointerTracker { + private static final String LOG_TAG = "PointerTracker"; + + // The coefficient by which to multiply + // ViewConfiguration.#getScaledTouchSlop() + // to compute #mThresholdActivePointer. + private static final int COEFFICIENT_ACTIVE_POINTER = 2; + + // Pointers that moved less than mThresholdActivePointer + // are considered active i.e. are ignored. + private final double mThresholdActivePointer; + + // Keep track of where and when a pointer went down. + private final float[] mReceivedPointerDownX = new float[MAX_POINTER_COUNT]; + private final float[] mReceivedPointerDownY = new float[MAX_POINTER_COUNT]; + private final long[] mReceivedPointerDownTime = new long[MAX_POINTER_COUNT]; + + // Which pointers are down. + private int mReceivedPointersDown; + + // Which down pointers are active. + private int mActivePointers; + + // Primary active pointer which is either the first that went down + // or if it goes up the next active that most recently went down. + private int mPrimaryActivePointerId; + + // Flag indicating that there is at least one active pointer moving. + private boolean mHasMovingActivePointer; + + // Keep track of which pointers sent to the system are down. + private int mInjectedPointersDown; + + // Keep track of the last up pointer data. + private float mLastReceivedUpPointerDownX; + private float mLastReveivedUpPointerDownY; + private long mLastReceivedUpPointerDownTime; + private int mLastReceivedUpPointerId; + private boolean mLastReceivedUpPointerActive; + + // The time of the last injected down. + private long mLastInjectedDownEventTime; + + // The action of the last injected hover event. + private int mLastInjectedHoverEventAction = MotionEvent.ACTION_HOVER_EXIT; + + /** + * Creates a new instance. + * + * @param context Context for looking up resources. + */ + public PointerTracker(Context context) { + mThresholdActivePointer = + ViewConfiguration.get(context).getScaledTouchSlop() * COEFFICIENT_ACTIVE_POINTER; + } + + /** + * Clears the internals state. + */ + public void clear() { + Arrays.fill(mReceivedPointerDownX, 0); + Arrays.fill(mReceivedPointerDownY, 0); + Arrays.fill(mReceivedPointerDownTime, 0); + mReceivedPointersDown = 0; + mActivePointers = 0; + mPrimaryActivePointerId = 0; + mHasMovingActivePointer = false; + mInjectedPointersDown = 0; + mLastReceivedUpPointerDownX = 0; + mLastReveivedUpPointerDownY = 0; + mLastReceivedUpPointerDownTime = 0; + mLastReceivedUpPointerId = 0; + mLastReceivedUpPointerActive = false; + } + + /** + * Processes a received {@link MotionEvent} event. + * + * @param event The event to process. + */ + public void onReceivedMotionEvent(MotionEvent event) { + final int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: { + // New gesture so restart tracking injected down pointers. + mInjectedPointersDown = 0; + handleReceivedPointerDown(0, event); + } break; + case MotionEvent.ACTION_POINTER_DOWN: { + handleReceivedPointerDown(event.getActionIndex(), event); + } break; + case MotionEvent.ACTION_MOVE: { + handleReceivedPointerMove(event); + } break; + case MotionEvent.ACTION_UP: { + handleReceivedPointerUp(0, event); + } break; + case MotionEvent.ACTION_POINTER_UP: { + handleReceivedPointerUp(event.getActionIndex(), event); + } break; + } + if (DEBUG) { + Slog.i(LOG_TAG, "Received pointer: " + toString()); + } + } + + /** + * Processes an injected {@link MotionEvent} event. + * + * @param event The event to process. + */ + public void onInjectedMotionEvent(MotionEvent event) { + final int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: { + handleInjectedPointerDown(0, event); + } break; + case MotionEvent.ACTION_POINTER_DOWN: { + handleInjectedPointerDown(event.getActionIndex(), event); + } break; + case MotionEvent.ACTION_UP: { + handleInjectedPointerUp(0, event); + } break; + case MotionEvent.ACTION_POINTER_UP: { + handleInjectedPointerUp(event.getActionIndex(), event); + } break; + case MotionEvent.ACTION_HOVER_ENTER: + case MotionEvent.ACTION_HOVER_MOVE: + case MotionEvent.ACTION_HOVER_EXIT: { + mLastInjectedHoverEventAction = event.getActionMasked(); + } break; + } + if (DEBUG) { + Slog.i(LOG_TAG, "Injected pointer: " + toString()); + } + } + + /** + * @return The number of received pointers that are down. + */ + public int getReceivedPointerDownCount() { + return Integer.bitCount(mReceivedPointersDown); + } + + /** + * @return The number of down input pointers that are active. + */ + public int getActivePointerCount() { + return Integer.bitCount(mActivePointers); + } + + /** + * Whether an received pointer is down. + * + * @param pointerId The unique pointer id. + * @return True if the pointer is down. + */ + public boolean isReceivedPointerDown(int pointerId) { + final int pointerFlag = (1 << pointerId); + return (mReceivedPointersDown & pointerFlag) != 0; + } + + /** + * Whether an injected pointer is down. + * + * @param pointerId The unique pointer id. + * @return True if the pointer is down. + */ + public boolean isInjectedPointerDown(int pointerId) { + final int pointerFlag = (1 << pointerId); + return (mInjectedPointersDown & pointerFlag) != 0; + } + + /** + * @return The number of down pointers injected to the view hierarchy. + */ + public int getInjectedPointerDownCount() { + return Integer.bitCount(mInjectedPointersDown); + } + + /** + * Whether an input pointer is active. + * + * @param pointerId The unique pointer id. + * @return True if the pointer is active. + */ + public boolean isActivePointer(int pointerId) { + final int pointerFlag = (1 << pointerId); + return (mActivePointers & pointerFlag) != 0; + } + + /** + * @param pointerId The unique pointer id. + * @return The X coordinate where the pointer went down. + */ + public float getReceivedPointerDownX(int pointerId) { + return mReceivedPointerDownX[pointerId]; + } + + /** + * @param pointerId The unique pointer id. + * @return The Y coordinate where the pointer went down. + */ + public float getReceivedPointerDownY(int pointerId) { + return mReceivedPointerDownY[pointerId]; + } + + /** + * @param pointerId The unique pointer id. + * @return The time when the pointer went down. + */ + public long getReceivedPointerDownTime(int pointerId) { + return mReceivedPointerDownTime[pointerId]; + } + + /** + * @return The id of the primary pointer. + */ + public int getPrimaryActivePointerId() { + if (mPrimaryActivePointerId == INVALID_POINTER_ID) { + mPrimaryActivePointerId = findPrimaryActivePointer(); + } + return mPrimaryActivePointerId; + } + + /** + * @return The X coordinate where the last up received pointer went down. + */ + public float getLastReceivedUpPointerDownX() { + return mLastReceivedUpPointerDownX; + } + + /** + * @return The Y coordinate where the last up received pointer went down. + */ + public float getLastReceivedUpPointerDownY() { + return mLastReveivedUpPointerDownY; + } + + /** + * @return The time when the last up received pointer went down. + */ + public long getLastReceivedUpPointerDownTime() { + return mLastReceivedUpPointerDownTime; + } + + /** + * @return The id of the last received pointer that went up. + */ + public int getLastReceivedUpPointerId() { + return mLastReceivedUpPointerId; + } + + /** + * @return Whether the last received pointer that went up was active. + */ + public boolean wasLastReceivedUpPointerActive() { + return mLastReceivedUpPointerActive; + } + + /** + * @return The time of the last injected down event. + */ + public long getLastInjectedDownEventTime() { + return mLastInjectedDownEventTime; + } + + /** + * @return The action of the last injected hover event. + */ + public int getLastInjectedHoverAction() { + return mLastInjectedHoverEventAction; + } + + /** + * Populates the active pointer IDs to the given array. + * <p> + * Note: The client is responsible for providing large enough array. + * + * @param outPointerIds The array to which to write the active pointers. + */ + public void populateActivePointerIds(int[] outPointerIds) { + int index = 0; + for (int idBits = mActivePointers; idBits != 0; ) { + final int id = Integer.numberOfTrailingZeros(idBits); + idBits &= ~(1 << id); + outPointerIds[index] = id; + index++; + } + } + + /** + * @return The number of non injected active pointers. + */ + public int getNotInjectedActivePointerCount() { + final int pointerState = mActivePointers & ~mInjectedPointersDown; + return Integer.bitCount(pointerState); + } + + /** + * @param pointerId The unique pointer id. + * @return Whether the pointer is active or was the last active than went up. + */ + private boolean isActiveOrWasLastActiveUpPointer(int pointerId) { + return (isActivePointer(pointerId) + || (mLastReceivedUpPointerId == pointerId + && mLastReceivedUpPointerActive)); + } + + /** + * Handles a received pointer down event. + * + * @param pointerIndex The index of the pointer that has changed. + * @param event The event to be handled. + */ + private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) { + final int pointerId = event.getPointerId(pointerIndex); + final int pointerFlag = (1 << pointerId); + + mLastReceivedUpPointerId = 0; + mLastReceivedUpPointerDownX = 0; + mLastReveivedUpPointerDownY = 0; + mLastReceivedUpPointerDownTime = 0; + mLastReceivedUpPointerActive = false; + + mReceivedPointersDown |= pointerFlag; + mReceivedPointerDownX[pointerId] = event.getX(pointerIndex); + mReceivedPointerDownY[pointerId] = event.getY(pointerIndex); + mReceivedPointerDownTime[pointerId] = event.getEventTime(); + + if (!mHasMovingActivePointer) { + // If still no moving active pointers every + // down pointer is the only active one. + mActivePointers = pointerFlag; + mPrimaryActivePointerId = pointerId; + } else { + // If at least one moving active pointer every + // subsequent down pointer is active. + mActivePointers |= pointerFlag; + } + } + + /** + * Handles a received pointer move event. + * + * @param event The event to be handled. + */ + private void handleReceivedPointerMove(MotionEvent event) { + detectActivePointers(event); + } + + /** + * Handles a received pointer up event. + * + * @param pointerIndex The index of the pointer that has changed. + * @param event The event to be handled. + */ + private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) { + final int pointerId = event.getPointerId(pointerIndex); + final int pointerFlag = (1 << pointerId); + + mLastReceivedUpPointerId = pointerId; + mLastReceivedUpPointerDownX = getReceivedPointerDownX(pointerId); + mLastReveivedUpPointerDownY = getReceivedPointerDownY(pointerId); + mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId); + mLastReceivedUpPointerActive = isActivePointer(pointerId); + + mReceivedPointersDown &= ~pointerFlag; + mActivePointers &= ~pointerFlag; + mReceivedPointerDownX[pointerId] = 0; + mReceivedPointerDownY[pointerId] = 0; + mReceivedPointerDownTime[pointerId] = 0; + + if (mActivePointers == 0) { + mHasMovingActivePointer = false; + } + if (mPrimaryActivePointerId == pointerId) { + mPrimaryActivePointerId = INVALID_POINTER_ID; + } + } + + /** + * Handles a injected pointer down event. + * + * @param pointerIndex The index of the pointer that has changed. + * @param event The event to be handled. + */ + private void handleInjectedPointerDown(int pointerIndex, MotionEvent event) { + final int pointerId = event.getPointerId(pointerIndex); + final int pointerFlag = (1 << pointerId); + mInjectedPointersDown |= pointerFlag; + mLastInjectedDownEventTime = event.getEventTime(); + } + + /** + * Handles a injected pointer up event. + * + * @param pointerIndex The index of the pointer that has changed. + * @param event The event to be handled. + */ + private void handleInjectedPointerUp(int pointerIndex, MotionEvent event) { + final int pointerId = event.getPointerId(pointerIndex); + final int pointerFlag = (1 << pointerId); + mInjectedPointersDown &= ~pointerFlag; + } + + /** + * Detects the active pointers in an event. + * + * @param event The event to examine. + */ + private void detectActivePointers(MotionEvent event) { + for (int i = 0, count = event.getPointerCount(); i < count; i++) { + final int pointerId = event.getPointerId(i); + if (mHasMovingActivePointer) { + // If already active => nothing to do. + if (isActivePointer(pointerId)) { + continue; + } + } + // Active pointers are ones that moved more than a given threshold. + final float pointerDeltaMove = computePointerDeltaMove(i, event); + if (pointerDeltaMove > mThresholdActivePointer) { + final int pointerFlag = (1 << pointerId); + mActivePointers |= pointerFlag; + mHasMovingActivePointer = true; + } + } + } + + /** + * @return The primary active pointer. + */ + private int findPrimaryActivePointer() { + int primaryActivePointerId = INVALID_POINTER_ID; + long minDownTime = Long.MAX_VALUE; + // Find the active pointer that went down first. + for (int i = 0, count = mReceivedPointerDownTime.length; i < count; i++) { + if (isActivePointer(i)) { + final long downPointerTime = mReceivedPointerDownTime[i]; + if (downPointerTime < minDownTime) { + minDownTime = downPointerTime; + primaryActivePointerId = i; + } + } + } + return primaryActivePointerId; + } + + /** + * Computes the move for a given action pointer index since the + * corresponding pointer went down. + * + * @param pointerIndex The action pointer index. + * @param event The event to examine. + * @return The distance the pointer has moved. + */ + private float computePointerDeltaMove(int pointerIndex, MotionEvent event) { + final int pointerId = event.getPointerId(pointerIndex); + final float deltaX = event.getX(pointerIndex) - mReceivedPointerDownX[pointerId]; + final float deltaY = event.getY(pointerIndex) - mReceivedPointerDownY[pointerId]; + return (float) Math.hypot(deltaX, deltaY); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("========================="); + builder.append("\nDown pointers #"); + builder.append(getReceivedPointerDownCount()); + builder.append(" [ "); + for (int i = 0; i < MAX_POINTER_COUNT; i++) { + if (isReceivedPointerDown(i)) { + builder.append(i); + builder.append(" "); + } + } + builder.append("]"); + builder.append("\nActive pointers #"); + builder.append(getActivePointerCount()); + builder.append(" [ "); + for (int i = 0; i < MAX_POINTER_COUNT; i++) { + if (isActivePointer(i)) { + builder.append(i); + builder.append(" "); + } + } + builder.append("]"); + builder.append("\nPrimary active pointer id [ "); + builder.append(getPrimaryActivePointerId()); + builder.append(" ]"); + builder.append("\n========================="); + return builder.toString(); + } + } + + /** + * Class for delayed sending of hover events. + */ + private final class SendHoverDelayed implements Runnable { + private static final String LOG_TAG = "SendHoverEnterOrExitDelayed"; + + private MotionEvent mEvent; + private int mAction; + private int mPointerIndex; + private int mPolicyFlags; + + public void post(MotionEvent prototype, int action, int pointerIndex, int policyFlags, + long delay) { + remove(); + mEvent = MotionEvent.obtain(prototype); + mAction = action; + mPointerIndex = pointerIndex; + mPolicyFlags = policyFlags; + mHandler.postDelayed(this, delay); + } + + public void remove() { + mHandler.removeCallbacks(this); + clear(); + } + + private boolean isPenidng() { + return (mEvent != null); + } + + private void clear() { + if (!isPenidng()) { + return; + } + mEvent.recycle(); + mEvent = null; + mAction = 0; + mPointerIndex = -1; + mPolicyFlags = 0; + } + + public void forceSendAndRemove() { + if (isPenidng()) { + run(); + remove(); + } + } + + public void run() { + if (DEBUG) { + if (mAction == MotionEvent.ACTION_HOVER_ENTER) { + Slog.d(LOG_TAG, "Injecting: " + MotionEvent.ACTION_HOVER_ENTER); + } else if (mAction == MotionEvent.ACTION_HOVER_MOVE) { + Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_MOVE"); + } else if (mAction == MotionEvent.ACTION_HOVER_EXIT) { + Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_EXIT"); + } + } + + sendHoverEvent(mEvent, mAction, mPointerIndex, mPolicyFlags); + clear(); + } + } +} diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index edc5bc0..f6ae523 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -44,6 +44,7 @@ import android.app.IInstrumentationWatcher; import android.app.INotificationManager; import android.app.IServiceConnection; import android.app.IThumbnailReceiver; +import android.app.IThumbnailRetriever; import android.app.Instrumentation; import android.app.Notification; import android.app.NotificationManager; @@ -90,6 +91,7 @@ import android.os.FileObserver; import android.os.FileUtils; import android.os.Handler; import android.os.IBinder; +import android.os.IInterface; import android.os.IPermissionController; import android.os.Looper; import android.os.Message; @@ -103,7 +105,6 @@ import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; -import android.util.Config; import android.util.EventLog; import android.util.Slog; import android.util.Log; @@ -163,7 +164,7 @@ public final class ActivityManagerService extends ActivityManagerNative static final boolean DEBUG_URI_PERMISSION = localLOGV || false; static final boolean DEBUG_USER_LEAVING = localLOGV || false; static final boolean DEBUG_RESULTS = localLOGV || false; - static final boolean DEBUG_BACKUP = localLOGV || false; + static final boolean DEBUG_BACKUP = localLOGV || true; static final boolean DEBUG_CONFIGURATION = localLOGV || false; static final boolean DEBUG_POWER = localLOGV || false; static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false; @@ -441,8 +442,7 @@ public final class ActivityManagerService extends ActivityManagerNative /** * List of intents that were used to start the most recent tasks. */ - final ArrayList<TaskRecord> mRecentTasks - = new ArrayList<TaskRecord>(); + final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>(); /** * All of the applications we currently have running organized by name. @@ -450,8 +450,7 @@ public final class ActivityManagerService extends ActivityManagerNative * returned by the package manager), and the keys are ApplicationRecord * objects. */ - final ProcessMap<ProcessRecord> mProcessNames - = new ProcessMap<ProcessRecord>(); + final ProcessMap<ProcessRecord> mProcessNames = new ProcessMap<ProcessRecord>(); /** * The currently running heavy-weight process, if any. @@ -480,8 +479,7 @@ public final class ActivityManagerService extends ActivityManagerNative * <p>NOTE: This object is protected by its own lock, NOT the global * activity manager lock! */ - final SparseArray<ProcessRecord> mPidsSelfLocked - = new SparseArray<ProcessRecord>(); + final SparseArray<ProcessRecord> mPidsSelfLocked = new SparseArray<ProcessRecord>(); /** * All of the processes that have been forced to be foreground. The key @@ -1283,6 +1281,7 @@ public final class ActivityManagerService extends ActivityManagerNative ServiceManager.addService("activity", m); ServiceManager.addService("meminfo", new MemBinder(m)); + ServiceManager.addService("gfxinfo", new GraphicsBinder(m)); if (MONITOR_CPU_USAGE) { ServiceManager.addService("cpuinfo", new CpuBinder(m)); } @@ -1404,35 +1403,19 @@ public final class ActivityManagerService extends ActivityManagerNative @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - ActivityManagerService service = mActivityManagerService; - ArrayList<ProcessRecord> procs; - synchronized (mActivityManagerService) { - if (args != null && args.length > 0 - && args[0].charAt(0) != '-') { - procs = new ArrayList<ProcessRecord>(); - int pid = -1; - try { - pid = Integer.parseInt(args[0]); - } catch (NumberFormatException e) { - - } - for (int i=service.mLruProcesses.size()-1; i>=0; i--) { - ProcessRecord proc = service.mLruProcesses.get(i); - if (proc.pid == pid) { - procs.add(proc); - } else if (proc.processName.equals(args[0])) { - procs.add(proc); - } - } - if (procs.size() <= 0) { - pw.println("No process found for: " + args[0]); - return; - } - } else { - procs = new ArrayList<ProcessRecord>(service.mLruProcesses); - } - } - dumpApplicationMemoryUsage(fd, pw, procs, " ", args); + mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, " ", args); + } + } + + static class GraphicsBinder extends Binder { + ActivityManagerService mActivityManagerService; + GraphicsBinder(ActivityManagerService activityManagerService) { + mActivityManagerService = activityManagerService; + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mActivityManagerService.dumpGraphicsHardwareUsage(fd, pw, args); } } @@ -2654,8 +2637,7 @@ public final class ActivityManagerService extends ActivityManagerNative + " finishing=" + r.finishing); r.makeFinishing(); mMainStack.mHistory.remove(i); - - r.inHistory = false; + r.takeFromHistory(); mWindowManager.removeAppToken(r); if (VALIDATE_TOKENS) { mWindowManager.validateAppTokens(mMainStack.mHistory); @@ -3033,7 +3015,10 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) { - Process.killProcess(app.pid); + Slog.w(TAG, "Killing " + app + ": background ANR"); + EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, + app.processName, app.setAdj, "background ANR"); + Process.killProcessQuiet(app.pid); return; } @@ -3285,7 +3270,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (callerUid == Process.SYSTEM_UID) { synchronized (this) { ProcessRecord app = getProcessRecordLocked(processName, uid); - if (app != null) { + if (app != null && app.thread != null) { try { app.thread.scheduleSuicide(); } catch (RemoteException e) { @@ -3498,7 +3483,9 @@ public final class ActivityManagerService extends ActivityManagerNative bringDownServiceLocked(sr, true); } } - Process.killProcess(pid); + EventLog.writeEvent(EventLogTags.AM_KILL, pid, + app.processName, app.setAdj, "start timeout"); + Process.killProcessQuiet(pid); if (mBackupTarget != null && mBackupTarget.app.pid == pid) { Slog.w(TAG, "Unattached app died before backup, skipping"); try { @@ -3544,7 +3531,7 @@ public final class ActivityManagerService extends ActivityManagerNative + " (IApplicationThread " + thread + "); dropping process"); EventLog.writeEvent(EventLogTags.AM_DROP_PROCESS, pid); if (pid > 0 && pid != MY_PID) { - Process.killProcess(pid); + Process.killProcessQuiet(pid); } else { try { thread.scheduleExit(); @@ -3863,16 +3850,7 @@ public final class ActivityManagerService extends ActivityManagerNative r = (ActivityRecord)mMainStack.mHistory.get(index); r.icicle = icicle; r.haveState = true; - if (thumbnail != null) { - r.thumbnail = thumbnail; - if (r.task != null) { - r.task.lastThumbnail = r.thumbnail; - } - } - r.description = description; - if (r.task != null) { - r.task.lastDescription = r.description; - } + r.updateThumbnail(thumbnail, description); r.stopped = true; r.state = ActivityState.STOPPED; if (!r.finishing) { @@ -4896,16 +4874,10 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } - final boolean canReadFb = (flags&ActivityManager.TASKS_GET_THUMBNAILS) != 0 - && checkCallingPermission( - android.Manifest.permission.READ_FRAME_BUFFER) - == PackageManager.PERMISSION_GRANTED; - int pos = mMainStack.mHistory.size()-1; ActivityRecord next = pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null; ActivityRecord top = null; - CharSequence topDescription = null; TaskRecord curTask = null; int numActivities = 0; int numRunning = 0; @@ -4919,7 +4891,6 @@ public final class ActivityManagerService extends ActivityManagerNative (top.state == ActivityState.INITIALIZING && top.task == r.task)) { top = r; - topDescription = r.description; curTask = r.task; numActivities = numRunning = 0; } @@ -4929,9 +4900,6 @@ public final class ActivityManagerService extends ActivityManagerNative if (r.app != null && r.app.thread != null) { numRunning++; } - if (topDescription == null) { - topDescription = r.description; - } if (localLOGV) Slog.v( TAG, r.intent.getComponent().flattenToShortString() @@ -4945,14 +4913,9 @@ public final class ActivityManagerService extends ActivityManagerNative ci.id = curTask.taskId; ci.baseActivity = r.intent.getComponent(); ci.topActivity = top.intent.getComponent(); - if (canReadFb) { - if (top.thumbnail != null) { - ci.thumbnail = top.thumbnail; - } else if (top.state == ActivityState.RESUMED) { - ci.thumbnail = top.stack.screenshotActivities(top); - } + if (top.thumbHolder != null) { + ci.description = top.thumbHolder.lastDescription; } - ci.description = topDescription; ci.numActivities = numActivities; ci.numRunning = numRunning; //System.out.println( @@ -5022,8 +4985,6 @@ public final class ActivityManagerService extends ActivityManagerNative IPackageManager pm = AppGlobals.getPackageManager(); - ActivityRecord resumed = mMainStack.mResumedActivity; - final int N = mRecentTasks.size(); ArrayList<ActivityManager.RecentTaskInfo> res = new ArrayList<ActivityManager.RecentTaskInfo>( @@ -5069,24 +5030,121 @@ public final class ActivityManagerService extends ActivityManagerNative } } - public Bitmap getTaskThumbnail(int id) { + private TaskRecord taskForIdLocked(int id) { + final int N = mRecentTasks.size(); + for (int i=0; i<N; i++) { + TaskRecord tr = mRecentTasks.get(i); + if (tr.taskId == id) { + return tr; + } + } + return null; + } + + public ActivityManager.TaskThumbnails getTaskThumbnails(int id) { synchronized (this) { enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER, - "getTaskThumbnail()"); - ActivityRecord resumed = mMainStack.mResumedActivity; - final int N = mRecentTasks.size(); - for (int i=0; i<N; i++) { - TaskRecord tr = mRecentTasks.get(i); - if (tr.taskId == id) { - if (resumed != null && resumed.task == tr) { - return resumed.stack.screenshotActivities(resumed); - } else { - return tr.lastThumbnail; + "getTaskThumbnails()"); + TaskRecord tr = taskForIdLocked(id); + if (tr != null) { + return mMainStack.getTaskThumbnailsLocked(tr); + } + } + return null; + } + + public boolean removeSubTask(int taskId, int subTaskIndex) { + synchronized (this) { + enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS, + "removeSubTask()"); + long ident = Binder.clearCallingIdentity(); + try { + return mMainStack.removeTaskActivitiesLocked(taskId, subTaskIndex) != null; + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + private void removeTaskProcessesLocked(ActivityRecord root) { + TaskRecord tr = root.task; + Intent baseIntent = new Intent( + tr.intent != null ? tr.intent : tr.affinityIntent); + ComponentName component = baseIntent.getComponent(); + if (component == null) { + Slog.w(TAG, "Now component for base intent of task: " + tr); + return; + } + + // Find any running services associated with this app. + ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); + for (ServiceRecord sr : mServices.values()) { + if (sr.packageName.equals(component.getPackageName())) { + services.add(sr); + } + } + + // Take care of any running services associated with the app. + for (int i=0; i<services.size(); i++) { + ServiceRecord sr = services.get(i); + if (sr.startRequested) { + if ((sr.serviceInfo.flags&ServiceInfo.FLAG_STOP_WITH_TASK) != 0) { + stopServiceLocked(sr); + } else { + sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true, + sr.makeNextStartId(), baseIntent, -1)); + if (sr.app != null && sr.app.thread != null) { + sendServiceArgsLocked(sr, false); } } } } - return null; + + // Find any running processes associated with this app. + ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>(); + SparseArray<ProcessRecord> appProcs + = mProcessNames.getMap().get(component.getPackageName()); + if (appProcs != null) { + for (int i=0; i<appProcs.size(); i++) { + procs.add(appProcs.valueAt(i)); + } + } + + // Kill the running processes. + for (int i=0; i<procs.size(); i++) { + ProcessRecord pr = procs.get(i); + if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) { + Slog.i(TAG, "Killing " + pr + ": remove task"); + EventLog.writeEvent(EventLogTags.AM_KILL, pr.pid, + pr.processName, pr.setAdj, "remove task"); + Process.killProcessQuiet(pr.pid); + } else { + pr.waitingToKill = "remove task"; + } + } + } + + public boolean removeTask(int taskId, int flags) { + synchronized (this) { + enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS, + "removeTask()"); + long ident = Binder.clearCallingIdentity(); + try { + ActivityRecord r = mMainStack.removeTaskActivitiesLocked(taskId, -1); + if (r != null) { + mRecentTasks.remove(r.task); + + if ((flags&ActivityManager.REMOVE_TASK_KILL_PROCESS) != 0) { + removeTaskProcessesLocked(r); + } + + return true; + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + return false; } private final int findAffinityTaskTopLocked(int startIndex, String affinity) { @@ -5140,21 +5198,18 @@ public final class ActivityManagerService extends ActivityManagerNative } final long origId = Binder.clearCallingIdentity(); try { - int N = mRecentTasks.size(); - for (int i=0; i<N; i++) { - TaskRecord tr = mRecentTasks.get(i); - if (tr.taskId == task) { - if ((flags&ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) { - mMainStack.mUserLeaving = true; - } - if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) { - // Caller wants the home activity moved with it. To accomplish this, - // we'll just move the home task to the top first. - mMainStack.moveHomeToFrontLocked(); - } - mMainStack.moveTaskToFrontLocked(tr, null); - return; + TaskRecord tr = taskForIdLocked(task); + if (tr != null) { + if ((flags&ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) { + mMainStack.mUserLeaving = true; + } + if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) { + // Caller wants the home activity moved with it. To accomplish this, + // we'll just move the home task to the top first. + mMainStack.moveHomeToFrontLocked(); } + mMainStack.moveTaskToFrontLocked(tr, null); + return; } for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { ActivityRecord hr = (ActivityRecord)mMainStack.mHistory.get(i); @@ -5308,9 +5363,9 @@ public final class ActivityManagerService extends ActivityManagerNative } r = (ActivityRecord)mMainStack.mHistory.get(index); } - if (thumbnail == null) { - thumbnail = r.thumbnail; - description = r.description; + if (thumbnail == null && r.thumbHolder != null) { + thumbnail = r.thumbHolder.lastThumbnail; + description = r.thumbHolder.lastDescription; } if (thumbnail == null && !always) { // If there is no thumbnail, and this entry is not actually @@ -6678,11 +6733,10 @@ public final class ActivityManagerService extends ActivityManagerNative } if (app.pid > 0 && app.pid != MY_PID) { handleAppCrashLocked(app); - Slog.i(ActivityManagerService.TAG, "Killing " - + app.processName + " (pid=" + app.pid + "): user's request"); + Slog.i(ActivityManagerService.TAG, "Killing " + app + ": user's request"); EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, app.processName, app.setAdj, "user's request after error"); - Process.killProcess(app.pid); + Process.killProcessQuiet(app.pid); } } } @@ -7502,6 +7556,7 @@ public final class ActivityManagerService extends ActivityManagerNative } boolean dumpAll = false; + boolean dumpClient = false; int opti = 0; while (opti < args.length) { @@ -7512,9 +7567,11 @@ public final class ActivityManagerService extends ActivityManagerNative opti++; if ("-a".equals(opt)) { dumpAll = true; + } else if ("-c".equals(opt)) { + dumpClient = true; } else if ("-h".equals(opt)) { pw.println("Activity manager dump options:"); - pw.println(" [-a] [-h] [cmd] ..."); + pw.println(" [-a] [-c] [-h] [cmd] ..."); pw.println(" cmd may be one of:"); pw.println(" a[ctivities]: activity stack state"); pw.println(" b[roadcasts]: broadcast state"); @@ -7523,10 +7580,14 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" o[om]: out of memory management"); pw.println(" prov[iders]: content provider state"); pw.println(" s[ervices]: service state"); - pw.println(" service [name]: service client-side state"); - pw.println(" cmd may also be a component name (com.foo/.myApp),"); - pw.println(" a partial substring in a component name, or an"); - pw.println(" ActivityRecord hex object identifier."); + pw.println(" service [COMP_SPEC]: service client-side state"); + pw.println(" cmd may also be a COMP_SPEC to dump activities."); + pw.println(" COMP_SPEC may also be a component name (com.foo/.myApp),"); + pw.println(" a partial substring in a component name, an"); + pw.println(" ActivityRecord hex object identifier, or"); + pw.println(" \"all\" for all objects"); + pw.println(" -a: include all available server state."); + pw.println(" -c: include client state."); return; } else { pw.println("Unknown argument: " + opt + "; use -h for help"); @@ -7539,7 +7600,7 @@ public final class ActivityManagerService extends ActivityManagerNative opti++; if ("activities".equals(cmd) || "a".equals(cmd)) { synchronized (this) { - dumpActivitiesLocked(fd, pw, args, opti, true, true); + dumpActivitiesLocked(fd, pw, args, opti, true, dumpClient); } return; } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) { @@ -7568,20 +7629,33 @@ public final class ActivityManagerService extends ActivityManagerNative } return; } else if ("service".equals(cmd)) { - dumpService(fd, pw, args, opti); + 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 (!dumpService(fd, pw, name, newArgs, 0, dumpAll)) { + pw.println("No services match: " + name); + pw.println("Use -h for help."); + } return; } else if ("services".equals(cmd) || "s".equals(cmd)) { synchronized (this) { - dumpServicesLocked(fd, pw, args, opti, true); + dumpServicesLocked(fd, pw, args, opti, true, dumpClient); } return; } else { // Dumping a single activity? - if (dumpActivity(fd, pw, cmd, args, opti, dumpAll)) { - return; + if (!dumpActivity(fd, pw, cmd, args, opti, dumpAll)) { + pw.println("Bad activity command, or no activities match: " + cmd); + pw.println("Use -h for help."); } - pw.println("Bad activity command, or no activities match: " + cmd); - pw.println("Use -h for help."); return; } } @@ -7589,16 +7663,12 @@ public final class ActivityManagerService extends ActivityManagerNative // No piece of data specified, dump everything. synchronized (this) { boolean needSep; - if (dumpAll) { - pw.println("Providers in Current Activity Manager State:"); - } - needSep = dumpProvidersLocked(fd, pw, args, opti, dumpAll); + needSep = dumpPendingIntentsLocked(fd, pw, args, opti, dumpAll); if (needSep) { pw.println(" "); } if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); - pw.println("Broadcasts in Current Activity Manager State:"); } needSep = dumpBroadcastsLocked(fd, pw, args, opti, dumpAll); if (needSep) { @@ -7606,88 +7676,95 @@ public final class ActivityManagerService extends ActivityManagerNative } if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); - pw.println("Services in Current Activity Manager State:"); } - needSep = dumpServicesLocked(fd, pw, args, opti, dumpAll); + needSep = dumpProvidersLocked(fd, pw, args, opti, dumpAll); if (needSep) { pw.println(" "); } if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); - pw.println("PendingIntents in Current Activity Manager State:"); } - needSep = dumpPendingIntentsLocked(fd, pw, args, opti, dumpAll); + needSep = dumpServicesLocked(fd, pw, args, opti, dumpAll, dumpClient); if (needSep) { pw.println(" "); } if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); - pw.println("Activities in Current Activity Manager State:"); } - needSep = dumpActivitiesLocked(fd, pw, args, opti, dumpAll, !dumpAll); + needSep = dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient); if (needSep) { pw.println(" "); } if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); - pw.println("Processes in Current Activity Manager State:"); } dumpProcessesLocked(fd, pw, args, opti, dumpAll); } } boolean dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args, - int opti, boolean dumpAll, boolean needHeader) { - if (needHeader) { - pw.println(" Activity stack:"); - } - dumpHistoryList(pw, mMainStack.mHistory, " ", "Hist", true); + int opti, boolean dumpAll, boolean dumpClient) { + pw.println("ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)"); + pw.println(" Main stack:"); + dumpHistoryList(fd, pw, mMainStack.mHistory, " ", "Hist", true, !dumpAll, dumpClient); pw.println(" "); pw.println(" Running activities (most recent first):"); - dumpHistoryList(pw, mMainStack.mLRUActivities, " ", "Run", false); + dumpHistoryList(fd, pw, mMainStack.mLRUActivities, " ", "Run", false, !dumpAll, false); if (mMainStack.mWaitingVisibleActivities.size() > 0) { pw.println(" "); pw.println(" Activities waiting for another to become visible:"); - dumpHistoryList(pw, mMainStack.mWaitingVisibleActivities, " ", "Wait", false); + dumpHistoryList(fd, pw, mMainStack.mWaitingVisibleActivities, " ", "Wait", false, + !dumpAll, false); } if (mMainStack.mStoppingActivities.size() > 0) { pw.println(" "); pw.println(" Activities waiting to stop:"); - dumpHistoryList(pw, mMainStack.mStoppingActivities, " ", "Stop", false); + dumpHistoryList(fd, pw, mMainStack.mStoppingActivities, " ", "Stop", false, + !dumpAll, false); } if (mMainStack.mGoingToSleepActivities.size() > 0) { pw.println(" "); pw.println(" Activities waiting to sleep:"); - dumpHistoryList(pw, mMainStack.mGoingToSleepActivities, " ", "Sleep", false); + dumpHistoryList(fd, pw, mMainStack.mGoingToSleepActivities, " ", "Sleep", false, + !dumpAll, false); } if (mMainStack.mFinishingActivities.size() > 0) { pw.println(" "); pw.println(" Activities waiting to finish:"); - dumpHistoryList(pw, mMainStack.mFinishingActivities, " ", "Fin", false); + dumpHistoryList(fd, pw, mMainStack.mFinishingActivities, " ", "Fin", false, + !dumpAll, false); } pw.println(" "); - pw.println(" mPausingActivity: " + mMainStack.mPausingActivity); + if (mMainStack.mPausingActivity != null) { + pw.println(" mPausingActivity: " + mMainStack.mPausingActivity); + } pw.println(" mResumedActivity: " + mMainStack.mResumedActivity); pw.println(" mFocusedActivity: " + mFocusedActivity); - pw.println(" mLastPausedActivity: " + mMainStack.mLastPausedActivity); - pw.println(" mSleepTimeout: " + mMainStack.mSleepTimeout); + if (dumpAll) { + pw.println(" mLastPausedActivity: " + mMainStack.mLastPausedActivity); + pw.println(" mSleepTimeout: " + mMainStack.mSleepTimeout); + } - if (dumpAll && mRecentTasks.size() > 0) { - pw.println(" "); - pw.println("Recent tasks in Current Activity Manager State:"); + if (mRecentTasks.size() > 0) { + pw.println(); + pw.println(" Recent tasks:"); final int N = mRecentTasks.size(); for (int i=0; i<N; i++) { TaskRecord tr = mRecentTasks.get(i); pw.print(" * Recent #"); pw.print(i); pw.print(": "); pw.println(tr); - mRecentTasks.get(i).dump(pw, " "); + if (dumpAll) { + mRecentTasks.get(i).dump(pw, " "); + } } } - pw.println(" "); - pw.println(" mCurTask: " + mCurTask); + if (dumpAll) { + pw.println(" "); + pw.println(" mCurTask: " + mCurTask); + } return true; } @@ -7697,6 +7774,8 @@ public final class ActivityManagerService extends ActivityManagerNative boolean needSep = false; int numPers = 0; + pw.println("ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)"); + if (dumpAll) { for (SparseArray<ProcessRecord> procs : mProcessNames.getMap().values()) { final int NA = procs.size(); @@ -7720,20 +7799,22 @@ public final class ActivityManagerService extends ActivityManagerNative if (mLruProcesses.size() > 0) { if (needSep) pw.println(" "); needSep = true; - pw.println(" Running processes (most recent first):"); + pw.println(" Process LRU list (most recent first):"); dumpProcessOomList(pw, this, mLruProcesses, " ", "Proc", "PERS", false); needSep = true; } - synchronized (mPidsSelfLocked) { - if (mPidsSelfLocked.size() > 0) { - if (needSep) pw.println(" "); - needSep = true; - pw.println(" PID mappings:"); - for (int i=0; i<mPidsSelfLocked.size(); i++) { - pw.print(" PID #"); pw.print(mPidsSelfLocked.keyAt(i)); - pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i)); + if (dumpAll) { + synchronized (mPidsSelfLocked) { + if (mPidsSelfLocked.size() > 0) { + if (needSep) pw.println(" "); + needSep = true; + pw.println(" PID mappings:"); + for (int i=0; i<mPidsSelfLocked.size(); i++) { + pw.print(" PID #"); pw.print(mPidsSelfLocked.keyAt(i)); + pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i)); + } } } } @@ -7818,16 +7899,18 @@ public final class ActivityManagerService extends ActivityManagerNative } } - pw.println(" "); + pw.println(); pw.println(" mHomeProcess: " + mHomeProcess); if (mHeavyWeightProcess != null) { pw.println(" mHeavyWeightProcess: " + mHeavyWeightProcess); } pw.println(" mConfiguration: " + mConfiguration); - pw.println(" mConfigWillChange: " + mMainStack.mConfigWillChange); - if (mCompatModePackages.getPackages().size() > 0) { - pw.print(" mScreenCompatPackages="); - pw.println(mCompatModePackages.getPackages()); + if (dumpAll) { + pw.println(" mConfigWillChange: " + mMainStack.mConfigWillChange); + if (mCompatModePackages.getPackages().size() > 0) { + pw.print(" mScreenCompatPackages="); + pw.println(mCompatModePackages.getPackages()); + } } pw.println(" mSleeping=" + mSleeping + " mShuttingDown=" + mShuttingDown); if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient @@ -7922,7 +8005,7 @@ public final class ActivityManagerService extends ActivityManagerNative needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll); - pw.println(" "); + pw.println(); pw.println(" mHomeProcess: " + mHomeProcess); if (mHeavyWeightProcess != null) { pw.println(" mHeavyWeightProcess: " + mHeavyWeightProcess); @@ -7939,61 +8022,96 @@ public final class ActivityManagerService extends ActivityManagerNative * - the first arg isn't the flattened component name of an existing service: * dump all services whose component contains the first arg as a substring */ - protected void dumpService(FileDescriptor fd, PrintWriter pw, String[] args, int opti) { - String[] newArgs; - String componentNameString; - ServiceRecord r; - if (opti >= args.length) { - componentNameString = null; - newArgs = EMPTY_STRING_ARRAY; - r = null; - } else { - componentNameString = args[opti]; - opti++; - ComponentName componentName = ComponentName.unflattenFromString(componentNameString); + protected boolean dumpService(FileDescriptor fd, PrintWriter pw, String name, String[] args, + int opti, boolean dumpAll) { + ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); + + if ("all".equals(name)) { synchronized (this) { - r = componentName != null ? mServices.get(componentName) : null; + for (ServiceRecord r1 : mServices.values()) { + services.add(r1); + } } - newArgs = new String[args.length - opti]; - if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti); - } - - if (r != null) { - dumpService(fd, pw, r, newArgs); } else { - ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); + 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 (ServiceRecord r1 : mServices.values()) { - if (componentNameString == null - || r1.name.flattenToString().contains(componentNameString)) { + 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); } } } - for (int i=0; i<services.size(); i++) { - dumpService(fd, pw, services.get(i), newArgs); + } + + if (services.size() <= 0) { + return false; + } + + boolean needSep = false; + for (int i=0; i<services.size(); i++) { + if (needSep) { + pw.println(); } + needSep = true; + dumpService("", fd, pw, services.get(i), args, dumpAll); } + return true; } /** * Invokes IApplicationThread.dumpService() on the thread of the specified service if * there is a thread associated with the service. */ - private void dumpService(FileDescriptor fd, PrintWriter pw, ServiceRecord r, String[] args) { - pw.println("------------------------------------------------------------" - + "-------------------"); - pw.println("APP SERVICE: " + r.name.flattenToString()); + private void dumpService(String prefix, FileDescriptor fd, PrintWriter pw, + final ServiceRecord r, String[] args, boolean dumpAll) { + String innerPrefix = prefix + " "; + synchronized (this) { + pw.print(prefix); pw.print("SERVICE "); + pw.print(r.shortName); pw.print(" "); + pw.print(Integer.toHexString(System.identityHashCode(r))); + pw.print(" pid="); + if (r.app != null) pw.println(r.app.pid); + else pw.println("(not running)"); + if (dumpAll) { + r.dump(pw, innerPrefix); + } + } if (r.app != null && r.app.thread != null) { + pw.print(prefix); pw.println(" Client:"); + pw.flush(); try { - // flush anything that is already in the PrintWriter since the thread is going - // to write to the file descriptor directly - pw.flush(); - r.app.thread.dumpService(fd, r, args); - pw.print("\n"); - pw.flush(); + TransferPipe tp = new TransferPipe(); + try { + r.app.thread.dumpService(tp.getWriteFd().getFileDescriptor(), r, args); + tp.setBufferPrefix(prefix + " "); + tp.go(fd); + } finally { + tp.kill(); + } + } catch (IOException e) { + pw.println(prefix + " Failure while dumping the service: " + e); } catch (RemoteException e) { - pw.println("got a RemoteException while dumping the service"); + pw.println(prefix + " Got a RemoteException while dumping the service"); } } } @@ -8007,34 +8125,40 @@ public final class ActivityManagerService extends ActivityManagerNative */ protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args, int opti, boolean dumpAll) { - String[] newArgs; - ComponentName componentName = ComponentName.unflattenFromString(name); - int objectId = 0; - if (componentName == null) { - // Not a '/' separated full component name; maybe an object ID? - try { - objectId = Integer.parseInt(name, 16); - name = null; - componentName = null; - } catch (RuntimeException e) { + ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>(); + + if ("all".equals(name)) { + synchronized (this) { + for (ActivityRecord r1 : (ArrayList<ActivityRecord>)mMainStack.mHistory) { + activities.add(r1); + } + } + } else { + ComponentName componentName = ComponentName.unflattenFromString(name); + int objectId = 0; + if (componentName == null) { + // Not a '/' separated full component name; maybe an object ID? + try { + objectId = Integer.parseInt(name, 16); + name = null; + componentName = null; + } catch (RuntimeException e) { + } } - } - newArgs = new String[args.length - opti]; - if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti); - ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>(); - synchronized (this) { - for (ActivityRecord r1 : (ArrayList<ActivityRecord>)mMainStack.mHistory) { - if (componentName != null) { - if (r1.intent.getComponent().equals(componentName)) { - activities.add(r1); - } - } else if (name != null) { - if (r1.intent.getComponent().flattenToString().contains(name)) { + synchronized (this) { + for (ActivityRecord r1 : (ArrayList<ActivityRecord>)mMainStack.mHistory) { + if (componentName != null) { + if (r1.intent.getComponent().equals(componentName)) { + activities.add(r1); + } + } else if (name != null) { + if (r1.intent.getComponent().flattenToString().contains(name)) { + activities.add(r1); + } + } else if (System.identityHashCode(r1) == objectId) { activities.add(r1); } - } else if (System.identityHashCode(r1) == objectId) { - activities.add(r1); } } } @@ -8043,15 +8167,25 @@ public final class ActivityManagerService extends ActivityManagerNative return false; } + String[] newArgs = new String[args.length - opti]; + if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti); + TaskRecord lastTask = null; + boolean needSep = false; for (int i=activities.size()-1; i>=0; i--) { ActivityRecord r = (ActivityRecord)activities.get(i); - if (lastTask != r.task) { - lastTask = r.task; - pw.print("* Task "); pw.print(lastTask.affinity); - pw.print(" id="); pw.println(lastTask.taskId); - if (dumpAll) { - lastTask.dump(pw, " "); + if (needSep) { + pw.println(); + } + needSep = true; + synchronized (this) { + if (lastTask != r.task) { + lastTask = r.task; + pw.print("TASK "); pw.print(lastTask.affinity); + pw.print(" id="); pw.println(lastTask.taskId); + if (dumpAll) { + lastTask.dump(pw, " "); + } } } dumpActivity(" ", fd, pw, activities.get(i), newArgs, dumpAll); @@ -8064,26 +8198,35 @@ public final class ActivityManagerService extends ActivityManagerNative * there is a thread associated with the activity. */ private void dumpActivity(String prefix, FileDescriptor fd, PrintWriter pw, - ActivityRecord r, String[] args, boolean dumpAll) { + final ActivityRecord r, String[] args, boolean dumpAll) { + String innerPrefix = prefix + " "; synchronized (this) { - pw.print(prefix); pw.print("* Activity "); - pw.print(Integer.toHexString(System.identityHashCode(r))); - pw.print(" "); pw.print(r.shortComponentName); pw.print(" pid="); + pw.print(prefix); pw.print("ACTIVITY "); pw.print(r.shortComponentName); + pw.print(" "); pw.print(Integer.toHexString(System.identityHashCode(r))); + pw.print(" pid="); if (r.app != null) pw.println(r.app.pid); else pw.println("(not running)"); if (dumpAll) { - r.dump(pw, prefix + " "); + r.dump(pw, innerPrefix); } } if (r.app != null && r.app.thread != null) { + // flush anything that is already in the PrintWriter since the thread is going + // to write to the file descriptor directly + pw.flush(); try { - // flush anything that is already in the PrintWriter since the thread is going - // to write to the file descriptor directly - pw.flush(); - r.app.thread.dumpActivity(fd, r, prefix + " ", args); - pw.flush(); + TransferPipe tp = new TransferPipe(); + try { + r.app.thread.dumpActivity(tp.getWriteFd().getFileDescriptor(), r, + innerPrefix, args); + tp.go(fd); + } finally { + tp.kill(); + } + } catch (IOException e) { + pw.println(innerPrefix + "Failure while dumping the activity: " + e); } catch (RemoteException e) { - pw.println("got a RemoteException while dumping the activity"); + pw.println(innerPrefix + "Got a RemoteException while dumping the activity"); } } } @@ -8092,9 +8235,9 @@ public final class ActivityManagerService extends ActivityManagerNative int opti, boolean dumpAll) { boolean needSep = false; + pw.println("ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)"); if (dumpAll) { if (mRegisteredReceivers.size() > 0) { - pw.println(" "); pw.println(" Registered Receivers:"); Iterator it = mRegisteredReceivers.values().iterator(); while (it.hasNext()) { @@ -8104,16 +8247,16 @@ public final class ActivityManagerService extends ActivityManagerNative } } - pw.println(" "); - pw.println("Receiver Resolver Table:"); - mReceiverResolver.dump(pw, null, " ", null, false); + pw.println(); + pw.println(" Receiver Resolver Table:"); + mReceiverResolver.dump(pw, null, " ", null, false); needSep = true; } if (mParallelBroadcasts.size() > 0 || mOrderedBroadcasts.size() > 0 || mPendingBroadcast != null) { if (mParallelBroadcasts.size() > 0) { - pw.println(" "); + pw.println(); pw.println(" Active broadcasts:"); } for (int i=mParallelBroadcasts.size()-1; i>=0; i--) { @@ -8121,14 +8264,14 @@ public final class ActivityManagerService extends ActivityManagerNative mParallelBroadcasts.get(i).dump(pw, " "); } if (mOrderedBroadcasts.size() > 0) { - pw.println(" "); + pw.println(); pw.println(" Active ordered broadcasts:"); } for (int i=mOrderedBroadcasts.size()-1; i>=0; i--) { pw.println(" Serialized Broadcast #" + i + ":"); mOrderedBroadcasts.get(i).dump(pw, " "); } - pw.println(" "); + pw.println(); pw.println(" Pending broadcast:"); if (mPendingBroadcast != null) { mPendingBroadcast.dump(pw, " "); @@ -8138,47 +8281,59 @@ public final class ActivityManagerService extends ActivityManagerNative needSep = true; } - if (dumpAll) { - pw.println(" "); - pw.println(" Historical broadcasts:"); - for (int i=0; i<MAX_BROADCAST_HISTORY; i++) { - BroadcastRecord r = mBroadcastHistory[i]; - if (r == null) { + if (needSep) { + pw.println(); + } + pw.println(" Historical broadcasts:"); + for (int i=0; i<MAX_BROADCAST_HISTORY; i++) { + BroadcastRecord r = mBroadcastHistory[i]; + if (r == null) { + break; + } + if (dumpAll) { + pw.print(" Historical Broadcast #"); pw.print(i); pw.println(":"); + r.dump(pw, " "); + } else { + if (i >= 50) { + pw.println(" ..."); break; } - pw.println(" Historical Broadcast #" + i + ":"); - r.dump(pw, " "); + pw.print(" #"); pw.print(i); pw.print(": "); pw.println(r); } - needSep = true; } + needSep = true; if (mStickyBroadcasts != null) { - pw.println(" "); + pw.println(); pw.println(" Sticky broadcasts:"); StringBuilder sb = new StringBuilder(128); for (Map.Entry<String, ArrayList<Intent>> ent : mStickyBroadcasts.entrySet()) { pw.print(" * Sticky action "); pw.print(ent.getKey()); - pw.println(":"); - ArrayList<Intent> intents = ent.getValue(); - final int N = intents.size(); - for (int i=0; i<N; i++) { - sb.setLength(0); - sb.append(" Intent: "); - intents.get(i).toShortString(sb, true, false); - pw.println(sb.toString()); - Bundle bundle = intents.get(i).getExtras(); - if (bundle != null) { - pw.print(" "); - pw.println(bundle.toString()); + if (dumpAll) { + pw.println(":"); + ArrayList<Intent> intents = ent.getValue(); + final int N = intents.size(); + for (int i=0; i<N; i++) { + sb.setLength(0); + sb.append(" Intent: "); + intents.get(i).toShortString(sb, true, false); + pw.println(sb.toString()); + Bundle bundle = intents.get(i).getExtras(); + if (bundle != null) { + pw.print(" "); + pw.println(bundle.toString()); + } } + } else { + pw.println(""); } } needSep = true; } if (dumpAll) { - pw.println(" "); + pw.println(); pw.println(" mBroadcastsScheduled=" + mBroadcastsScheduled); pw.println(" mHandler:"); mHandler.dump(new PrintWriterPrinter(pw), " "); @@ -8189,20 +8344,55 @@ public final class ActivityManagerService extends ActivityManagerNative } boolean dumpServicesLocked(FileDescriptor fd, PrintWriter pw, String[] args, - int opti, boolean dumpAll) { + int opti, boolean dumpAll, boolean dumpClient) { boolean needSep = false; - if (dumpAll) { - if (mServices.size() > 0) { - pw.println(" Active services:"); - Iterator<ServiceRecord> it = mServices.values().iterator(); - while (it.hasNext()) { - ServiceRecord r = it.next(); - pw.print(" * "); pw.println(r); + pw.println("ACTIVITY MANAGER SERVICES (dumpsys activity services)"); + if (mServices.size() > 0) { + pw.println(" Active services:"); + long nowReal = SystemClock.elapsedRealtime(); + Iterator<ServiceRecord> it = mServices.values().iterator(); + needSep = false; + while (it.hasNext()) { + ServiceRecord r = it.next(); + 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 (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; } - needSep = true; } + needSep = true; } if (mPendingServices.size() > 0) { @@ -8262,21 +8452,32 @@ public final class ActivityManagerService extends ActivityManagerNative int opti, boolean dumpAll) { boolean needSep = false; - if (dumpAll) { - if (mProvidersByClass.size() > 0) { - if (needSep) pw.println(" "); - pw.println(" Published content providers (by class):"); - Iterator<Map.Entry<String, ContentProviderRecord>> it - = mProvidersByClass.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<String, ContentProviderRecord> e = it.next(); - ContentProviderRecord r = e.getValue(); + pw.println("ACTIVITY MANAGER CONTENT PROVIDERS (dumpsys activity providers)"); + if (mProvidersByClass.size() > 0) { + if (needSep) pw.println(" "); + pw.println(" Published content providers (by class):"); + Iterator<Map.Entry<String, ContentProviderRecord>> it + = mProvidersByClass.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<String, 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(); + } } - needSep = true; } + needSep = true; + } + if (dumpAll) { if (mProvidersByName.size() > 0) { pw.println(" "); pw.println(" Authority to provider mappings:"); @@ -8313,7 +8514,9 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" holds:"); for (UriPermission perm : perms.values()) { pw.print(" "); pw.println(perm); - perm.dump(pw, " "); + if (dumpAll) { + perm.dump(pw, " "); + } } } needSep = true; @@ -8326,20 +8529,21 @@ public final class ActivityManagerService extends ActivityManagerNative int opti, boolean dumpAll) { boolean needSep = false; - if (dumpAll) { - if (this.mIntentSenderRecords.size() > 0) { - Iterator<WeakReference<PendingIntentRecord>> it - = mIntentSenderRecords.values().iterator(); - while (it.hasNext()) { - WeakReference<PendingIntentRecord> ref = it.next(); - PendingIntentRecord rec = ref != null ? ref.get(): null; - needSep = true; - if (rec != null) { - pw.print(" * "); pw.println(rec); + if (this.mIntentSenderRecords.size() > 0) { + pw.println("ACTIVITY MANAGER PENDING INTENTS (dumpsys activity intents)"); + Iterator<WeakReference<PendingIntentRecord>> it + = mIntentSenderRecords.values().iterator(); + while (it.hasNext()) { + WeakReference<PendingIntentRecord> ref = it.next(); + PendingIntentRecord rec = ref != null ? ref.get(): null; + needSep = true; + if (rec != null) { + pw.print(" * "); pw.println(rec); + if (dumpAll) { rec.dump(pw, " "); - } else { - pw.print(" * "); pw.print(ref); } + } else { + pw.print(" * "); pw.println(ref); } } } @@ -8347,12 +8551,19 @@ public final class ActivityManagerService extends ActivityManagerNative return needSep; } - private static final void dumpHistoryList(PrintWriter pw, List list, - String prefix, String label, boolean complete) { + private static final void dumpHistoryList(FileDescriptor fd, PrintWriter pw, List list, + String prefix, String label, boolean complete, boolean brief, boolean client) { TaskRecord lastTask = null; + boolean needNL = false; + final String innerPrefix = prefix + " "; + final String[] args = new String[0]; for (int i=list.size()-1; i>=0; i--) { - ActivityRecord r = (ActivityRecord)list.get(i); - final boolean full = complete || !r.inHistory; + final ActivityRecord r = (ActivityRecord)list.get(i); + final boolean full = !brief && (complete || !r.isInHistory()); + if (needNL) { + pw.println(" "); + needNL = false; + } if (lastTask != r.task) { lastTask = r.task; pw.print(prefix); @@ -8360,13 +8571,46 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(lastTask); if (full) { lastTask.dump(pw, prefix + " "); + } else if (complete) { + // Complete + brief == give a summary. Isn't that obvious?!? + if (lastTask.intent != null) { + pw.print(prefix); pw.print(" "); pw.println(lastTask.intent); + } } } pw.print(prefix); pw.print(full ? " * " : " "); pw.print(label); pw.print(" #"); pw.print(i); pw.print(": "); pw.println(r); if (full) { - r.dump(pw, prefix + " "); + r.dump(pw, innerPrefix); + } else if (complete) { + // Complete + brief == give a summary. Isn't that obvious?!? + pw.print(innerPrefix); pw.println(r.intent); + if (r.app != null) { + pw.print(innerPrefix); pw.println(r.app); + } + } + if (client && r.app != null && r.app.thread != null) { + // flush anything that is already in the PrintWriter since the thread is going + // to write to the file descriptor directly + pw.flush(); + try { + TransferPipe tp = new TransferPipe(); + try { + r.app.thread.dumpActivity(tp.getWriteFd().getFileDescriptor(), r, + innerPrefix, args); + // Short timeout, since blocking here can + // deadlock with the application. + tp.go(fd, 2000); + } finally { + tp.kill(); + } + } catch (IOException e) { + pw.println(innerPrefix + "Failure while dumping the activity: " + e); + } catch (RemoteException e) { + pw.println(innerPrefix + "Got a RemoteException while dumping the activity"); + } + needNL = true; } } } @@ -8452,7 +8696,7 @@ public final class ActivityManagerService extends ActivityManagerNative N-i, oomAdj, schedGroup, r.toShortString(), r.adjType)); if (r.adjSource != null || r.adjTarget != null) { pw.print(prefix); - pw.print(" "); + pw.print(" "); if (r.adjTarget instanceof ComponentName) { pw.print(((ComponentName)r.adjTarget).flattenToShortString()); } else if (r.adjTarget != null) { @@ -8522,8 +8766,75 @@ public final class ActivityManagerService extends ActivityManagerNative } } - static final void dumpApplicationMemoryUsage(FileDescriptor fd, - PrintWriter pw, List list, String prefix, String[] args) { + ArrayList<ProcessRecord> collectProcesses(PrintWriter pw, String[] args) { + ArrayList<ProcessRecord> procs; + synchronized (this) { + if (args != null && args.length > 0 + && args[0].charAt(0) != '-') { + procs = new ArrayList<ProcessRecord>(); + int pid = -1; + try { + pid = Integer.parseInt(args[0]); + } catch (NumberFormatException e) { + + } + for (int i=mLruProcesses.size()-1; i>=0; i--) { + ProcessRecord proc = mLruProcesses.get(i); + if (proc.pid == pid) { + procs.add(proc); + } else if (proc.processName.equals(args[0])) { + procs.add(proc); + } + } + if (procs.size() <= 0) { + pw.println("No process found for: " + args[0]); + return null; + } + } else { + procs = new ArrayList<ProcessRecord>(mLruProcesses); + } + } + return procs; + } + + final void dumpGraphicsHardwareUsage(FileDescriptor fd, + PrintWriter pw, String[] args) { + ArrayList<ProcessRecord> procs = collectProcesses(pw, args); + if (procs == null) { + return; + } + + long uptime = SystemClock.uptimeMillis(); + long realtime = SystemClock.elapsedRealtime(); + pw.println("Applications Graphics Acceleration Info:"); + pw.println("Uptime: " + uptime + " Realtime: " + realtime); + + String callArgs[] = {"graphics"}; + for (int i = procs.size() - 1 ; i >= 0 ; i--) { + ProcessRecord r = procs.get(i); + if (r.thread != null) { + pw.println("\n** Graphics info for pid " + r.pid + " [" + r.processName + "] **"); + pw.flush(); + try { + TransferPipe.goDump(r.thread.asBinder(), fd, callArgs); + } catch (IOException e) { + pw.println("Failure: " + e); + pw.flush(); + } catch (RemoteException e) { + pw.println("Got RemoteException!"); + pw.flush(); + } + } + } + } + + final void dumpApplicationMemoryUsage(FileDescriptor fd, + PrintWriter pw, String prefix, String[] args) { + ArrayList<ProcessRecord> procs = collectProcesses(pw, args); + if (procs == null) { + return; + } + final boolean isCheckinRequest = scanArgs(args, "--checkin"); long uptime = SystemClock.uptimeMillis(); long realtime = SystemClock.elapsedRealtime(); @@ -8536,15 +8847,18 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println("Applications Memory Usage (kB):"); pw.println("Uptime: " + uptime + " Realtime: " + realtime); } - for (int i = list.size() - 1 ; i >= 0 ; i--) { - ProcessRecord r = (ProcessRecord)list.get(i); + for (int i = procs.size() - 1 ; i >= 0 ; i--) { + ProcessRecord r = procs.get(i); if (r.thread != null) { if (!isCheckinRequest) { pw.println("\n** MEMINFO in pid " + r.pid + " [" + r.processName + "] **"); pw.flush(); } try { - r.thread.asBinder().dump(fd, args); + TransferPipe.goDump(r.thread.asBinder(), fd, args); + } catch (IOException e) { + pw.println("Failure: " + e); + pw.flush(); } catch (RemoteException e) { if (!isCheckinRequest) { pw.println("Got RemoteException!"); @@ -8714,7 +9028,7 @@ public final class ActivityManagerService extends ActivityManagerNative + " in dying process " + proc.processName); EventLog.writeEvent(EventLogTags.AM_KILL, capp.pid, capp.processName, capp.setAdj, "dying provider " + proc.processName); - Process.killProcess(capp.pid); + Process.killProcessQuiet(capp.pid); } } @@ -9221,7 +9535,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (si.doneExecutingCount > 0) { flags |= Service.START_FLAG_REDELIVERY; } - r.app.thread.scheduleServiceArgs(r, si.id, flags, si.intent); + r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent); } catch (RemoteException e) { // Remote process gone... we'll let the normal cleanup take // care of this. @@ -9308,11 +9622,8 @@ public final class ActivityManagerService extends ActivityManagerNative // pending arguments, then fake up one so its onStartCommand() will // be called. if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) { - r.lastStartId++; - if (r.lastStartId < 1) { - r.lastStartId = 1; - } - r.pendingStarts.add(new ServiceRecord.StartItem(r, r.lastStartId, null, -1)); + r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), + null, -1)); } sendServiceArgsLocked(r, true); @@ -9666,11 +9977,7 @@ public final class ActivityManagerService extends ActivityManagerNative } r.startRequested = true; r.callStart = false; - r.lastStartId++; - if (r.lastStartId < 1) { - r.lastStartId = 1; - } - r.pendingStarts.add(new ServiceRecord.StartItem(r, r.lastStartId, + r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), service, targetPermissionUid)); r.lastActivity = SystemClock.uptimeMillis(); synchronized (r.stats.getBatteryStats()) { @@ -9712,6 +10019,15 @@ public final class ActivityManagerService extends ActivityManagerNative } } + private void stopServiceLocked(ServiceRecord service) { + synchronized (service.stats.getBatteryStats()) { + service.stats.stopRunningLocked(); + } + service.startRequested = false; + service.callStart = false; + bringDownServiceLocked(service, false); + } + public int stopService(IApplicationThread caller, Intent service, String resolvedType) { // Refuse possible leaked file descriptors @@ -9735,14 +10051,12 @@ public final class ActivityManagerService extends ActivityManagerNative ServiceLookupResult r = findServiceLocked(service, resolvedType); if (r != null) { if (r.record != null) { - synchronized (r.record.stats.getBatteryStats()) { - r.record.stats.stopRunningLocked(); - } - r.record.startRequested = false; - r.record.callStart = false; final long origId = Binder.clearCallingIdentity(); - bringDownServiceLocked(r.record, false); - Binder.restoreCallingIdentity(origId); + try { + stopServiceLocked(r.record); + } finally { + Binder.restoreCallingIdentity(origId); + } return 1; } return -1; @@ -9804,7 +10118,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - if (r.lastStartId != startId) { + if (r.getLastStartId() != startId) { return false; } @@ -10245,7 +10559,7 @@ public final class ActivityManagerService extends ActivityManagerNative case Service.START_NOT_STICKY: { // We are done with the associated start arguments. r.findDeliveredStart(startId, true); - if (r.lastStartId == startId) { + if (r.getLastStartId() == startId) { // There is no more work, and this service // doesn't want to hang around if killed. r.stopIfKilled = true; @@ -10265,6 +10579,12 @@ public final class ActivityManagerService extends ActivityManagerNative } break; } + case Service.START_TASK_REMOVED_COMPLETE: { + // Special processing for onTaskRemoved(). Don't + // impact normal onStartCommand() processing. + r.findDeliveredStart(startId, true); + break; + } default: throw new IllegalArgumentException( "Unknown service start result: " + res); @@ -10375,7 +10695,9 @@ public final class ActivityManagerService extends ActivityManagerNative } BackupRecord r = new BackupRecord(ss, app, backupMode); - ComponentName hostingName = new ComponentName(app.packageName, app.backupAgentName); + ComponentName hostingName = (backupMode == IApplicationThread.BACKUP_MODE_INCREMENTAL) + ? new ComponentName(app.packageName, app.backupAgentName) + : new ComponentName("android", "FullBackupAgent"); // startProcessLocked() returns existing proc's record if it's already running ProcessRecord proc = startProcessLocked(app.processName, app, false, 0, "backup", hostingName, false); @@ -12669,23 +12991,31 @@ public final class ActivityManagerService extends ActivityManagerNative if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG, "Setting process group of " + app.processName + " to " + app.curSchedGroup); - if (true) { - long oldId = Binder.clearCallingIdentity(); - try { - Process.setProcessGroup(app.pid, app.curSchedGroup); - } catch (Exception e) { - Slog.w(TAG, "Failed setting process group of " + app.pid - + " to " + app.curSchedGroup); - e.printStackTrace(); - } finally { - Binder.restoreCallingIdentity(oldId); - } - } - if (false) { - if (app.thread != null) { + if (app.waitingToKill != null && + app.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) { + Slog.i(TAG, "Killing " + app + ": " + app.waitingToKill); + EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, + app.processName, app.setAdj, app.waitingToKill); + Process.killProcessQuiet(app.pid); + } else { + if (true) { + long oldId = Binder.clearCallingIdentity(); try { - app.thread.setSchedulingGroup(app.curSchedGroup); - } catch (RemoteException e) { + Process.setProcessGroup(app.pid, app.curSchedGroup); + } catch (Exception e) { + Slog.w(TAG, "Failed setting process group of " + app.pid + + " to " + app.curSchedGroup); + e.printStackTrace(); + } finally { + Binder.restoreCallingIdentity(oldId); + } + } + if (false) { + if (app.thread != null) { + try { + app.thread.setSchedulingGroup(app.curSchedGroup); + } catch (RemoteException e) { + } } } } @@ -12808,7 +13138,9 @@ public final class ActivityManagerService extends ActivityManagerNative + (app.thread != null ? app.thread.asBinder() : null) + ")\n"); if (app.pid > 0 && app.pid != MY_PID) { - Process.killProcess(app.pid); + EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, + app.processName, app.setAdj, "empty"); + Process.killProcessQuiet(app.pid); } else { try { app.thread.scheduleExit(); @@ -12874,7 +13206,9 @@ public final class ActivityManagerService extends ActivityManagerNative + (app.thread != null ? app.thread.asBinder() : null) + ")\n"); if (app.pid > 0 && app.pid != MY_PID) { - Process.killProcess(app.pid); + EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, + app.processName, app.setAdj, "empty"); + Process.killProcessQuiet(app.pid); } else { try { app.thread.scheduleExit(); @@ -12893,7 +13227,7 @@ public final class ActivityManagerService extends ActivityManagerNative // If we still have too many processes, now from the least // recently used process we start finishing activities. - if (Config.LOGV) Slog.v( + if (false) Slog.v( TAG, "*** NOW HAVE " + mLruProcesses.size() + " of " + curMaxProcs + " processes"); for ( i=0; @@ -12907,11 +13241,11 @@ public final class ActivityManagerService extends ActivityManagerNative && app.services.size() == 0; int NUMA = app.activities.size(); int j; - if (Config.LOGV) Slog.v( + if (false) Slog.v( TAG, "Looking to quit " + app.processName); for (j=0; j<NUMA && canQuit; j++) { ActivityRecord r = app.activities.get(j); - if (Config.LOGV) Slog.v( + if (false) Slog.v( TAG, " " + r.intent.getComponent().flattenToShortString() + ": frozen=" + r.haveState + ", visible=" + r.visible); canQuit = (r.haveState || !r.stateNotNeeded) @@ -12931,7 +13265,9 @@ public final class ActivityManagerService extends ActivityManagerNative + (app.thread != null ? app.thread.asBinder() : null) + ")\n"); if (app.pid > 0 && app.pid != MY_PID) { - Process.killProcess(app.pid); + EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, + app.processName, app.setAdj, "old background"); + Process.killProcessQuiet(app.pid); } else { try { app.thread.scheduleExit(); @@ -13148,4 +13484,11 @@ public final class ActivityManagerService extends ActivityManagerNative } } } + + // Multi-user methods + + public boolean switchUser(int userid) { + // TODO + return true; + } } diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index cb0a0f0..cc9e78e 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -47,7 +47,7 @@ import java.util.HashSet; /** * An entry in the history stack, representing an activity. */ -class ActivityRecord extends IApplicationToken.Stub { +final class ActivityRecord extends IApplicationToken.Stub { final ActivityManagerService service; // owner final ActivityStack stack; // owner final ActivityInfo info; // all about me @@ -74,6 +74,7 @@ class ActivityRecord extends IApplicationToken.Stub { int realTheme; // actual theme resource we will use, never 0. int windowFlags; // custom window flags for preview window. TaskRecord task; // the task this is in. + ThumbnailHolder thumbHolder; // where our thumbnails should go. long launchTime; // when we starting launching this activity long startTime; // last time this activity was started long cpuTimeAtResume; // the cpu time of host process at the time of resuming activity @@ -86,9 +87,7 @@ class ActivityRecord extends IApplicationToken.Stub { ArrayList newIntents; // any pending new intents for single-top mode HashSet<ConnectionRecord> connections; // All ConnectionRecord we hold UriPermissionOwner uriPermissions; // current special URI access perms. - ProcessRecord app; // if non-null, hosting application - Bitmap thumbnail; // icon representation of paused screen - CharSequence description; // textual description of paused screen + ProcessRecord app; // if non-null, hosting application ActivityState state; // current state we are in Bundle icicle; // last saved activity state boolean frontOfTask; // is this the root activity of its task? @@ -100,7 +99,6 @@ class ActivityRecord extends IApplicationToken.Stub { boolean configDestroy; // need to destroy due to config change? int configChangeFlags; // which config values have changed boolean keysPaused; // has key dispatching been paused for it? - boolean inHistory; // are we in the history stack? int launchMode; // the launch mode activity attribute. boolean visible; // does this activity's window need to be shown? boolean sleeping; // have we told the activity to sleep? @@ -115,6 +113,8 @@ class ActivityRecord extends IApplicationToken.Stub { String stringName; // for caching of toString(). + private boolean inHistory; // are we in the history stack? + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("packageName="); pw.print(packageName); pw.print(" processName="); pw.println(processName); @@ -136,7 +136,7 @@ class ActivityRecord extends IApplicationToken.Stub { pw.print(prefix); pw.print("stateNotNeeded="); pw.print(stateNotNeeded); pw.print(" componentSpecified="); pw.print(componentSpecified); pw.print(" isHomeActivity="); pw.println(isHomeActivity); - pw.print(prefix); pw.print("configuration="); pw.println(configuration); + pw.print(prefix); pw.print("config="); pw.println(configuration); if (resultTo != null || resultWho != null) { pw.print(prefix); pw.print("resultTo="); pw.print(resultTo); pw.print(" resultWho="); pw.print(resultWho); @@ -177,6 +177,7 @@ class ActivityRecord extends IApplicationToken.Stub { pw.print(prefix); pw.print("frozenBeforeDestroy="); pw.print(frozenBeforeDestroy); pw.print(" thumbnailNeeded="); pw.print(thumbnailNeeded); pw.print(" forceNewConfig="); pw.println(forceNewConfig); + pw.print(prefix); pw.print("thumbHolder="); pw.println(thumbHolder); if (launchTime != 0 || startTime != 0) { pw.print(prefix); pw.print("launchTime="); TimeUtils.formatDuration(launchTime, pw); pw.print(" startTime="); @@ -330,10 +331,55 @@ class ActivityRecord extends IApplicationToken.Stub { } } + void setTask(TaskRecord newTask, ThumbnailHolder newThumbHolder, boolean isRoot) { + if (inHistory && !finishing) { + if (task != null) { + task.numActivities--; + } + if (newTask != null) { + newTask.numActivities++; + } + } + if (newThumbHolder == null) { + newThumbHolder = newTask; + } + task = newTask; + if (!isRoot && (intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { + // This is the start of a new sub-task. + if (thumbHolder == null) { + thumbHolder = new ThumbnailHolder(); + } + } else { + thumbHolder = newThumbHolder; + } + } + + void putInHistory() { + if (!inHistory) { + inHistory = true; + if (task != null && !finishing) { + task.numActivities++; + } + } + } + + void takeFromHistory() { + if (inHistory) { + inHistory = false; + if (task != null && !finishing) { + task.numActivities--; + } + } + } + + boolean isInHistory() { + return inHistory; + } + void makeFinishing() { if (!finishing) { finishing = true; - if (task != null) { + if (task != null && inHistory) { task.numActivities--; } } @@ -432,6 +478,25 @@ class ActivityRecord extends IApplicationToken.Stub { } } + void updateThumbnail(Bitmap newThumbnail, CharSequence description) { + if ((intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { + // This is a logical break in the task; it repre + } + if (thumbHolder != null) { + if (newThumbnail != null) { + thumbHolder.lastThumbnail = newThumbnail; + } + thumbHolder.lastDescription = description; + } + } + + void clearThumbnail() { + if (thumbHolder != null) { + thumbHolder.lastThumbnail = null; + thumbHolder.lastDescription = null; + } + } + // IApplicationToken public boolean mayFreezeScreenLocked(ProcessRecord app) { diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index f385042..d5ac19e 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -21,8 +21,10 @@ import com.android.internal.os.BatteryStatsImpl; import com.android.server.am.ActivityManagerService.PendingActivityLaunch; import android.app.Activity; +import android.app.ActivityManager; import android.app.AppGlobals; import android.app.IActivityManager; +import android.app.IThumbnailRetriever; import static android.app.IActivityManager.START_CLASS_NOT_FOUND; import static android.app.IActivityManager.START_DELIVERED_TO_TOP; import static android.app.IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; @@ -69,9 +71,9 @@ import java.util.List; /** * State and management of a single stack of activities. */ -public class ActivityStack { +final class ActivityStack { static final String TAG = ActivityManagerService.TAG; - static final boolean localLOGV = ActivityManagerService.localLOGV; + static final boolean localLOGV = ActivityManagerService.localLOGV || true; static final boolean DEBUG_SWITCH = ActivityManagerService.DEBUG_SWITCH; static final boolean DEBUG_PAUSE = ActivityManagerService.DEBUG_PAUSE; static final boolean DEBUG_VISBILITY = ActivityManagerService.DEBUG_VISBILITY; @@ -135,14 +137,14 @@ public class ActivityStack { * The back history of all previous (and possibly still * running) activities. It contains HistoryRecord objects. */ - final ArrayList mHistory = new ArrayList(); + final ArrayList<ActivityRecord> mHistory = new ArrayList<ActivityRecord>(); /** * List of running activities, sorted by recent usage. * The first entry in the list is the least recently used. * It contains HistoryRecord objects. */ - final ArrayList mLRUActivities = new ArrayList(); + final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<ActivityRecord>(); /** * List of activities that are waiting for a new activity @@ -346,7 +348,7 @@ public class ActivityStack { final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) { int i = mHistory.size()-1; while (i >= 0) { - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (!r.finishing && r != notTop) { return r; } @@ -358,7 +360,7 @@ public class ActivityStack { final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) { int i = mHistory.size()-1; while (i >= 0) { - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (!r.finishing && !r.delayedResume && r != notTop) { return r; } @@ -379,7 +381,7 @@ public class ActivityStack { final ActivityRecord topRunningActivityLocked(IBinder token, int taskId) { int i = mHistory.size()-1; while (i >= 0) { - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); // Note: the taskId check depends on real taskId fields being non-zero if (!r.finishing && (token != r) && (taskId != r.task.taskId)) { return r; @@ -425,7 +427,7 @@ public class ActivityStack { final int N = mHistory.size(); for (int i=(N-1); i>=0; i--) { - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (!r.finishing && r.task != cp && r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE) { cp = r.task; @@ -469,7 +471,7 @@ public class ActivityStack { final int N = mHistory.size(); for (int i=(N-1); i>=0; i--) { - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (!r.finishing) { if (r.intent.getComponent().equals(cls)) { //Slog.i(TAG, "Found matching class!"); @@ -504,6 +506,7 @@ public class ActivityStack { } r.app = app; + app.waitingToKill = null; if (localLOGV) Slog.v(TAG, "Launching: " + r); @@ -680,7 +683,7 @@ public class ActivityStack { } // Ensure activities are no longer sleeping. for (int i=mHistory.size()-1; i>=0; i--) { - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); r.setSleeping(false); } mGoingToSleepActivities.clear(); @@ -726,7 +729,7 @@ public class ActivityStack { // Make sure any stopped but visible activities are now sleeping. // This ensures that the activity's onStop() is called. for (int i=mHistory.size()-1; i>=0; i--) { - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (r.state == ActivityState.STOPPING || r.state == ActivityState.STOPPED) { r.setSleeping(true); } @@ -791,10 +794,7 @@ public class ActivityStack { mLastPausedActivity = prev; prev.state = ActivityState.PAUSING; prev.task.touchActiveTime(); - prev.thumbnail = screenshotActivities(prev); - if (prev.task != null) { - prev.task.lastThumbnail = prev.thumbnail; - } + prev.updateThumbnail(screenshotActivities(prev), null); mService.updateCpuStats(); @@ -867,7 +867,7 @@ public class ActivityStack { synchronized (mService) { int index = indexOfTokenLocked(token); if (index >= 0) { - r = (ActivityRecord)mHistory.get(index); + r = mHistory.get(index); mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); if (mPausingActivity == r) { r.state = ActivityState.PAUSED; @@ -988,7 +988,7 @@ public class ActivityStack { mService.reportResumedActivityLocked(next); } - next.thumbnail = null; + next.clearThumbnail(); if (mMainStack) { mService.setFocusedActivityLocked(next); } @@ -1029,7 +1029,7 @@ public class ActivityStack { ActivityRecord r; boolean behindFullscreen = false; for (; i>=0; i--) { - r = (ActivityRecord)mHistory.get(i); + r = mHistory.get(i); if (DEBUG_VISBILITY) Slog.v( TAG, "Make visible? " + r + " finishing=" + r.finishing + " state=" + r.state); @@ -1113,7 +1113,7 @@ public class ActivityStack { // Now for any activities that aren't visible to the user, make // sure they no longer are keeping the screen frozen. while (i >= 0) { - r = (ActivityRecord)mHistory.get(i); + r = mHistory.get(i); if (DEBUG_VISBILITY) Slog.v( TAG, "Make invisible? " + r + " finishing=" + r.finishing + " state=" + r.state @@ -1504,7 +1504,7 @@ public class ActivityStack { // If starting in an existing task, find where that is... boolean startIt = true; for (int i = NH-1; i >= 0; i--) { - ActivityRecord p = (ActivityRecord)mHistory.get(i); + ActivityRecord p = mHistory.get(i); if (p.finishing) { continue; } @@ -1515,8 +1515,7 @@ public class ActivityStack { addPos = i+1; if (!startIt) { mHistory.add(addPos, r); - r.inHistory = true; - r.task.numActivities++; + r.putInHistory(); mService.mWindowManager.addAppToken(addPos, r, r.task.taskId, r.info.screenOrientation, r.fullscreen); if (VALIDATE_TOKENS) { @@ -1548,9 +1547,8 @@ public class ActivityStack { // Slot the activity into the history stack and proceed mHistory.add(addPos, r); - r.inHistory = true; + r.putInHistory(); r.frontOfTask = newTask; - r.task.numActivities++; if (NH > 0) { // We want to show the starting preview window if we are // switching to a new task, or the next activity's process is @@ -1653,7 +1651,7 @@ public class ActivityStack { int replyChainEnd = -1; int lastReparentPos = -1; for (int i=mHistory.size()-1; i>=-1; i--) { - ActivityRecord below = i >= 0 ? (ActivityRecord)mHistory.get(i) : null; + ActivityRecord below = i >= 0 ? mHistory.get(i) : null; if (below != null && below.finishing) { continue; @@ -1707,13 +1705,13 @@ public class ActivityStack { // bottom of the activity stack. This also keeps it // correctly ordered with any activities we previously // moved. - ActivityRecord p = (ActivityRecord)mHistory.get(0); + ActivityRecord p = mHistory.get(0); if (target.taskAffinity != null && target.taskAffinity.equals(p.task.affinity)) { // If the activity currently at the bottom has the // same task affinity as the one we are moving, // then merge it into the same task. - target.task = p.task; + target.setTask(p.task, p.thumbHolder, false); if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + " out to bottom task " + p.task); } else { @@ -1721,7 +1719,8 @@ public class ActivityStack { if (mService.mCurTask <= 0) { mService.mCurTask = 1; } - target.task = new TaskRecord(mService.mCurTask, target.info, null); + target.setTask(new TaskRecord(mService.mCurTask, target.info, null), + null, false); target.task.affinityIntent = target.intent; if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + " out to new task " + target.task); @@ -1731,16 +1730,16 @@ public class ActivityStack { replyChainEnd = targetI; } int dstPos = 0; + ThumbnailHolder curThumbHolder = target.thumbHolder; for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { - p = (ActivityRecord)mHistory.get(srcPos); + p = mHistory.get(srcPos); if (p.finishing) { continue; } if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p + " out to target's task " + target.task); - task.numActivities--; - p.task = target.task; - target.task.numActivities++; + p.setTask(target.task, curThumbHolder, false); + curThumbHolder = p.thumbHolder; mHistory.remove(srcPos); mHistory.add(dstPos, p); mService.mWindowManager.moveAppToken(dstPos, p); @@ -1770,7 +1769,7 @@ public class ActivityStack { // like these are all in the reply chain. replyChainEnd = targetI+1; while (replyChainEnd < mHistory.size() && - ((ActivityRecord)mHistory.get( + (mHistory.get( replyChainEnd)).task == task) { replyChainEnd++; } @@ -1780,7 +1779,7 @@ public class ActivityStack { } ActivityRecord p = null; for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { - p = (ActivityRecord)mHistory.get(srcPos); + p = mHistory.get(srcPos); if (p.finishing) { continue; } @@ -1840,7 +1839,7 @@ public class ActivityStack { } ActivityRecord p = null; for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { - p = (ActivityRecord)mHistory.get(srcPos); + p = mHistory.get(srcPos); if (p.finishing) { continue; } @@ -1858,7 +1857,7 @@ public class ActivityStack { replyChainEnd = targetI; } for (int srcPos=replyChainEnd; srcPos>=targetI; srcPos--) { - ActivityRecord p = (ActivityRecord)mHistory.get(srcPos); + ActivityRecord p = mHistory.get(srcPos); if (p.finishing) { continue; } @@ -1869,12 +1868,10 @@ public class ActivityStack { lastReparentPos--; } mHistory.remove(srcPos); - p.task.numActivities--; - p.task = task; + p.setTask(task, null, false); mHistory.add(lastReparentPos, p); if (DEBUG_TASKS) Slog.v(TAG, "Pulling activity " + p + " in to resetting task " + task); - task.numActivities++; mService.mWindowManager.moveAppToken(lastReparentPos, p); mService.mWindowManager.setAppGroupId(p, p.task.taskId); if (VALIDATE_TOKENS) { @@ -1889,7 +1886,7 @@ public class ActivityStack { // below so it remains singleTop. if (target.info.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) { for (int j=lastReparentPos-1; j>=0; j--) { - ActivityRecord p = (ActivityRecord)mHistory.get(j); + ActivityRecord p = mHistory.get(j); if (p.finishing) { continue; } @@ -1930,7 +1927,7 @@ public class ActivityStack { // First find the requested task. while (i > 0) { i--; - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (r.task.taskId == taskId) { i++; break; @@ -1940,7 +1937,7 @@ public class ActivityStack { // Now clear it. while (i > 0) { i--; - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (r.finishing) { continue; } @@ -1952,7 +1949,7 @@ public class ActivityStack { ActivityRecord ret = r; while (i < (mHistory.size()-1)) { i++; - r = (ActivityRecord)mHistory.get(i); + r = mHistory.get(i); if (r.task.taskId != taskId) { break; } @@ -1988,6 +1985,28 @@ public class ActivityStack { } /** + * Completely remove all activities associated with an existing + * task starting at a specified index. + */ + private final void performClearTaskAtIndexLocked(int taskId, int i) { + while (i < (mHistory.size()-1)) { + ActivityRecord r = mHistory.get(i); + if (r.task.taskId != taskId) { + // Whoops hit the end. + return; + } + if (r.finishing) { + i++; + continue; + } + if (!finishActivityLocked(r, i, Activity.RESULT_CANCELED, + null, "clear")) { + i++; + } + } + } + + /** * Completely remove all activities associated with an existing task. */ private final void performClearTaskLocked(int taskId) { @@ -1996,37 +2015,23 @@ public class ActivityStack { // First find the requested task. while (i > 0) { i--; - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (r.task.taskId == taskId) { i++; break; } } - // Now clear it. + // Now find the start and clear it. while (i > 0) { i--; - ActivityRecord r = (ActivityRecord)mHistory.get(i); + ActivityRecord r = mHistory.get(i); if (r.finishing) { continue; } if (r.task.taskId != taskId) { // We hit the bottom. Now finish it all... - while (i < (mHistory.size()-1)) { - i++; - r = (ActivityRecord)mHistory.get(i); - if (r.task.taskId != taskId) { - // Whoops hit the end. - return; - } - if (r.finishing) { - continue; - } - if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "clear")) { - i--; - } - } + performClearTaskAtIndexLocked(taskId, i+1); return; } } @@ -2040,7 +2045,7 @@ public class ActivityStack { int i = mHistory.size(); while (i > 0) { i--; - ActivityRecord candidate = (ActivityRecord)mHistory.get(i); + ActivityRecord candidate = mHistory.get(i); if (candidate.task.taskId != task) { break; } @@ -2057,9 +2062,9 @@ public class ActivityStack { * brought to the front. */ private final ActivityRecord moveActivityToFrontLocked(int where) { - ActivityRecord newTop = (ActivityRecord)mHistory.remove(where); + ActivityRecord newTop = mHistory.remove(where); int top = mHistory.size(); - ActivityRecord oldTop = (ActivityRecord)mHistory.get(top-1); + ActivityRecord oldTop = mHistory.get(top-1); mHistory.add(top, newTop); oldTop.frontOfTask = false; newTop.frontOfTask = true; @@ -2102,7 +2107,7 @@ public class ActivityStack { if (DEBUG_RESULTS) Slog.v( TAG, "Sending result to " + resultTo + " (index " + index + ")"); if (index >= 0) { - sourceRecord = (ActivityRecord)mHistory.get(index); + sourceRecord = mHistory.get(index); if (requestCode >= 0 && !sourceRecord.finishing) { resultRecord = sourceRecord; } @@ -2527,11 +2532,11 @@ public class ActivityStack { if (mService.mCurTask <= 0) { mService.mCurTask = 1; } - r.task = new TaskRecord(mService.mCurTask, r.info, intent); + r.setTask(new TaskRecord(mService.mCurTask, r.info, intent), null, true); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " + r.task); } else { - r.task = reuseTask; + r.setTask(reuseTask, reuseTask, true); } newTask = true; moveHomeToFrontFromLaunchLocked(launchFlags); @@ -2574,7 +2579,7 @@ public class ActivityStack { // An existing activity is starting this new activity, so we want // to keep the new one in the same task as the one that is starting // it. - r.task = sourceRecord.task; + r.setTask(sourceRecord.task, sourceRecord.thumbHolder, false); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in existing task " + r.task); @@ -2584,10 +2589,10 @@ public class ActivityStack { // this case should never happen. final int N = mHistory.size(); ActivityRecord prev = - N > 0 ? (ActivityRecord)mHistory.get(N-1) : null; - r.task = prev != null + N > 0 ? mHistory.get(N-1) : null; + r.setTask(prev != null ? prev.task - : new TaskRecord(mService.mCurTask, r.info, intent); + : new TaskRecord(mService.mCurTask, r.info, intent), null, true); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new guessed " + r.task); } @@ -3029,7 +3034,7 @@ public class ActivityStack { // Get the activity record. int index = indexOfTokenLocked(token); if (index >= 0) { - ActivityRecord r = (ActivityRecord)mHistory.get(index); + ActivityRecord r = mHistory.get(index); if (fromTimeout) { reportActivityLaunchedLocked(fromTimeout, r, -1, -1); @@ -3161,12 +3166,12 @@ public class ActivityStack { if (index < 0) { return false; } - ActivityRecord r = (ActivityRecord)mHistory.get(index); + ActivityRecord r = mHistory.get(index); // Is this the last activity left? boolean lastActivity = true; for (int i=mHistory.size()-1; i>=0; i--) { - ActivityRecord p = (ActivityRecord)mHistory.get(i); + ActivityRecord p = mHistory.get(i); if (!p.finishing && p != r) { lastActivity = false; break; @@ -3201,7 +3206,7 @@ public class ActivityStack { System.identityHashCode(r), r.task.taskId, r.shortComponentName, reason); if (index < (mHistory.size()-1)) { - ActivityRecord next = (ActivityRecord)mHistory.get(index+1); + ActivityRecord next = mHistory.get(index+1); if (next.task == r.task) { if (r.frontOfTask) { // The next activity is now the front of the task. @@ -3257,7 +3262,7 @@ public class ActivityStack { if (mResumedActivity == r) { boolean endTask = index <= 0 - || ((ActivityRecord)mHistory.get(index-1)).task != r.task; + || (mHistory.get(index-1)).task != r.task; if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare close transition: finishing " + r); mService.mWindowManager.prepareAppTransition(endTask @@ -3399,13 +3404,14 @@ public class ActivityStack { // Get rid of any pending idle timeouts. mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); mHandler.removeMessages(IDLE_TIMEOUT_MSG, r); + mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r); } private final void removeActivityFromHistoryLocked(ActivityRecord r) { if (r.state != ActivityState.DESTROYED) { r.makeFinishing(); mHistory.remove(r); - r.inHistory = false; + r.takeFromHistory(); r.state = ActivityState.DESTROYED; mService.mWindowManager.removeAppToken(r); if (VALIDATE_TOKENS) { @@ -3524,7 +3530,7 @@ public class ActivityStack { int index = indexOfTokenLocked(token); if (index >= 0) { - ActivityRecord r = (ActivityRecord)mHistory.get(index); + ActivityRecord r = mHistory.get(index); if (r.state == ActivityState.DESTROYING) { final long origId = Binder.clearCallingIdentity(); removeActivityFromHistoryLocked(r); @@ -3566,7 +3572,7 @@ public class ActivityStack { final void moveHomeToFrontLocked() { TaskRecord homeTask = null; for (int i=mHistory.size()-1; i>=0; i--) { - ActivityRecord hr = (ActivityRecord)mHistory.get(i); + ActivityRecord hr = mHistory.get(i); if (hr.isHomeActivity) { homeTask = hr.task; break; @@ -3584,7 +3590,7 @@ public class ActivityStack { final int task = tr.taskId; int top = mHistory.size()-1; - if (top < 0 || ((ActivityRecord)mHistory.get(top)).task.taskId == task) { + if (top < 0 || (mHistory.get(top)).task.taskId == task) { // nothing to do! return; } @@ -3599,7 +3605,7 @@ public class ActivityStack { // Shift all activities with this task up to the top // of the stack, keeping them in the same internal order. while (pos >= 0) { - ActivityRecord r = (ActivityRecord)mHistory.get(pos); + ActivityRecord r = mHistory.get(pos); if (localLOGV) Slog.v( TAG, "At " + pos + " ckp " + r.task + ": " + r); if (r.task.taskId == task) { @@ -3688,7 +3694,7 @@ public class ActivityStack { // Shift all activities with this task down to the bottom // of the stack, keeping them in the same internal order. while (pos < N) { - ActivityRecord r = (ActivityRecord)mHistory.get(pos); + ActivityRecord r = mHistory.get(pos); if (localLOGV) Slog.v( TAG, "At " + pos + " ckp " + r.task + ": " + r); if (r.task.taskId == task) { @@ -3722,6 +3728,106 @@ public class ActivityStack { return true; } + public ActivityManager.TaskThumbnails getTaskThumbnailsLocked(TaskRecord tr) { + TaskAccessInfo info = getTaskAccessInfoLocked(tr.taskId, true); + ActivityRecord resumed = mResumedActivity; + if (resumed != null && resumed.thumbHolder == tr) { + info.mainThumbnail = resumed.stack.screenshotActivities(resumed); + } else { + info.mainThumbnail = tr.lastThumbnail; + } + return info; + } + + public ActivityRecord removeTaskActivitiesLocked(int taskId, int subTaskIndex) { + TaskAccessInfo info = getTaskAccessInfoLocked(taskId, false); + if (info.root == null) { + Slog.w(TAG, "removeTaskLocked: unknown taskId " + taskId); + return null; + } + + if (subTaskIndex < 0) { + // Just remove the entire task. + performClearTaskAtIndexLocked(taskId, info.rootIndex); + return info.root; + } + + if (subTaskIndex >= info.subtasks.size()) { + Slog.w(TAG, "removeTaskLocked: unknown subTaskIndex " + subTaskIndex); + return null; + } + + // Remove all of this task's activies starting at the sub task. + TaskAccessInfo.SubTask subtask = info.subtasks.get(subTaskIndex); + performClearTaskAtIndexLocked(taskId, subtask.index); + return subtask.activity; + } + + public TaskAccessInfo getTaskAccessInfoLocked(int taskId, boolean inclThumbs) { + ActivityRecord resumed = mResumedActivity; + final TaskAccessInfo thumbs = new TaskAccessInfo(); + // How many different sub-thumbnails? + final int NA = mHistory.size(); + int j = 0; + ThumbnailHolder holder = null; + while (j < NA) { + ActivityRecord ar = mHistory.get(j); + if (!ar.finishing && ar.task.taskId == taskId) { + holder = ar.thumbHolder; + break; + } + j++; + } + + if (j >= NA) { + return thumbs; + } + + thumbs.root = mHistory.get(j); + thumbs.rootIndex = j; + + ArrayList<TaskAccessInfo.SubTask> subtasks = new ArrayList<TaskAccessInfo.SubTask>(); + thumbs.subtasks = subtasks; + ActivityRecord lastActivity = null; + while (j < NA) { + ActivityRecord ar = mHistory.get(j); + j++; + if (ar.finishing) { + continue; + } + if (ar.task.taskId != taskId) { + break; + } + lastActivity = ar; + if (ar.thumbHolder != holder && holder != null) { + thumbs.numSubThumbbails++; + holder = ar.thumbHolder; + TaskAccessInfo.SubTask sub = new TaskAccessInfo.SubTask(); + sub.thumbnail = holder.lastThumbnail; + sub.activity = ar; + sub.index = j-1; + subtasks.add(sub); + } + } + if (lastActivity != null && subtasks.size() > 0) { + if (resumed == lastActivity) { + TaskAccessInfo.SubTask sub = subtasks.get(subtasks.size()-1); + sub.thumbnail = lastActivity.stack.screenshotActivities(lastActivity); + } + } + if (thumbs.numSubThumbbails > 0) { + thumbs.retriever = new IThumbnailRetriever.Stub() { + public Bitmap getThumbnail(int index) { + if (index < 0 || index >= thumbs.subtasks.size()) { + return null; + } + return thumbs.subtasks.get(index).thumbnail; + } + }; + } + return thumbs; + } + private final void logStartActivity(int tag, ActivityRecord r, TaskRecord task) { EventLog.writeEvent(tag, diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index 963a691..b4fdc9f 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -449,6 +449,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { boolean isCheckin = false; + boolean noOutput = false; if (args != null) { for (String arg : args) { if ("--checkin".equals(arg)) { @@ -457,10 +458,22 @@ public final class BatteryStatsService extends IBatteryStats.Stub { synchronized (mStats) { mStats.resetAllStatsLocked(); pw.println("Battery stats reset."); + noOutput = true; } + } else if ("--write".equals(arg)) { + synchronized (mStats) { + mStats.writeSyncLocked(); + pw.println("Battery stats written."); + noOutput = true; + } + } else { + pw.println("Unknown option: " + arg); } } } + if (noOutput) { + return; + } if (isCheckin) { List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0); synchronized (mStats) { diff --git a/services/java/com/android/server/am/ContentProviderRecord.java b/services/java/com/android/server/am/ContentProviderRecord.java index 44c9742..db235ee 100644 --- a/services/java/com/android/server/am/ContentProviderRecord.java +++ b/services/java/com/android/server/am/ContentProviderRecord.java @@ -60,7 +60,7 @@ class ContentProviderRecord extends ContentProviderHolder { void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("package="); pw.print(info.applicationInfo.packageName); - pw.print("process="); pw.println(info.processName); + pw.print(" process="); pw.println(info.processName); pw.print(prefix); pw.print("app="); pw.println(app); if (launchingApp != null) { pw.print(prefix); pw.print("launchingApp="); pw.println(launchingApp); diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java index a63ffae..5be35ee 100644 --- a/services/java/com/android/server/am/ProcessRecord.java +++ b/services/java/com/android/server/am/ProcessRecord.java @@ -66,6 +66,7 @@ class ProcessRecord { boolean foregroundServices; // Running any services that are foreground? boolean bad; // True if disabled in the bad process list boolean killedBackground; // True when proc has been killed due to too many bg + String waitingToKill; // Process is waiting to be killed when in the bg; reason IBinder forcingToForeground;// Token that is forcing this process to be foreground int adjSeq; // Sequence id for identifying oom_adj assignment cycles int lruSeq; // Sequence id for identifying LRU update cycles @@ -203,8 +204,9 @@ class ProcessRecord { pw.print(" lastLowMemory="); TimeUtils.formatDuration(lastLowMemory, now, pw); pw.print(" reportLowMemory="); pw.println(reportLowMemory); - if (killedBackground) { - pw.print(prefix); pw.print("killedBackground="); pw.println(killedBackground); + if (killedBackground || waitingToKill != null) { + pw.print(prefix); pw.print("killedBackground="); pw.print(killedBackground); + pw.print(" waitingToKill="); pw.println(waitingToKill); } if (debugging || crashing || crashDialog != null || notResponding || anrDialog != null || bad) { diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index e5aceb4..004e963 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -84,7 +84,6 @@ class ServiceRecord extends Binder { boolean startRequested; // someone explicitly called start? boolean stopIfKilled; // last onStart() said to stop if service killed? boolean callStart; // last onStart() has asked to alway be called on restart. - int lastStartId; // identifier of most recent start request. int executeNesting; // number of outstanding operations keeping foreground. long executingStart; // start time of last execute request. int crashCount; // number of times proc has crashed with service running @@ -96,8 +95,11 @@ class ServiceRecord extends Binder { String stringName; // caching of toString + private int lastStartId; // identifier of most recent start request. + static class StartItem { final ServiceRecord sr; + final boolean taskRemoved; final int id; final Intent intent; final int targetPermissionUid; @@ -108,8 +110,10 @@ class ServiceRecord extends Binder { String stringName; // caching of toString - StartItem(ServiceRecord _sr, int _id, Intent _intent, int _targetPermissionUid) { + StartItem(ServiceRecord _sr, boolean _taskRemoved, int _id, Intent _intent, + int _targetPermissionUid) { sr = _sr; + taskRemoved = _taskRemoved; id = _id; intent = _intent; targetPermissionUid = _targetPermissionUid; @@ -198,7 +202,9 @@ class ServiceRecord extends Binder { long now = SystemClock.uptimeMillis(); long nowReal = SystemClock.elapsedRealtime(); pw.print(prefix); pw.print("baseDir="); pw.println(baseDir); - if (!resDir.equals(baseDir)) pw.print(prefix); pw.print("resDir="); pw.println(resDir); + if (!resDir.equals(baseDir)) { + pw.print(prefix); pw.print("resDir="); pw.println(resDir); + } pw.print(prefix); pw.print("dataDir="); pw.println(dataDir); pw.print(prefix); pw.print("app="); pw.println(app); if (isForeground || foregroundId != 0) { @@ -211,7 +217,7 @@ class ServiceRecord extends Binder { pw.print(" lastActivity="); TimeUtils.formatDuration(lastActivity, now, pw); pw.println(""); - pw.print(prefix); pw.print(" executingStart="); + pw.print(prefix); pw.print("executingStart="); TimeUtils.formatDuration(executingStart, now, pw); pw.print(" restartTime="); TimeUtils.formatDuration(restartTime, now, pw); @@ -321,6 +327,18 @@ class ServiceRecord extends Binder { return null; } + public int getLastStartId() { + return lastStartId; + } + + public int makeNextStartId() { + lastStartId++; + if (lastStartId < 1) { + lastStartId = 1; + } + return lastStartId; + } + public void postNotification() { final int appUid = appInfo.uid; final int appPid = app.pid; diff --git a/services/java/com/android/server/am/TaskAccessInfo.java b/services/java/com/android/server/am/TaskAccessInfo.java new file mode 100644 index 0000000..5618c1a --- /dev/null +++ b/services/java/com/android/server/am/TaskAccessInfo.java @@ -0,0 +1,35 @@ +/* + * 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 java.util.ArrayList; + +import android.app.ActivityManager.TaskThumbnails; +import android.graphics.Bitmap; + +final class TaskAccessInfo extends TaskThumbnails { + final static class SubTask { + Bitmap thumbnail; + ActivityRecord activity; + int index; + } + + public ActivityRecord root; + public int rootIndex; + + public ArrayList<SubTask> subtasks; +} diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java index 86cec42..e8c87e1 100644 --- a/services/java/com/android/server/am/TaskRecord.java +++ b/services/java/com/android/server/am/TaskRecord.java @@ -23,7 +23,7 @@ import android.graphics.Bitmap; import java.io.PrintWriter; -class TaskRecord { +class TaskRecord extends ThumbnailHolder { final int taskId; // Unique identifier for this task. final String affinity; // The affinity name for this task, or null. Intent intent; // The original intent that started the task. @@ -34,8 +34,6 @@ class TaskRecord { long lastActiveTime; // Last time this task was active, including sleep. boolean rootWasReset; // True if the intent at the root of the task had // the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag. - Bitmap lastThumbnail; // Last thumbnail captured for this task. - CharSequence lastDescription; // Last description captured for this task. String stringName; // caching of toString() result. diff --git a/services/java/com/android/server/am/ThumbnailHolder.java b/services/java/com/android/server/am/ThumbnailHolder.java new file mode 100644 index 0000000..02f4fcb --- /dev/null +++ b/services/java/com/android/server/am/ThumbnailHolder.java @@ -0,0 +1,24 @@ +/* + * 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.graphics.Bitmap; + +public class ThumbnailHolder { + Bitmap lastThumbnail; // Last thumbnail captured for this item. + CharSequence lastDescription; // Last description captured for this item. +} diff --git a/services/java/com/android/server/am/TransferPipe.java b/services/java/com/android/server/am/TransferPipe.java new file mode 100644 index 0000000..c3f4f93 --- /dev/null +++ b/services/java/com/android/server/am/TransferPipe.java @@ -0,0 +1,242 @@ +/* + * 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 java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Slog; + +/** + * Helper for transferring data through a pipe from a client app. + */ +class TransferPipe implements Runnable { + static final String TAG = "TransferPipe"; + static final boolean DEBUG = false; + + static final long DEFAULT_TIMEOUT = 5000; // 5 seconds + + final Thread mThread;; + final ParcelFileDescriptor[] mFds; + + FileDescriptor mOutFd; + long mEndTime; + String mFailure; + boolean mComplete; + + String mBufferPrefix; + + interface Caller { + void go(IInterface iface, FileDescriptor fd, String prefix, + String[] args) throws RemoteException; + } + + TransferPipe() throws IOException { + mThread = new Thread(this, "TransferPipe"); + mFds = ParcelFileDescriptor.createPipe(); + } + + ParcelFileDescriptor getReadFd() { + return mFds[0]; + } + + ParcelFileDescriptor getWriteFd() { + return mFds[1]; + } + + void setBufferPrefix(String prefix) { + mBufferPrefix = prefix; + } + + static void go(Caller caller, IInterface iface, FileDescriptor out, + String prefix, String[] args) throws IOException, RemoteException { + go(caller, iface, out, prefix, args, DEFAULT_TIMEOUT); + } + + static void go(Caller caller, IInterface iface, FileDescriptor out, + String prefix, String[] args, long timeout) throws IOException, RemoteException { + if ((iface.asBinder()) instanceof Binder) { + // This is a local object... just call it directly. + try { + caller.go(iface, out, prefix, args); + } catch (RemoteException e) { + } + return; + } + + TransferPipe tp = new TransferPipe(); + try { + caller.go(iface, tp.getWriteFd().getFileDescriptor(), prefix, args); + tp.go(out, timeout); + } finally { + tp.kill(); + } + } + + static void goDump(IBinder binder, FileDescriptor out, + String[] args) throws IOException, RemoteException { + goDump(binder, out, args, DEFAULT_TIMEOUT); + } + + static void goDump(IBinder binder, FileDescriptor out, + String[] args, long timeout) throws IOException, RemoteException { + if (binder instanceof Binder) { + // This is a local object... just call it directly. + try { + binder.dump(out, args); + } catch (RemoteException e) { + } + return; + } + + TransferPipe tp = new TransferPipe(); + try { + binder.dumpAsync(tp.getWriteFd().getFileDescriptor(), args); + tp.go(out, timeout); + } finally { + tp.kill(); + } + } + + void go(FileDescriptor out) throws IOException { + go(out, DEFAULT_TIMEOUT); + } + + void go(FileDescriptor out, long timeout) throws IOException { + try { + synchronized (this) { + mOutFd = out; + mEndTime = SystemClock.uptimeMillis() + timeout; + + if (DEBUG) Slog.i(TAG, "read=" + getReadFd() + " write=" + getWriteFd() + + " out=" + out); + + // Close the write fd, so we know when the other side is done. + closeFd(1); + + mThread.start(); + + while (mFailure == null && !mComplete) { + long waitTime = mEndTime - SystemClock.uptimeMillis(); + if (waitTime <= 0) { + if (DEBUG) Slog.i(TAG, "TIMEOUT!"); + mThread.interrupt(); + throw new IOException("Timeout"); + } + + try { + wait(waitTime); + } catch (InterruptedException e) { + } + } + + if (DEBUG) Slog.i(TAG, "Finished: " + mFailure); + if (mFailure != null) { + throw new IOException(mFailure); + } + } + } finally { + kill(); + } + } + + void closeFd(int num) { + if (mFds[num] != null) { + if (DEBUG) Slog.i(TAG, "Closing: " + mFds[num]); + try { + mFds[num].close(); + } catch (IOException e) { + } + mFds[num] = null; + } + } + + void kill() { + closeFd(0); + closeFd(1); + } + + @Override + public void run() { + final byte[] buffer = new byte[1024]; + final FileInputStream fis = new FileInputStream(getReadFd().getFileDescriptor()); + final FileOutputStream fos = new FileOutputStream(mOutFd); + + if (DEBUG) Slog.i(TAG, "Ready to read pipe..."); + byte[] bufferPrefix = null; + boolean needPrefix = true; + if (mBufferPrefix != null) { + bufferPrefix = mBufferPrefix.getBytes(); + } + + int size; + try { + while ((size=fis.read(buffer)) > 0) { + if (DEBUG) Slog.i(TAG, "Got " + size + " bytes"); + if (bufferPrefix == null) { + fos.write(buffer, 0, size); + } else { + int start = 0; + for (int i=0; i<size; i++) { + if (buffer[i] != '\n') { + if (i > start) { + fos.write(buffer, start, i-start); + } + start = i; + if (needPrefix) { + fos.write(bufferPrefix); + needPrefix = false; + } + do { + i++; + } while (i<size && buffer[i] != '\n'); + if (i < size) { + needPrefix = true; + } + } + } + if (size > start) { + fos.write(buffer, start, size-start); + } + } + } + if (DEBUG) Slog.i(TAG, "End of pipe: size=" + size); + if (mThread.isInterrupted()) { + if (DEBUG) Slog.i(TAG, "Interrupted!"); + } + } catch (IOException e) { + synchronized (this) { + mFailure = e.toString(); + notifyAll(); + return; + } + } + + synchronized (this) { + mComplete = true; + notifyAll(); + } + } +} diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index 9ff5233..316db2e 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -84,6 +84,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private String[] mTetherableBluetoothRegexs; private String[] mUpstreamIfaceRegexs; + private INetworkManagementService mNMService; private Looper mLooper; private HandlerThread mThread; @@ -100,21 +101,12 @@ public class Tethering extends INetworkManagementEventObserver.Stub { // with 255.255.255.0 private String[] mDhcpRange; - private static final String DHCP_DEFAULT_RANGE1_START = "192.168.42.2"; - private static final String DHCP_DEFAULT_RANGE1_STOP = "192.168.42.254"; - private static final String DHCP_DEFAULT_RANGE2_START = "192.168.43.2"; - private static final String DHCP_DEFAULT_RANGE2_STOP = "192.168.43.254"; - private static final String DHCP_DEFAULT_RANGE3_START = "192.168.44.2"; - private static final String DHCP_DEFAULT_RANGE3_STOP = "192.168.44.254"; - private static final String DHCP_DEFAULT_RANGE4_START = "192.168.45.2"; - private static final String DHCP_DEFAULT_RANGE4_STOP = "192.168.45.254"; - private static final String DHCP_DEFAULT_RANGE5_START = "192.168.46.2"; - private static final String DHCP_DEFAULT_RANGE5_STOP = "192.168.46.254"; - private static final String DHCP_DEFAULT_RANGE6_START = "192.168.47.2"; - private static final String DHCP_DEFAULT_RANGE6_STOP = "192.168.47.254"; - private static final String DHCP_DEFAULT_RANGE7_START = "192.168.48.2"; - private static final String DHCP_DEFAULT_RANGE7_STOP = "192.168.48.254"; - + private static final String[] DHCP_DEFAULT_RANGE = { + "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254", + "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254", + "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254", + "192.168.48.2", "192.168.48.254", + }; private String[] mDnsServers; private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8"; @@ -132,15 +124,14 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private boolean mUsbMassStorageOff; // track the status of USB Mass Storage private boolean mUsbConnected; // track the status of USB connection - public Tethering(Context context, Looper looper) { + public Tethering(Context context, INetworkManagementService nmService, Looper looper) { mContext = context; + mNMService = nmService; mLooper = looper; // register for notifications from NetworkManagement Service - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); try { - service.registerObserver(this); + mNMService.registerObserver(this); } catch (RemoteException e) { Log.e(TAG, "Error registering observer :" + e); } @@ -173,21 +164,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mDhcpRange = context.getResources().getStringArray( com.android.internal.R.array.config_tether_dhcp_range); if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) { - mDhcpRange = new String[14]; - mDhcpRange[0] = DHCP_DEFAULT_RANGE1_START; - mDhcpRange[1] = DHCP_DEFAULT_RANGE1_STOP; - mDhcpRange[2] = DHCP_DEFAULT_RANGE2_START; - mDhcpRange[3] = DHCP_DEFAULT_RANGE2_STOP; - mDhcpRange[4] = DHCP_DEFAULT_RANGE3_START; - mDhcpRange[5] = DHCP_DEFAULT_RANGE3_STOP; - mDhcpRange[6] = DHCP_DEFAULT_RANGE4_START; - mDhcpRange[7] = DHCP_DEFAULT_RANGE4_STOP; - mDhcpRange[8] = DHCP_DEFAULT_RANGE5_START; - mDhcpRange[9] = DHCP_DEFAULT_RANGE5_STOP; - mDhcpRange[10] = DHCP_DEFAULT_RANGE6_START; - mDhcpRange[11] = DHCP_DEFAULT_RANGE6_STOP; - mDhcpRange[12] = DHCP_DEFAULT_RANGE7_START; - mDhcpRange[13] = DHCP_DEFAULT_RANGE7_STOP; + mDhcpRange = DHCP_DEFAULT_RANGE; } mDunRequired = false; // resample when we turn on @@ -258,8 +235,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return false; } public void interfaceAdded(String iface) { - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); boolean found = false; boolean usb = false; if (isWifi(iface)) { @@ -354,9 +329,9 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private void sendTetherStateChangedBroadcast() { IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); - IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); + IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b); try { - if (!service.isTetheringSupported()) return; + if (!cm.isTetheringSupported()) return; } catch (RemoteException e) { return; } @@ -503,11 +478,9 @@ public class Tethering extends INetworkManagementEventObserver.Stub { // used on cable insert/remove private void enableUsbIfaces(boolean enable) { - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); String[] ifaces = new String[0]; try { - ifaces = service.listInterfaces(); + ifaces = mNMService.listInterfaces(); } catch (Exception e) { Log.e(TAG, "Error listing Interfaces :" + e); return; @@ -526,19 +499,17 @@ public class Tethering extends INetworkManagementEventObserver.Stub { // toggled when we enter/leave the fully tethered state private boolean enableUsbRndis(boolean enabled) { if (DEBUG) Log.d(TAG, "enableUsbRndis(" + enabled + ")"); - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); try { if (enabled) { synchronized (this) { - if (!service.isUsbRNDISStarted()) { - service.startUsbRNDIS(); + if (!mNMService.isUsbRNDISStarted()) { + mNMService.startUsbRNDIS(); } } } else { - if (service.isUsbRNDISStarted()) { - service.stopUsbRNDIS(); + if (mNMService.isUsbRNDISStarted()) { + mNMService.stopUsbRNDIS(); } } } catch (Exception e) { @@ -552,13 +523,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private boolean configureUsbIface(boolean enabled) { if (DEBUG) Log.d(TAG, "configureUsbIface(" + enabled + ")"); - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); - // bring toggle the interfaces String[] ifaces = new String[0]; try { - ifaces = service.listInterfaces(); + ifaces = mNMService.listInterfaces(); } catch (Exception e) { Log.e(TAG, "Error listing Interfaces :" + e); return false; @@ -567,7 +535,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { if (isUsb(iface)) { InterfaceConfiguration ifcg = null; try { - ifcg = service.getInterfaceConfig(iface); + ifcg = mNMService.getInterfaceConfig(iface); if (ifcg != null) { InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR); ifcg.addr = new LinkAddress(addr, USB_PREFIX_LENGTH); @@ -578,7 +546,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } ifcg.interfaceFlags = ifcg.interfaceFlags.replace("running", ""); ifcg.interfaceFlags = ifcg.interfaceFlags.replace(" "," "); - service.setInterfaceConfig(iface, ifcg); + mNMService.setInterfaceConfig(iface, ifcg); } } catch (Exception e) { Log.e(TAG, "Error configuring interface " + iface + ", :" + e); @@ -874,11 +842,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { class TetheredState extends State { @Override public void enter() { - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = - INetworkManagementService.Stub.asInterface(b); try { - service.tetherInterface(mIfaceName); + mNMService.tetherInterface(mIfaceName); } catch (Exception e) { setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR); @@ -903,16 +868,13 @@ public class Tethering extends INetworkManagementEventObserver.Stub { switch (message.what) { case CMD_TETHER_UNREQUESTED: case CMD_INTERFACE_DOWN: - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = - INetworkManagementService.Stub.asInterface(b); if (mMyUpstreamIfaceName != null) { try { - service.disableNat(mIfaceName, mMyUpstreamIfaceName); + mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName); mMyUpstreamIfaceName = null; } catch (Exception e) { try { - service.untetherInterface(mIfaceName); + mNMService.untetherInterface(mIfaceName); } catch (Exception ee) {} setLastErrorAndTransitionToInitialState( @@ -921,7 +883,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } try { - service.untetherInterface(mIfaceName); + mNMService.untetherInterface(mIfaceName); } catch (Exception e) { setLastErrorAndTransitionToInitialState( ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); @@ -944,8 +906,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { break; case CMD_TETHER_CONNECTION_CHANGED: String newUpstreamIfaceName = (String)(message.obj); - b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - service = INetworkManagementService.Stub.asInterface(b); if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) || (mMyUpstreamIfaceName != null && mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) { @@ -954,11 +914,11 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } if (mMyUpstreamIfaceName != null) { try { - service.disableNat(mIfaceName, mMyUpstreamIfaceName); + mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName); mMyUpstreamIfaceName = null; } catch (Exception e) { try { - service.untetherInterface(mIfaceName); + mNMService.untetherInterface(mIfaceName); } catch (Exception ee) {} setLastErrorAndTransitionToInitialState( @@ -968,10 +928,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } if (newUpstreamIfaceName != null) { try { - service.enableNat(mIfaceName, newUpstreamIfaceName); + mNMService.enableNat(mIfaceName, newUpstreamIfaceName); } catch (Exception e) { try { - service.untetherInterface(mIfaceName); + mNMService.untetherInterface(mIfaceName); } catch (Exception ee) {} setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR); @@ -990,15 +950,13 @@ public class Tethering extends INetworkManagementEventObserver.Stub { error = true; // fall through case CMD_TETHER_MODE_DEAD: - b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - service = INetworkManagementService.Stub.asInterface(b); if (mMyUpstreamIfaceName != null) { try { - service.disableNat(mIfaceName, mMyUpstreamIfaceName); + mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName); mMyUpstreamIfaceName = null; } catch (Exception e) { try { - service.untetherInterface(mIfaceName); + mNMService.untetherInterface(mIfaceName); } catch (Exception ee) {} setLastErrorAndTransitionToInitialState( @@ -1007,7 +965,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } try { - service.untetherInterface(mIfaceName); + mNMService.untetherInterface(mIfaceName); } catch (Exception e) { setLastErrorAndTransitionToInitialState( ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); @@ -1138,10 +1096,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub { boolean retValue = true; if (mMobileReserved) return retValue; IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); - IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); + IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b); int result = Phone.APN_REQUEST_FAILED; try { - result = service.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, + result = cm.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, (mDunRequired ? Phone.FEATURE_ENABLE_DUN_ALWAYS : Phone.FEATURE_ENABLE_HIPRI), new Binder()); } catch (Exception e) { @@ -1165,10 +1123,9 @@ public class Tethering extends INetworkManagementEventObserver.Stub { protected boolean turnOffMobileConnection() { if (mMobileReserved) { IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); - IConnectivityManager service = - IConnectivityManager.Stub.asInterface(b); + IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b); try { - service.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, + cm.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, (mDunRequired? Phone.FEATURE_ENABLE_DUN_ALWAYS : Phone.FEATURE_ENABLE_HIPRI)); } catch (Exception e) { @@ -1179,28 +1136,25 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return true; } protected boolean turnOnMasterTetherSettings() { - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = - INetworkManagementService.Stub.asInterface(b); try { - service.setIpForwardingEnabled(true); + mNMService.setIpForwardingEnabled(true); } catch (Exception e) { transitionTo(mSetIpForwardingEnabledErrorState); return false; } try { - service.startTethering(mDhcpRange); + mNMService.startTethering(mDhcpRange); } catch (Exception e) { try { - service.stopTethering(); - service.startTethering(mDhcpRange); + mNMService.stopTethering(); + mNMService.startTethering(mDhcpRange); } catch (Exception ee) { transitionTo(mStartTetheringErrorState); return false; } } try { - service.setDnsForwarders(mDnsServers); + mNMService.setDnsForwarders(mDnsServers); } catch (Exception e) { transitionTo(mSetDnsForwardersErrorState); return false; @@ -1208,17 +1162,14 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return true; } protected boolean turnOffMasterTetherSettings() { - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = - INetworkManagementService.Stub.asInterface(b); try { - service.stopTethering(); + mNMService.stopTethering(); } catch (Exception e) { transitionTo(mStopTetheringErrorState); return false; } try { - service.setIpForwardingEnabled(false); + mNMService.setIpForwardingEnabled(false); } catch (Exception e) { transitionTo(mSetIpForwardingDisabledErrorState); return false; @@ -1241,12 +1192,9 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } catch (RemoteException e) { } - b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); - String[] ifaces = new String[0]; try { - ifaces = service.listInterfaces(); + ifaces = mNMService.listInterfaces(); } catch (Exception e) { Log.e(TAG, "Error listing Interfaces :" + e); return null; @@ -1258,7 +1206,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { // verify it is active InterfaceConfiguration ifcg = null; try { - ifcg = service.getInterfaceConfig(iface); + ifcg = mNMService.getInterfaceConfig(iface); if (ifcg.isActive()) { return iface; } @@ -1486,11 +1434,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { public void enter() { Log.e(TAG, "Error in startTethering"); notify(TetherInterfaceSM.CMD_START_TETHERING_ERROR); - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = - INetworkManagementService.Stub.asInterface(b); try { - service.setIpForwardingEnabled(false); + mNMService.setIpForwardingEnabled(false); } catch (Exception e) {} } } @@ -1500,11 +1445,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { public void enter() { Log.e(TAG, "Error in stopTethering"); notify(TetherInterfaceSM.CMD_STOP_TETHERING_ERROR); - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = - INetworkManagementService.Stub.asInterface(b); try { - service.setIpForwardingEnabled(false); + mNMService.setIpForwardingEnabled(false); } catch (Exception e) {} } } @@ -1514,14 +1456,11 @@ public class Tethering extends INetworkManagementEventObserver.Stub { public void enter() { Log.e(TAG, "Error in setDnsForwarders"); notify(TetherInterfaceSM.CMD_SET_DNS_FORWARDERS_ERROR); - IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); - INetworkManagementService service = - INetworkManagementService.Stub.asInterface(b); try { - service.stopTethering(); + mNMService.stopTethering(); } catch (Exception e) {} try { - service.setIpForwardingEnabled(false); + mNMService.setIpForwardingEnabled(false); } catch (Exception e) {} } } diff --git a/services/java/com/android/server/location/ComprehensiveCountryDetector.java b/services/java/com/android/server/location/ComprehensiveCountryDetector.java index e9ce3ce..bb9e60f 100755 --- a/services/java/com/android/server/location/ComprehensiveCountryDetector.java +++ b/services/java/com/android/server/location/ComprehensiveCountryDetector.java @@ -56,6 +56,7 @@ import java.util.TimerTask; public class ComprehensiveCountryDetector extends CountryDetectorBase { private final static String TAG = "ComprehensiveCountryDetector"; + /* package */ static final boolean DEBUG = false; /** * The refresh interval when the location based country was used @@ -90,7 +91,9 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase { * The listener for receiving the notification from LocationBasedCountryDetector. */ private CountryListener mLocationBasedCountryDetectionListener = new CountryListener() { + @Override public void onCountryDetected(Country country) { + if (DEBUG) Slog.d(TAG, "Country detected via LocationBasedCountryDetector"); mCountryFromLocation = country; // Don't start the LocationBasedCountryDetector. detectCountry(true, false); @@ -206,6 +209,7 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase { protected void runAfterDetectionAsync(final Country country, final Country detectedCountry, final boolean notifyChange, final boolean startLocationBasedDetection) { mHandler.post(new Runnable() { + @Override public void run() { runAfterDetection( country, detectedCountry, notifyChange, startLocationBasedDetection); @@ -233,9 +237,20 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase { if (notifyChange) { notifyIfCountryChanged(country, detectedCountry); } + if (DEBUG) { + Slog.d(TAG, "startLocationBasedDetection=" + startLocationBasedDetection + + " detectCountry=" + (detectedCountry == null ? null : + "(source: " + detectedCountry.getSource() + + ", countryISO: " + detectedCountry.getCountryIso() + ")") + + " isAirplaneModeOff()=" + isAirplaneModeOff() + + " mListener=" + mListener + + " isGeoCoderImplemnted()=" + isGeoCoderImplemented()); + } + if (startLocationBasedDetection && (detectedCountry == null || detectedCountry.getSource() > Country.COUNTRY_SOURCE_LOCATION) && isAirplaneModeOff() && mListener != null && isGeoCoderImplemented()) { + if (DEBUG) Slog.d(TAG, "run startLocationBasedDetector()"); // Start finding location when the source is less reliable than the // location and the airplane mode is off (as geocoder will not // work). @@ -266,12 +281,20 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase { if (mLocationBasedCountryDetector != null) { return; } + if (DEBUG) { + Slog.d(TAG, "starts LocationBasedDetector to detect Country code via Location info " + + "(e.g. GPS)"); + } mLocationBasedCountryDetector = createLocationBasedCountryDetector(); mLocationBasedCountryDetector.setCountryListener(listener); mLocationBasedCountryDetector.detectCountry(); } private synchronized void stopLocationBasedDetector() { + if (DEBUG) { + Slog.d(TAG, "tries to stop LocationBasedDetector " + + "(current detector: " + mLocationBasedCountryDetector + ")"); + } if (mLocationBasedCountryDetector != null) { mLocationBasedCountryDetector.stop(); mLocationBasedCountryDetector = null; @@ -305,10 +328,17 @@ public class ComprehensiveCountryDetector extends CountryDetectorBase { */ private synchronized void scheduleLocationRefresh() { if (mLocationRefreshTimer != null) return; + if (DEBUG) { + Slog.d(TAG, "start periodic location refresh timer. Interval: " + + LOCATION_REFRESH_INTERVAL); + } mLocationRefreshTimer = new Timer(); mLocationRefreshTimer.schedule(new TimerTask() { @Override public void run() { + if (DEBUG) { + Slog.d(TAG, "periodic location refresh event. Starts detecting Country code"); + } mLocationRefreshTimer = null; detectCountry(false, true); } diff --git a/services/java/com/android/server/location/GpsXtraDownloader.java b/services/java/com/android/server/location/GpsXtraDownloader.java index 813d255..e420073 100644 --- a/services/java/com/android/server/location/GpsXtraDownloader.java +++ b/services/java/com/android/server/location/GpsXtraDownloader.java @@ -19,7 +19,6 @@ package com.android.server.location; import android.content.Context; import android.net.Proxy; import android.net.http.AndroidHttpClient; -import android.util.Config; import android.util.Log; import org.apache.http.HttpEntity; diff --git a/services/java/com/android/server/location/LocationBasedCountryDetector.java b/services/java/com/android/server/location/LocationBasedCountryDetector.java index 139f05d..d4fb8ee 100755 --- a/services/java/com/android/server/location/LocationBasedCountryDetector.java +++ b/services/java/com/android/server/location/LocationBasedCountryDetector.java @@ -16,12 +16,6 @@ package com.android.server.location; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; - import android.content.Context; import android.location.Address; import android.location.Country; @@ -32,6 +26,12 @@ import android.location.LocationManager; import android.os.Bundle; import android.util.Slog; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + /** * This class detects which country the user currently is in through the enabled * location providers and the GeoCoder @@ -86,24 +86,23 @@ public class LocationBasedCountryDetector extends CountryDetectorBase { return country; } + protected boolean isAcceptableProvider(String provider) { + // We don't want to actively initiate a location fix here (with gps or network providers). + return LocationManager.PASSIVE_PROVIDER.equals(provider); + } + /** - * Register the listeners with the location providers + * Register a listener with a provider name */ - protected void registerEnabledProviders(List<LocationListener> listeners) { - int total = listeners.size(); - for (int i = 0; i< total; i++) { - mLocationManager.requestLocationUpdates( - mEnabledProviders.get(i), 0, 0, listeners.get(i)); - } + protected void registerListener(String provider, LocationListener listener) { + mLocationManager.requestLocationUpdates(provider, 0, 0, listener); } /** - * Unregister the listeners with the location providers + * Unregister an already registered listener */ - protected void unregisterProviders(List<LocationListener> listeners) { - for (LocationListener listener : listeners) { - mLocationManager.removeUpdates(listener); - } + protected void unregisterListener(LocationListener listener) { + mLocationManager.removeUpdates(listener); } /** @@ -130,14 +129,11 @@ public class LocationBasedCountryDetector extends CountryDetectorBase { return QUERY_LOCATION_TIMEOUT; } - /** - * @return the total number of enabled location providers - */ - protected int getTotalEnabledProviders() { + protected List<String> getEnabledProviders() { if (mEnabledProviders == null) { mEnabledProviders = mLocationManager.getProviders(true); } - return mEnabledProviders.size(); + return mEnabledProviders; } /** @@ -152,27 +148,36 @@ public class LocationBasedCountryDetector extends CountryDetectorBase { throw new IllegalStateException(); } // Request the location from all enabled providers. - int totalProviders = getTotalEnabledProviders(); + List<String> enabledProviders = getEnabledProviders(); + int totalProviders = enabledProviders.size(); if (totalProviders > 0) { mLocationListeners = new ArrayList<LocationListener>(totalProviders); for (int i = 0; i < totalProviders; i++) { - LocationListener listener = new LocationListener () { - public void onLocationChanged(Location location) { - if (location != null) { - LocationBasedCountryDetector.this.stop(); - queryCountryCode(location); + String provider = enabledProviders.get(i); + if (isAcceptableProvider(provider)) { + LocationListener listener = new LocationListener () { + @Override + public void onLocationChanged(Location location) { + if (location != null) { + LocationBasedCountryDetector.this.stop(); + queryCountryCode(location); + } } - } - public void onProviderDisabled(String provider) { - } - public void onProviderEnabled(String provider) { - } - public void onStatusChanged(String provider, int status, Bundle extras) { - } - }; - mLocationListeners.add(listener); + @Override + public void onProviderDisabled(String provider) { + } + @Override + public void onProviderEnabled(String provider) { + } + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + } + }; + mLocationListeners.add(listener); + registerListener(provider, listener); + } } - registerEnabledProviders(mLocationListeners); + mTimer = new Timer(); mTimer.schedule(new TimerTask() { @Override @@ -197,7 +202,9 @@ public class LocationBasedCountryDetector extends CountryDetectorBase { @Override public synchronized void stop() { if (mLocationListeners != null) { - unregisterProviders(mLocationListeners); + for (LocationListener listener : mLocationListeners) { + unregisterListener(listener); + } mLocationListeners = null; } if (mTimer != null) { @@ -216,6 +223,7 @@ public class LocationBasedCountryDetector extends CountryDetectorBase { } if (mQueryThread != null) return; mQueryThread = new Thread(new Runnable() { + @Override public void run() { String countryIso = null; if (location != null) { diff --git a/services/java/com/android/server/pm/BasePermission.java b/services/java/com/android/server/pm/BasePermission.java new file mode 100644 index 0000000..4f27408 --- /dev/null +++ b/services/java/com/android/server/pm/BasePermission.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.content.pm.PackageParser; +import android.content.pm.PermissionInfo; + +final class BasePermission { + final static int TYPE_NORMAL = 0; + + final static int TYPE_BUILTIN = 1; + + final static int TYPE_DYNAMIC = 2; + + final String name; + + String sourcePackage; + + PackageSettingBase packageSetting; + + final int type; + + int protectionLevel; + + PackageParser.Permission perm; + + PermissionInfo pendingInfo; + + int uid; + + int[] gids; + + BasePermission(String _name, String _sourcePackage, int _type) { + name = _name; + sourcePackage = _sourcePackage; + type = _type; + // Default to most conservative protection level. + protectionLevel = PermissionInfo.PROTECTION_SIGNATURE; + } + + public String toString() { + return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name + + "}"; + } +} diff --git a/services/java/com/android/server/pm/GrantedPermissions.java b/services/java/com/android/server/pm/GrantedPermissions.java new file mode 100644 index 0000000..c7629b9 --- /dev/null +++ b/services/java/com/android/server/pm/GrantedPermissions.java @@ -0,0 +1,50 @@ +/* + * 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.pm; + +import android.content.pm.ApplicationInfo; + +import java.util.HashSet; + +class GrantedPermissions { + int pkgFlags; + + HashSet<String> grantedPermissions = new HashSet<String>(); + + int[] gids; + + GrantedPermissions(int pkgFlags) { + setFlags(pkgFlags); + } + + @SuppressWarnings("unchecked") + GrantedPermissions(GrantedPermissions base) { + pkgFlags = base.pkgFlags; + grantedPermissions = (HashSet<String>) base.grantedPermissions.clone(); + + if (base.gids != null) { + gids = base.gids.clone(); + } + } + + void setFlags(int pkgFlags) { + this.pkgFlags = pkgFlags + & (ApplicationInfo.FLAG_SYSTEM + | ApplicationInfo.FLAG_FORWARD_LOCK + | ApplicationInfo.FLAG_EXTERNAL_STORAGE); + } +} diff --git a/services/java/com/android/server/Installer.java b/services/java/com/android/server/pm/Installer.java index 08d1b82..d10aa97 100644 --- a/services/java/com/android/server/Installer.java +++ b/services/java/com/android/server/pm/Installer.java @@ -14,28 +14,31 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.pm; import android.content.pm.PackageStats; -import android.net.LocalSocketAddress; import android.net.LocalSocket; -import android.util.Config; +import android.net.LocalSocketAddress; import android.util.Slog; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.Socket; - class Installer { private static final String TAG = "Installer"; - InputStream mIn; - OutputStream mOut; - LocalSocket mSocket; - byte buf[] = new byte[1024]; - int buflen = 0; + private static final boolean LOCAL_DEBUG = false; + + InputStream mIn; + + OutputStream mOut; + + LocalSocket mSocket; + + byte buf[] = new byte[1024]; + + int buflen = 0; private boolean connect() { if (mSocket != null) { @@ -45,8 +48,8 @@ class Installer { try { mSocket = new LocalSocket(); - LocalSocketAddress address = new LocalSocketAddress( - "installd", LocalSocketAddress.Namespace.RESERVED); + LocalSocketAddress address = new LocalSocketAddress("installd", + LocalSocketAddress.Namespace.RESERVED); mSocket.connect(address); @@ -59,112 +62,131 @@ class Installer { return true; } - private void disconnect() { - Slog.i(TAG,"disconnecting..."); - try { - if (mSocket != null) mSocket.close(); - } catch (IOException ex) { } - try { - if (mIn != null) mIn.close(); - } catch (IOException ex) { } - try { - if (mOut != null) mOut.close(); - } catch (IOException ex) { } - mSocket = null; - mIn = null; - mOut = null; - } - - private boolean readBytes(byte buffer[], int len) { - int off = 0, count; - if (len < 0) return false; - while (off != len) { - try { - count = mIn.read(buffer, off, len - off); - if (count <= 0) { + private void disconnect() { + Slog.i(TAG, "disconnecting..."); + try { + if (mSocket != null) + mSocket.close(); + } catch (IOException ex) { + } + try { + if (mIn != null) + mIn.close(); + } catch (IOException ex) { + } + try { + if (mOut != null) + mOut.close(); + } catch (IOException ex) { + } + mSocket = null; + mIn = null; + mOut = null; + } + + private boolean readBytes(byte buffer[], int len) { + int off = 0, count; + if (len < 0) + return false; + while (off != len) { + try { + count = mIn.read(buffer, off, len - off); + if (count <= 0) { Slog.e(TAG, "read error " + count); break; } - off += count; - } catch (IOException ex) { - Slog.e(TAG,"read exception"); - break; - } - } -// Slog.i(TAG, "read "+len+" bytes"); - if (off == len) return true; - disconnect(); - return false; - } - - private boolean readReply() { - int len; - buflen = 0; - if (!readBytes(buf, 2)) return false; - len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8); - if ((len < 1) || (len > 1024)) { - Slog.e(TAG,"invalid reply length ("+len+")"); - disconnect(); - return false; - } - if (!readBytes(buf, len)) return false; - buflen = len; - return true; - } - - private boolean writeCommand(String _cmd) { - byte[] cmd = _cmd.getBytes(); - int len = cmd.length; - if ((len < 1) || (len > 1024)) return false; - buf[0] = (byte) (len & 0xff); - buf[1] = (byte) ((len >> 8) & 0xff); - try { - mOut.write(buf, 0, 2); - mOut.write(cmd, 0, len); - } catch (IOException ex) { - Slog.e(TAG,"write error"); - disconnect(); - return false; - } - return true; - } - - private synchronized String transaction(String cmd) { - if (!connect()) { + off += count; + } catch (IOException ex) { + Slog.e(TAG, "read exception"); + break; + } + } + if (LOCAL_DEBUG) { + Slog.i(TAG, "read " + len + " bytes"); + } + if (off == len) + return true; + disconnect(); + return false; + } + + private boolean readReply() { + int len; + buflen = 0; + if (!readBytes(buf, 2)) + return false; + len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8); + if ((len < 1) || (len > 1024)) { + Slog.e(TAG, "invalid reply length (" + len + ")"); + disconnect(); + return false; + } + if (!readBytes(buf, len)) + return false; + buflen = len; + return true; + } + + private boolean writeCommand(String _cmd) { + byte[] cmd = _cmd.getBytes(); + int len = cmd.length; + if ((len < 1) || (len > 1024)) + return false; + buf[0] = (byte) (len & 0xff); + buf[1] = (byte) ((len >> 8) & 0xff); + try { + mOut.write(buf, 0, 2); + mOut.write(cmd, 0, len); + } catch (IOException ex) { + Slog.e(TAG, "write error"); + disconnect(); + return false; + } + return true; + } + + private synchronized String transaction(String cmd) { + if (!connect()) { Slog.e(TAG, "connection failed"); return "-1"; } if (!writeCommand(cmd)) { - /* If installd died and restarted in the background - * (unlikely but possible) we'll fail on the next - * write (this one). Try to reconnect and write - * the command one more time before giving up. - */ + /* + * If installd died and restarted in the background (unlikely but + * possible) we'll fail on the next write (this one). Try to + * reconnect and write the command one more time before giving up. + */ Slog.e(TAG, "write command failed? reconnect!"); if (!connect() || !writeCommand(cmd)) { return "-1"; } } -// Slog.i(TAG,"send: '"+cmd+"'"); - if (readReply()) { + if (LOCAL_DEBUG) { + Slog.i(TAG, "send: '" + cmd + "'"); + } + if (readReply()) { String s = new String(buf, 0, buflen); -// Slog.i(TAG,"recv: '"+s+"'"); - return s; - } else { -// Slog.i(TAG,"fail"); - return "-1"; - } - } - - private int execute(String cmd) { - String res = transaction(cmd); - try { - return Integer.parseInt(res); - } catch (NumberFormatException ex) { - return -1; - } - } + if (LOCAL_DEBUG) { + Slog.i(TAG, "recv: '" + s + "'"); + } + return s; + } else { + if (LOCAL_DEBUG) { + Slog.i(TAG, "fail"); + } + return "-1"; + } + } + + private int execute(String cmd) { + String res = transaction(cmd); + try { + return Integer.parseInt(res); + } catch (NumberFormatException ex) { + return -1; + } + } public int install(String name, int uid, int gid) { StringBuilder builder = new StringBuilder("install"); @@ -203,10 +225,12 @@ class Installer { return execute(builder.toString()); } - public int remove(String name) { + public int remove(String name, int userId) { StringBuilder builder = new StringBuilder("remove"); builder.append(' '); builder.append(name); + builder.append(' '); + builder.append(userId); return execute(builder.toString()); } @@ -225,14 +249,34 @@ class Installer { builder.append(name); return execute(builder.toString()); } - - public int clearUserData(String name) { + + public int createUserData(String name, int uid, int userId) { + StringBuilder builder = new StringBuilder("mkuserdata"); + builder.append(' '); + builder.append(name); + builder.append(' '); + builder.append(uid); + builder.append(' '); + builder.append(userId); + return execute(builder.toString()); + } + + public int removeUserDataDirs(int userId) { + StringBuilder builder = new StringBuilder("rmuser"); + builder.append(' '); + builder.append(userId); + return execute(builder.toString()); + } + + public int clearUserData(String name, int userId) { StringBuilder builder = new StringBuilder("rmuserdata"); builder.append(' '); builder.append(name); + builder.append(' '); + builder.append(userId); return execute(builder.toString()); } - + public boolean ping() { if (execute("ping") < 0) { return false; @@ -240,7 +284,7 @@ class Installer { return true; } } - + public int freeCache(long freeStorageSize) { StringBuilder builder = new StringBuilder("freecache"); builder.append(' '); @@ -250,8 +294,8 @@ class Installer { /* * @param packagePathSuffix The name of the path relative to install - * directory. Say if the path name is /data/app/com.test-1.apk, - * the package suffix path will be com.test-1 + * directory. Say if the path name is /data/app/com.test-1.apk, the package + * suffix path will be com.test-1 */ public int setForwardLockPerm(String packagePathSuffix, int gid) { StringBuilder builder = new StringBuilder("protect"); @@ -261,7 +305,7 @@ class Installer { builder.append(gid); return execute(builder.toString()); } - + public int getSizeInfo(String pkgName, String apkPath, String fwdLockApkPath, PackageStats pStats) { StringBuilder builder = new StringBuilder("getsize"); @@ -275,7 +319,7 @@ class Installer { String s = transaction(builder.toString()); String res[] = s.split(" "); - if((res == null) || (res.length != 4)) { + if ((res == null) || (res.length != 4)) { return -1; } try { @@ -286,7 +330,7 @@ class Installer { } catch (NumberFormatException e) { return -1; } - } + } public int moveFiles() { return execute("movefiles"); diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index 4a66a40..de439ca 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -14,26 +14,30 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.pm; + +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import com.android.internal.app.IMediaContainerService; import com.android.internal.app.ResolverActivity; import com.android.internal.content.NativeLibraryHelper; import com.android.internal.content.PackageHelper; -import com.android.internal.util.FastXmlSerializer; -import com.android.internal.util.JournaledFile; import com.android.internal.util.XmlUtils; +import com.android.server.DeviceStorageMonitorService; +import com.android.server.EventLogTags; +import com.android.server.IntentResolver; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; import android.app.ActivityManagerNative; import android.app.IActivityManager; import android.app.admin.IDevicePolicyManager; import android.app.backup.IBackupManager; -import android.content.Context; import android.content.ComponentName; +import android.content.Context; import android.content.IIntentReceiver; import android.content.Intent; import android.content.IntentFilter; @@ -42,7 +46,6 @@ import android.content.ServiceConnection; import android.content.IntentSender.SendIntentException; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; -import android.content.pm.ComponentInfo; import android.content.pm.FeatureInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; @@ -54,43 +57,45 @@ import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageManager; -import android.content.pm.PackageStats; -import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; -import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; -import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import android.content.pm.PackageParser; -import android.content.pm.PermissionInfo; +import android.content.pm.PackageStats; import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.Signature; +import android.content.pm.UserInfo; import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; -import android.os.Debug; +import android.os.Environment; +import android.os.FileObserver; +import android.os.FileUtils; +import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Parcel; -import android.os.RemoteException; -import android.os.Environment; -import android.os.FileObserver; -import android.os.FileUtils; -import android.os.Handler; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.security.SystemKeyStore; -import android.util.*; +import android.util.DisplayMetrics; +import android.util.EventLog; +import android.util.Log; +import android.util.LogPrinter; +import android.util.Slog; +import android.util.SparseArray; +import android.util.Xml; import android.view.Display; import android.view.WindowManager; -import java.io.BufferedOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -129,25 +134,30 @@ import java.util.zip.ZipOutputStream; mmm frameworks/base/tests/AndroidTests adb install -r -f out/target/product/passion/data/app/AndroidTests.apk adb shell am instrument -w -e class com.android.unit_tests.PackageManagerTests com.android.unit_tests/android.test.InstrumentationTestRunner - * + * + * {@hide} */ -class PackageManagerService extends IPackageManager.Stub { - private static final String TAG = "PackageManager"; - private static final boolean DEBUG_SETTINGS = false; +public class PackageManagerService extends IPackageManager.Stub { + static final String TAG = "PackageManager"; + static final boolean DEBUG_SETTINGS = false; private static final boolean DEBUG_PREFERRED = false; - private static final boolean DEBUG_UPGRADE = false; + static final boolean DEBUG_UPGRADE = false; private static final boolean DEBUG_INSTALL = false; - private static final boolean DEBUG_STOPPED = false; - - private static final boolean MULTIPLE_APPLICATION_UIDS = true; + private static final boolean DEBUG_REMOVE = false; + private static final boolean DEBUG_SHOW_INFO = false; + private static final boolean DEBUG_PACKAGE_INFO = false; + private static final boolean DEBUG_INTENT_MATCHING = false; + private static final boolean DEBUG_PACKAGE_SCANNING = false; + private static final boolean DEBUG_APP_DIR_OBSERVER = false; + + static final boolean MULTIPLE_APPLICATION_UIDS = true; private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; private static final int NFC_UID = Process.NFC_UID; - private static final int FIRST_APPLICATION_UID = + private static final int KEYCHAIN_UID = Process.KEYCHAIN_UID; + static final int FIRST_APPLICATION_UID = Process.FIRST_APPLICATION_UID; - private static final int MAX_APPLICATION_UIDS = 1000; - - private static final boolean SHOW_INFO = false; + static final int MAX_APPLICATION_UIDS = 1000; private static final boolean GET_CERTIFICATES = true; @@ -161,19 +171,6 @@ class PackageManagerService extends IPackageManager.Stub { // package apks to install directory. private static final String INSTALL_PACKAGE_SUFFIX = "-"; - /** - * Indicates the state of installation. Used by PackageManager to - * figure out incomplete installations. Say a package is being installed - * (the state is set to PKG_INSTALL_INCOMPLETE) and remains so till - * the package installation is successful or unsuccesful lin which case - * the PackageManager will no longer maintain state information associated - * with the package. If some exception(like device freeze or battery being - * pulled out) occurs during installation of a package, the PackageManager - * needs this information to clean up the previously failed installation. - */ - private static final int PKG_INSTALL_INCOMPLETE = 0; - private static final int PKG_INSTALL_COMPLETE = 1; - static final int SCAN_MONITOR = 1<<0; static final int SCAN_NO_DEX = 1<<1; static final int SCAN_FORCE_DEX = 1<<2; @@ -212,6 +209,9 @@ class PackageManagerService extends IPackageManager.Stub { // This is where all application persistent data goes. final File mAppDataDir; + // This is where all application persistent data goes for secondary users. + final File mUserAppDataDir; + // This is the object monitoring the framework dir. final FileObserver mFrameworkInstallObserver; @@ -362,6 +362,9 @@ class PackageManagerService extends IPackageManager.Stub { // Delay time in millisecs static final int BROADCAST_DELAY = 10 * 1000; + + final UserManager mUserManager; + final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection(); class DefaultContainerConnection implements ServiceConnection { @@ -526,12 +529,12 @@ class PackageManagerService extends IPackageManager.Stub { } case MCS_GIVE_UP: { if (DEBUG_SD_INSTALL) Log.i(TAG, "mcs_giveup too many retries"); - HandlerParams params = mPendingInstalls.remove(0); + mPendingInstalls.remove(0); break; } case SEND_PENDING_BROADCAST : { String packages[]; - ArrayList components[]; + ArrayList<String> components[]; int size = 0; int uids[]; Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); @@ -563,8 +566,7 @@ class PackageManagerService extends IPackageManager.Stub { } // Send broadcasts for (int i = 0; i < size; i++) { - sendPackageChangedBroadcast(packages[i], true, - (ArrayList<String>)components[i], uids[i]); + sendPackageChangedBroadcast(packages[i], true, components[i], uids[i]); } Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); break; @@ -662,7 +664,7 @@ class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { removeMessages(WRITE_SETTINGS); removeMessages(WRITE_STOPPED_PACKAGES); - mSettings.writeLP(); + mSettings.writeLPr(); } Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } break; @@ -670,7 +672,7 @@ class PackageManagerService extends IPackageManager.Stub { Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); synchronized (mPackages) { removeMessages(WRITE_STOPPED_PACKAGES); - mSettings.writeStoppedLP(); + mSettings.writeStoppedLPr(); } Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } break; @@ -742,20 +744,24 @@ class PackageManagerService extends IPackageManager.Stub { mNoDexOpt = "eng".equals(SystemProperties.get("ro.build.type")); mMetrics = new DisplayMetrics(); mSettings = new Settings(); - mSettings.addSharedUserLP("android.uid.system", + mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM); - mSettings.addSharedUserLP("android.uid.phone", + mSettings.addSharedUserLPw("android.uid.phone", MULTIPLE_APPLICATION_UIDS ? RADIO_UID : FIRST_APPLICATION_UID, ApplicationInfo.FLAG_SYSTEM); - mSettings.addSharedUserLP("android.uid.log", + mSettings.addSharedUserLPw("android.uid.log", MULTIPLE_APPLICATION_UIDS ? LOG_UID : FIRST_APPLICATION_UID, ApplicationInfo.FLAG_SYSTEM); - mSettings.addSharedUserLP("android.uid.nfc", + mSettings.addSharedUserLPw("android.uid.nfc", MULTIPLE_APPLICATION_UIDS ? NFC_UID : FIRST_APPLICATION_UID, ApplicationInfo.FLAG_SYSTEM); + mSettings.addSharedUserLPw("android.uid.keychain", + MULTIPLE_APPLICATION_UIDS + ? KEYCHAIN_UID : FIRST_APPLICATION_UID, + ApplicationInfo.FLAG_SYSTEM); String separateProcesses = SystemProperties.get("debug.separate_processes"); if (separateProcesses != null && separateProcesses.length() > 0) { @@ -790,14 +796,18 @@ class PackageManagerService extends IPackageManager.Stub { d.getMetrics(mMetrics); synchronized (mInstallLock) { + // writer synchronized (mPackages) { mHandlerThread.start(); mHandler = new PackageHandler(mHandlerThread.getLooper()); File dataDir = Environment.getDataDirectory(); mAppDataDir = new File(dataDir, "data"); + mUserAppDataDir = new File(dataDir, "user"); mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); + mUserManager = new UserManager(mInstaller, mUserAppDataDir); + if (mInstaller == null) { // Make sure these dirs exist, when we are running in // the simulator. @@ -805,12 +815,13 @@ class PackageManagerService extends IPackageManager.Stub { File miscDir = new File(dataDir, "misc"); miscDir.mkdirs(); mAppDataDir.mkdirs(); + mUserAppDataDir.mkdirs(); mDrmAppPrivateInstallDir.mkdirs(); } readPermissions(); - mRestoredSettings = mSettings.readLP(); + mRestoredSettings = mSettings.readLPw(); long startTime = SystemClock.uptimeMillis(); EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START, @@ -973,7 +984,8 @@ class PackageManagerService extends IPackageManager.Stub { + " no longer exists; wiping its data"; reportSettingsProblem(Log.WARN, msg); if (mInstaller != null) { - mInstaller.remove(ps.name); + mInstaller.remove(ps.name, 0); + mUserManager.removePackageForAllUsers(ps.name); } } } @@ -985,7 +997,7 @@ class PackageManagerService extends IPackageManager.Stub { mAppInstallDir.mkdirs(); // scanDirLI() assumes this dir exists } //look for any incomplete package installations - ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackages(); + ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackagesLPr(); //clean up list for(int i = 0; i < deletePkgsList.size(); i++) { //clean up here @@ -1026,9 +1038,10 @@ class PackageManagerService extends IPackageManager.Stub { + "; regranting permissions for internal storage"); mSettings.mInternalSdkPlatform = mSdkVersion; - updatePermissionsLP(null, null, true, regrantPermissions, regrantPermissions); + updatePermissionsLPw(null, null, true, regrantPermissions, regrantPermissions); - mSettings.writeLP(); + // can downgrade to reader + mSettings.writeLPr(); EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY, SystemClock.uptimeMillis()); @@ -1057,10 +1070,12 @@ class PackageManagerService extends IPackageManager.Stub { void cleanupInstallFailedPackage(PackageSetting ps) { Slog.i(TAG, "Cleaning up incompletely installed app: " + ps.name); if (mInstaller != null) { - int retCode = mInstaller.remove(ps.name); + int retCode = mInstaller.remove(ps.name, 0); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data directory for package: " + ps.name + ", retcode=" + retCode); + } else { + mUserManager.removePackageForAllUsers(ps.name); } } else { //for emulator @@ -1078,7 +1093,7 @@ class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Unable to remove old code file: " + ps.resourcePath); } } - mSettings.removePackageLP(ps.name); + mSettings.removePackageLPw(ps.name); } void readPermissions() { @@ -1345,16 +1360,16 @@ class PackageManagerService extends IPackageManager.Stub { } public PackageInfo getPackageInfo(String packageName, int flags) { + // reader synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); - if (Config.LOGV) Log.v( - TAG, "getPackageInfo " + packageName - + ": " + p); + if (DEBUG_PACKAGE_INFO) + Log.v(TAG, "getPackageInfo " + packageName + ": " + p); if (p != null) { return generatePackageInfo(p, flags); } if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { - return generatePackageInfoFromSettingsLP(packageName, flags); + return generatePackageInfoFromSettingsLPw(packageName, flags); } } return null; @@ -1362,6 +1377,7 @@ class PackageManagerService extends IPackageManager.Stub { public String[] currentToCanonicalPackageNames(String[] names) { String[] out = new String[names.length]; + // reader synchronized (mPackages) { for (int i=names.length-1; i>=0; i--) { PackageSetting ps = mSettings.mPackages.get(names[i]); @@ -1373,6 +1389,7 @@ class PackageManagerService extends IPackageManager.Stub { public String[] canonicalToCurrentPackageNames(String[] names) { String[] out = new String[names.length]; + // reader synchronized (mPackages) { for (int i=names.length-1; i>=0; i--) { String cur = mSettings.mRenamedPackages.get(names[i]); @@ -1383,6 +1400,7 @@ class PackageManagerService extends IPackageManager.Stub { } public int getPackageUid(String packageName) { + // reader synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); if(p != null) { @@ -1398,11 +1416,11 @@ class PackageManagerService extends IPackageManager.Stub { } public int[] getPackageGids(String packageName) { + // reader synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); - if (Config.LOGV) Log.v( - TAG, "getPackageGids" + packageName - + ": " + p); + if (DEBUG_PACKAGE_INFO) + Log.v(TAG, "getPackageGids" + packageName + ": " + p); if (p != null) { final PackageSetting ps = (PackageSetting)p.mExtras; final SharedUserSetting suid = ps.sharedUser; @@ -1427,6 +1445,7 @@ class PackageManagerService extends IPackageManager.Stub { } public PermissionInfo getPermissionInfo(String name, int flags) { + // reader synchronized (mPackages) { final BasePermission p = mSettings.mPermissions.get(name); if (p != null) { @@ -1437,6 +1456,7 @@ class PackageManagerService extends IPackageManager.Stub { } public List<PermissionInfo> queryPermissionsByGroup(String group, int flags) { + // reader synchronized (mPackages) { ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10); for (BasePermission p : mSettings.mPermissions.values()) { @@ -1459,6 +1479,7 @@ class PackageManagerService extends IPackageManager.Stub { } public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) { + // reader synchronized (mPackages) { return PackageParser.generatePermissionGroupInfo( mPermissionGroups.get(name), flags); @@ -1466,6 +1487,7 @@ class PackageManagerService extends IPackageManager.Stub { } public List<PermissionGroupInfo> getAllPermissionGroups(int flags) { + // reader synchronized (mPackages) { final int N = mPermissionGroups.size(); ArrayList<PermissionGroupInfo> out @@ -1477,12 +1499,12 @@ class PackageManagerService extends IPackageManager.Stub { } } - private ApplicationInfo generateApplicationInfoFromSettingsLP(String packageName, int flags) { + private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags) { PackageSetting ps = mSettings.mPackages.get(packageName); - if(ps != null) { - if(ps.pkg == null) { - PackageInfo pInfo = generatePackageInfoFromSettingsLP(packageName, flags); - if(pInfo != null) { + if (ps != null) { + if (ps.pkg == null) { + PackageInfo pInfo = generatePackageInfoFromSettingsLPw(packageName, flags); + if (pInfo != null) { return pInfo.applicationInfo; } return null; @@ -1492,16 +1514,17 @@ class PackageManagerService extends IPackageManager.Stub { return null; } - private PackageInfo generatePackageInfoFromSettingsLP(String packageName, int flags) { + private PackageInfo generatePackageInfoFromSettingsLPw(String packageName, int flags) { PackageSetting ps = mSettings.mPackages.get(packageName); - if(ps != null) { - if(ps.pkg == null) { + if (ps != null) { + if (ps.pkg == null) { ps.pkg = new PackageParser.Package(packageName); ps.pkg.applicationInfo.packageName = packageName; ps.pkg.applicationInfo.flags = ps.pkgFlags; ps.pkg.applicationInfo.publicSourceDir = ps.resourcePathString; ps.pkg.applicationInfo.sourceDir = ps.codePathString; - ps.pkg.applicationInfo.dataDir = getDataPathForPackage(ps.pkg).getPath(); + ps.pkg.applicationInfo.dataDir = + getDataPathForPackage(ps.pkg.packageName, 0).getPath(); ps.pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString; ps.pkg.mSetEnabled = ps.enabled; ps.pkg.mSetStopped = ps.stopped; @@ -1512,9 +1535,10 @@ class PackageManagerService extends IPackageManager.Stub { } public ApplicationInfo getApplicationInfo(String packageName, int flags) { + // writer synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); - if (Config.LOGV) Log.v( + if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getApplicationInfo " + packageName + ": " + p); if (p != null) { @@ -1525,7 +1549,7 @@ class PackageManagerService extends IPackageManager.Stub { return mAndroidApplication; } if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { - return generateApplicationInfoFromSettingsLP(packageName, flags); + return generateApplicationInfoFromSettingsLPw(packageName, flags); } } return null; @@ -1589,8 +1613,8 @@ class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { PackageParser.Activity a = mActivities.mActivities.get(component); - if (Config.LOGV) Log.v(TAG, "getActivityInfo " + component + ": " + a); - if (a != null && mSettings.isEnabledLP(a.info, flags)) { + if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a); + if (a != null && mSettings.isEnabledLPr(a.info, flags)) { return PackageParser.generateActivityInfo(a, flags); } if (mResolveComponentName.equals(component)) { @@ -1603,9 +1627,9 @@ class PackageManagerService extends IPackageManager.Stub { public ActivityInfo getReceiverInfo(ComponentName component, int flags) { synchronized (mPackages) { PackageParser.Activity a = mReceivers.mActivities.get(component); - if (Config.LOGV) Log.v( + if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getReceiverInfo " + component + ": " + a); - if (a != null && mSettings.isEnabledLP(a.info, flags)) { + if (a != null && mSettings.isEnabledLPr(a.info, flags)) { return PackageParser.generateActivityInfo(a, flags); } } @@ -1615,9 +1639,9 @@ class PackageManagerService extends IPackageManager.Stub { public ServiceInfo getServiceInfo(ComponentName component, int flags) { synchronized (mPackages) { PackageParser.Service s = mServices.mServices.get(component); - if (Config.LOGV) Log.v( + if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getServiceInfo " + component + ": " + s); - if (s != null && mSettings.isEnabledLP(s.info, flags)) { + if (s != null && mSettings.isEnabledLPr(s.info, flags)) { return PackageParser.generateServiceInfo(s, flags); } } @@ -1627,9 +1651,9 @@ class PackageManagerService extends IPackageManager.Stub { public ProviderInfo getProviderInfo(ComponentName component, int flags) { synchronized (mPackages) { PackageParser.Provider p = mProvidersByComponent.get(component); - if (Config.LOGV) Log.v( + if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getProviderInfo " + component + ": " + p); - if (p != null && mSettings.isEnabledLP(p.info, flags)) { + if (p != null && mSettings.isEnabledLPr(p.info, flags)) { return PackageParser.generateProviderInfo(p, flags); } } @@ -1693,7 +1717,7 @@ class PackageManagerService extends IPackageManager.Stub { public int checkUidPermission(String permName, int uid) { synchronized (mPackages) { - Object obj = mSettings.getUserIdLP(uid); + Object obj = mSettings.getUserIdLPr(uid); if (obj != null) { GrantedPermissions gp = (GrantedPermissions)obj; if (gp.grantedPermissions.contains(permName)) { @@ -1798,7 +1822,7 @@ class PackageManagerService extends IPackageManager.Stub { } if (changed) { if (!async) { - mSettings.writeLP(); + mSettings.writeLPr(); } else { scheduleWriteSettingsLocked(); } @@ -1829,7 +1853,7 @@ class PackageManagerService extends IPackageManager.Stub { + name); } mSettings.mPermissions.remove(name); - mSettings.writeLP(); + mSettings.writeLPr(); } } } @@ -1842,21 +1866,22 @@ class PackageManagerService extends IPackageManager.Stub { public int checkSignatures(String pkg1, String pkg2) { synchronized (mPackages) { - PackageParser.Package p1 = mPackages.get(pkg1); - PackageParser.Package p2 = mPackages.get(pkg2); + final PackageParser.Package p1 = mPackages.get(pkg1); + final PackageParser.Package p2 = mPackages.get(pkg2); if (p1 == null || p1.mExtras == null || p2 == null || p2.mExtras == null) { return PackageManager.SIGNATURE_UNKNOWN_PACKAGE; } - return checkSignaturesLP(p1.mSignatures, p2.mSignatures); + return compareSignatures(p1.mSignatures, p2.mSignatures); } } public int checkUidSignatures(int uid1, int uid2) { + // reader synchronized (mPackages) { Signature[] s1; Signature[] s2; - Object obj = mSettings.getUserIdLP(uid1); + Object obj = mSettings.getUserIdLPr(uid1); if (obj != null) { if (obj instanceof SharedUserSetting) { s1 = ((SharedUserSetting)obj).signatures.mSignatures; @@ -1868,7 +1893,7 @@ class PackageManagerService extends IPackageManager.Stub { } else { return PackageManager.SIGNATURE_UNKNOWN_PACKAGE; } - obj = mSettings.getUserIdLP(uid2); + obj = mSettings.getUserIdLPr(uid2); if (obj != null) { if (obj instanceof SharedUserSetting) { s2 = ((SharedUserSetting)obj).signatures.mSignatures; @@ -1880,11 +1905,11 @@ class PackageManagerService extends IPackageManager.Stub { } else { return PackageManager.SIGNATURE_UNKNOWN_PACKAGE; } - return checkSignaturesLP(s1, s2); + return compareSignatures(s1, s2); } } - int checkSignaturesLP(Signature[] s1, Signature[] s2) { + static int compareSignatures(Signature[] s1, Signature[] s2) { if (s1 == null) { return s2 == null ? PackageManager.SIGNATURE_NEITHER_SIGNED @@ -1909,20 +1934,21 @@ class PackageManagerService extends IPackageManager.Stub { } public String[] getPackagesForUid(int uid) { + // reader synchronized (mPackages) { - Object obj = mSettings.getUserIdLP(uid); + Object obj = mSettings.getUserIdLPr(uid); if (obj instanceof SharedUserSetting) { - SharedUserSetting sus = (SharedUserSetting)obj; + final SharedUserSetting sus = (SharedUserSetting) obj; final int N = sus.packages.size(); - String[] res = new String[N]; - Iterator<PackageSetting> it = sus.packages.iterator(); - int i=0; + final String[] res = new String[N]; + final Iterator<PackageSetting> it = sus.packages.iterator(); + int i = 0; while (it.hasNext()) { res[i++] = it.next().name; } return res; } else if (obj instanceof PackageSetting) { - PackageSetting ps = (PackageSetting)obj; + final PackageSetting ps = (PackageSetting) obj; return new String[] { ps.name }; } } @@ -1930,13 +1956,14 @@ class PackageManagerService extends IPackageManager.Stub { } public String getNameForUid(int uid) { + // reader synchronized (mPackages) { - Object obj = mSettings.getUserIdLP(uid); + Object obj = mSettings.getUserIdLPr(uid); if (obj instanceof SharedUserSetting) { - SharedUserSetting sus = (SharedUserSetting)obj; + final SharedUserSetting sus = (SharedUserSetting) obj; return sus.name + ":" + sus.userId; } else if (obj instanceof PackageSetting) { - PackageSetting ps = (PackageSetting)obj; + final PackageSetting ps = (PackageSetting) obj; return ps.name; } } @@ -1947,8 +1974,9 @@ class PackageManagerService extends IPackageManager.Stub { if(sharedUserName == null) { return -1; } + // reader synchronized (mPackages) { - SharedUserSetting suid = mSettings.getSharedUserLP(sharedUserName, 0, false); + final SharedUserSetting suid = mSettings.getSharedUserLPw(sharedUserName, 0, false); if(suid == null) { return -1; } @@ -1973,11 +2001,9 @@ class PackageManagerService extends IPackageManager.Stub { // then let the user decide between them. ResolveInfo r0 = query.get(0); ResolveInfo r1 = query.get(1); - if (false) { - System.out.println(r0.activityInfo.name + - "=" + r0.priority + " vs " + - r1.activityInfo.name + - "=" + r1.priority); + if (DEBUG_INTENT_MATCHING) { + Log.d(TAG, r0.activityInfo.name + "=" + r0.priority + " vs " + + r1.activityInfo.name + "=" + r1.priority); } // If the first activity has a higher priority, or a different // default, then it is always desireable to pick it. @@ -2001,6 +2027,7 @@ class PackageManagerService extends IPackageManager.Stub { ResolveInfo findPreferredActivity(Intent intent, String resolvedType, int flags, List<ResolveInfo> query, int priority) { + // writer synchronized (mPackages) { if (DEBUG_PREFERRED) intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION); List<PreferredActivity> prefs = @@ -2011,24 +2038,35 @@ class PackageManagerService extends IPackageManager.Stub { // We will only allow preferred activities that came // from the same match quality. int match = 0; + + if (DEBUG_PREFERRED) { + Log.v(TAG, "Figuring out best match..."); + } + final int N = query.size(); - if (DEBUG_PREFERRED) Log.v(TAG, "Figuring out best match..."); for (int j=0; j<N; j++) { - ResolveInfo ri = query.get(j); - if (DEBUG_PREFERRED) Log.v(TAG, "Match for " + ri.activityInfo - + ": 0x" + Integer.toHexString(match)); - if (ri.match > match) match = ri.match; + final ResolveInfo ri = query.get(j); + if (DEBUG_PREFERRED) { + Log.v(TAG, "Match for " + ri.activityInfo + ": 0x" + + Integer.toHexString(match)); + } + if (ri.match > match) { + match = ri.match; + } + } + + if (DEBUG_PREFERRED) { + Log.v(TAG, "Best match: 0x" + Integer.toHexString(match)); } - if (DEBUG_PREFERRED) Log.v(TAG, "Best match: 0x" - + Integer.toHexString(match)); + match &= IntentFilter.MATCH_CATEGORY_MASK; final int M = prefs.size(); for (int i=0; i<M; i++) { - PreferredActivity pa = prefs.get(i); + final PreferredActivity pa = prefs.get(i); if (pa.mPref.mMatch != match) { continue; } - ActivityInfo ai = getActivityInfo(pa.mPref.mComponent, flags); + final ActivityInfo ai = getActivityInfo(pa.mPref.mComponent, flags); if (DEBUG_PREFERRED) { Log.v(TAG, "Got preferred activity:"); if (ai != null) { @@ -2039,7 +2077,7 @@ class PackageManagerService extends IPackageManager.Stub { } if (ai != null) { for (int j=0; j<N; j++) { - ResolveInfo ri = query.get(j); + final ResolveInfo ri = query.get(j); if (!ri.activityInfo.applicationInfo.packageName .equals(ai.applicationInfo.packageName)) { continue; @@ -2071,28 +2109,28 @@ class PackageManagerService extends IPackageManager.Stub { public List<ResolveInfo> queryIntentActivities(Intent intent, String resolvedType, int flags) { - ComponentName comp = intent.getComponent(); + final ComponentName comp = intent.getComponent(); if (comp != null) { - List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); - ActivityInfo ai = getActivityInfo(comp, flags); + final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); + final ActivityInfo ai = getActivityInfo(comp, flags); if (ai != null) { - ResolveInfo ri = new ResolveInfo(); + final ResolveInfo ri = new ResolveInfo(); ri.activityInfo = ai; list.add(ri); } return list; } + // reader synchronized (mPackages) { - String pkgName = intent.getPackage(); + final String pkgName = intent.getPackage(); if (pkgName == null) { - return (List<ResolveInfo>)mActivities.queryIntent(intent, - resolvedType, flags); + return mActivities.queryIntent(intent, resolvedType, flags); } - PackageParser.Package pkg = mPackages.get(pkgName); + final PackageParser.Package pkg = mPackages.get(pkgName); if (pkg != null) { - return (List<ResolveInfo>) mActivities.queryIntentForPackage(intent, - resolvedType, flags, pkg.activities); + return mActivities.queryIntentForPackage(intent, resolvedType, flags, + pkg.activities); } return new ArrayList<ResolveInfo>(); } @@ -2103,9 +2141,12 @@ class PackageManagerService extends IPackageManager.Stub { String resolvedType, int flags) { final String resultsAction = intent.getAction(); - List<ResolveInfo> results = queryIntentActivities( - intent, resolvedType, flags|PackageManager.GET_RESOLVED_FILTER); - if (Config.LOGV) Log.v(TAG, "Query " + intent + ": " + results); + List<ResolveInfo> results = queryIntentActivities(intent, resolvedType, flags + | PackageManager.GET_RESOLVED_FILTER); + + if (DEBUG_INTENT_MATCHING) { + Log.v(TAG, "Query " + intent + ": " + results); + } int specificsPos = 0; int N; @@ -2125,16 +2166,21 @@ class PackageManagerService extends IPackageManager.Stub { continue; } - if (Config.LOGV) Log.v(TAG, "Specific #" + i + ": " + sintent); + if (DEBUG_INTENT_MATCHING) { + Log.v(TAG, "Specific #" + i + ": " + sintent); + } + String action = sintent.getAction(); if (resultsAction != null && resultsAction.equals(action)) { // If this action was explicitly requested, then don't // remove things that have it. action = null; } - ComponentName comp = sintent.getComponent(); + ResolveInfo ri = null; ActivityInfo ai = null; + + ComponentName comp = sintent.getComponent(); if (comp == null) { ri = resolveIntent( sintent, @@ -2158,7 +2204,7 @@ class PackageManagerService extends IPackageManager.Stub { // Look for any generic query activities that are duplicates // of this specific one, and remove them from the results. - if (Config.LOGV) Log.v(TAG, "Specific #" + i + ": " + ai); + if (DEBUG_INTENT_MATCHING) Log.v(TAG, "Specific #" + i + ": " + ai); N = results.size(); int j; for (j=specificsPos; j<N; j++) { @@ -2168,7 +2214,7 @@ class PackageManagerService extends IPackageManager.Stub { comp.getPackageName())) || (action != null && sri.filter.matchAction(action))) { results.remove(j); - if (Config.LOGV) Log.v( + if (DEBUG_INTENT_MATCHING) Log.v( TAG, "Removing duplicate item from " + j + " due to specific " + specificsPos); if (ri == null) { @@ -2216,7 +2262,7 @@ class PackageManagerService extends IPackageManager.Stub { final ResolveInfo rij = results.get(j); if (rij.filter != null && rij.filter.hasAction(action)) { results.remove(j); - if (Config.LOGV) Log.v( + if (DEBUG_INTENT_MATCHING) Log.v( TAG, "Removing duplicate item from " + j + " due to action " + action + " at " + i); j--; @@ -2255,12 +2301,11 @@ class PackageManagerService extends IPackageManager.Stub { } } - if (Config.LOGV) Log.v(TAG, "Result: " + results); + if (DEBUG_INTENT_MATCHING) Log.v(TAG, "Result: " + results); return results; } - public List<ResolveInfo> queryIntentReceivers(Intent intent, - String resolvedType, int flags) { + public List<ResolveInfo> queryIntentReceivers(Intent intent, String resolvedType, int flags) { ComponentName comp = intent.getComponent(); if (comp != null) { List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); @@ -2273,25 +2318,22 @@ class PackageManagerService extends IPackageManager.Stub { return list; } + // reader synchronized (mPackages) { String pkgName = intent.getPackage(); if (pkgName == null) { - return (List<ResolveInfo>)mReceivers.queryIntent(intent, - resolvedType, flags); + return mReceivers.queryIntent(intent, resolvedType, flags); } - PackageParser.Package pkg = mPackages.get(pkgName); + final PackageParser.Package pkg = mPackages.get(pkgName); if (pkg != null) { - return (List<ResolveInfo>) mReceivers.queryIntentForPackage(intent, - resolvedType, flags, pkg.receivers); + return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers); } return null; } } - public ResolveInfo resolveService(Intent intent, String resolvedType, - int flags) { - List<ResolveInfo> query = queryIntentServices(intent, resolvedType, - flags); + public ResolveInfo resolveService(Intent intent, String resolvedType, int flags) { + List<ResolveInfo> query = queryIntentServices(intent, resolvedType, flags); if (query != null) { if (query.size() >= 1) { // If there is more than one service with the same priority, @@ -2302,56 +2344,54 @@ class PackageManagerService extends IPackageManager.Stub { return null; } - public List<ResolveInfo> queryIntentServices(Intent intent, - String resolvedType, int flags) { - ComponentName comp = intent.getComponent(); + public List<ResolveInfo> queryIntentServices(Intent intent, String resolvedType, int flags) { + final ComponentName comp = intent.getComponent(); if (comp != null) { - List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); - ServiceInfo si = getServiceInfo(comp, flags); + final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); + final ServiceInfo si = getServiceInfo(comp, flags); if (si != null) { - ResolveInfo ri = new ResolveInfo(); + final ResolveInfo ri = new ResolveInfo(); ri.serviceInfo = si; list.add(ri); } return list; } + // reader synchronized (mPackages) { String pkgName = intent.getPackage(); if (pkgName == null) { - return (List<ResolveInfo>)mServices.queryIntent(intent, - resolvedType, flags); + return mServices.queryIntent(intent, resolvedType, flags); } - PackageParser.Package pkg = mPackages.get(pkgName); + final PackageParser.Package pkg = mPackages.get(pkgName); if (pkg != null) { - return (List<ResolveInfo>)mServices.queryIntentForPackage(intent, - resolvedType, flags, pkg.services); + return mServices.queryIntentForPackage(intent, resolvedType, flags, pkg.services); } return null; } } public List<PackageInfo> getInstalledPackages(int flags) { - ArrayList<PackageInfo> finalList = new ArrayList<PackageInfo>(); + final ArrayList<PackageInfo> finalList = new ArrayList<PackageInfo>(); + // writer synchronized (mPackages) { if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { - Iterator<PackageSetting> i = mSettings.mPackages.values().iterator(); + final Iterator<PackageSetting> i = mSettings.mPackages.values().iterator(); while (i.hasNext()) { final PackageSetting ps = i.next(); - PackageInfo psPkg = generatePackageInfoFromSettingsLP(ps.name, flags); - if(psPkg != null) { + final PackageInfo psPkg = generatePackageInfoFromSettingsLPw(ps.name, flags); + if (psPkg != null) { finalList.add(psPkg); } } - } - else { - Iterator<PackageParser.Package> i = mPackages.values().iterator(); + } else { + final Iterator<PackageParser.Package> i = mPackages.values().iterator(); while (i.hasNext()) { final PackageParser.Package p = i.next(); if (p.applicationInfo != null) { - PackageInfo pi = generatePackageInfo(p, flags); - if(pi != null) { + final PackageInfo pi = generatePackageInfo(p, flags); + if (pi != null) { finalList.add(pi); } } @@ -2362,20 +2402,20 @@ class PackageManagerService extends IPackageManager.Stub { } public List<ApplicationInfo> getInstalledApplications(int flags) { - ArrayList<ApplicationInfo> finalList = new ArrayList<ApplicationInfo>(); + final ArrayList<ApplicationInfo> finalList = new ArrayList<ApplicationInfo>(); + // writer synchronized(mPackages) { if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { - Iterator<PackageSetting> i = mSettings.mPackages.values().iterator(); + final Iterator<PackageSetting> i = mSettings.mPackages.values().iterator(); while (i.hasNext()) { final PackageSetting ps = i.next(); - ApplicationInfo ai = generateApplicationInfoFromSettingsLP(ps.name, flags); + ApplicationInfo ai = generateApplicationInfoFromSettingsLPw(ps.name, flags); if(ai != null) { finalList.add(ai); } } - } - else { - Iterator<PackageParser.Package> i = mPackages.values().iterator(); + } else { + final Iterator<PackageParser.Package> i = mPackages.values().iterator(); while (i.hasNext()) { final PackageParser.Package p = i.next(); if (p.applicationInfo != null) { @@ -2391,12 +2431,13 @@ class PackageManagerService extends IPackageManager.Stub { } public List<ApplicationInfo> getPersistentApplications(int flags) { - ArrayList<ApplicationInfo> finalList = new ArrayList<ApplicationInfo>(); + final ArrayList<ApplicationInfo> finalList = new ArrayList<ApplicationInfo>(); + // reader synchronized (mPackages) { - Iterator<PackageParser.Package> i = mPackages.values().iterator(); + final Iterator<PackageParser.Package> i = mPackages.values().iterator(); while (i.hasNext()) { - PackageParser.Package p = i.next(); + final PackageParser.Package p = i.next(); if (p.applicationInfo != null && (p.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) != 0 && (!mSafeMode || isSystemApp(p))) { @@ -2409,10 +2450,11 @@ class PackageManagerService extends IPackageManager.Stub { } public ProviderInfo resolveContentProvider(String name, int flags) { + // reader synchronized (mPackages) { final PackageParser.Provider provider = mProviders.get(name); return provider != null - && mSettings.isEnabledLP(provider.info, flags) + && mSettings.isEnabledLPr(provider.info, flags) && (!mSafeMode || (provider.info.applicationInfo.flags &ApplicationInfo.FLAG_SYSTEM) != 0) ? PackageParser.generateProviderInfo(provider, flags) @@ -2423,10 +2465,12 @@ class PackageManagerService extends IPackageManager.Stub { /** * @deprecated */ - public void querySyncProviders(List outNames, List outInfo) { + @Deprecated + public void querySyncProviders(List<String> outNames, List<ProviderInfo> outInfo) { + // reader synchronized (mPackages) { - Iterator<Map.Entry<String, PackageParser.Provider>> i - = mProviders.entrySet().iterator(); + final Iterator<Map.Entry<String, PackageParser.Provider>> i = mProviders.entrySet() + .iterator(); while (i.hasNext()) { Map.Entry<String, PackageParser.Provider> entry = i.next(); @@ -2446,22 +2490,22 @@ class PackageManagerService extends IPackageManager.Stub { int uid, int flags) { ArrayList<ProviderInfo> finalList = null; + // reader synchronized (mPackages) { - Iterator<PackageParser.Provider> i = mProvidersByComponent.values().iterator(); + final Iterator<PackageParser.Provider> i = mProvidersByComponent.values().iterator(); while (i.hasNext()) { - PackageParser.Provider p = i.next(); + final PackageParser.Provider p = i.next(); if (p.info.authority != null - && (processName == null || - (p.info.processName.equals(processName) - && p.info.applicationInfo.uid == uid)) - && mSettings.isEnabledLP(p.info, flags) - && (!mSafeMode || (p.info.applicationInfo.flags - &ApplicationInfo.FLAG_SYSTEM) != 0)) { + && (processName == null + || (p.info.processName.equals(processName) + && p.info.applicationInfo.uid == 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)); } } } @@ -2475,6 +2519,7 @@ class PackageManagerService extends IPackageManager.Stub { public InstrumentationInfo getInstrumentationInfo(ComponentName name, int flags) { + // reader synchronized (mPackages) { final PackageParser.Instrumentation i = mInstrumentation.get(name); return PackageParser.generateInstrumentationInfo(i, flags); @@ -2486,10 +2531,11 @@ class PackageManagerService extends IPackageManager.Stub { ArrayList<InstrumentationInfo> finalList = new ArrayList<InstrumentationInfo>(); + // reader synchronized (mPackages) { - Iterator<PackageParser.Instrumentation> i = mInstrumentation.values().iterator(); + final Iterator<PackageParser.Instrumentation> i = mInstrumentation.values().iterator(); while (i.hasNext()) { - PackageParser.Instrumentation p = i.next(); + final PackageParser.Instrumentation p = i.next(); if (targetPackage == null || targetPackage.equals(p.info.targetPackage)) { finalList.add(PackageParser.generateInstrumentationInfo(p, @@ -2508,7 +2554,7 @@ class PackageManagerService extends IPackageManager.Stub { return; } - if (false) { + if (DEBUG_PACKAGE_SCANNING) { Log.d(TAG, "Scanning app dir " + dir); } @@ -2538,7 +2584,7 @@ class PackageManagerService extends IPackageManager.Stub { return fname; } - private static void reportSettingsProblem(int priority, String msg) { + static void reportSettingsProblem(int priority, String msg) { try { File fname = getSettingsProblemFile(); FileOutputStream out = new FileOutputStream(fname, true); @@ -2602,17 +2648,18 @@ class PackageManagerService extends IPackageManager.Stub { } PackageSetting ps = null; PackageSetting updatedPkg; + // reader synchronized (mPackages) { // Look to see if we already know about this package. String oldName = mSettings.mRenamedPackages.get(pkg.packageName); if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) { // This package has been renamed to its original name. Let's // use that. - ps = mSettings.peekPackageLP(oldName); + ps = mSettings.peekPackageLPr(oldName); } // If there was no original package, see one for the real package name. if (ps == null) { - ps = mSettings.peekPackageLP(pkg.packageName); + ps = mSettings.peekPackageLPr(pkg.packageName); } // Check to see if this package could be hiding/updating a system // package. Must look for it either under the original or real @@ -2641,6 +2688,7 @@ class PackageManagerService extends IPackageManager.Stub { // At this point, its safely assumed that package installation for // apps in system partition will go through. If not there won't be a working // version of the app + // writer synchronized (mPackages) { // Just remove the loaded entries from package lists. mPackages.remove(ps.name); @@ -2652,7 +2700,7 @@ class PackageManagerService extends IPackageManager.Stub { InstallArgs args = new FileInstallArgs(ps.codePathString, ps.resourcePathString, ps.nativeLibraryPathString); args.cleanUpResourcesLI(); - mSettings.enableSystemPackageLP(ps.name); + mSettings.enableSystemPackageLPw(ps.name); } } } @@ -2710,7 +2758,7 @@ class PackageManagerService extends IPackageManager.Stub { PackageParser.Package pkg) { if (pkgSetting.signatures.mSignatures != null) { // Already existing package. Make sure signatures match - if (checkSignaturesLP(pkgSetting.signatures.mSignatures, pkg.mSignatures) != + if (compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) { Slog.e(TAG, "Package " + pkg.packageName + " signatures do not match the previously installed version; ignoring!"); @@ -2720,7 +2768,7 @@ class PackageManagerService extends IPackageManager.Stub { } // Check for shared user signatures if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) { - if (checkSignaturesLP(pkgSetting.sharedUser.signatures.mSignatures, + if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures, pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) { Slog.e(TAG, "Package " + pkg.packageName + " has no signatures that match those in shared user " @@ -2787,7 +2835,7 @@ class PackageManagerService extends IPackageManager.Stub { return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED; } - private boolean verifyPackageUpdate(PackageSetting oldPkg, PackageParser.Package newPkg) { + private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, PackageParser.Package newPkg) { if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { Slog.w(TAG, "Unable to update from " + oldPkg.name + " to " + newPkg.packageName @@ -2802,11 +2850,15 @@ class PackageManagerService extends IPackageManager.Stub { return true; } - private File getDataPathForPackage(PackageParser.Package pkg) { - final File dataPath = new File(mAppDataDir, pkg.packageName); - return dataPath; + File getDataPathForUser(int userId) { + return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId); } - + + private File getDataPathForPackage(String packageName, int userId) { + return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId + + File.separator + packageName); + } + private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags, int scanMode, long currentTime) { File scanFile = new File(pkg.mScanPath); @@ -2861,8 +2913,11 @@ class PackageManagerService extends IPackageManager.Stub { } } - if ((parseFlags&PackageParser.PARSE_CHATTY) != 0 && Config.LOGD) Log.d( - TAG, "Scanning package " + pkg.packageName); + if (DEBUG_PACKAGE_SCANNING) { + if ((parseFlags & PackageParser.PARSE_CHATTY) != 0) + Log.d(TAG, "Scanning package " + pkg.packageName); + } + if (mPackages.containsKey(pkg.packageName) || mSharedLibraries.containsKey(pkg.packageName)) { Slog.w(TAG, "Application package " + pkg.packageName @@ -2885,6 +2940,7 @@ class PackageManagerService extends IPackageManager.Stub { pkg.mAdoptPermissions = null; } + // writer synchronized (mPackages) { // Check all shared libraries and map to their actual file path. if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) { @@ -2895,7 +2951,7 @@ class PackageManagerService extends IPackageManager.Stub { int num = 0; int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0; for (int i=0; i<N; i++) { - String file = mSharedLibraries.get(pkg.usesLibraries.get(i)); + final String file = mSharedLibraries.get(pkg.usesLibraries.get(i)); if (file == null) { Slog.e(TAG, "Package " + pkg.packageName + " requires unavailable shared library " @@ -2908,7 +2964,7 @@ class PackageManagerService extends IPackageManager.Stub { } N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0; for (int i=0; i<N; i++) { - String file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i)); + final String file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i)); if (file == null) { Slog.w(TAG, "Package " + pkg.packageName + " desires unavailable shared library " @@ -2926,7 +2982,7 @@ class PackageManagerService extends IPackageManager.Stub { } if (pkg.mSharedUserId != null) { - suid = mSettings.getSharedUserLP(pkg.mSharedUserId, + suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, pkg.applicationInfo.flags, true); if (suid == null) { Slog.w(TAG, "Creating application package " + pkg.packageName @@ -2934,18 +2990,10 @@ class PackageManagerService extends IPackageManager.Stub { mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; return null; } - if ((parseFlags&PackageParser.PARSE_CHATTY) != 0 && Config.LOGD) { - Log.d(TAG, "Shared UserID " + pkg.mSharedUserId + " (uid=" - + suid.userId + "): packages=" + suid.packages); - } - } - - if (false) { - if (pkg.mOriginalPackages != null) { - Log.w(TAG, "WAITING FOR DEBUGGER"); - Debug.waitForDebugger(); - Log.i(TAG, "Package " + pkg.packageName + " from original packages" - + pkg.mOriginalPackages); + if (DEBUG_PACKAGE_SCANNING) { + if ((parseFlags & PackageParser.PARSE_CHATTY) != 0) + Log.d(TAG, "Shared UserID " + pkg.mSharedUserId + " (uid=" + suid.userId + + "): packages=" + suid.packages); } } @@ -2955,7 +3003,7 @@ class PackageManagerService extends IPackageManager.Stub { if (pkg.mOriginalPackages != null) { // This package may need to be renamed to a previously // installed name. Let's check on that... - String renamed = mSettings.mRenamedPackages.get(pkg.mRealPackage); + final String renamed = mSettings.mRenamedPackages.get(pkg.mRealPackage); if (pkg.mOriginalPackages.contains(renamed)) { // This package had originally been installed as the // original name, and we have already taken care of @@ -2971,11 +3019,11 @@ class PackageManagerService extends IPackageManager.Stub { } else { for (int i=pkg.mOriginalPackages.size()-1; i>=0; i--) { - if ((origPackage=mSettings.peekPackageLP( + if ((origPackage = mSettings.peekPackageLPr( pkg.mOriginalPackages.get(i))) != null) { // We do have the package already installed under its // original name... should we use it? - if (!verifyPackageUpdate(origPackage, pkg)) { + if (!verifyPackageUpdateLPr(origPackage, pkg)) { // New package is not compatible with original. origPackage = null; continue; @@ -3006,7 +3054,7 @@ class PackageManagerService extends IPackageManager.Stub { // Just create the setting, don't add it yet. For already existing packages // the PkgSetting exists already and doesn't have to be created. - pkgSetting = mSettings.getPackageLP(pkg, origPackage, realName, suid, destCodeFile, + pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile, destResourceFile, pkg.applicationInfo.nativeLibraryDir, pkg.applicationInfo.flags, true, false); if (pkgSetting == null) { @@ -3059,7 +3107,7 @@ class PackageManagerService extends IPackageManager.Stub { // associated with an overall shared user, which doesn't seem all // that unreasonable. if (pkgSetting.sharedUser != null) { - if (checkSignaturesLP(pkgSetting.sharedUser.signatures.mSignatures, + if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures, pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) { Log.w(TAG, "Signature mismatch for shared user : " + pkgSetting.sharedUser); mLastScanError = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; @@ -3077,7 +3125,7 @@ class PackageManagerService extends IPackageManager.Stub { // package isn't already installed, since we don't want to break // things that are installed. if ((scanMode&SCAN_NEW_INSTALL) != 0) { - int N = pkg.providers.size(); + final int N = pkg.providers.size(); int i; for (i=0; i<N; i++) { PackageParser.Provider p = pkg.providers.get(i); @@ -3098,29 +3146,28 @@ class PackageManagerService extends IPackageManager.Stub { } } } - } - final String pkgName = pkg.packageName; - - if (pkg.mAdoptPermissions != null) { - // This package wants to adopt ownership of permissions from - // another package. - for (int i=pkg.mAdoptPermissions.size()-1; i>=0; i--) { - String origName = pkg.mAdoptPermissions.get(i); - PackageSetting orig = mSettings.peekPackageLP(origName); - if (orig != null) { - if (verifyPackageUpdate(orig, pkg)) { - Slog.i(TAG, "Adopting permissions from " - + origName + " to " + pkg.packageName); - mSettings.transferPermissions(origName, pkg.packageName); + if (pkg.mAdoptPermissions != null) { + // This package wants to adopt ownership of permissions from + // another package. + for (int i = pkg.mAdoptPermissions.size() - 1; i >= 0; i--) { + final String origName = pkg.mAdoptPermissions.get(i); + final PackageSetting orig = mSettings.peekPackageLPr(origName); + if (orig != null) { + if (verifyPackageUpdateLPr(orig, pkg)) { + Slog.i(TAG, "Adopting permissions from " + origName + " to " + + pkg.packageName); + mSettings.transferPermissionsLPw(origName, pkg.packageName); + } } } } } + + final String pkgName = pkg.packageName; final long scanFileTime = scanFile.lastModified(); final boolean forceDex = (scanMode&SCAN_FORCE_DEX) != 0; - final boolean scanFileNewer = forceDex || scanFileTime != pkgSetting.timeStamp; pkg.applicationInfo.processName = fixProcessName( pkg.applicationInfo.packageName, pkg.applicationInfo.processName, @@ -3133,7 +3180,7 @@ class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.dataDir = dataPath.getPath(); } else { // This is a normal package, need to make its data directory. - dataPath = getDataPathForPackage(pkg); + dataPath = getDataPathForPackage(pkg.packageName, 0); boolean uidError = false; @@ -3149,8 +3196,11 @@ class PackageManagerService extends IPackageManager.Stub { // If this is a system app, we can at least delete its // current data so the application will still work. if (mInstaller != null) { - int ret = mInstaller.remove(pkgName); + int ret = mInstaller.remove(pkgName, 0); if (ret >= 0) { + // TODO: Kill the processes first + // Remove the data directories for all users + mUserManager.removePackageForAllUsers(pkgName); // Old data gone! String msg = "System package " + pkg.packageName + " has changed from uid: " @@ -3170,6 +3220,9 @@ class PackageManagerService extends IPackageManager.Stub { mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; return null; } + // Create data directories for all users + mUserManager.installPackageForAllUsers(pkgName, + pkg.applicationInfo.uid); } } if (!recovered) { @@ -3185,6 +3238,7 @@ class PackageManagerService extends IPackageManager.Stub { + " has mismatched uid: " + mOutPermissions[1] + " on disk, " + pkg.applicationInfo.uid + " in settings"; + // writer synchronized (mPackages) { mSettings.mReadMessages.append(msg); mSettings.mReadMessages.append('\n'); @@ -3197,17 +3251,21 @@ class PackageManagerService extends IPackageManager.Stub { } pkg.applicationInfo.dataDir = dataPath.getPath(); } else { - if ((parseFlags&PackageParser.PARSE_CHATTY) != 0 && Config.LOGV) - Log.v(TAG, "Want this data dir: " + dataPath); + if (DEBUG_PACKAGE_SCANNING) { + if ((parseFlags & PackageParser.PARSE_CHATTY) != 0) + Log.v(TAG, "Want this data dir: " + dataPath); + } //invoke installer to do the actual installation if (mInstaller != null) { int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, pkg.applicationInfo.uid); - if(ret < 0) { + if (ret < 0) { // Error from installer mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; return null; } + // Create data directories for all users + mUserManager.installPackageForAllUsers(pkgName, pkg.applicationInfo.uid); } else { dataPath.mkdirs(); if (dataPath.exists()) { @@ -3261,36 +3319,42 @@ class PackageManagerService extends IPackageManager.Stub { * only for non-system apps and system app upgrades. */ if (pkg.applicationInfo.nativeLibraryDir != null) { - final File nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir); - final String dataPathString = dataPath.getPath(); + try { + final File nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir); + final String dataPathString = dataPath.getCanonicalFile().getPath(); - if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) { - /* - * Upgrading from a previous version of the OS sometimes - * leaves native libraries in the /data/data/<app>/lib - * directory for system apps even when they shouldn't be. - * Recent changes in the JNI library search path - * necessitates we remove those to match previous behavior. - */ - if (NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryDir)) { - Log.i(TAG, "removed obsolete native libraries for system package " + path); + if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) { + /* + * Upgrading from a previous version of the OS sometimes + * leaves native libraries in the /data/data/<app>/lib + * directory for system apps even when they shouldn't be. + * Recent changes in the JNI library search path + * necessitates we remove those to match previous behavior. + */ + if (NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryDir)) { + Log.i(TAG, "removed obsolete native libraries for system package " + + path); + } + } else if (nativeLibraryDir.getCanonicalFile().getParent() + .equals(dataPathString)) { + /* + * If this is an internal application or our + * nativeLibraryPath points to our data directory, unpack + * the libraries. The native library path pointing to the + * data directory for an application in an ASEC container + * can happen for older apps that existed before an OTA to + * Gingerbread. + */ + Slog.i(TAG, "Unpacking native libraries for " + path); + mInstaller.unlinkNativeLibraryDirectory(dataPathString); + NativeLibraryHelper.copyNativeBinariesLI(scanFile, nativeLibraryDir); + } else { + Slog.i(TAG, "Linking native library dir for " + path); + mInstaller.linkNativeLibraryDirectory(dataPathString, + pkg.applicationInfo.nativeLibraryDir); } - } else if (nativeLibraryDir.getParent().equals(dataPathString)) { - /* - * If this is an internal application or our - * nativeLibraryPath points to our data directory, unpack - * the libraries. The native library path pointing to the - * data directory for an application in an ASEC container - * can happen for older apps that existed before an OTA to - * Gingerbread. - */ - Slog.i(TAG, "Unpacking native libraries for " + path); - mInstaller.unlinkNativeLibraryDirectory(dataPathString); - NativeLibraryHelper.copyNativeBinariesLI(scanFile, nativeLibraryDir); - } else { - Slog.i(TAG, "Linking native library dir for " + path); - mInstaller.linkNativeLibraryDirectory(dataPathString, - pkg.applicationInfo.nativeLibraryDir); + } catch (IOException ioe) { + Log.e(TAG, "Unable to get canonical file " + ioe.toString()); } } pkg.mScanPath = path; @@ -3316,13 +3380,14 @@ class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.uid); } + // writer synchronized (mPackages) { // We don't expect installation to fail beyond this point, if ((scanMode&SCAN_MONITOR) != 0) { mAppDirs.put(pkg.mPath, pkg); } // Add the new setting to mSettings - mSettings.insertPackageSettingLP(pkgSetting, pkg); + mSettings.insertPackageSettingLPw(pkgSetting, pkg); // Add the new setting to mPackages mPackages.put(pkg.applicationInfo.packageName, pkg); // Make sure we don't accidentally delete its data. @@ -3378,10 +3443,12 @@ class PackageManagerService extends IPackageManager.Stub { } else { p.info.authority = p.info.authority + ";" + names[j]; } - if ((parseFlags&PackageParser.PARSE_CHATTY) != 0 && Config.LOGD) - Log.d(TAG, "Registered content provider: " + names[j] + - ", className = " + p.info.name + - ", isSyncable = " + p.info.isSyncable); + if (DEBUG_PACKAGE_SCANNING) { + if ((parseFlags & PackageParser.PARSE_CHATTY) != 0) + Log.d(TAG, "Registered content provider: " + names[j] + + ", className = " + p.info.name + ", isSyncable = " + + p.info.isSyncable); + } } else { PackageParser.Provider other = mProviders.get(names[j]); Slog.w(TAG, "Skipping provider name " + names[j] + @@ -3402,7 +3469,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Providers: " + r); + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Providers: " + r); } N = pkg.services.size(); @@ -3422,7 +3489,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Services: " + r); + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Services: " + r); } N = pkg.receivers.size(); @@ -3442,7 +3509,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Receivers: " + r); + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Receivers: " + r); } N = pkg.activities.size(); @@ -3462,7 +3529,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Activities: " + r); + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Activities: " + r); } N = pkg.permissionGroups.size(); @@ -3496,7 +3563,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Permission Groups: " + r); + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Permission Groups: " + r); } N = pkg.permissions.size(); @@ -3561,7 +3628,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Permissions: " + r); + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Permissions: " + r); } N = pkg.instrumentation.size(); @@ -3584,7 +3651,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Instrumentation: " + r); + if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Instrumentation: " + r); } if (pkg.protectedBroadcasts != null) { @@ -3613,25 +3680,15 @@ class PackageManagerService extends IPackageManager.Stub { } } - // Return the path of the directory that will contain the native binaries - // of a given installed package. This is relative to the data path. - // - private File getNativeBinaryDirForPackage(PackageParser.Package pkg) { - final String nativeLibraryDir = pkg.applicationInfo.nativeLibraryDir; - if (nativeLibraryDir != null) { - return new File(nativeLibraryDir); - } else { - // Fall back for old packages - return new File(pkg.applicationInfo.dataDir, LIB_DIR_NAME); - } - } - void removePackageLI(PackageParser.Package pkg, boolean chatty) { - if (chatty && Config.LOGD) Log.d( - TAG, "Removing package " + pkg.applicationInfo.packageName ); + if (DEBUG_INSTALL) { + if (chatty) + Log.d(TAG, "Removing package " + pkg.applicationInfo.packageName); + } + // writer synchronized (mPackages) { - clearPackagePreferredActivitiesLP(pkg.packageName); + clearPackagePreferredActivitiesLPw(pkg.packageName); mPackages.remove(pkg.applicationInfo.packageName); if (pkg.mPath != null) { @@ -3657,10 +3714,12 @@ class PackageManagerService extends IPackageManager.Stub { for (int j = 0; j < names.length; j++) { if (mProviders.get(names[j]) == p) { mProviders.remove(names[j]); - if (chatty && Config.LOGD) Log.d( - TAG, "Unregistered content provider: " + names[j] + - ", className = " + p.info.name + - ", isSyncable = " + p.info.isSyncable); + if (DEBUG_REMOVE) { + if (chatty) + Log.d(TAG, "Unregistered content provider: " + names[j] + + ", className = " + p.info.name + ", isSyncable = " + + p.info.isSyncable); + } } } if (chatty) { @@ -3673,7 +3732,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Providers: " + r); + if (DEBUG_REMOVE) Log.d(TAG, " Providers: " + r); } N = pkg.services.size(); @@ -3691,7 +3750,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Services: " + r); + if (DEBUG_REMOVE) Log.d(TAG, " Services: " + r); } N = pkg.receivers.size(); @@ -3709,7 +3768,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Receivers: " + r); + if (DEBUG_REMOVE) Log.d(TAG, " Receivers: " + r); } N = pkg.activities.size(); @@ -3727,17 +3786,15 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Activities: " + r); + if (DEBUG_REMOVE) Log.d(TAG, " Activities: " + r); } N = pkg.permissions.size(); r = null; for (i=0; i<N; i++) { PackageParser.Permission p = pkg.permissions.get(i); - boolean tree = false; BasePermission bp = mSettings.mPermissions.get(p.info.name); if (bp == null) { - tree = true; bp = mSettings.mPermissionTrees.get(p.info.name); } if (bp != null && bp.perm == p) { @@ -3753,7 +3810,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Permissions: " + r); + if (DEBUG_REMOVE) Log.d(TAG, " Permissions: " + r); } N = pkg.instrumentation.size(); @@ -3771,7 +3828,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (r != null) { - if (Config.LOGD) Log.d(TAG, " Instrumentation: " + r); + if (DEBUG_REMOVE) Log.d(TAG, " Instrumentation: " + r); } } } @@ -3789,14 +3846,13 @@ class PackageManagerService extends IPackageManager.Stub { return false; } - private void updatePermissionsLP(String changingPkg, + private void updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo, boolean grantPermissions, boolean replace, boolean replaceAll) { // Make sure there are no dangling permission trees. - Iterator<BasePermission> it = mSettings.mPermissionTrees - .values().iterator(); + Iterator<BasePermission> it = mSettings.mPermissionTrees.values().iterator(); while (it.hasNext()) { - BasePermission bp = it.next(); + final BasePermission bp = it.next(); if (bp.packageSetting == null) { // We may not yet have parsed the package, so just see if // we still know about its settings. @@ -3820,13 +3876,13 @@ class PackageManagerService extends IPackageManager.Stub { // and make sure there are no dangling permissions. it = mSettings.mPermissions.values().iterator(); while (it.hasNext()) { - BasePermission bp = it.next(); + final BasePermission bp = it.next(); if (bp.type == BasePermission.TYPE_DYNAMIC) { if (DEBUG_SETTINGS) Log.v(TAG, "Dynamic permission: name=" + bp.name + " pkg=" + bp.sourcePackage + " info=" + bp.pendingInfo); if (bp.packageSetting == null && bp.pendingInfo != null) { - BasePermission tree = findPermissionTreeLP(bp.name); + final BasePermission tree = findPermissionTreeLP(bp.name); if (tree != null) { bp.packageSetting = tree.packageSetting; bp.perm = new PackageParser.Permission(tree.perm.owner, @@ -3861,18 +3917,18 @@ class PackageManagerService extends IPackageManager.Stub { if (grantPermissions) { for (PackageParser.Package pkg : mPackages.values()) { if (pkg != pkgInfo) { - grantPermissionsLP(pkg, replaceAll); + grantPermissionsLPw(pkg, replaceAll); } } } if (pkgInfo != null) { - grantPermissionsLP(pkgInfo, replace); + grantPermissionsLPw(pkgInfo, replace); } } - private void grantPermissionsLP(PackageParser.Package pkg, boolean replace) { - final PackageSetting ps = (PackageSetting)pkg.mExtras; + private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace) { + final PackageSetting ps = (PackageSetting) pkg.mExtras; if (ps == null) { return; } @@ -3893,12 +3949,11 @@ class PackageManagerService extends IPackageManager.Stub { final int N = pkg.requestedPermissions.size(); for (int i=0; i<N; i++) { - String name = pkg.requestedPermissions.get(i); - BasePermission bp = mSettings.mPermissions.get(name); - if (false) { + final String name = pkg.requestedPermissions.get(i); + final BasePermission bp = mSettings.mPermissions.get(name); + if (DEBUG_INSTALL) { if (gp != ps) { - Log.i(TAG, "Package " + pkg.packageName + " checking " + name - + ": " + bp); + Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp); } } if (bp != null && bp.packageSetting != null) { @@ -3913,10 +3968,10 @@ class PackageManagerService extends IPackageManager.Stub { allowed = false; } else if (bp.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE || bp.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) { - allowed = (checkSignaturesLP( + allowed = (compareSignatures( bp.packageSetting.signatures.mSignatures, pkg.mSignatures) == PackageManager.SIGNATURE_MATCH) - || (checkSignaturesLP(mPlatformPackage.mSignatures, pkg.mSignatures) + || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures) == PackageManager.SIGNATURE_MATCH); if (!allowed && bp.protectionLevel == PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM) { @@ -3924,8 +3979,8 @@ class PackageManagerService extends IPackageManager.Stub { // For updated system applications, the signatureOrSystem permission // is granted only if it had been defined by the original application. if (isUpdatedSystemApp(pkg)) { - PackageSetting sysPs = mSettings.getDisabledSystemPkg( - pkg.packageName); + final PackageSetting sysPs = mSettings + .getDisabledSystemPkgLPr(pkg.packageName); final GrantedPermissions origGp = sysPs.sharedUser != null ? sysPs.sharedUser : sysPs; if (origGp.grantedPermissions.contains(perm)) { @@ -3944,7 +3999,7 @@ class PackageManagerService extends IPackageManager.Stub { } else { allowed = false; } - if (false) { + if (DEBUG_INSTALL) { if (gp != ps) { Log.i(TAG, "Package " + pkg.packageName + " granting " + perm); } @@ -4022,25 +4077,26 @@ class PackageManagerService extends IPackageManager.Stub { private final class ActivityIntentResolver extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> { - public List queryIntent(Intent intent, String resolvedType, boolean defaultOnly) { + public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, + boolean defaultOnly) { mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0; return super.queryIntent(intent, resolvedType, defaultOnly); } - public List queryIntent(Intent intent, String resolvedType, int flags) { + public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags) { mFlags = flags; return super.queryIntent(intent, resolvedType, (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0); } - public List queryIntentForPackage(Intent intent, String resolvedType, int flags, - ArrayList<PackageParser.Activity> packageActivities) { + public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType, + int flags, ArrayList<PackageParser.Activity> packageActivities) { if (packageActivities == null) { return null; } mFlags = flags; final boolean defaultOnly = (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0; - int N = packageActivities.size(); + final int N = packageActivities.size(); ArrayList<ArrayList<PackageParser.ActivityIntentInfo>> listCut = new ArrayList<ArrayList<PackageParser.ActivityIntentInfo>>(N); @@ -4057,11 +4113,13 @@ class PackageManagerService extends IPackageManager.Stub { public final void addActivity(PackageParser.Activity a, String type) { final boolean systemApp = isSystemApp(a.info.applicationInfo); mActivities.put(a.getComponentName(), a); - if (SHOW_INFO || Config.LOGV) Log.v( + if (DEBUG_SHOW_INFO) + Log.v( TAG, " " + type + " " + (a.info.nonLocalizedLabel != null ? a.info.nonLocalizedLabel : a.info.name) + ":"); - if (SHOW_INFO || Config.LOGV) Log.v(TAG, " Class=" + a.info.name); - int NI = a.intents.size(); + if (DEBUG_SHOW_INFO) + Log.v(TAG, " Class=" + a.info.name); + final int NI = a.intents.size(); for (int j=0; j<NI; j++) { PackageParser.ActivityIntentInfo intent = a.intents.get(j); if (!systemApp && intent.getPriority() > 0 && "activity".equals(type)) { @@ -4069,7 +4127,7 @@ class PackageManagerService extends IPackageManager.Stub { Log.w(TAG, "Package " + a.info.applicationInfo.packageName + " has activity " + a.className + " with priority > 0, forcing to 0"); } - if (SHOW_INFO || Config.LOGV) { + if (DEBUG_SHOW_INFO) { Log.v(TAG, " IntentFilter:"); intent.dump(new LogPrinter(Log.VERBOSE, TAG), " "); } @@ -4082,14 +4140,16 @@ class PackageManagerService extends IPackageManager.Stub { public final void removeActivity(PackageParser.Activity a, String type) { mActivities.remove(a.getComponentName()); - if (SHOW_INFO || Config.LOGV) Log.v( - TAG, " " + type + " " + - (a.info.nonLocalizedLabel != null ? a.info.nonLocalizedLabel : a.info.name) + ":"); - if (SHOW_INFO || Config.LOGV) Log.v(TAG, " Class=" + a.info.name); - int NI = a.intents.size(); + if (DEBUG_SHOW_INFO) { + Log.v(TAG, " " + type + " " + + (a.info.nonLocalizedLabel != null ? a.info.nonLocalizedLabel + : a.info.name) + ":"); + Log.v(TAG, " Class=" + a.info.name); + } + final int NI = a.intents.size(); for (int j=0; j<NI; j++) { PackageParser.ActivityIntentInfo intent = a.intents.get(j); - if (SHOW_INFO || Config.LOGV) { + if (DEBUG_SHOW_INFO) { Log.v(TAG, " IntentFilter:"); intent.dump(new LogPrinter(Log.VERBOSE, TAG), " "); } @@ -4131,7 +4191,7 @@ class PackageManagerService extends IPackageManager.Stub { @Override protected ResolveInfo newResult(PackageParser.ActivityIntentInfo info, int match) { - if (!mSettings.isEnabledLP(info.activity.info, mFlags)) { + if (!mSettings.isEnabledLPr(info.activity.info, mFlags)) { return null; } final PackageParser.Activity activity = info.activity; @@ -4193,25 +4253,26 @@ class PackageManagerService extends IPackageManager.Stub { private final class ServiceIntentResolver extends IntentResolver<PackageParser.ServiceIntentInfo, ResolveInfo> { - public List queryIntent(Intent intent, String resolvedType, boolean defaultOnly) { + public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, + boolean defaultOnly) { mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0; return super.queryIntent(intent, resolvedType, defaultOnly); } - public List queryIntent(Intent intent, String resolvedType, int flags) { + public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags) { mFlags = flags; return super.queryIntent(intent, resolvedType, (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0); } - public List queryIntentForPackage(Intent intent, String resolvedType, int flags, - ArrayList<PackageParser.Service> packageServices) { + public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType, + int flags, ArrayList<PackageParser.Service> packageServices) { if (packageServices == null) { return null; } mFlags = flags; final boolean defaultOnly = (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0; - int N = packageServices.size(); + final int N = packageServices.size(); ArrayList<ArrayList<PackageParser.ServiceIntentInfo>> listCut = new ArrayList<ArrayList<PackageParser.ServiceIntentInfo>>(N); @@ -4227,16 +4288,17 @@ class PackageManagerService extends IPackageManager.Stub { public final void addService(PackageParser.Service s) { mServices.put(s.getComponentName(), s); - if (SHOW_INFO || Config.LOGV) Log.v( - TAG, " " + (s.info.nonLocalizedLabel != null + if (DEBUG_SHOW_INFO) { + Log.v(TAG, " " + + (s.info.nonLocalizedLabel != null ? s.info.nonLocalizedLabel : s.info.name) + ":"); - if (SHOW_INFO || Config.LOGV) Log.v( - TAG, " Class=" + s.info.name); - int NI = s.intents.size(); + Log.v(TAG, " Class=" + s.info.name); + } + final int NI = s.intents.size(); int j; for (j=0; j<NI; j++) { PackageParser.ServiceIntentInfo intent = s.intents.get(j); - if (SHOW_INFO || Config.LOGV) { + if (DEBUG_SHOW_INFO) { Log.v(TAG, " IntentFilter:"); intent.dump(new LogPrinter(Log.VERBOSE, TAG), " "); } @@ -4249,16 +4311,16 @@ class PackageManagerService extends IPackageManager.Stub { public final void removeService(PackageParser.Service s) { mServices.remove(s.getComponentName()); - if (SHOW_INFO || Config.LOGV) Log.v( - TAG, " " + (s.info.nonLocalizedLabel != null + if (DEBUG_SHOW_INFO) { + Log.v(TAG, " " + (s.info.nonLocalizedLabel != null ? s.info.nonLocalizedLabel : s.info.name) + ":"); - if (SHOW_INFO || Config.LOGV) Log.v( - TAG, " Class=" + s.info.name); - int NI = s.intents.size(); + Log.v(TAG, " Class=" + s.info.name); + } + final int NI = s.intents.size(); int j; for (j=0; j<NI; j++) { PackageParser.ServiceIntentInfo intent = s.intents.get(j); - if (SHOW_INFO || Config.LOGV) { + if (DEBUG_SHOW_INFO) { Log.v(TAG, " IntentFilter:"); intent.dump(new LogPrinter(Log.VERBOSE, TAG), " "); } @@ -4301,7 +4363,7 @@ class PackageManagerService extends IPackageManager.Stub { protected ResolveInfo newResult(PackageParser.ServiceIntentInfo filter, int match) { final PackageParser.ServiceIntentInfo info = (PackageParser.ServiceIntentInfo)filter; - if (!mSettings.isEnabledLP(info.service.info, mFlags)) { + if (!mSettings.isEnabledLPr(info.service.info, mFlags)) { return null; } final PackageParser.Service service = info.service; @@ -4394,7 +4456,7 @@ class PackageManagerService extends IPackageManager.Stub { } }; - private static final void sendPackageBroadcast(String action, String pkg, + static final void sendPackageBroadcast(String action, String pkg, Bundle extras, String targetPkg, IIntentReceiver finishedReceiver) { IActivityManager am = ActivityManagerNative.getDefault(); if (am != null) { @@ -4425,6 +4487,7 @@ class PackageManagerService extends IPackageManager.Stub { } public String nextPackageToClean(String lastPackage) { + // writer synchronized (mPackages) { if (!isExternalMediaAvailable()) { // If the external storage is no longer mounted at this point, @@ -4445,6 +4508,7 @@ class PackageManagerService extends IPackageManager.Stub { } void startCleaningPackages() { + // reader synchronized (mPackages) { if (!isExternalMediaAvailable()) { return; @@ -4477,6 +4541,7 @@ class PackageManagerService extends IPackageManager.Stub { String addedPackage = null; int addedUid = -1; + // TODO post a message to the handler to obtain serial ordering synchronized (mInstallLock) { String fullPathStr = null; File fullPath = null; @@ -4485,13 +4550,12 @@ class PackageManagerService extends IPackageManager.Stub { fullPathStr = fullPath.getPath(); } - if (Config.LOGV) Log.v( - TAG, "File " + fullPathStr + " changed: " - + Integer.toHexString(event)); + if (DEBUG_APP_DIR_OBSERVER) + Log.v(TAG, "File " + fullPathStr + " changed: " + Integer.toHexString(event)); if (!isPackageFilename(path)) { - if (Config.LOGV) Log.v( - TAG, "Ignoring change of non-package file: " + fullPathStr); + if (DEBUG_APP_DIR_OBSERVER) + Log.v(TAG, "Ignoring change of non-package file: " + fullPathStr); return; } @@ -4501,6 +4565,7 @@ class PackageManagerService extends IPackageManager.Stub { return; } PackageParser.Package p = null; + // reader synchronized (mPackages) { p = mAppDirs.get(fullPathStr); } @@ -4522,8 +4587,14 @@ class PackageManagerService extends IPackageManager.Stub { SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME, System.currentTimeMillis()); if (p != null) { + /* + * TODO this seems dangerous as the package may have + * changed since we last acquired the mPackages + * lock. + */ + // writer synchronized (mPackages) { - updatePermissionsLP(p.packageName, p, + updatePermissionsLPw(p.packageName, p, p.permissions.size() > 0, false, false); } addedPackage = p.applicationInfo.packageName; @@ -4532,8 +4603,9 @@ class PackageManagerService extends IPackageManager.Stub { } } + // reader synchronized (mPackages) { - mSettings.writeLP(); + mSettings.writeLPr(); } } @@ -4581,13 +4653,9 @@ class PackageManagerService extends IPackageManager.Stub { mHandler.sendMessage(msg); } - public void setInstallerPackageName(String targetPackage, - String installerPackageName) { - PackageSetting pkgSetting; + public void setInstallerPackageName(String targetPackage, String installerPackageName) { final int uid = Binder.getCallingUid(); - final int permission = mContext.checkCallingPermission( - android.Manifest.permission.INSTALL_PACKAGES); - final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); + // writer synchronized (mPackages) { PackageSetting targetPackageSetting = mSettings.mPackages.get(targetPackage); if (targetPackageSetting == null) { @@ -4606,7 +4674,7 @@ class PackageManagerService extends IPackageManager.Stub { } Signature[] callerSignature; - Object obj = mSettings.getUserIdLP(uid); + Object obj = mSettings.getUserIdLPr(uid); if (obj != null) { if (obj instanceof SharedUserSetting) { callerSignature = ((SharedUserSetting)obj).signatures.mSignatures; @@ -4622,7 +4690,7 @@ class PackageManagerService extends IPackageManager.Stub { // Verify: can't set installerPackageName to a package that is // not signed with the same cert as the caller. if (installerPackageSetting != null) { - if (checkSignaturesLP(callerSignature, + if (compareSignatures(callerSignature, installerPackageSetting.signatures.mSignatures) != PackageManager.SIGNATURE_MATCH) { throw new SecurityException( @@ -4639,7 +4707,7 @@ class PackageManagerService extends IPackageManager.Stub { // If the currently set package isn't valid, then it's always // okay to change it. if (setting != null) { - if (checkSignaturesLP(callerSignature, + if (compareSignatures(callerSignature, setting.signatures.mSignatures) != PackageManager.SIGNATURE_MATCH) { throw new SecurityException( @@ -4855,6 +4923,7 @@ class PackageManagerService extends IPackageManager.Stub { String packageName = pkgLite.packageName; int installLocation = pkgLite.installLocation; boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0; + // reader synchronized (mPackages) { PackageParser.Package pkg = mPackages.get(packageName); if (pkg != null) { @@ -4919,12 +4988,24 @@ class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Cannot install fwd locked apps on sdcard"); ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; } else { + final long lowThreshold; + + final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager + .getService(DeviceStorageMonitorService.SERVICE); + if (dsm == null) { + Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed"); + lowThreshold = 0L; + } else { + lowThreshold = dsm.getMemoryLowThreshold(); + } + // Remote call to find out default install location final PackageInfoLite pkgLite; try { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); - pkgLite = mContainerService.getMinimalPackageInfo(packageURI, flags); + pkgLite = mContainerService.getMinimalPackageInfo(packageURI, flags, + lowThreshold); } finally { mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); } @@ -5144,10 +5225,26 @@ class PackageManagerService extends IPackageManager.Stub { } boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException { + final long lowThreshold; + + final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager + .getService(DeviceStorageMonitorService.SERVICE); + if (dsm == null) { + Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed"); + lowThreshold = 0L; + } else { + if (dsm.isMemoryLow()) { + Log.w(TAG, "Memory is reported as being too low; aborting package install"); + return false; + } + + lowThreshold = dsm.getMemoryLowThreshold(); + } + try { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); - return imcs.checkFreeStorage(false, packageURI); + return imcs.checkInternalFreeStorage(packageURI, lowThreshold); } finally { mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); } @@ -5373,7 +5470,7 @@ class PackageManagerService extends IPackageManager.Stub { try { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); - return imcs.checkFreeStorage(true, packageURI); + return imcs.checkExternalFreeStorage(packageURI); } finally { mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); } @@ -5638,7 +5735,7 @@ class PackageManagerService extends IPackageManager.Stub { // Remember this for later, in case we need to rollback this install String pkgName = pkg.packageName; - boolean dataDirExists = getDataPathForPackage(pkg).exists(); + boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists(); res.name = pkgName; synchronized(mPackages) { if (mSettings.mRenamedPackages.containsKey(pkgName)) { @@ -5697,7 +5794,7 @@ class PackageManagerService extends IPackageManager.Stub { // First find the old package info and check signatures synchronized(mPackages) { oldPackage = mPackages.get(pkgName); - if (checkSignaturesLP(oldPackage.mSignatures, pkg.mSignatures) + if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) { res.returnCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; return; @@ -5720,11 +5817,6 @@ class PackageManagerService extends IPackageManager.Stub { boolean deletedPkg = true; boolean updatedSettings = false; - String oldInstallerPackageName = null; - synchronized (mPackages) { - oldInstallerPackageName = mSettings.getInstallerPackageName(pkgName); - } - long origUpdateTime; if (pkg.mExtras != null) { origUpdateTime = ((PackageSetting)pkg.mExtras).lastUpdateTime; @@ -5788,10 +5880,12 @@ class PackageManagerService extends IPackageManager.Stub { return; } // Restore of old package succeeded. Update permissions. + // writer synchronized (mPackages) { - updatePermissionsLP(deletedPackage.packageName, deletedPackage, + updatePermissionsLPw(deletedPackage.packageName, deletedPackage, true, false, false); - mSettings.writeLP(); + // can downgrade to reader + mSettings.writeLPr(); } Slog.i(TAG, "Successfully restored package : " + pkgName + " after failed upgrade"); } @@ -5814,6 +5908,7 @@ class PackageManagerService extends IPackageManager.Stub { } PackageParser.Package oldPkg; PackageSetting oldPkgSetting; + // reader synchronized (mPackages) { oldPkg = mPackages.get(packageName); oldPkgSetting = mSettings.mPackages.get(packageName); @@ -5830,8 +5925,9 @@ class PackageManagerService extends IPackageManager.Stub { res.removedInfo.removedPackage = packageName; // Remove existing system package removePackageLI(oldPkg, true); + // writer synchronized (mPackages) { - if (!mSettings.disableSystemPackageLP(packageName) && deletedPackage != null) { + if (!mSettings.disableSystemPackageLPw(packageName) && deletedPackage != null) { // We didn't need to disable the .apk as a current system package, // which means we are replacing another update that is already // installed. We need to make sure to delete the older one's .apk. @@ -5875,11 +5971,11 @@ class PackageManagerService extends IPackageManager.Stub { // Restore the old system information in Settings synchronized(mPackages) { if (updatedSettings) { - mSettings.enableSystemPackageLP(packageName); + mSettings.enableSystemPackageLPw(packageName); mSettings.setInstallerPackageName(packageName, oldPkgSetting.installerPackageName); } - mSettings.writeLP(); + mSettings.writeLPr(); } } } @@ -5913,8 +6009,8 @@ class PackageManagerService extends IPackageManager.Stub { //write settings. the installStatus will be incomplete at this stage. //note that the new package setting would have already been //added to mPackages. It hasn't been persisted yet. - mSettings.setInstallStatus(pkgName, PKG_INSTALL_INCOMPLETE); - mSettings.writeLP(); + mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_INCOMPLETE); + mSettings.writeLPr(); } if ((res.returnCode = moveDexFilesLI(newPackage)) @@ -5932,16 +6028,16 @@ class PackageManagerService extends IPackageManager.Stub { Log.d(TAG, "New package installed in " + newPackage.mPath); } synchronized (mPackages) { - updatePermissionsLP(newPackage.packageName, newPackage, + updatePermissionsLPw(newPackage.packageName, newPackage, newPackage.permissions.size() > 0, true, false); res.name = pkgName; res.uid = newPackage.applicationInfo.uid; res.pkg = newPackage; - mSettings.setInstallStatus(pkgName, PKG_INSTALL_COMPLETE); + mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_COMPLETE); mSettings.setInstallerPackageName(pkgName, installerPackageName); res.returnCode = PackageManager.INSTALL_SUCCEEDED; //to update install status - mSettings.writeLP(); + mSettings.writeLPr(); } } @@ -6040,7 +6136,6 @@ class PackageManagerService extends IPackageManager.Stub { } private int setPermissionsLI(PackageParser.Package newPackage) { - String pkgName = newPackage.packageName; int retCode = 0; // TODO Gross hack but fix later. Ideally move this to be a post installation // check after alloting uid. @@ -6070,9 +6165,8 @@ class PackageManagerService extends IPackageManager.Stub { } if (retCode != 0) { - Slog.e(TAG, "Couldn't set new package file permissions for " + - newPackage.mPath - + ". The return code was: " + retCode); + Slog.e(TAG, "Couldn't set new package file permissions for " + newPackage.mPath + + ". The return code was: " + retCode); // TODO Define new internal error return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; } @@ -6321,37 +6415,46 @@ class PackageManagerService extends IPackageManager.Stub { } removePackageLI(p, (flags&REMOVE_CHATTY) != 0); // Retrieve object to delete permissions for shared user later on - PackageSetting deletedPs; + final PackageSetting deletedPs; + // reader synchronized (mPackages) { deletedPs = mSettings.mPackages.get(packageName); } if ((flags&PackageManager.DONT_DELETE_DATA) == 0) { if (mInstaller != null) { - int retCode = mInstaller.remove(packageName); + int retCode = mInstaller.remove(packageName, 0); if (retCode < 0) { Slog.w(TAG, "Couldn't remove app data or cache directory for package: " + packageName + ", retcode=" + retCode); // we don't consider this to be a failure of the core package deletion + } else { + // TODO: Kill the processes first + mUserManager.removePackageForAllUsers(packageName); } } else { // for simulator - PackageParser.Package pkg = mPackages.get(packageName); - File dataDir = new File(pkg.applicationInfo.dataDir); + File dataDir; + // reader + synchronized (mPackages) { + PackageParser.Package pkg = mPackages.get(packageName); + dataDir = new File(pkg.applicationInfo.dataDir); + } dataDir.delete(); } schedulePackageCleaning(packageName); } + // writer synchronized (mPackages) { if (deletedPs != null) { if ((flags&PackageManager.DONT_DELETE_DATA) == 0) { if (outInfo != null) { - outInfo.removedUid = mSettings.removePackageLP(packageName); + outInfo.removedUid = mSettings.removePackageLPw(packageName); } if (deletedPs != null) { - updatePermissionsLP(deletedPs.name, null, false, false, false); + updatePermissionsLPw(deletedPs.name, null, false, false, false); if (deletedPs.sharedUser != null) { // remove permissions associated with package - mSettings.updateSharedUserPermsLP(deletedPs, mGlobalGids); + mSettings.updateSharedUserPermsLPw(deletedPs, mGlobalGids); } } } @@ -6366,9 +6469,10 @@ class PackageManagerService extends IPackageManager.Stub { mSettings.mPreferredActivities.removeFilter(pa); } } + // can downgrade to reader if (writeSettings) { // Save settings now - mSettings.writeLP(); + mSettings.writeLPr(); } } } @@ -6388,8 +6492,9 @@ class PackageManagerService extends IPackageManager.Stub { // Confirm if the system package has been updated // An updated system app can be deleted. This will also have to restore // the system pkg from system partition + // reader synchronized (mPackages) { - ps = mSettings.getDisabledSystemPkg(p.packageName); + ps = mSettings.getDisabledSystemPkgLPr(p.packageName); } if (ps == null) { Slog.w(TAG, "Attempt to delete unknown system package "+ p.packageName); @@ -6411,9 +6516,10 @@ class PackageManagerService extends IPackageManager.Stub { if (!ret) { return false; } + // writer synchronized (mPackages) { // Reinstate the old system package - mSettings.enableSystemPackageLP(p.packageName); + mSettings.enableSystemPackageLPw(p.packageName); // Remove any native libraries from the upgraded package. NativeLibraryHelper.removeNativeBinariesLI(p.applicationInfo.nativeLibraryDir); } @@ -6426,10 +6532,12 @@ class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Failed to restore system package:"+p.packageName+" with error:" + mLastScanError); return false; } + // writer synchronized (mPackages) { - updatePermissionsLP(newPkg.packageName, newPkg, true, true, false); + updatePermissionsLPw(newPkg.packageName, newPkg, true, true, false); + // can downgrade to reader here if (writeSettings) { - mSettings.writeLP(); + mSettings.writeLPr(); } } return true; @@ -6581,7 +6689,7 @@ class PackageManagerService extends IPackageManager.Stub { } } if (mInstaller != null) { - int retCode = mInstaller.clearUserData(packageName); + int retCode = mInstaller.clearUserData(packageName, 0); // TODO - correct userId if (retCode < 0) { Slog.w(TAG, "Couldn't remove cache files for package: " + packageName); @@ -6717,16 +6825,14 @@ class PackageManagerService extends IPackageManager.Stub { return new ArrayList<PackageInfo>(); } - int getUidTargetSdkVersionLockedLP(int uid) { - Object obj = mSettings.getUserIdLP(uid); + private int getUidTargetSdkVersionLockedLPr(int uid) { + Object obj = mSettings.getUserIdLPr(uid); if (obj instanceof SharedUserSetting) { - SharedUserSetting sus = (SharedUserSetting)obj; - final int N = sus.packages.size(); + final SharedUserSetting sus = (SharedUserSetting) obj; int vers = Build.VERSION_CODES.CUR_DEVELOPMENT; - Iterator<PackageSetting> it = sus.packages.iterator(); - int i=0; + final Iterator<PackageSetting> it = sus.packages.iterator(); while (it.hasNext()) { - PackageSetting ps = it.next(); + final PackageSetting ps = it.next(); if (ps.pkg != null) { int v = ps.pkg.applicationInfo.targetSdkVersion; if (v < vers) vers = v; @@ -6734,7 +6840,7 @@ class PackageManagerService extends IPackageManager.Stub { } return vers; } else if (obj instanceof PackageSetting) { - PackageSetting ps = (PackageSetting)obj; + final PackageSetting ps = (PackageSetting) obj; if (ps.pkg != null) { return ps.pkg.applicationInfo.targetSdkVersion; } @@ -6744,11 +6850,12 @@ class PackageManagerService extends IPackageManager.Stub { public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) { + // writer synchronized (mPackages) { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.SET_PREFERRED_APPLICATIONS) != PackageManager.PERMISSION_GRANTED) { - if (getUidTargetSdkVersionLockedLP(Binder.getCallingUid()) + if (getUidTargetSdkVersionLockedLPr(Binder.getCallingUid()) < Build.VERSION_CODES.FROYO) { Slog.w(TAG, "Ignoring addPreferredActivity() from uid " + Binder.getCallingUid()); @@ -6788,7 +6895,7 @@ class PackageManagerService extends IPackageManager.Stub { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.SET_PREFERRED_APPLICATIONS) != PackageManager.PERMISSION_GRANTED) { - if (getUidTargetSdkVersionLockedLP(Binder.getCallingUid()) + if (getUidTargetSdkVersionLockedLPr(Binder.getCallingUid()) < Build.VERSION_CODES.FROYO) { Slog.w(TAG, "Ignoring replacePreferredActivity() from uid " + Binder.getCallingUid()); @@ -6814,14 +6921,15 @@ class PackageManagerService extends IPackageManager.Stub { } public void clearPackagePreferredActivities(String packageName) { + final int uid = Binder.getCallingUid(); + // writer synchronized (mPackages) { - int uid = Binder.getCallingUid(); PackageParser.Package pkg = mPackages.get(packageName); if (pkg == null || pkg.applicationInfo.uid != uid) { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.SET_PREFERRED_APPLICATIONS) != PackageManager.PERMISSION_GRANTED) { - if (getUidTargetSdkVersionLockedLP(Binder.getCallingUid()) + if (getUidTargetSdkVersionLockedLPr(Binder.getCallingUid()) < Build.VERSION_CODES.FROYO) { Slog.w(TAG, "Ignoring clearPackagePreferredActivities() from uid " + Binder.getCallingUid()); @@ -6832,13 +6940,13 @@ class PackageManagerService extends IPackageManager.Stub { } } - if (clearPackagePreferredActivitiesLP(packageName)) { + if (clearPackagePreferredActivitiesLPw(packageName)) { scheduleWriteSettingsLocked(); } } } - boolean clearPackagePreferredActivitiesLP(String packageName) { + boolean clearPackagePreferredActivitiesLPw(String packageName) { boolean changed = false; Iterator<PreferredActivity> it = mSettings.mPreferredActivities.filterIterator(); while (it.hasNext()) { @@ -6855,10 +6963,11 @@ class PackageManagerService extends IPackageManager.Stub { List<ComponentName> outActivities, String packageName) { int num = 0; + // reader synchronized (mPackages) { - Iterator<PreferredActivity> it = mSettings.mPreferredActivities.filterIterator(); + final Iterator<PreferredActivity> it = mSettings.mPreferredActivities.filterIterator(); while (it.hasNext()) { - PreferredActivity pa = it.next(); + final PreferredActivity pa = it.next(); if (packageName == null || pa.mPref.mComponent.getPackageName().equals(packageName)) { if (outFilters != null) { @@ -6903,6 +7012,8 @@ class PackageManagerService extends IPackageManager.Stub { String componentName = isApp ? packageName : className; int packageUid = -1; ArrayList<String> components; + + // writer synchronized (mPackages) { pkgSetting = mSettings.mPackages.get(packageName); if (pkgSetting == null) { @@ -6932,17 +7043,17 @@ class PackageManagerService extends IPackageManager.Stub { // We're dealing with a component level state change switch (newState) { case COMPONENT_ENABLED_STATE_ENABLED: - if (!pkgSetting.enableComponentLP(className)) { + if (!pkgSetting.enableComponentLPw(className)) { return; } break; case COMPONENT_ENABLED_STATE_DISABLED: - if (!pkgSetting.disableComponentLP(className)) { + if (!pkgSetting.disableComponentLPw(className)) { return; } break; case COMPONENT_ENABLED_STATE_DEFAULT: - if (!pkgSetting.restoreComponentLP(className)) { + if (!pkgSetting.restoreComponentLPw(className)) { return; } break; @@ -6951,10 +7062,10 @@ class PackageManagerService extends IPackageManager.Stub { return; } } - mSettings.writeLP(); + mSettings.writeLPr(); packageUid = pkgSetting.userId; components = mPendingBroadcasts.get(packageName); - boolean newPackage = components == null; + final boolean newPackage = components == null; if (newPackage) { components = new ArrayList<String>(); } @@ -6990,8 +7101,9 @@ class PackageManagerService extends IPackageManager.Stub { private void sendPackageChangedBroadcast(String packageName, boolean killFlag, ArrayList<String> componentNames, int packageUid) { - if (false) Log.v(TAG, "Sending package changed: package=" + packageName - + " components=" + componentNames); + if (DEBUG_INSTALL) + Log.v(TAG, "Sending package changed: package=" + packageName + " components=" + + componentNames); Bundle extras = new Bundle(4); extras.putString(Intent.EXTRA_CHANGED_COMPONENT_NAME, componentNames.get(0)); String nameList[] = new String[componentNames.size()]; @@ -7003,72 +7115,37 @@ class PackageManagerService extends IPackageManager.Stub { } public void setPackageStoppedState(String packageName, boolean stopped) { - PackageSetting pkgSetting; final int uid = Binder.getCallingUid(); final int permission = mContext.checkCallingOrSelfPermission( android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE); final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); + // writer synchronized (mPackages) { - pkgSetting = mSettings.mPackages.get(packageName); - if (pkgSetting == null) { - throw new IllegalArgumentException("Unknown package: " + packageName); - } - if (!allowedByPermission && (uid != pkgSetting.userId)) { - throw new SecurityException( - "Permission Denial: attempt to change stopped state from pid=" - + Binder.getCallingPid() - + ", uid=" + uid + ", package uid=" + pkgSetting.userId); - } - if (DEBUG_STOPPED && stopped) { - RuntimeException e = new RuntimeException("here"); - e.fillInStackTrace(); - Slog.i(TAG, "Stopping package " + packageName, e); - } - if (pkgSetting.stopped != stopped) { - pkgSetting.stopped = stopped; - pkgSetting.pkg.mSetStopped = stopped; - if (pkgSetting.notLaunched) { - if (pkgSetting.installerPackageName != null) { - sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, - pkgSetting.name, null, - pkgSetting.installerPackageName, null); - } - pkgSetting.notLaunched = false; - } + if (mSettings.setPackageStoppedStateLPw(packageName, stopped, allowedByPermission, + uid)) { scheduleWriteStoppedPackagesLocked(); } } } public String getInstallerPackageName(String packageName) { + // reader synchronized (mPackages) { - PackageSetting pkg = mSettings.mPackages.get(packageName); - if (pkg == null) { - throw new IllegalArgumentException("Unknown package: " + packageName); - } - return pkg.installerPackageName; + return mSettings.getInstallerPackageNameLPr(packageName); } } - public int getApplicationEnabledSetting(String appPackageName) { + public int getApplicationEnabledSetting(String packageName) { + // reader synchronized (mPackages) { - PackageSetting pkg = mSettings.mPackages.get(appPackageName); - if (pkg == null) { - throw new IllegalArgumentException("Unknown package: " + appPackageName); - } - return pkg.enabled; + return mSettings.getApplicationEnabledSettingLPr(packageName); } } public int getComponentEnabledSetting(ComponentName componentName) { + // reader synchronized (mPackages) { - final String packageNameStr = componentName.getPackageName(); - PackageSetting pkg = mSettings.mPackages.get(packageNameStr); - if (pkg == null) { - throw new IllegalArgumentException("Unknown component: " + componentName); - } - final String classNameStr = componentName.getClassName(); - return pkg.currentEnabledStateLP(classNameStr); + return mSettings.getComponentEnabledSettingLPr(componentName); } } @@ -7112,6 +7189,76 @@ class PackageManagerService extends IPackageManager.Stub { return buf.toString(); } + static class DumpState { + public static final int DUMP_LIBS = 1 << 0; + + public static final int DUMP_FEATURES = 1 << 1; + + public static final int DUMP_RESOLVERS = 1 << 2; + + public static final int DUMP_PERMISSIONS = 1 << 3; + + public static final int DUMP_PACKAGES = 1 << 4; + + public static final int DUMP_SHARED_USERS = 1 << 5; + + public static final int DUMP_MESSAGES = 1 << 6; + + public static final int DUMP_PROVIDERS = 1 << 7; + + public static final int OPTION_SHOW_FILTERS = 1 << 0; + + private int mTypes; + + private int mOptions; + + private boolean mTitlePrinted; + + private SharedUserSetting mSharedUser; + + public boolean isDumping(int type) { + if (mTypes == 0) { + return true; + } + + return (mTypes & type) != 0; + } + + public void setDump(int type) { + mTypes |= type; + } + + public boolean isOptionEnabled(int option) { + return (mOptions & option) != 0; + } + + public void setOptionEnabled(int option) { + mOptions |= option; + } + + public boolean onTitlePrinted() { + final boolean printed = mTitlePrinted; + mTitlePrinted = true; + return printed; + } + + public boolean getTitlePrinted() { + return mTitlePrinted; + } + + public void setTitlePrinted(boolean enabled) { + mTitlePrinted = enabled; + } + + public SharedUserSetting getSharedUser() { + return mSharedUser; + } + + public void setSharedUser(SharedUserSetting user) { + mSharedUser = user; + } + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) @@ -7124,18 +7271,9 @@ class PackageManagerService extends IPackageManager.Stub { return; } - boolean dumpStar = true; - boolean dumpLibs = false; - boolean dumpFeatures = false; - boolean dumpResolvers = false; - boolean dumpPermissions = false; - boolean dumpPackages = false; - boolean dumpSharedUsers = false; - boolean dumpMessages = false; - boolean dumpProviders = false; + DumpState dumpState = new DumpState(); String packageName = null; - boolean showFilters = false; int opti = 0; while (opti < args.length) { @@ -7163,7 +7301,7 @@ class PackageManagerService extends IPackageManager.Stub { pw.println(" <package.name>: info about given package"); return; } else if ("-f".equals(opt)) { - showFilters = true; + dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS); } else { pw.println("Unknown argument: " + opt + "; use -h for help"); } @@ -7177,40 +7315,31 @@ class PackageManagerService extends IPackageManager.Stub { if ("android".equals(cmd) || cmd.contains(".")) { packageName = cmd; } else if ("l".equals(cmd) || "libraries".equals(cmd)) { - dumpStar = false; - dumpLibs = true; + dumpState.setDump(DumpState.DUMP_LIBS); } else if ("f".equals(cmd) || "features".equals(cmd)) { - dumpStar = false; - dumpFeatures = true; + dumpState.setDump(DumpState.DUMP_FEATURES); } else if ("r".equals(cmd) || "resolvers".equals(cmd)) { - dumpStar = false; - dumpResolvers = true; + dumpState.setDump(DumpState.DUMP_RESOLVERS); } else if ("perm".equals(cmd) || "permissions".equals(cmd)) { - dumpStar = false; - dumpPermissions = true; + dumpState.setDump(DumpState.DUMP_PERMISSIONS); } else if ("p".equals(cmd) || "packages".equals(cmd)) { - dumpStar = false; - dumpPackages = true; + dumpState.setDump(DumpState.DUMP_PACKAGES); } else if ("s".equals(cmd) || "shared-users".equals(cmd)) { - dumpStar = false; - dumpSharedUsers = true; + dumpState.setDump(DumpState.DUMP_SHARED_USERS); } else if ("prov".equals(cmd) || "providers".equals(cmd)) { - dumpStar = false; - dumpProviders = true; + dumpState.setDump(DumpState.DUMP_PROVIDERS); } else if ("m".equals(cmd) || "messages".equals(cmd)) { - dumpStar = false; - dumpMessages = true; + dumpState.setDump(DumpState.DUMP_MESSAGES); } } - - boolean printedTitle = false; - + + // reader synchronized (mPackages) { - if ((dumpStar || dumpLibs) && packageName == null) { - if (printedTitle) pw.println(" "); - printedTitle = true; + if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) { + if (dumpState.onTitlePrinted()) + pw.println(" "); pw.println("Libraries:"); - Iterator<String> it = mSharedLibraries.keySet().iterator(); + final Iterator<String> it = mSharedLibraries.keySet().iterator(); while (it.hasNext()) { String name = it.next(); pw.print(" "); @@ -7220,9 +7349,9 @@ class PackageManagerService extends IPackageManager.Stub { } } - if ((dumpStar || dumpFeatures) && packageName == null) { - if (printedTitle) pw.println(" "); - printedTitle = true; + if (dumpState.isDumping(DumpState.DUMP_FEATURES) && packageName == null) { + if (dumpState.onTitlePrinted()) + pw.println(" "); pw.println("Features:"); Iterator<String> it = mAvailableFeatures.keySet().iterator(); while (it.hasNext()) { @@ -7232,276 +7361,72 @@ class PackageManagerService extends IPackageManager.Stub { } } - if (dumpStar || dumpResolvers) { - if (mActivities.dump(pw, printedTitle - ? "\nActivity Resolver Table:" : "Activity Resolver Table:", - " ", packageName, showFilters)) { - printedTitle = true; + if (dumpState.isDumping(DumpState.DUMP_RESOLVERS)) { + if (mActivities.dump(pw, dumpState.getTitlePrinted() ? "\nActivity Resolver Table:" + : "Activity Resolver Table:", " ", packageName, + dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { + dumpState.setTitlePrinted(true); } - if (mReceivers.dump(pw, printedTitle - ? "\nReceiver Resolver Table:" : "Receiver Resolver Table:", - " ", packageName, showFilters)) { - printedTitle = true; + if (mReceivers.dump(pw, dumpState.getTitlePrinted() ? "\nReceiver Resolver Table:" + : "Receiver Resolver Table:", " ", packageName, + dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { + dumpState.setTitlePrinted(true); } - if (mServices.dump(pw, printedTitle - ? "\nService Resolver Table:" : "Service Resolver Table:", - " ", packageName, showFilters)) { - printedTitle = true; + if (mServices.dump(pw, dumpState.getTitlePrinted() ? "\nService Resolver Table:" + : "Service Resolver Table:", " ", packageName, + dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { + dumpState.setTitlePrinted(true); } - if (mSettings.mPreferredActivities.dump(pw, printedTitle - ? "\nPreferred Activities:" : "Preferred Activities:", - " ", packageName, showFilters)) { - printedTitle = true; + if (mSettings.mPreferredActivities.dump(pw, + dumpState.getTitlePrinted() ? "\nPreferred Activities:" + : "Preferred Activities:", " ", + packageName, dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { + dumpState.setTitlePrinted(true); } } - boolean printedSomething = false; - if (dumpStar || dumpPermissions) { - for (BasePermission p : mSettings.mPermissions.values()) { - if (packageName != null && !packageName.equals(p.sourcePackage)) { - continue; - } - if (!printedSomething) { - if (printedTitle) pw.println(" "); - pw.println("Permissions:"); - printedSomething = true; - printedTitle = true; - } - pw.print(" Permission ["); pw.print(p.name); pw.print("] ("); - pw.print(Integer.toHexString(System.identityHashCode(p))); - pw.println("):"); - pw.print(" sourcePackage="); pw.println(p.sourcePackage); - pw.print(" uid="); pw.print(p.uid); - pw.print(" gids="); pw.print(arrayToString(p.gids)); - pw.print(" type="); pw.print(p.type); - pw.print(" prot="); pw.println(p.protectionLevel); - if (p.packageSetting != null) { - pw.print(" packageSetting="); pw.println(p.packageSetting); - } - if (p.perm != null) { - pw.print(" perm="); pw.println(p.perm); - } - } + if (dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) { + mSettings.dumpPermissionsLPr(pw, packageName, dumpState); } - if (dumpStar || dumpProviders) { - printedSomething = false; + if (dumpState.isDumping(DumpState.DUMP_PROVIDERS)) { + boolean printedSomething = false; for (PackageParser.Provider p : mProviders.values()) { if (packageName != null && !packageName.equals(p.info.packageName)) { continue; } if (!printedSomething) { - if (printedTitle) pw.println(" "); + if (dumpState.onTitlePrinted()) + pw.println(" "); pw.println("Registered ContentProviders:"); printedSomething = true; - printedTitle = true; } pw.print(" ["); pw.print(p.info.authority); pw.print("]: "); pw.println(p.toString()); } } - printedSomething = false; - SharedUserSetting packageSharedUser = null; - if (dumpStar || dumpPackages) { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - Date date = new Date(); - for (PackageSetting ps : mSettings.mPackages.values()) { - if (packageName != null && !packageName.equals(ps.realName) - && !packageName.equals(ps.name)) { - continue; - } - if (!printedSomething) { - if (printedTitle) pw.println(" "); - pw.println("Packages:"); - printedSomething = true; - printedTitle = true; - } - packageSharedUser = ps.sharedUser; - pw.print(" Package ["); - pw.print(ps.realName != null ? ps.realName : ps.name); - pw.print("] ("); - pw.print(Integer.toHexString(System.identityHashCode(ps))); - pw.println("):"); - if (ps.realName != null) { - pw.print(" compat name="); pw.println(ps.name); - } - pw.print(" userId="); pw.print(ps.userId); - pw.print(" gids="); pw.println(arrayToString(ps.gids)); - pw.print(" sharedUser="); pw.println(ps.sharedUser); - pw.print(" pkg="); pw.println(ps.pkg); - pw.print(" codePath="); pw.println(ps.codePathString); - pw.print(" resourcePath="); pw.println(ps.resourcePathString); - pw.print(" nativeLibraryPath="); pw.println(ps.nativeLibraryPathString); - pw.print(" versionCode="); pw.println(ps.versionCode); - if (ps.pkg != null) { - pw.print(" versionName="); pw.println(ps.pkg.mVersionName); - pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir); - pw.print(" targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion); - if (ps.pkg.mOperationPending) { - pw.println(" mOperationPending=true"); - } - pw.print(" supportsScreens=["); - boolean first = true; - if ((ps.pkg.applicationInfo.flags & - ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) { - if (!first) pw.print(", "); - first = false; - pw.print("small"); - } - if ((ps.pkg.applicationInfo.flags & - ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) { - if (!first) pw.print(", "); - first = false; - pw.print("medium"); - } - if ((ps.pkg.applicationInfo.flags & - ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { - if (!first) pw.print(", "); - first = false; - pw.print("large"); - } - if ((ps.pkg.applicationInfo.flags & - ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { - if (!first) pw.print(", "); - first = false; - pw.print("xlarge"); - } - if ((ps.pkg.applicationInfo.flags & - ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) { - if (!first) pw.print(", "); - first = false; - pw.print("resizeable"); - } - if ((ps.pkg.applicationInfo.flags & - ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) { - if (!first) pw.print(", "); - first = false; - pw.print("anyDensity"); - } - } - pw.println("]"); - pw.print(" timeStamp="); - date.setTime(ps.timeStamp); pw.println(sdf.format(date)); - pw.print(" firstInstallTime="); - date.setTime(ps.firstInstallTime); pw.println(sdf.format(date)); - pw.print(" lastUpdateTime="); - date.setTime(ps.lastUpdateTime); pw.println(sdf.format(date)); - if (ps.installerPackageName != null) { - pw.print(" installerPackageName="); pw.println(ps.installerPackageName); - } - pw.print(" signatures="); pw.println(ps.signatures); - pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed); - pw.print(" haveGids="); pw.println(ps.haveGids); - pw.print(" pkgFlags=0x"); pw.print(Integer.toHexString(ps.pkgFlags)); - pw.print(" installStatus="); pw.print(ps.installStatus); - pw.print(" stopped="); pw.print(ps.stopped); - pw.print(" enabled="); pw.println(ps.enabled); - if (ps.disabledComponents.size() > 0) { - pw.println(" disabledComponents:"); - for (String s : ps.disabledComponents) { - pw.print(" "); pw.println(s); - } - } - if (ps.enabledComponents.size() > 0) { - pw.println(" enabledComponents:"); - for (String s : ps.enabledComponents) { - pw.print(" "); pw.println(s); - } - } - if (ps.grantedPermissions.size() > 0) { - pw.println(" grantedPermissions:"); - for (String s : ps.grantedPermissions) { - pw.print(" "); pw.println(s); - } - } - } + if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) { + mSettings.dumpPackagesLPr(pw, packageName, dumpState); } - printedSomething = false; - if (dumpStar || dumpPackages) { - if (mSettings.mRenamedPackages.size() > 0) { - for (HashMap.Entry<String, String> e - : mSettings.mRenamedPackages.entrySet()) { - if (packageName != null && !packageName.equals(e.getKey()) - && !packageName.equals(e.getValue())) { - continue; - } - if (!printedSomething) { - if (printedTitle) pw.println(" "); - pw.println("Renamed packages:"); - printedSomething = true; - printedTitle = true; - } - pw.print(" "); pw.print(e.getKey()); pw.print(" -> "); - pw.println(e.getValue()); - } - } - printedSomething = false; - if (mSettings.mDisabledSysPackages.size() > 0) { - for (PackageSetting ps : mSettings.mDisabledSysPackages.values()) { - if (packageName != null && !packageName.equals(ps.realName) - && !packageName.equals(ps.name)) { - continue; - } - if (!printedSomething) { - if (printedTitle) pw.println(" "); - pw.println("Hidden system packages:"); - printedSomething = true; - printedTitle = true; - } - pw.print(" Package ["); - pw.print(ps.realName != null ? ps.realName : ps.name); - pw.print("] ("); - pw.print(Integer.toHexString(System.identityHashCode(ps))); - pw.println("):"); - if (ps.realName != null) { - pw.print(" compat name="); pw.println(ps.name); - } - pw.print(" userId="); pw.println(ps.userId); - pw.print(" sharedUser="); pw.println(ps.sharedUser); - pw.print(" codePath="); pw.println(ps.codePathString); - pw.print(" resourcePath="); pw.println(ps.resourcePathString); - } - } - } - printedSomething = false; - if (dumpStar || dumpSharedUsers) { - for (SharedUserSetting su : mSettings.mSharedUsers.values()) { - if (packageName != null && su != packageSharedUser) { - continue; - } - if (!printedSomething) { - if (printedTitle) pw.println(" "); - pw.println("Shared users:"); - printedSomething = true; - printedTitle = true; - } - pw.print(" SharedUser ["); pw.print(su.name); pw.print("] ("); - pw.print(Integer.toHexString(System.identityHashCode(su))); - pw.println("):"); - pw.print(" userId="); pw.print(su.userId); - pw.print(" gids="); pw.println(arrayToString(su.gids)); - pw.println(" grantedPermissions:"); - for (String s : su.grantedPermissions) { - pw.print(" "); pw.println(s); - } - } + + if (dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) { + mSettings.dumpSharedUsersLPr(pw, packageName, dumpState); } - - if ((dumpStar || dumpMessages) && packageName == null) { - if (printedTitle) pw.println(" "); - printedTitle = true; - pw.println("Settings parse messages:"); - pw.print(mSettings.mReadMessages.toString()); - + + if (dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) { + if (dumpState.onTitlePrinted()) + pw.println(" "); + mSettings.dumpReadMessagesLPr(pw, dumpState); + pw.println(" "); pw.println("Package warning messages:"); - File fname = getSettingsProblemFile(); + final File fname = getSettingsProblemFile(); FileInputStream in = null; try { in = new FileInputStream(fname); - int avail = in.available(); - byte[] data = new byte[avail]; + final int avail = in.available(); + final byte[] data = new byte[avail]; in.read(data); pw.print(new String(data)); } catch (FileNotFoundException e) { @@ -7510,2918 +7435,513 @@ class PackageManagerService extends IPackageManager.Stub { if (in != null) { try { in.close(); - } catch (IOException e) { - } - } - } - } - } - } - - static final class BasePermission { - final static int TYPE_NORMAL = 0; - final static int TYPE_BUILTIN = 1; - final static int TYPE_DYNAMIC = 2; - - final String name; - String sourcePackage; - PackageSettingBase packageSetting; - final int type; - int protectionLevel; - PackageParser.Permission perm; - PermissionInfo pendingInfo; - int uid; - int[] gids; - - BasePermission(String _name, String _sourcePackage, int _type) { - name = _name; - sourcePackage = _sourcePackage; - type = _type; - // Default to most conservative protection level. - protectionLevel = PermissionInfo.PROTECTION_SIGNATURE; - } - - public String toString() { - return "BasePermission{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + name + "}"; - } - } - - static class PackageSignatures { - private Signature[] mSignatures; - - PackageSignatures(PackageSignatures orig) { - if (orig != null && orig.mSignatures != null) { - mSignatures = orig.mSignatures.clone(); - } - } - - PackageSignatures(Signature[] sigs) { - assignSignatures(sigs); - } - - PackageSignatures() { - } - - void writeXml(XmlSerializer serializer, String tagName, - ArrayList<Signature> pastSignatures) throws IOException { - if (mSignatures == null) { - return; - } - serializer.startTag(null, tagName); - serializer.attribute(null, "count", - Integer.toString(mSignatures.length)); - for (int i=0; i<mSignatures.length; i++) { - serializer.startTag(null, "cert"); - final Signature sig = mSignatures[i]; - final int sigHash = sig.hashCode(); - final int numPast = pastSignatures.size(); - int j; - for (j=0; j<numPast; j++) { - Signature pastSig = pastSignatures.get(j); - if (pastSig.hashCode() == sigHash && pastSig.equals(sig)) { - serializer.attribute(null, "index", Integer.toString(j)); - break; - } - } - if (j >= numPast) { - pastSignatures.add(sig); - serializer.attribute(null, "index", Integer.toString(numPast)); - serializer.attribute(null, "key", sig.toCharsString()); - } - serializer.endTag(null, "cert"); - } - serializer.endTag(null, tagName); - } - - void readXml(XmlPullParser parser, ArrayList<Signature> pastSignatures) - throws IOException, XmlPullParserException { - String countStr = parser.getAttributeValue(null, "count"); - if (countStr == null) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <signatures> has" - + " no count at " + parser.getPositionDescription()); - XmlUtils.skipCurrentTag(parser); - } - final int count = Integer.parseInt(countStr); - mSignatures = new Signature[count]; - int pos = 0; - - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals("cert")) { - if (pos < count) { - String index = parser.getAttributeValue(null, "index"); - if (index != null) { - try { - int idx = Integer.parseInt(index); - String key = parser.getAttributeValue(null, "key"); - if (key == null) { - if (idx >= 0 && idx < pastSignatures.size()) { - Signature sig = pastSignatures.get(idx); - if (sig != null) { - mSignatures[pos] = pastSignatures.get(idx); - pos++; - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <cert> " - + "index " + index + " is not defined at " - + parser.getPositionDescription()); - } - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <cert> " - + "index " + index + " is out of bounds at " - + parser.getPositionDescription()); - } - } else { - while (pastSignatures.size() <= idx) { - pastSignatures.add(null); - } - Signature sig = new Signature(key); - pastSignatures.set(idx, sig); - mSignatures[pos] = sig; - pos++; - } - } catch (NumberFormatException e) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <cert> " - + "index " + index + " is not a number at " - + parser.getPositionDescription()); - } - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <cert> has" - + " no index at " + parser.getPositionDescription()); + } catch (IOException e) { } - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: too " - + "many <cert> tags, expected " + count - + " at " + parser.getPositionDescription()); } - } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <cert>: " - + parser.getName()); - } - XmlUtils.skipCurrentTag(parser); - } - - if (pos < count) { - // Should never happen -- there is an error in the written - // settings -- but if it does we don't want to generate - // a bad array. - Signature[] newSigs = new Signature[pos]; - System.arraycopy(mSignatures, 0, newSigs, 0, pos); - mSignatures = newSigs; - } - } - - private void assignSignatures(Signature[] sigs) { - if (sigs == null) { - mSignatures = null; - return; - } - mSignatures = new Signature[sigs.length]; - for (int i=0; i<sigs.length; i++) { - mSignatures[i] = sigs[i]; - } - } - - @Override - public String toString() { - StringBuffer buf = new StringBuffer(128); - buf.append("PackageSignatures{"); - buf.append(Integer.toHexString(System.identityHashCode(this))); - buf.append(" ["); - if (mSignatures != null) { - for (int i=0; i<mSignatures.length; i++) { - if (i > 0) buf.append(", "); - buf.append(Integer.toHexString( - System.identityHashCode(mSignatures[i]))); } } - buf.append("]}"); - return buf.toString(); - } - } - - static class PreferredActivity extends IntentFilter implements PreferredComponent.Callbacks { - final PreferredComponent mPref; - - PreferredActivity(IntentFilter filter, int match, ComponentName[] set, - ComponentName activity) { - super(filter); - mPref = new PreferredComponent(this, match, set, activity); - } - - PreferredActivity(XmlPullParser parser) throws XmlPullParserException, - IOException { - mPref = new PreferredComponent(this, parser); - } - - public void writeToXml(XmlSerializer serializer) throws IOException { - mPref.writeToXml(serializer); - serializer.startTag(null, "filter"); - super.writeToXml(serializer); - serializer.endTag(null, "filter"); - } - - public boolean onReadTag(String tagName, XmlPullParser parser) - throws XmlPullParserException, IOException { - if (tagName.equals("filter")) { - //Log.i(TAG, "Starting to parse filter..."); - readFromXml(parser); - //Log.i(TAG, "Finished filter: outerDepth=" + outerDepth + " depth=" - // + parser.getDepth() + " tag=" + parser.getName()); - } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <preferred-activities>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - return true; - } - } - - static class GrantedPermissions { - int pkgFlags; - - HashSet<String> grantedPermissions = new HashSet<String>(); - int[] gids; - - GrantedPermissions(int pkgFlags) { - setFlags(pkgFlags); - } - - GrantedPermissions(GrantedPermissions base) { - pkgFlags = base.pkgFlags; - grantedPermissions = (HashSet<String>) base.grantedPermissions.clone(); - - if (base.gids != null) { - gids = base.gids.clone(); - } - } - - void setFlags(int pkgFlags) { - this.pkgFlags = pkgFlags & ( - ApplicationInfo.FLAG_SYSTEM | - ApplicationInfo.FLAG_FORWARD_LOCK | - ApplicationInfo.FLAG_EXTERNAL_STORAGE); - } - } - - /** - * Settings base class for pending and resolved classes. - */ - static class PackageSettingBase extends GrantedPermissions { - final String name; - final String realName; - File codePath; - String codePathString; - File resourcePath; - String resourcePathString; - String nativeLibraryPathString; - long timeStamp; - long firstInstallTime; - long lastUpdateTime; - int versionCode; - - boolean uidError; - - PackageSignatures signatures = new PackageSignatures(); - - boolean permissionsFixed; - boolean haveGids; - - // Whether this package is currently stopped, thus can not be - // started until explicitly launched by the user. - public boolean stopped; - - // Set to true if we have never launched this app. - public boolean notLaunched; - - /* Explicitly disabled components */ - HashSet<String> disabledComponents = new HashSet<String>(0); - /* Explicitly enabled components */ - HashSet<String> enabledComponents = new HashSet<String>(0); - int enabled = COMPONENT_ENABLED_STATE_DEFAULT; - int installStatus = PKG_INSTALL_COMPLETE; - - PackageSettingBase origPackage; - - /* package name of the app that installed this package */ - String installerPackageName; - - PackageSettingBase(String name, String realName, File codePath, File resourcePath, - String nativeLibraryPathString, int pVersionCode, int pkgFlags) { - super(pkgFlags); - this.name = name; - this.realName = realName; - init(codePath, resourcePath, nativeLibraryPathString, pVersionCode); - } - - /** - * New instance of PackageSetting with one-level-deep cloning. - */ - PackageSettingBase(PackageSettingBase base) { - super(base); - - name = base.name; - realName = base.realName; - codePath = base.codePath; - codePathString = base.codePathString; - resourcePath = base.resourcePath; - resourcePathString = base.resourcePathString; - nativeLibraryPathString = base.nativeLibraryPathString; - timeStamp = base.timeStamp; - firstInstallTime = base.firstInstallTime; - lastUpdateTime = base.lastUpdateTime; - versionCode = base.versionCode; - - uidError = base.uidError; - - signatures = new PackageSignatures(base.signatures); - - permissionsFixed = base.permissionsFixed; - haveGids = base.haveGids; - stopped = base.stopped; - notLaunched = base.notLaunched; - - disabledComponents = (HashSet<String>) base.disabledComponents.clone(); - - enabledComponents = (HashSet<String>) base.enabledComponents.clone(); - - enabled = base.enabled; - installStatus = base.installStatus; - - origPackage = base.origPackage; - - installerPackageName = base.installerPackageName; - } - - void init(File codePath, File resourcePath, String nativeLibraryPathString, - int pVersionCode) { - this.codePath = codePath; - this.codePathString = codePath.toString(); - this.resourcePath = resourcePath; - this.resourcePathString = resourcePath.toString(); - this.nativeLibraryPathString = nativeLibraryPathString; - this.versionCode = pVersionCode; - } - - public void setInstallerPackageName(String packageName) { - installerPackageName = packageName; - } - - String getInstallerPackageName() { - return installerPackageName; - } - - public void setInstallStatus(int newStatus) { - installStatus = newStatus; - } - - public int getInstallStatus() { - return installStatus; - } - - public void setTimeStamp(long newStamp) { - timeStamp = newStamp; - } - - /** - * Make a shallow copy of this package settings. - */ - public void copyFrom(PackageSettingBase base) { - grantedPermissions = base.grantedPermissions; - gids = base.gids; - - timeStamp = base.timeStamp; - firstInstallTime = base.firstInstallTime; - lastUpdateTime = base.lastUpdateTime; - signatures = base.signatures; - permissionsFixed = base.permissionsFixed; - haveGids = base.haveGids; - stopped = base.stopped; - notLaunched = base.notLaunched; - disabledComponents = base.disabledComponents; - enabledComponents = base.enabledComponents; - enabled = base.enabled; - installStatus = base.installStatus; - } - - boolean enableComponentLP(String componentClassName) { - boolean changed = disabledComponents.remove(componentClassName); - changed |= enabledComponents.add(componentClassName); - return changed; - } - - boolean disableComponentLP(String componentClassName) { - boolean changed = enabledComponents.remove(componentClassName); - changed |= disabledComponents.add(componentClassName); - return changed; - } - - boolean restoreComponentLP(String componentClassName) { - boolean changed = enabledComponents.remove(componentClassName); - changed |= disabledComponents.remove(componentClassName); - return changed; - } - - int currentEnabledStateLP(String componentName) { - if (enabledComponents.contains(componentName)) { - return COMPONENT_ENABLED_STATE_ENABLED; - } else if (disabledComponents.contains(componentName)) { - return COMPONENT_ENABLED_STATE_DISABLED; - } else { - return COMPONENT_ENABLED_STATE_DEFAULT; - } - } - } - - /** - * Settings data for a particular package we know about. - */ - static final class PackageSetting extends PackageSettingBase { - int userId; - PackageParser.Package pkg; - SharedUserSetting sharedUser; - - PackageSetting(String name, String realName, File codePath, File resourcePath, - String nativeLibraryPathString, int pVersionCode, int pkgFlags) { - super(name, realName, codePath, resourcePath, nativeLibraryPathString, pVersionCode, - pkgFlags); - } - - /** - * New instance of PackageSetting replicating the original settings. - * Note that it keeps the same PackageParser.Package instance. - */ - PackageSetting(PackageSetting orig) { - super(orig); - - userId = orig.userId; - pkg = orig.pkg; - sharedUser = orig.sharedUser; - } - - @Override - public String toString() { - return "PackageSetting{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + name + "/" + userId + "}"; } } - /** - * Settings data for a particular shared user ID we know about. - */ - static final class SharedUserSetting extends GrantedPermissions { - final String name; - int userId; - final HashSet<PackageSetting> packages = new HashSet<PackageSetting>(); - final PackageSignatures signatures = new PackageSignatures(); - - SharedUserSetting(String _name, int _pkgFlags) { - super(_pkgFlags); - name = _name; - } - - @Override - public String toString() { - return "SharedUserSetting{" - + Integer.toHexString(System.identityHashCode(this)) - + " " + name + "/" + userId + "}"; - } - } - - /** - * Holds information about dynamic settings. - */ - private static final class Settings { - private final File mSettingsFilename; - private final File mBackupSettingsFilename; - private final File mPackageListFilename; - private final File mStoppedPackagesFilename; - private final File mBackupStoppedPackagesFilename; - private final HashMap<String, PackageSetting> mPackages = - new HashMap<String, PackageSetting>(); - // List of replaced system applications - final HashMap<String, PackageSetting> mDisabledSysPackages = - new HashMap<String, PackageSetting>(); - - // These are the last platform API version we were using for - // the apps installed on internal and external storage. It is - // used to grant newer permissions one time during a system upgrade. - int mInternalSdkPlatform; - int mExternalSdkPlatform; - - // The user's preferred activities associated with particular intent - // filters. - private final IntentResolver<PreferredActivity, PreferredActivity> mPreferredActivities = - new IntentResolver<PreferredActivity, PreferredActivity>() { - @Override - protected String packageForFilter(PreferredActivity filter) { - return filter.mPref.mComponent.getPackageName(); - } - @Override - protected void dumpFilter(PrintWriter out, String prefix, - PreferredActivity filter) { - filter.mPref.dump(out, prefix, filter); - } - }; - private final HashMap<String, SharedUserSetting> mSharedUsers = - new HashMap<String, SharedUserSetting>(); - private final ArrayList<Object> mUserIds = new ArrayList<Object>(); - private final SparseArray<Object> mOtherUserIds = - new SparseArray<Object>(); - - // For reading/writing settings file. - private final ArrayList<Signature> mPastSignatures = - new ArrayList<Signature>(); - - // Mapping from permission names to info about them. - final HashMap<String, BasePermission> mPermissions = - new HashMap<String, BasePermission>(); - - // Mapping from permission tree names to info about them. - final HashMap<String, BasePermission> mPermissionTrees = - new HashMap<String, BasePermission>(); - - // Packages that have been uninstalled and still need their external - // storage data deleted. - final ArrayList<String> mPackagesToBeCleaned = new ArrayList<String>(); - - // Packages that have been renamed since they were first installed. - // Keys are the new names of the packages, values are the original - // names. The packages appear everwhere else under their original - // names. - final HashMap<String, String> mRenamedPackages = new HashMap<String, String>(); - - private final StringBuilder mReadMessages = new StringBuilder(); - - private static final class PendingPackage extends PackageSettingBase { - final int sharedId; - - PendingPackage(String name, String realName, File codePath, File resourcePath, - String nativeLibraryPathString, int sharedId, int pVersionCode, int pkgFlags) { - super(name, realName, codePath, resourcePath, nativeLibraryPathString, - pVersionCode, pkgFlags); - this.sharedId = sharedId; - } - } - private final ArrayList<PendingPackage> mPendingPackages - = new ArrayList<PendingPackage>(); - - Settings() { - File dataDir = Environment.getDataDirectory(); - File systemDir = new File(dataDir, "system"); - systemDir.mkdirs(); - FileUtils.setPermissions(systemDir.toString(), - FileUtils.S_IRWXU|FileUtils.S_IRWXG - |FileUtils.S_IROTH|FileUtils.S_IXOTH, - -1, -1); - mSettingsFilename = new File(systemDir, "packages.xml"); - mBackupSettingsFilename = new File(systemDir, "packages-backup.xml"); - mPackageListFilename = new File(systemDir, "packages.list"); - mStoppedPackagesFilename = new File(systemDir, "packages-stopped.xml"); - mBackupStoppedPackagesFilename = new File(systemDir, "packages-stopped-backup.xml"); - } - - PackageSetting getPackageLP(PackageParser.Package pkg, PackageSetting origPackage, - String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, - String nativeLibraryPathString, int pkgFlags, boolean create, boolean add) { - final String name = pkg.packageName; - PackageSetting p = getPackageLP(name, origPackage, realName, sharedUser, codePath, - resourcePath, nativeLibraryPathString, pkg.mVersionCode, pkgFlags, create, add); - return p; - } - - PackageSetting peekPackageLP(String name) { - return mPackages.get(name); - /* - PackageSetting p = mPackages.get(name); - if (p != null && p.codePath.getPath().equals(codePath)) { - return p; - } - return null; - */ - } - - void setInstallStatus(String pkgName, int status) { - PackageSetting p = mPackages.get(pkgName); - if(p != null) { - if(p.getInstallStatus() != status) { - p.setInstallStatus(status); - } - } - } + // ------- apps on sdcard specific code ------- + static final boolean DEBUG_SD_INSTALL = false; - void setInstallerPackageName(String pkgName, - String installerPkgName) { - PackageSetting p = mPackages.get(pkgName); - if(p != null) { - p.setInstallerPackageName(installerPkgName); - } - } + private static final String SD_ENCRYPTION_KEYSTORE_NAME = "AppsOnSD"; - String getInstallerPackageName(String pkgName) { - PackageSetting p = mPackages.get(pkgName); - return (p == null) ? null : p.getInstallerPackageName(); - } + private static final String SD_ENCRYPTION_ALGORITHM = "AES"; - int getInstallStatus(String pkgName) { - PackageSetting p = mPackages.get(pkgName); - if(p != null) { - return p.getInstallStatus(); - } - return -1; - } + private boolean mMediaMounted = false; - SharedUserSetting getSharedUserLP(String name, - int pkgFlags, boolean create) { - SharedUserSetting s = mSharedUsers.get(name); - if (s == null) { - if (!create) { + private String getEncryptKey() { + try { + String sdEncKey = SystemKeyStore.getInstance().retrieveKeyHexString( + SD_ENCRYPTION_KEYSTORE_NAME); + if (sdEncKey == null) { + sdEncKey = SystemKeyStore.getInstance().generateNewKeyHexString(128, + SD_ENCRYPTION_ALGORITHM, SD_ENCRYPTION_KEYSTORE_NAME); + if (sdEncKey == null) { + Slog.e(TAG, "Failed to create encryption keys"); return null; } - s = new SharedUserSetting(name, pkgFlags); - if (MULTIPLE_APPLICATION_UIDS) { - s.userId = newUserIdLP(s); - } else { - s.userId = FIRST_APPLICATION_UID; - } - Log.i(TAG, "New shared user " + name + ": id=" + s.userId); - // < 0 means we couldn't assign a userid; fall out and return - // s, which is currently null - if (s.userId >= 0) { - mSharedUsers.put(name, s); - } - } - - return s; - } - - boolean disableSystemPackageLP(String name) { - PackageSetting p = mPackages.get(name); - if(p == null) { - Log.w(TAG, "Package:"+name+" is not an installed package"); - return false; - } - PackageSetting dp = mDisabledSysPackages.get(name); - // always make sure the system package code and resource paths dont change - if (dp == null) { - if((p.pkg != null) && (p.pkg.applicationInfo != null)) { - p.pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; - } - mDisabledSysPackages.put(name, p); - - // a little trick... when we install the new package, we don't - // want to modify the existing PackageSetting for the built-in - // version. so at this point we need a new PackageSetting that - // is okay to muck with. - PackageSetting newp = new PackageSetting(p); - replacePackageLP(name, newp); - return true; - } - return false; - } - - PackageSetting enableSystemPackageLP(String name) { - PackageSetting p = mDisabledSysPackages.get(name); - if(p == null) { - Log.w(TAG, "Package:"+name+" is not disabled"); - return null; - } - // Reset flag in ApplicationInfo object - if((p.pkg != null) && (p.pkg.applicationInfo != null)) { - p.pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; - } - PackageSetting ret = addPackageLP(name, p.realName, p.codePath, p.resourcePath, - p.nativeLibraryPathString, p.userId, p.versionCode, p.pkgFlags); - mDisabledSysPackages.remove(name); - return ret; - } - - PackageSetting addPackageLP(String name, String realName, File codePath, File resourcePath, - String nativeLibraryPathString, int uid, int vc, int pkgFlags) { - PackageSetting p = mPackages.get(name); - if (p != null) { - if (p.userId == uid) { - return p; - } - reportSettingsProblem(Log.ERROR, - "Adding duplicate package, keeping first: " + name); - return null; - } - p = new PackageSetting(name, realName, codePath, resourcePath, nativeLibraryPathString, - vc, pkgFlags); - p.userId = uid; - if (addUserIdLP(uid, p, name)) { - mPackages.put(name, p); - return p; } + return sdEncKey; + } catch (NoSuchAlgorithmException nsae) { + Slog.e(TAG, "Failed to create encryption keys with exception: " + nsae); return null; - } - - SharedUserSetting addSharedUserLP(String name, int uid, int pkgFlags) { - SharedUserSetting s = mSharedUsers.get(name); - if (s != null) { - if (s.userId == uid) { - return s; - } - reportSettingsProblem(Log.ERROR, - "Adding duplicate shared user, keeping first: " + name); - return null; - } - s = new SharedUserSetting(name, pkgFlags); - s.userId = uid; - if (addUserIdLP(uid, s, name)) { - mSharedUsers.put(name, s); - return s; - } + } catch (IOException ioe) { + Slog.e(TAG, "Failed to retrieve encryption keys with exception: " + ioe); return null; } - // Transfer ownership of permissions from one package to another. - private void transferPermissions(String origPkg, String newPkg) { - // Transfer ownership of permissions to the new package. - for (int i=0; i<2; i++) { - HashMap<String, BasePermission> permissions = - i == 0 ? mPermissionTrees : mPermissions; - for (BasePermission bp : permissions.values()) { - if (origPkg.equals(bp.sourcePackage)) { - if (DEBUG_UPGRADE) Log.v(TAG, - "Moving permission " + bp.name - + " from pkg " + bp.sourcePackage - + " to " + newPkg); - bp.sourcePackage = newPkg; - bp.packageSetting = null; - bp.perm = null; - if (bp.pendingInfo != null) { - bp.pendingInfo.packageName = newPkg; - } - bp.uid = 0; - bp.gids = null; - } - } - } - } - - private PackageSetting getPackageLP(String name, PackageSetting origPackage, - String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, - String nativeLibraryPathString, int vc, int pkgFlags, boolean create, boolean add) { - PackageSetting p = mPackages.get(name); - if (p != null) { - if (!p.codePath.equals(codePath)) { - // Check to see if its a disabled system app - if ((p.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) { - // This is an updated system app with versions in both system - // and data partition. Just let the most recent version - // take precedence. - Slog.w(TAG, "Trying to update system app code path from " + - p.codePathString + " to " + codePath.toString()); - } else { - // Just a change in the code path is not an issue, but - // let's log a message about it. - Slog.i(TAG, "Package " + name + " codePath changed from " + p.codePath - + " to " + codePath + "; Retaining data and using new"); - /* - * Since we've changed paths, we need to prefer the new - * native library path over the one stored in the - * package settings since we might have moved from - * internal to external storage or vice versa. - */ - p.nativeLibraryPathString = nativeLibraryPathString; - } - } - if (p.sharedUser != sharedUser) { - reportSettingsProblem(Log.WARN, - "Package " + name + " shared user changed from " - + (p.sharedUser != null ? p.sharedUser.name : "<nothing>") - + " to " - + (sharedUser != null ? sharedUser.name : "<nothing>") - + "; replacing with new"); - p = null; - } else { - if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0) { - // If what we are scanning is a system package, then - // make it so, regardless of whether it was previously - // installed only in the data partition. - p.pkgFlags |= ApplicationInfo.FLAG_SYSTEM; - } - } - } - if (p == null) { - // Create a new PackageSettings entry. this can end up here because - // of code path mismatch or user id mismatch of an updated system partition - if (!create) { - return null; - } - if (origPackage != null) { - // We are consuming the data from an existing package. - p = new PackageSetting(origPackage.name, name, codePath, resourcePath, - nativeLibraryPathString, vc, pkgFlags); - if (DEBUG_UPGRADE) Log.v(TAG, "Package " + name - + " is adopting original package " + origPackage.name); - // Note that we will retain the new package's signature so - // that we can keep its data. - PackageSignatures s = p.signatures; - p.copyFrom(origPackage); - p.signatures = s; - p.sharedUser = origPackage.sharedUser; - p.userId = origPackage.userId; - p.origPackage = origPackage; - mRenamedPackages.put(name, origPackage.name); - name = origPackage.name; - // Update new package state. - p.setTimeStamp(codePath.lastModified()); - } else { - p = new PackageSetting(name, realName, codePath, resourcePath, - nativeLibraryPathString, vc, pkgFlags); - p.setTimeStamp(codePath.lastModified()); - p.sharedUser = sharedUser; - // If this is not a system app, it starts out stopped. - if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { - if (DEBUG_STOPPED) { - RuntimeException e = new RuntimeException("here"); - e.fillInStackTrace(); - Slog.i(TAG, "Stopping package " + name, e); - } - p.stopped = true; - p.notLaunched = true; - } - if (sharedUser != null) { - p.userId = sharedUser.userId; - } else if (MULTIPLE_APPLICATION_UIDS) { - // Clone the setting here for disabled system packages - PackageSetting dis = mDisabledSysPackages.get(name); - if (dis != null) { - // For disabled packages a new setting is created - // from the existing user id. This still has to be - // added to list of user id's - // Copy signatures from previous setting - if (dis.signatures.mSignatures != null) { - p.signatures.mSignatures = dis.signatures.mSignatures.clone(); - } - p.userId = dis.userId; - // Clone permissions - p.grantedPermissions = new HashSet<String>(dis.grantedPermissions); - // Clone component info - p.disabledComponents = new HashSet<String>(dis.disabledComponents); - p.enabledComponents = new HashSet<String>(dis.enabledComponents); - // Add new setting to list of user ids - addUserIdLP(p.userId, p, name); - } else { - // Assign new user id - p.userId = newUserIdLP(p); - } - } else { - p.userId = FIRST_APPLICATION_UID; - } - } - if (p.userId < 0) { - reportSettingsProblem(Log.WARN, - "Package " + name + " could not be assigned a valid uid"); - return null; - } - if (add) { - // Finish adding new package by adding it and updating shared - // user preferences - addPackageSettingLP(p, name, sharedUser); - } - } - return p; - } - - private void insertPackageSettingLP(PackageSetting p, PackageParser.Package pkg) { - p.pkg = pkg; - pkg.mSetEnabled = p.enabled; - pkg.mSetStopped = p.stopped; - final String codePath = pkg.applicationInfo.sourceDir; - final String resourcePath = pkg.applicationInfo.publicSourceDir; - // Update code path if needed - if (!codePath.equalsIgnoreCase(p.codePathString)) { - Slog.w(TAG, "Code path for pkg : " + p.pkg.packageName + - " changing from " + p.codePathString + " to " + codePath); - p.codePath = new File(codePath); - p.codePathString = codePath; - } - //Update resource path if needed - if (!resourcePath.equalsIgnoreCase(p.resourcePathString)) { - Slog.w(TAG, "Resource path for pkg : " + p.pkg.packageName + - " changing from " + p.resourcePathString + " to " + resourcePath); - p.resourcePath = new File(resourcePath); - p.resourcePathString = resourcePath; - } - // Update the native library path if needed - final String nativeLibraryPath = pkg.applicationInfo.nativeLibraryDir; - if (nativeLibraryPath != null - && !nativeLibraryPath.equalsIgnoreCase(p.nativeLibraryPathString)) { - p.nativeLibraryPathString = nativeLibraryPath; - } - // Update version code if needed - if (pkg.mVersionCode != p.versionCode) { - p.versionCode = pkg.mVersionCode; - } - // Update signatures if needed. - if (p.signatures.mSignatures == null) { - p.signatures.assignSignatures(pkg.mSignatures); - } - // If this app defines a shared user id initialize - // the shared user signatures as well. - if (p.sharedUser != null && p.sharedUser.signatures.mSignatures == null) { - p.sharedUser.signatures.assignSignatures(pkg.mSignatures); - } - addPackageSettingLP(p, pkg.packageName, p.sharedUser); - } - - // Utility method that adds a PackageSetting to mPackages and - // completes updating the shared user attributes - private void addPackageSettingLP(PackageSetting p, String name, - SharedUserSetting sharedUser) { - mPackages.put(name, p); - if (sharedUser != null) { - if (p.sharedUser != null && p.sharedUser != sharedUser) { - reportSettingsProblem(Log.ERROR, - "Package " + p.name + " was user " - + p.sharedUser + " but is now " + sharedUser - + "; I am not changing its files so it will probably fail!"); - p.sharedUser.packages.remove(p); - } else if (p.userId != sharedUser.userId) { - reportSettingsProblem(Log.ERROR, - "Package " + p.name + " was user id " + p.userId - + " but is now user " + sharedUser - + " with id " + sharedUser.userId - + "; I am not changing its files so it will probably fail!"); - } - - sharedUser.packages.add(p); - p.sharedUser = sharedUser; - p.userId = sharedUser.userId; - } - } + } - /* - * Update the shared user setting when a package using - * specifying the shared user id is removed. The gids - * associated with each permission of the deleted package - * are removed from the shared user's gid list only if its - * not in use by other permissions of packages in the - * shared user setting. - */ - private void updateSharedUserPermsLP(PackageSetting deletedPs, int[] globalGids) { - if ( (deletedPs == null) || (deletedPs.pkg == null)) { - Slog.i(TAG, "Trying to update info for null package. Just ignoring"); - return; - } - // No sharedUserId - if (deletedPs.sharedUser == null) { - return; - } - SharedUserSetting sus = deletedPs.sharedUser; - // Update permissions - for (String eachPerm: deletedPs.pkg.requestedPermissions) { - boolean used = false; - if (!sus.grantedPermissions.contains (eachPerm)) { + /* package */static String getTempContainerId() { + int tmpIdx = 1; + String list[] = PackageHelper.getSecureContainerList(); + if (list != null) { + for (final String name : list) { + // Ignore null and non-temporary container entries + if (name == null || !name.startsWith(mTempContainerPrefix)) { continue; } - for (PackageSetting pkg:sus.packages) { - if (pkg.pkg != null && - !pkg.pkg.packageName.equals(deletedPs.pkg.packageName) && - pkg.pkg.requestedPermissions.contains(eachPerm)) { - used = true; - break; - } - } - if (!used) { - // can safely delete this permission from list - sus.grantedPermissions.remove(eachPerm); - } - } - // Update gids - int newGids[] = globalGids; - for (String eachPerm : sus.grantedPermissions) { - BasePermission bp = mPermissions.get(eachPerm); - if (bp != null) { - newGids = appendInts(newGids, bp.gids); - } - } - sus.gids = newGids; - } - private int removePackageLP(String name) { - PackageSetting p = mPackages.get(name); - if (p != null) { - mPackages.remove(name); - if (p.sharedUser != null) { - p.sharedUser.packages.remove(p); - if (p.sharedUser.packages.size() == 0) { - mSharedUsers.remove(p.sharedUser.name); - removeUserIdLP(p.sharedUser.userId); - return p.sharedUser.userId; + String subStr = name.substring(mTempContainerPrefix.length()); + try { + int cid = Integer.parseInt(subStr); + if (cid >= tmpIdx) { + tmpIdx = cid + 1; } - } else { - removeUserIdLP(p.userId); - return p.userId; - } - } - return -1; - } - - private void replacePackageLP(String name, PackageSetting newp) { - PackageSetting p = mPackages.get(name); - if (p != null) { - if (p.sharedUser != null) { - p.sharedUser.packages.remove(p); - p.sharedUser.packages.add(newp); - } else { - replaceUserIdLP(p.userId, newp); - } - } - mPackages.put(name, newp); - } - - private boolean addUserIdLP(int uid, Object obj, Object name) { - if (uid >= FIRST_APPLICATION_UID + MAX_APPLICATION_UIDS) { - return false; - } - - if (uid >= FIRST_APPLICATION_UID) { - int N = mUserIds.size(); - final int index = uid - FIRST_APPLICATION_UID; - while (index >= N) { - mUserIds.add(null); - N++; - } - if (mUserIds.get(index) != null) { - reportSettingsProblem(Log.ERROR, - "Adding duplicate user id: " + uid - + " name=" + name); - return false; - } - mUserIds.set(index, obj); - } else { - if (mOtherUserIds.get(uid) != null) { - reportSettingsProblem(Log.ERROR, - "Adding duplicate shared id: " + uid - + " name=" + name); - return false; - } - mOtherUserIds.put(uid, obj); - } - return true; - } - - public Object getUserIdLP(int uid) { - if (uid >= FIRST_APPLICATION_UID) { - int N = mUserIds.size(); - final int index = uid - FIRST_APPLICATION_UID; - return index < N ? mUserIds.get(index) : null; - } else { - return mOtherUserIds.get(uid); - } - } - - private Set<String> findPackagesWithFlag(int flag) { - Set<String> ret = new HashSet<String>(); - for (PackageSetting ps : mPackages.values()) { - // Has to match atleast all the flag bits set on flag - if ((ps.pkgFlags & flag) == flag) { - ret.add(ps.name); + } catch (NumberFormatException e) { } } - return ret; } + return mTempContainerPrefix + tmpIdx; + } - private void removeUserIdLP(int uid) { - if (uid >= FIRST_APPLICATION_UID) { - int N = mUserIds.size(); - final int index = uid - FIRST_APPLICATION_UID; - if (index < N) mUserIds.set(index, null); - } else { - mOtherUserIds.remove(uid); - } + /* + * Update media status on PackageManager. + */ + public void updateExternalMediaStatus(final boolean mediaStatus, final boolean reportStatus) { + int callingUid = Binder.getCallingUid(); + if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { + throw new SecurityException("Media status can only be updated by the system"); } - - private void replaceUserIdLP(int uid, Object obj) { - if (uid >= FIRST_APPLICATION_UID) { - int N = mUserIds.size(); - final int index = uid - FIRST_APPLICATION_UID; - if (index < N) mUserIds.set(index, obj); - } else { - mOtherUserIds.put(uid, obj); - } - } - - void writeStoppedLP() { - // Keep the old stopped packages around until we know the new ones have - // been successfully written. - if (mStoppedPackagesFilename.exists()) { - // Presence of backup settings file indicates that we failed - // to persist packages earlier. So preserve the older - // backup for future reference since the current packages - // might have been corrupted. - if (!mBackupStoppedPackagesFilename.exists()) { - if (!mStoppedPackagesFilename.renameTo(mBackupStoppedPackagesFilename)) { - Log.wtf(TAG, "Unable to backup package manager stopped packages, " - + "current changes will be lost at reboot"); - return; - } - } else { - mStoppedPackagesFilename.delete(); - Slog.w(TAG, "Preserving older stopped packages backup"); - } - } - - try { - FileOutputStream fstr = new FileOutputStream(mStoppedPackagesFilename); - BufferedOutputStream str = new BufferedOutputStream(fstr); - - //XmlSerializer serializer = XmlUtils.serializerInstance(); - XmlSerializer serializer = new FastXmlSerializer(); - serializer.setOutput(str, "utf-8"); - serializer.startDocument(null, true); - serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - - serializer.startTag(null, "stopped-packages"); - - for (PackageSetting pkg : mPackages.values()) { - if (pkg.stopped) { - serializer.startTag(null, "pkg"); - serializer.attribute(null, "name", pkg.name); - if (pkg.notLaunched) { - serializer.attribute(null, "nl", "1"); - } - serializer.endTag(null, "pkg"); - } - } - - serializer.endTag(null, "stopped-packages"); - - serializer.endDocument(); - - str.flush(); - FileUtils.sync(fstr); - str.close(); - - // New settings successfully written, old ones are no longer - // needed. - mBackupStoppedPackagesFilename.delete(); - FileUtils.setPermissions(mStoppedPackagesFilename.toString(), - FileUtils.S_IRUSR|FileUtils.S_IWUSR - |FileUtils.S_IRGRP|FileUtils.S_IWGRP - |FileUtils.S_IROTH, - -1, -1); - - // Done, all is good! + // reader; this apparently protects mMediaMounted, but should probably + // be a different lock in that case. + synchronized (mPackages) { + Log.i(TAG, "Updating external media status from " + + (mMediaMounted ? "mounted" : "unmounted") + " to " + + (mediaStatus ? "mounted" : "unmounted")); + if (DEBUG_SD_INSTALL) + Log.i(TAG, "updateExternalMediaStatus:: mediaStatus=" + mediaStatus + + ", mMediaMounted=" + mMediaMounted); + if (mediaStatus == mMediaMounted) { + final Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, reportStatus ? 1 + : 0, -1); + mHandler.sendMessage(msg); return; - - } catch(java.io.IOException e) { - Log.wtf(TAG, "Unable to write package manager stopped packages, " - + " current changes will be lost at reboot", e); - } - - // Clean up partially written files - if (mStoppedPackagesFilename.exists()) { - if (!mStoppedPackagesFilename.delete()) { - Log.i(TAG, "Failed to clean up mangled file: " + mStoppedPackagesFilename); - } } + mMediaMounted = mediaStatus; } - - // Note: assumed "stopped" field is already cleared in all packages. - void readStoppedLP() { - FileInputStream str = null; - if (mBackupStoppedPackagesFilename.exists()) { - try { - str = new FileInputStream(mBackupStoppedPackagesFilename); - mReadMessages.append("Reading from backup stopped packages file\n"); - reportSettingsProblem(Log.INFO, "Need to read from backup stopped packages file"); - if (mSettingsFilename.exists()) { - // If both the backup and normal file exist, we - // ignore the normal one since it might have been - // corrupted. - Slog.w(TAG, "Cleaning up stopped packages file " - + mStoppedPackagesFilename); - mStoppedPackagesFilename.delete(); - } - } catch (java.io.IOException e) { - // We'll try for the normal settings file. - } + // Queue up an async operation since the package installation may take a + // little while. + mHandler.post(new Runnable() { + public void run() { + // TODO fix this; this does nothing. + mHandler.removeCallbacks(this); + updateExternalMediaStatusInner(mediaStatus, reportStatus); } + }); + } - try { - if (str == null) { - if (!mStoppedPackagesFilename.exists()) { - mReadMessages.append("No stopped packages file found\n"); - reportSettingsProblem(Log.INFO, "No stopped packages file file; " - + "assuming all started"); - // At first boot, make sure no packages are stopped. - // We usually want to have third party apps initialize - // in the stopped state, but not at first boot. - for (PackageSetting pkg : mPackages.values()) { - pkg.stopped = false; - pkg.notLaunched = false; - } - return; - } - str = new FileInputStream(mStoppedPackagesFilename); - } - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(str, null); - - int type; - while ((type=parser.next()) != XmlPullParser.START_TAG - && type != XmlPullParser.END_DOCUMENT) { - ; - } - - if (type != XmlPullParser.START_TAG) { - mReadMessages.append("No start tag found in stopped packages file\n"); - reportSettingsProblem(Log.WARN, - "No start tag found in package manager stopped packages"); - return; - } - - int outerDepth = parser.getDepth(); - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { + /* + * Collect information of applications on external media, map them against + * existing containers and update information based on current mount status. + * Please note that we always have to report status if reportStatus has been + * set to true especially when unloading packages. + */ + private void updateExternalMediaStatusInner(boolean mediaStatus, boolean reportStatus) { + // Collection of uids + int uidArr[] = null; + // Collection of stale containers + HashSet<String> removeCids = new HashSet<String>(); + // Collection of packages on external media with valid containers. + HashMap<SdInstallArgs, String> processCids = new HashMap<SdInstallArgs, String>(); + // Get list of secure containers. + final String list[] = PackageHelper.getSecureContainerList(); + if (list == null || list.length == 0) { + Log.i(TAG, "No secure containers on sdcard"); + } else { + // Process list of secure containers and categorize them + // as active or stale based on their package internal state. + int uidList[] = new int[list.length]; + int num = 0; + // reader + synchronized (mPackages) { + for (String cid : list) { + SdInstallArgs args = new SdInstallArgs(cid); + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Processing container " + cid); + String pkgName = args.getPackageName(); + if (pkgName == null) { + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Container : " + cid + " stale"); + removeCids.add(cid); continue; } - - String tagName = parser.getName(); - if (tagName.equals("pkg")) { - String name = parser.getAttributeValue(null, "name"); - PackageSetting ps = mPackages.get(name); - if (ps != null) { - ps.stopped = true; - if ("1".equals(parser.getAttributeValue(null, "nl"))) { - ps.notLaunched = true; - } - } else { - Slog.w(TAG, "No package known for stopped package: " + name); + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Looking for pkg : " + pkgName); + PackageSetting ps = mSettings.mPackages.get(pkgName); + // The package status is changed only if the code path + // matches between settings and the container id. + if (ps != null && ps.codePathString != null + && ps.codePathString.equals(args.getCodePath())) { + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Container : " + cid + " corresponds to pkg : " + pkgName + + " at code path: " + ps.codePathString); + // We do have a valid package installed on sdcard + processCids.put(args, ps.codePathString); + int uid = ps.userId; + if (uid != -1) { + uidList[num++] = uid; } - XmlUtils.skipCurrentTag(parser); } else { - Slog.w(TAG, "Unknown element under <stopped-packages>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - } - - str.close(); - - } catch(XmlPullParserException e) { - mReadMessages.append("Error reading: " + e.toString()); - reportSettingsProblem(Log.ERROR, "Error reading stopped packages: " + e); - Log.wtf(TAG, "Error reading package manager stopped packages", e); - - } catch(java.io.IOException e) { - mReadMessages.append("Error reading: " + e.toString()); - reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); - Log.wtf(TAG, "Error reading package manager stopped packages", e); - - } - } - - void writeLP() { - //Debug.startMethodTracing("/data/system/packageprof", 8 * 1024 * 1024); - - // Keep the old settings around until we know the new ones have - // been successfully written. - if (mSettingsFilename.exists()) { - // Presence of backup settings file indicates that we failed - // to persist settings earlier. So preserve the older - // backup for future reference since the current settings - // might have been corrupted. - if (!mBackupSettingsFilename.exists()) { - if (!mSettingsFilename.renameTo(mBackupSettingsFilename)) { - Log.wtf(TAG, "Unable to backup package manager settings, " - + " current changes will be lost at reboot"); - return; - } - } else { - mSettingsFilename.delete(); - Slog.w(TAG, "Preserving older settings backup"); - } - } - - mPastSignatures.clear(); - - try { - FileOutputStream fstr = new FileOutputStream(mSettingsFilename); - BufferedOutputStream str = new BufferedOutputStream(fstr); - - //XmlSerializer serializer = XmlUtils.serializerInstance(); - XmlSerializer serializer = new FastXmlSerializer(); - serializer.setOutput(str, "utf-8"); - serializer.startDocument(null, true); - serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - - serializer.startTag(null, "packages"); - - serializer.startTag(null, "last-platform-version"); - serializer.attribute(null, "internal", Integer.toString(mInternalSdkPlatform)); - serializer.attribute(null, "external", Integer.toString(mExternalSdkPlatform)); - serializer.endTag(null, "last-platform-version"); - - serializer.startTag(null, "permission-trees"); - for (BasePermission bp : mPermissionTrees.values()) { - writePermission(serializer, bp); - } - serializer.endTag(null, "permission-trees"); - - serializer.startTag(null, "permissions"); - for (BasePermission bp : mPermissions.values()) { - writePermission(serializer, bp); - } - serializer.endTag(null, "permissions"); - - for (PackageSetting pkg : mPackages.values()) { - writePackage(serializer, pkg); - } - - for (PackageSetting pkg : mDisabledSysPackages.values()) { - writeDisabledSysPackage(serializer, pkg); - } - - serializer.startTag(null, "preferred-activities"); - for (PreferredActivity pa : mPreferredActivities.filterSet()) { - serializer.startTag(null, "item"); - pa.writeToXml(serializer); - serializer.endTag(null, "item"); - } - serializer.endTag(null, "preferred-activities"); - - for (SharedUserSetting usr : mSharedUsers.values()) { - serializer.startTag(null, "shared-user"); - serializer.attribute(null, "name", usr.name); - serializer.attribute(null, "userId", - Integer.toString(usr.userId)); - usr.signatures.writeXml(serializer, "sigs", mPastSignatures); - serializer.startTag(null, "perms"); - for (String name : usr.grantedPermissions) { - serializer.startTag(null, "item"); - serializer.attribute(null, "name", name); - serializer.endTag(null, "item"); - } - serializer.endTag(null, "perms"); - serializer.endTag(null, "shared-user"); - } - - if (mPackagesToBeCleaned.size() > 0) { - for (int i=0; i<mPackagesToBeCleaned.size(); i++) { - serializer.startTag(null, "cleaning-package"); - serializer.attribute(null, "name", mPackagesToBeCleaned.get(i)); - serializer.endTag(null, "cleaning-package"); - } - } - - if (mRenamedPackages.size() > 0) { - for (HashMap.Entry<String, String> e : mRenamedPackages.entrySet()) { - serializer.startTag(null, "renamed-package"); - serializer.attribute(null, "new", e.getKey()); - serializer.attribute(null, "old", e.getValue()); - serializer.endTag(null, "renamed-package"); - } - } - - serializer.endTag(null, "packages"); - - serializer.endDocument(); - - str.flush(); - FileUtils.sync(fstr); - str.close(); - - // New settings successfully written, old ones are no longer - // needed. - mBackupSettingsFilename.delete(); - FileUtils.setPermissions(mSettingsFilename.toString(), - FileUtils.S_IRUSR|FileUtils.S_IWUSR - |FileUtils.S_IRGRP|FileUtils.S_IWGRP - |FileUtils.S_IROTH, - -1, -1); - - // Write package list file now, use a JournaledFile. - // - File tempFile = new File(mPackageListFilename.toString() + ".tmp"); - JournaledFile journal = new JournaledFile(mPackageListFilename, tempFile); - - fstr = new FileOutputStream(journal.chooseForWrite()); - str = new BufferedOutputStream(fstr); - try { - StringBuilder sb = new StringBuilder(); - for (PackageSetting pkg : mPackages.values()) { - ApplicationInfo ai = pkg.pkg.applicationInfo; - String dataPath = ai.dataDir; - boolean isDebug = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; - - // Avoid any application that has a space in its path - // or that is handled by the system. - if (dataPath.indexOf(" ") >= 0 || ai.uid <= Process.FIRST_APPLICATION_UID) - continue; - - // we store on each line the following information for now: - // - // pkgName - package name - // userId - application-specific user id - // debugFlag - 0 or 1 if the package is debuggable. - // dataPath - path to package's data path - // - // NOTE: We prefer not to expose all ApplicationInfo flags for now. - // - // DO NOT MODIFY THIS FORMAT UNLESS YOU CAN ALSO MODIFY ITS USERS - // FROM NATIVE CODE. AT THE MOMENT, LOOK AT THE FOLLOWING SOURCES: - // system/core/run-as/run-as.c - // - sb.setLength(0); - sb.append(ai.packageName); - sb.append(" "); - sb.append((int)ai.uid); - sb.append(isDebug ? " 1 " : " 0 "); - sb.append(dataPath); - sb.append("\n"); - str.write(sb.toString().getBytes()); + // Stale container on sdcard. Just delete + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Container : " + cid + " stale"); + removeCids.add(cid); } - str.flush(); - FileUtils.sync(fstr); - str.close(); - journal.commit(); } - catch (Exception e) { - journal.rollback(); - } - - FileUtils.setPermissions(mPackageListFilename.toString(), - FileUtils.S_IRUSR|FileUtils.S_IWUSR - |FileUtils.S_IRGRP|FileUtils.S_IWGRP - |FileUtils.S_IROTH, - -1, -1); - - writeStoppedLP(); - - return; - - } catch(XmlPullParserException e) { - Log.wtf(TAG, "Unable to write package manager settings, " - + "current changes will be lost at reboot", e); - } catch(java.io.IOException e) { - Log.wtf(TAG, "Unable to write package manager settings, " - + "current changes will be lost at reboot", e); } - // Clean up partially written files - if (mSettingsFilename.exists()) { - if (!mSettingsFilename.delete()) { - Log.wtf(TAG, "Failed to clean up mangled file: " + mSettingsFilename); - } - } - //Debug.stopMethodTracing(); - } - void writeDisabledSysPackage(XmlSerializer serializer, final PackageSetting pkg) - throws java.io.IOException { - serializer.startTag(null, "updated-package"); - serializer.attribute(null, "name", pkg.name); - if (pkg.realName != null) { - serializer.attribute(null, "realName", pkg.realName); - } - serializer.attribute(null, "codePath", pkg.codePathString); - serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp)); - serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime)); - serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime)); - serializer.attribute(null, "version", String.valueOf(pkg.versionCode)); - if (!pkg.resourcePathString.equals(pkg.codePathString)) { - serializer.attribute(null, "resourcePath", pkg.resourcePathString); - } - if (pkg.nativeLibraryPathString != null) { - serializer.attribute(null, "nativeLibraryPath", pkg.nativeLibraryPathString); - } - if (pkg.sharedUser == null) { - serializer.attribute(null, "userId", - Integer.toString(pkg.userId)); - } else { - serializer.attribute(null, "sharedUserId", - Integer.toString(pkg.userId)); - } - serializer.startTag(null, "perms"); - if (pkg.sharedUser == null) { - // If this is a shared user, the permissions will - // be written there. We still need to write an - // empty permissions list so permissionsFixed will - // be set. - for (final String name : pkg.grantedPermissions) { - BasePermission bp = mPermissions.get(name); - if (bp != null) { - // We only need to write signature or system permissions but this wont - // match the semantics of grantedPermissions. So write all permissions. - serializer.startTag(null, "item"); - serializer.attribute(null, "name", name); - serializer.endTag(null, "item"); + if (num > 0) { + // Sort uid list + Arrays.sort(uidList, 0, num); + // Throw away duplicates + uidArr = new int[num]; + uidArr[0] = uidList[0]; + int di = 0; + for (int i = 1; i < num; i++) { + if (uidList[i - 1] != uidList[i]) { + uidArr[di++] = uidList[i]; } } } - serializer.endTag(null, "perms"); - serializer.endTag(null, "updated-package"); } - - void writePackage(XmlSerializer serializer, final PackageSetting pkg) - throws java.io.IOException { - serializer.startTag(null, "package"); - serializer.attribute(null, "name", pkg.name); - if (pkg.realName != null) { - serializer.attribute(null, "realName", pkg.realName); - } - serializer.attribute(null, "codePath", pkg.codePathString); - if (!pkg.resourcePathString.equals(pkg.codePathString)) { - serializer.attribute(null, "resourcePath", pkg.resourcePathString); - } - if (pkg.nativeLibraryPathString != null) { - serializer.attribute(null, "nativeLibraryPath", pkg.nativeLibraryPathString); - } - serializer.attribute(null, "flags", - Integer.toString(pkg.pkgFlags)); - serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp)); - serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime)); - serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime)); - serializer.attribute(null, "version", String.valueOf(pkg.versionCode)); - if (pkg.sharedUser == null) { - serializer.attribute(null, "userId", - Integer.toString(pkg.userId)); - } else { - serializer.attribute(null, "sharedUserId", - Integer.toString(pkg.userId)); - } - if (pkg.uidError) { - serializer.attribute(null, "uidError", "true"); - } - if (pkg.enabled != COMPONENT_ENABLED_STATE_DEFAULT) { - serializer.attribute(null, "enabled", - pkg.enabled == COMPONENT_ENABLED_STATE_ENABLED - ? "true" : "false"); - } - if(pkg.installStatus == PKG_INSTALL_INCOMPLETE) { - serializer.attribute(null, "installStatus", "false"); - } - if (pkg.installerPackageName != null) { - serializer.attribute(null, "installer", pkg.installerPackageName); - } - pkg.signatures.writeXml(serializer, "sigs", mPastSignatures); - if ((pkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { - serializer.startTag(null, "perms"); - if (pkg.sharedUser == null) { - // If this is a shared user, the permissions will - // be written there. We still need to write an - // empty permissions list so permissionsFixed will - // be set. - for (final String name : pkg.grantedPermissions) { - serializer.startTag(null, "item"); - serializer.attribute(null, "name", name); - serializer.endTag(null, "item"); - } - } - serializer.endTag(null, "perms"); - } - if (pkg.disabledComponents.size() > 0) { - serializer.startTag(null, "disabled-components"); - for (final String name : pkg.disabledComponents) { - serializer.startTag(null, "item"); - serializer.attribute(null, "name", name); - serializer.endTag(null, "item"); - } - serializer.endTag(null, "disabled-components"); - } - if (pkg.enabledComponents.size() > 0) { - serializer.startTag(null, "enabled-components"); - for (final String name : pkg.enabledComponents) { - serializer.startTag(null, "item"); - serializer.attribute(null, "name", name); - serializer.endTag(null, "item"); - } - serializer.endTag(null, "enabled-components"); - } - - serializer.endTag(null, "package"); - } - - void writePermission(XmlSerializer serializer, BasePermission bp) - throws XmlPullParserException, java.io.IOException { - if (bp.type != BasePermission.TYPE_BUILTIN - && bp.sourcePackage != null) { - serializer.startTag(null, "item"); - serializer.attribute(null, "name", bp.name); - serializer.attribute(null, "package", bp.sourcePackage); - if (bp.protectionLevel != - PermissionInfo.PROTECTION_NORMAL) { - serializer.attribute(null, "protection", - Integer.toString(bp.protectionLevel)); - } - if (DEBUG_SETTINGS) Log.v(TAG, - "Writing perm: name=" + bp.name + " type=" + bp.type); - if (bp.type == BasePermission.TYPE_DYNAMIC) { - PermissionInfo pi = bp.perm != null ? bp.perm.info - : bp.pendingInfo; - if (pi != null) { - serializer.attribute(null, "type", "dynamic"); - if (pi.icon != 0) { - serializer.attribute(null, "icon", - Integer.toString(pi.icon)); - } - if (pi.nonLocalizedLabel != null) { - serializer.attribute(null, "label", - pi.nonLocalizedLabel.toString()); - } - } - } - serializer.endTag(null, "item"); - } - } - - String getReadMessagesLP() { - return mReadMessages.toString(); - } - - ArrayList<PackageSetting> getListOfIncompleteInstallPackages() { - HashSet<String> kList = new HashSet<String>(mPackages.keySet()); - Iterator<String> its = kList.iterator(); - ArrayList<PackageSetting> ret = new ArrayList<PackageSetting>(); - while(its.hasNext()) { - String key = its.next(); - PackageSetting ps = mPackages.get(key); - if(ps.getInstallStatus() == PKG_INSTALL_INCOMPLETE) { - ret.add(ps); - } - } - return ret; + // Process packages with valid entries. + if (mediaStatus) { + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Loading packages"); + loadMediaPackages(processCids, uidArr, removeCids); + startCleaningPackages(); + } else { + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Unloading packages"); + unloadMediaPackages(processCids, uidArr, reportStatus); } + } - boolean readLP() { - FileInputStream str = null; - if (mBackupSettingsFilename.exists()) { - try { - str = new FileInputStream(mBackupSettingsFilename); - mReadMessages.append("Reading from backup settings file\n"); - reportSettingsProblem(Log.INFO, "Need to read from backup settings file"); - if (mSettingsFilename.exists()) { - // If both the backup and settings file exist, we - // ignore the settings since it might have been - // corrupted. - Slog.w(TAG, "Cleaning up settings file " + mSettingsFilename); - mSettingsFilename.delete(); - } - } catch (java.io.IOException e) { - // We'll try for the normal settings file. - } - } - - mPastSignatures.clear(); - - try { - if (str == null) { - if (!mSettingsFilename.exists()) { - mReadMessages.append("No settings file found\n"); - reportSettingsProblem(Log.INFO, "No settings file; creating initial state"); - return false; - } - str = new FileInputStream(mSettingsFilename); - } - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(str, null); - - int type; - while ((type=parser.next()) != XmlPullParser.START_TAG - && type != XmlPullParser.END_DOCUMENT) { - ; - } - - if (type != XmlPullParser.START_TAG) { - mReadMessages.append("No start tag found in settings file\n"); - reportSettingsProblem(Log.WARN, "No start tag found in package manager settings"); - Log.wtf(TAG, "No start tag found in package manager settings"); - return false; - } - - int outerDepth = parser.getDepth(); - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals("package")) { - readPackageLP(parser); - } else if (tagName.equals("permissions")) { - readPermissionsLP(mPermissions, parser); - } else if (tagName.equals("permission-trees")) { - readPermissionsLP(mPermissionTrees, parser); - } else if (tagName.equals("shared-user")) { - readSharedUserLP(parser); - } else if (tagName.equals("preferred-packages")) { - // no longer used. - } else if (tagName.equals("preferred-activities")) { - readPreferredActivitiesLP(parser); - } else if(tagName.equals("updated-package")) { - readDisabledSysPackageLP(parser); - } else if (tagName.equals("cleaning-package")) { - String name = parser.getAttributeValue(null, "name"); - if (name != null) { - mPackagesToBeCleaned.add(name); - } - } else if (tagName.equals("renamed-package")) { - String nname = parser.getAttributeValue(null, "new"); - String oname = parser.getAttributeValue(null, "old"); - if (nname != null && oname != null) { - mRenamedPackages.put(nname, oname); - } - } else if (tagName.equals("last-platform-version")) { - mInternalSdkPlatform = mExternalSdkPlatform = 0; - try { - String internal = parser.getAttributeValue(null, "internal"); - if (internal != null) { - mInternalSdkPlatform = Integer.parseInt(internal); - } - String external = parser.getAttributeValue(null, "external"); - if (external != null) { - mExternalSdkPlatform = Integer.parseInt(external); - } - } catch (NumberFormatException e) { - } - } else { - Slog.w(TAG, "Unknown element under <packages>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - } - - str.close(); - - } catch(XmlPullParserException e) { - mReadMessages.append("Error reading: " + e.toString()); - reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); - Log.wtf(TAG, "Error reading package manager settings", e); - - } catch(java.io.IOException e) { - mReadMessages.append("Error reading: " + e.toString()); - reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); - Log.wtf(TAG, "Error reading package manager settings", e); - - } - - int N = mPendingPackages.size(); - for (int i=0; i<N; i++) { - final PendingPackage pp = mPendingPackages.get(i); - Object idObj = getUserIdLP(pp.sharedId); - if (idObj != null && idObj instanceof SharedUserSetting) { - PackageSetting p = getPackageLP(pp.name, null, pp.realName, - (SharedUserSetting) idObj, pp.codePath, pp.resourcePath, - pp.nativeLibraryPathString, pp.versionCode, pp.pkgFlags, true, true); - if (p == null) { - reportSettingsProblem(Log.WARN, "Unable to create application package for " - + pp.name); - continue; - } - p.copyFrom(pp); - } else if (idObj != null) { - String msg = "Bad package setting: package " + pp.name - + " has shared uid " + pp.sharedId - + " that is not a shared uid\n"; - mReadMessages.append(msg); - reportSettingsProblem(Log.ERROR, msg); - } else { - String msg = "Bad package setting: package " + pp.name - + " has shared uid " + pp.sharedId - + " that is not defined\n"; - mReadMessages.append(msg); - reportSettingsProblem(Log.ERROR, msg); - } - } - mPendingPackages.clear(); - - /* - * Make sure all the updated system packages have their shared users - * associated with them. - */ - final Iterator<PackageSetting> disabledIt = mDisabledSysPackages.values().iterator(); - while (disabledIt.hasNext()) { - final PackageSetting disabledPs = disabledIt.next(); - final Object id = getUserIdLP(disabledPs.userId); - if (id != null && id instanceof SharedUserSetting) { - disabledPs.sharedUser = (SharedUserSetting) id; - } + private void sendResourcesChangedBroadcast(boolean mediaStatus, ArrayList<String> pkgList, + int uidArr[], IIntentReceiver finishedReceiver) { + int size = pkgList.size(); + if (size > 0) { + // Send broadcasts here + Bundle extras = new Bundle(); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList + .toArray(new String[size])); + if (uidArr != null) { + extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidArr); } - - readStoppedLP(); - - mReadMessages.append("Read completed successfully: " - + mPackages.size() + " packages, " - + mSharedUsers.size() + " shared uids\n"); - - return true; + String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE + : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE; + sendPackageBroadcast(action, null, extras, null, finishedReceiver); } + } - private int readInt(XmlPullParser parser, String ns, String name, - int defValue) { - String v = parser.getAttributeValue(ns, name); + /* + * Look at potentially valid container ids from processCids If package + * information doesn't match the one on record or package scanning fails, + * the cid is added to list of removeCids. We currently don't delete stale + * containers. + */ + private void loadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[], + HashSet<String> removeCids) { + ArrayList<String> pkgList = new ArrayList<String>(); + Set<SdInstallArgs> keys = processCids.keySet(); + boolean doGc = false; + for (SdInstallArgs args : keys) { + String codePath = processCids.get(args); + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Loading container : " + args.cid); + int retCode = PackageManager.INSTALL_FAILED_CONTAINER_ERROR; try { - if (v == null) { - return defValue; - } - return Integer.parseInt(v); - } catch (NumberFormatException e) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: attribute " + - name + " has bad integer value " + v + " at " - + parser.getPositionDescription()); - } - return defValue; - } - - private void readPermissionsLP(HashMap<String, BasePermission> out, - XmlPullParser parser) - throws IOException, XmlPullParserException { - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { + // Make sure there are no container errors first. + if (args.doPreInstall(PackageManager.INSTALL_SUCCEEDED) != PackageManager.INSTALL_SUCCEEDED) { + Slog.e(TAG, "Failed to mount cid : " + args.cid + + " when installing from sdcard"); continue; } - - String tagName = parser.getName(); - if (tagName.equals("item")) { - String name = parser.getAttributeValue(null, "name"); - String sourcePackage = parser.getAttributeValue(null, "package"); - String ptype = parser.getAttributeValue(null, "type"); - if (name != null && sourcePackage != null) { - boolean dynamic = "dynamic".equals(ptype); - BasePermission bp = new BasePermission(name, sourcePackage, - dynamic - ? BasePermission.TYPE_DYNAMIC - : BasePermission.TYPE_NORMAL); - bp.protectionLevel = readInt(parser, null, "protection", - PermissionInfo.PROTECTION_NORMAL); - if (dynamic) { - PermissionInfo pi = new PermissionInfo(); - pi.packageName = sourcePackage.intern(); - pi.name = name.intern(); - pi.icon = readInt(parser, null, "icon", 0); - pi.nonLocalizedLabel = parser.getAttributeValue( - null, "label"); - pi.protectionLevel = bp.protectionLevel; - bp.pendingInfo = pi; - } - out.put(bp.name, bp); - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: permissions has" - + " no name at " + parser.getPositionDescription()); - } - } else { - reportSettingsProblem(Log.WARN, - "Unknown element reading permissions: " - + parser.getName() + " at " - + parser.getPositionDescription()); - } - XmlUtils.skipCurrentTag(parser); - } - } - - private void readDisabledSysPackageLP(XmlPullParser parser) - throws XmlPullParserException, IOException { - String name = parser.getAttributeValue(null, "name"); - String realName = parser.getAttributeValue(null, "realName"); - String codePathStr = parser.getAttributeValue(null, "codePath"); - String resourcePathStr = parser.getAttributeValue(null, "resourcePath"); - String nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath"); - if (resourcePathStr == null) { - resourcePathStr = codePathStr; - } - String version = parser.getAttributeValue(null, "version"); - int versionCode = 0; - if (version != null) { - try { - versionCode = Integer.parseInt(version); - } catch (NumberFormatException e) { - } - } - - int pkgFlags = 0; - pkgFlags |= ApplicationInfo.FLAG_SYSTEM; - PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr), - new File(resourcePathStr), nativeLibraryPathStr, versionCode, pkgFlags); - String timeStampStr = parser.getAttributeValue(null, "ft"); - if (timeStampStr != null) { - try { - long timeStamp = Long.parseLong(timeStampStr, 16); - ps.setTimeStamp(timeStamp); - } catch (NumberFormatException e) { - } - } else { - timeStampStr = parser.getAttributeValue(null, "ts"); - if (timeStampStr != null) { - try { - long timeStamp = Long.parseLong(timeStampStr); - ps.setTimeStamp(timeStamp); - } catch (NumberFormatException e) { - } - } - } - timeStampStr = parser.getAttributeValue(null, "it"); - if (timeStampStr != null) { - try { - ps.firstInstallTime = Long.parseLong(timeStampStr, 16); - } catch (NumberFormatException e) { - } - } - timeStampStr = parser.getAttributeValue(null, "ut"); - if (timeStampStr != null) { - try { - ps.lastUpdateTime = Long.parseLong(timeStampStr, 16); - } catch (NumberFormatException e) { - } - } - String idStr = parser.getAttributeValue(null, "userId"); - ps.userId = idStr != null ? Integer.parseInt(idStr) : 0; - if(ps.userId <= 0) { - String sharedIdStr = parser.getAttributeValue(null, "sharedUserId"); - ps.userId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0; - } - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { + // Check code path here. + if (codePath == null || !codePath.equals(args.getCodePath())) { + Slog.e(TAG, "Container " + args.cid + " cachepath " + args.getCodePath() + + " does not match one in settings " + codePath); continue; } - - String tagName = parser.getName(); - if (tagName.equals("perms")) { - readGrantedPermissionsLP(parser, - ps.grantedPermissions); - } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <updated-package>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - } - mDisabledSysPackages.put(name, ps); - } - - private void readPackageLP(XmlPullParser parser) - throws XmlPullParserException, IOException { - String name = null; - String realName = null; - String idStr = null; - String sharedIdStr = null; - String codePathStr = null; - String resourcePathStr = null; - String nativeLibraryPathStr = null; - String systemStr = null; - String installerPackageName = null; - String uidError = null; - int pkgFlags = 0; - long timeStamp = 0; - long firstInstallTime = 0; - long lastUpdateTime = 0; - PackageSettingBase packageSetting = null; - String version = null; - int versionCode = 0; - try { - name = parser.getAttributeValue(null, "name"); - realName = parser.getAttributeValue(null, "realName"); - idStr = parser.getAttributeValue(null, "userId"); - uidError = parser.getAttributeValue(null, "uidError"); - sharedIdStr = parser.getAttributeValue(null, "sharedUserId"); - codePathStr = parser.getAttributeValue(null, "codePath"); - resourcePathStr = parser.getAttributeValue(null, "resourcePath"); - nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath"); - version = parser.getAttributeValue(null, "version"); - if (version != null) { - try { - versionCode = Integer.parseInt(version); - } catch (NumberFormatException e) { - } - } - installerPackageName = parser.getAttributeValue(null, "installer"); - - systemStr = parser.getAttributeValue(null, "flags"); - if (systemStr != null) { - try { - pkgFlags = Integer.parseInt(systemStr); - } catch (NumberFormatException e) { - } - } else { - // For backward compatibility - systemStr = parser.getAttributeValue(null, "system"); - if (systemStr != null) { - pkgFlags |= ("true".equalsIgnoreCase(systemStr)) ? ApplicationInfo.FLAG_SYSTEM : 0; - } else { - // Old settings that don't specify system... just treat - // them as system, good enough. - pkgFlags |= ApplicationInfo.FLAG_SYSTEM; - } - } - String timeStampStr = parser.getAttributeValue(null, "ft"); - if (timeStampStr != null) { - try { - timeStamp = Long.parseLong(timeStampStr, 16); - } catch (NumberFormatException e) { - } - } else { - timeStampStr = parser.getAttributeValue(null, "ts"); - if (timeStampStr != null) { - try { - timeStamp = Long.parseLong(timeStampStr); - } catch (NumberFormatException e) { + // Parse package + int parseFlags = PackageParser.PARSE_ON_SDCARD | mDefParseFlags; + doGc = true; + synchronized (mInstallLock) { + final PackageParser.Package pkg = scanPackageLI(new File(codePath), parseFlags, + 0, 0); + // Scan the package + if (pkg != null) { + /* + * TODO why is the lock being held? doPostInstall is + * called in other places without the lock. This needs + * to be straightened out. + */ + // writer + synchronized (mPackages) { + retCode = PackageManager.INSTALL_SUCCEEDED; + pkgList.add(pkg.packageName); + // Post process args + args.doPostInstall(PackageManager.INSTALL_SUCCEEDED); } - } - } - timeStampStr = parser.getAttributeValue(null, "it"); - if (timeStampStr != null) { - try { - firstInstallTime = Long.parseLong(timeStampStr, 16); - } catch (NumberFormatException e) { - } - } - timeStampStr = parser.getAttributeValue(null, "ut"); - if (timeStampStr != null) { - try { - lastUpdateTime = Long.parseLong(timeStampStr, 16); - } catch (NumberFormatException e) { - } - } - if (DEBUG_SETTINGS) Log.v(TAG, "Reading package: " + name - + " userId=" + idStr + " sharedUserId=" + sharedIdStr); - int userId = idStr != null ? Integer.parseInt(idStr) : 0; - if (resourcePathStr == null) { - resourcePathStr = codePathStr; - } - if (realName != null) { - realName = realName.intern(); - } - if (name == null) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <package> has no name at " - + parser.getPositionDescription()); - } else if (codePathStr == null) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <package> has no codePath at " - + parser.getPositionDescription()); - } else if (userId > 0) { - packageSetting = addPackageLP(name.intern(), realName, new File(codePathStr), - new File(resourcePathStr), nativeLibraryPathStr, userId, versionCode, - pkgFlags); - if (DEBUG_SETTINGS) Log.i(TAG, "Reading package " + name - + ": userId=" + userId + " pkg=" + packageSetting); - if (packageSetting == null) { - reportSettingsProblem(Log.ERROR, - "Failure adding uid " + userId - + " while parsing settings at " - + parser.getPositionDescription()); } else { - packageSetting.setTimeStamp(timeStamp); - packageSetting.firstInstallTime = firstInstallTime; - packageSetting.lastUpdateTime = lastUpdateTime; - } - } else if (sharedIdStr != null) { - userId = sharedIdStr != null - ? Integer.parseInt(sharedIdStr) : 0; - if (userId > 0) { - packageSetting = new PendingPackage(name.intern(), realName, - new File(codePathStr), new File(resourcePathStr), - nativeLibraryPathStr, userId, versionCode, pkgFlags); - packageSetting.setTimeStamp(timeStamp); - packageSetting.firstInstallTime = firstInstallTime; - packageSetting.lastUpdateTime = lastUpdateTime; - mPendingPackages.add((PendingPackage) packageSetting); - if (DEBUG_SETTINGS) Log.i(TAG, "Reading package " + name - + ": sharedUserId=" + userId + " pkg=" - + packageSetting); - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: package " - + name + " has bad sharedId " + sharedIdStr - + " at " + parser.getPositionDescription()); - } - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: package " - + name + " has bad userId " + idStr + " at " - + parser.getPositionDescription()); - } - } catch (NumberFormatException e) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: package " - + name + " has bad userId " + idStr + " at " - + parser.getPositionDescription()); - } - if (packageSetting != null) { - packageSetting.uidError = "true".equals(uidError); - packageSetting.installerPackageName = installerPackageName; - packageSetting.nativeLibraryPathString = nativeLibraryPathStr; - final String enabledStr = parser.getAttributeValue(null, "enabled"); - if (enabledStr != null) { - if (enabledStr.equalsIgnoreCase("true")) { - packageSetting.enabled = COMPONENT_ENABLED_STATE_ENABLED; - } else if (enabledStr.equalsIgnoreCase("false")) { - packageSetting.enabled = COMPONENT_ENABLED_STATE_DISABLED; - } else if (enabledStr.equalsIgnoreCase("default")) { - packageSetting.enabled = COMPONENT_ENABLED_STATE_DEFAULT; - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: package " - + name + " has bad enabled value: " + idStr - + " at " + parser.getPositionDescription()); - } - } else { - packageSetting.enabled = COMPONENT_ENABLED_STATE_DEFAULT; - } - final String installStatusStr = parser.getAttributeValue(null, "installStatus"); - if (installStatusStr != null) { - if (installStatusStr.equalsIgnoreCase("false")) { - packageSetting.installStatus = PKG_INSTALL_INCOMPLETE; - } else { - packageSetting.installStatus = PKG_INSTALL_COMPLETE; + Slog.i(TAG, "Failed to install pkg from " + codePath + " from sdcard"); } } - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals("disabled-components")) { - readDisabledComponentsLP(packageSetting, parser); - } else if (tagName.equals("enabled-components")) { - readEnabledComponentsLP(packageSetting, parser); - } else if (tagName.equals("sigs")) { - packageSetting.signatures.readXml(parser, mPastSignatures); - } else if (tagName.equals("perms")) { - readGrantedPermissionsLP(parser, - packageSetting.grantedPermissions); - packageSetting.permissionsFixed = true; - } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <package>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } + } finally { + if (retCode != PackageManager.INSTALL_SUCCEEDED) { + // Don't destroy container here. Wait till gc clears things + // up. + removeCids.add(args.cid); } - } else { - XmlUtils.skipCurrentTag(parser); } } - - private void readDisabledComponentsLP(PackageSettingBase packageSetting, - XmlPullParser parser) - throws IOException, XmlPullParserException { - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals("item")) { - String name = parser.getAttributeValue(null, "name"); - if (name != null) { - packageSetting.disabledComponents.add(name.intern()); - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <disabled-components> has" - + " no name at " + parser.getPositionDescription()); - } + // writer + synchronized (mPackages) { + // If the platform SDK has changed since the last time we booted, + // we need to re-grant app permission to catch any new ones that + // appear. This is really a hack, and means that apps can in some + // cases get permissions that the user didn't initially explicitly + // allow... it would be nice to have some better way to handle + // this situation. + final boolean regrantPermissions = mSettings.mExternalSdkPlatform != mSdkVersion; + if (regrantPermissions) + Slog.i(TAG, "Platform changed from " + mSettings.mExternalSdkPlatform + " to " + + mSdkVersion + "; regranting permissions for external storage"); + mSettings.mExternalSdkPlatform = mSdkVersion; + + // Make sure group IDs have been assigned, and any permission + // changes in other apps are accounted for + updatePermissionsLPw(null, null, true, regrantPermissions, regrantPermissions); + // can downgrade to reader + // Persist settings + mSettings.writeLPr(); + } + // Send a broadcast to let everyone know we are done processing + if (pkgList.size() > 0) { + sendResourcesChangedBroadcast(true, pkgList, uidArr, null); + } + // Force gc to avoid any stale parser references that we might have. + if (doGc) { + Runtime.getRuntime().gc(); + } + // List stale containers and destroy stale temporary containers. + if (removeCids != null) { + for (String cid : removeCids) { + if (cid.startsWith(mTempContainerPrefix)) { + Log.i(TAG, "Destroying stale temporary container " + cid); + PackageHelper.destroySdDir(cid); } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <disabled-components>: " - + parser.getName()); - } - XmlUtils.skipCurrentTag(parser); - } + Log.w(TAG, "Container " + cid + " is stale"); + } + } } + } - private void readEnabledComponentsLP(PackageSettingBase packageSetting, - XmlPullParser parser) - throws IOException, XmlPullParserException { - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } + /* + * Utility method to unload a list of specified containers + */ + private void unloadAllContainers(Set<SdInstallArgs> cidArgs) { + // Just unmount all valid containers. + for (SdInstallArgs arg : cidArgs) { + synchronized (mInstallLock) { + arg.doPostDeleteLI(false); + } + } + } - String tagName = parser.getName(); - if (tagName.equals("item")) { - String name = parser.getAttributeValue(null, "name"); - if (name != null) { - packageSetting.enabledComponents.add(name.intern()); - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <enabled-components> has" - + " no name at " + parser.getPositionDescription()); - } + /* + * Unload packages mounted on external media. This involves deleting package + * data from internal structures, sending broadcasts about diabled packages, + * gc'ing to free up references, unmounting all secure containers + * corresponding to packages on external media, and posting a + * UPDATED_MEDIA_STATUS message if status has been requested. Please note + * that we always have to post this message if status has been requested no + * matter what. + */ + private void unloadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[], + final boolean reportStatus) { + if (DEBUG_SD_INSTALL) + Log.i(TAG, "unloading media packages"); + ArrayList<String> pkgList = new ArrayList<String>(); + ArrayList<SdInstallArgs> failedList = new ArrayList<SdInstallArgs>(); + final Set<SdInstallArgs> keys = processCids.keySet(); + for (SdInstallArgs args : keys) { + String pkgName = args.getPackageName(); + if (DEBUG_SD_INSTALL) + Log.i(TAG, "Trying to unload pkg : " + pkgName); + // Delete package internally + PackageRemovedInfo outInfo = new PackageRemovedInfo(); + synchronized (mInstallLock) { + boolean res = deletePackageLI(pkgName, false, PackageManager.DONT_DELETE_DATA, + outInfo, false); + if (res) { + pkgList.add(pkgName); } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <enabled-components>: " - + parser.getName()); + Slog.e(TAG, "Failed to delete pkg from sdcard : " + pkgName); + failedList.add(args); } - XmlUtils.skipCurrentTag(parser); } } - private void readSharedUserLP(XmlPullParser parser) - throws XmlPullParserException, IOException { - String name = null; - String idStr = null; - int pkgFlags = 0; - SharedUserSetting su = null; - try { - name = parser.getAttributeValue(null, "name"); - idStr = parser.getAttributeValue(null, "userId"); - int userId = idStr != null ? Integer.parseInt(idStr) : 0; - if ("true".equals(parser.getAttributeValue(null, "system"))) { - pkgFlags |= ApplicationInfo.FLAG_SYSTEM; - } - if (name == null) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <shared-user> has no name at " - + parser.getPositionDescription()); - } else if (userId == 0) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: shared-user " - + name + " has bad userId " + idStr + " at " - + parser.getPositionDescription()); - } else { - if ((su=addSharedUserLP(name.intern(), userId, pkgFlags)) == null) { - reportSettingsProblem(Log.ERROR, - "Occurred while parsing settings at " - + parser.getPositionDescription()); - } - } - } catch (NumberFormatException e) { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: package " - + name + " has bad userId " + idStr + " at " - + parser.getPositionDescription()); - }; - - if (su != null) { - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals("sigs")) { - su.signatures.readXml(parser, mPastSignatures); - } else if (tagName.equals("perms")) { - readGrantedPermissionsLP(parser, su.grantedPermissions); - } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <shared-user>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } + // reader + synchronized (mPackages) { + // We didn't update the settings after removing each package; + // write them now for all packages. + mSettings.writeLPr(); + } + + // We have to absolutely send UPDATED_MEDIA_STATUS only + // after confirming that all the receivers processed the ordered + // broadcast when packages get disabled, force a gc to clean things up. + // and unload all the containers. + if (pkgList.size() > 0) { + sendResourcesChangedBroadcast(false, pkgList, uidArr, new IIntentReceiver.Stub() { + public void performReceive(Intent intent, int resultCode, String data, + Bundle extras, boolean ordered, boolean sticky) throws RemoteException { + Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, + reportStatus ? 1 : 0, 1, keys); + mHandler.sendMessage(msg); } - - } else { - XmlUtils.skipCurrentTag(parser); - } + }); + } else { + Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, reportStatus ? 1 : 0, -1, + keys); + mHandler.sendMessage(msg); } + } - private void readGrantedPermissionsLP(XmlPullParser parser, - HashSet<String> outPerms) throws IOException, XmlPullParserException { - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals("item")) { - String name = parser.getAttributeValue(null, "name"); - if (name != null) { - outPerms.add(name.intern()); - } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <perms> has" - + " no name at " + parser.getPositionDescription()); - } + public void movePackage(final String packageName, final IPackageMoveObserver observer, + final int flags) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null); + int returnCode = PackageManager.MOVE_SUCCEEDED; + int currFlags = 0; + int newFlags = 0; + // reader + synchronized (mPackages) { + PackageParser.Package pkg = mPackages.get(packageName); + if (pkg == null) { + returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST; + } else { + // Disable moving fwd locked apps and system packages + if (pkg.applicationInfo != null && isSystemApp(pkg)) { + Slog.w(TAG, "Cannot move system application"); + returnCode = PackageManager.MOVE_FAILED_SYSTEM_PACKAGE; + } else if (pkg.applicationInfo != null && isForwardLocked(pkg)) { + Slog.w(TAG, "Cannot move forward locked app."); + returnCode = PackageManager.MOVE_FAILED_FORWARD_LOCKED; + } else if (pkg.mOperationPending) { + Slog.w(TAG, "Attempt to move package which has pending operations"); + returnCode = PackageManager.MOVE_FAILED_OPERATION_PENDING; } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <perms>: " - + parser.getName()); - } - XmlUtils.skipCurrentTag(parser); - } - } - - private void readPreferredActivitiesLP(XmlPullParser parser) - throws XmlPullParserException, IOException { - int outerDepth = parser.getDepth(); - int type; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals("item")) { - PreferredActivity pa = new PreferredActivity(parser); - if (pa.mPref.getParseError() == null) { - mPreferredActivities.addFilter(pa); + // Find install location first + if ((flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 + && (flags & PackageManager.MOVE_INTERNAL) != 0) { + Slog.w(TAG, "Ambigous flags specified for move location."); + returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION; } else { - reportSettingsProblem(Log.WARN, - "Error in package manager settings: <preferred-activity> " - + pa.mPref.getParseError() + " at " - + parser.getPositionDescription()); + newFlags = (flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 ? PackageManager.INSTALL_EXTERNAL + : PackageManager.INSTALL_INTERNAL; + currFlags = isExternal(pkg) ? PackageManager.INSTALL_EXTERNAL + : PackageManager.INSTALL_INTERNAL; + if (newFlags == currFlags) { + Slog.w(TAG, "No move required. Trying to move to same location"); + returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION; + } } - } else { - reportSettingsProblem(Log.WARN, - "Unknown element under <preferred-activities>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - } - } - - // Returns -1 if we could not find an available UserId to assign - private int newUserIdLP(Object obj) { - // Let's be stupidly inefficient for now... - final int N = mUserIds.size(); - for (int i=0; i<N; i++) { - if (mUserIds.get(i) == null) { - mUserIds.set(i, obj); - return FIRST_APPLICATION_UID + i; - } - } - - // None left? - if (N >= MAX_APPLICATION_UIDS) { - return -1; - } - - mUserIds.add(obj); - return FIRST_APPLICATION_UID + N; - } - - public PackageSetting getDisabledSystemPkg(String name) { - synchronized(mPackages) { - PackageSetting ps = mDisabledSysPackages.get(name); - return ps; - } - } - - boolean isEnabledLP(ComponentInfo componentInfo, int flags) { - if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { - return true; - } - final PackageSetting packageSettings = mPackages.get(componentInfo.packageName); - if (Config.LOGV) { - Log.v(TAG, "isEnabledLock - packageName = " + componentInfo.packageName - + " componentName = " + componentInfo.name); - Log.v(TAG, "enabledComponents: " - + Arrays.toString(packageSettings.enabledComponents.toArray())); - Log.v(TAG, "disabledComponents: " - + Arrays.toString(packageSettings.disabledComponents.toArray())); - } - if (packageSettings == null) { - if (false) { - Log.w(TAG, "WAITING FOR DEBUGGER"); - Debug.waitForDebugger(); - Log.i(TAG, "We will crash!"); - } - return false; - } - if (packageSettings.enabled == COMPONENT_ENABLED_STATE_DISABLED - || (packageSettings.pkg != null && !packageSettings.pkg.applicationInfo.enabled - && packageSettings.enabled == COMPONENT_ENABLED_STATE_DEFAULT)) { - return false; - } - if (packageSettings.enabledComponents.contains(componentInfo.name)) { - return true; - } - if (packageSettings.disabledComponents.contains(componentInfo.name)) { - return false; - } - return componentInfo.enabled; - } - } - - // ------- apps on sdcard specific code ------- - static final boolean DEBUG_SD_INSTALL = false; - private static final String SD_ENCRYPTION_KEYSTORE_NAME = "AppsOnSD"; - private static final String SD_ENCRYPTION_ALGORITHM = "AES"; - static final int MAX_CONTAINERS = 250; - private boolean mMediaMounted = false; - - private String getEncryptKey() { - try { - String sdEncKey = SystemKeyStore.getInstance().retrieveKeyHexString( - SD_ENCRYPTION_KEYSTORE_NAME); - if (sdEncKey == null) { - sdEncKey = SystemKeyStore.getInstance().generateNewKeyHexString(128, - SD_ENCRYPTION_ALGORITHM, SD_ENCRYPTION_KEYSTORE_NAME); - if (sdEncKey == null) { - Slog.e(TAG, "Failed to create encryption keys"); - return null; - } - } - return sdEncKey; - } catch (NoSuchAlgorithmException nsae) { - Slog.e(TAG, "Failed to create encryption keys with exception: " + nsae); - return null; - } catch (IOException ioe) { - Slog.e(TAG, "Failed to retrieve encryption keys with exception: " - + ioe); - return null; - } - - } - - /* package */ static String getTempContainerId() { - int tmpIdx = 1; - String list[] = PackageHelper.getSecureContainerList(); - if (list != null) { - for (final String name : list) { - // Ignore null and non-temporary container entries - if (name == null || !name.startsWith(mTempContainerPrefix)) { - continue; - } - - String subStr = name.substring(mTempContainerPrefix.length()); - try { - int cid = Integer.parseInt(subStr); - if (cid >= tmpIdx) { - tmpIdx = cid + 1; + if (returnCode == PackageManager.MOVE_SUCCEEDED) { + pkg.mOperationPending = true; } - } catch (NumberFormatException e) { } } - } - return mTempContainerPrefix + tmpIdx; - } - - /* - * Update media status on PackageManager. - */ - public void updateExternalMediaStatus(final boolean mediaStatus, final boolean reportStatus) { - int callingUid = Binder.getCallingUid(); - if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { - throw new SecurityException("Media status can only be updated by the system"); - } - synchronized (mPackages) { - Log.i(TAG, "Updating external media status from " + - (mMediaMounted ? "mounted" : "unmounted") + " to " + - (mediaStatus ? "mounted" : "unmounted")); - if (DEBUG_SD_INSTALL) Log.i(TAG, "updateExternalMediaStatus:: mediaStatus=" + - mediaStatus+", mMediaMounted=" + mMediaMounted); - if (mediaStatus == mMediaMounted) { - Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, - reportStatus ? 1 : 0, -1); - mHandler.sendMessage(msg); - return; - } - mMediaMounted = mediaStatus; - } - // Queue up an async operation since the package installation may take a little while. - mHandler.post(new Runnable() { - public void run() { - mHandler.removeCallbacks(this); - updateExternalMediaStatusInner(mediaStatus, reportStatus); - } - }); - } - - /* - * Collect information of applications on external media, map them - * against existing containers and update information based on current - * mount status. Please note that we always have to report status - * if reportStatus has been set to true especially when unloading packages. - */ - private void updateExternalMediaStatusInner(boolean mediaStatus, - boolean reportStatus) { - // Collection of uids - int uidArr[] = null; - // Collection of stale containers - HashSet<String> removeCids = new HashSet<String>(); - // Collection of packages on external media with valid containers. - HashMap<SdInstallArgs, String> processCids = new HashMap<SdInstallArgs, String>(); - // Get list of secure containers. - final String list[] = PackageHelper.getSecureContainerList(); - if (list == null || list.length == 0) { - Log.i(TAG, "No secure containers on sdcard"); - } else { - // Process list of secure containers and categorize them - // as active or stale based on their package internal state. - int uidList[] = new int[list.length]; - int num = 0; - synchronized (mPackages) { - for (String cid : list) { - SdInstallArgs args = new SdInstallArgs(cid); - if (DEBUG_SD_INSTALL) Log.i(TAG, "Processing container " + cid); - String pkgName = args.getPackageName(); - if (pkgName == null) { - if (DEBUG_SD_INSTALL) Log.i(TAG, "Container : " + cid + " stale"); - removeCids.add(cid); - continue; - } - if (DEBUG_SD_INSTALL) Log.i(TAG, "Looking for pkg : " + pkgName); - PackageSetting ps = mSettings.mPackages.get(pkgName); - // The package status is changed only if the code path - // matches between settings and the container id. - if (ps != null && ps.codePathString != null && - ps.codePathString.equals(args.getCodePath())) { - if (DEBUG_SD_INSTALL) Log.i(TAG, "Container : " + cid + - " corresponds to pkg : " + pkgName + - " at code path: " + ps.codePathString); - // We do have a valid package installed on sdcard - processCids.put(args, ps.codePathString); - int uid = ps.userId; - if (uid != -1) { - uidList[num++] = uid; - } - } else { - // Stale container on sdcard. Just delete - if (DEBUG_SD_INSTALL) Log.i(TAG, "Container : " + cid + " stale"); - removeCids.add(cid); - } - } - } - - if (num > 0) { - // Sort uid list - Arrays.sort(uidList, 0, num); - // Throw away duplicates - uidArr = new int[num]; - uidArr[0] = uidList[0]; - int di = 0; - for (int i = 1; i < num; i++) { - if (uidList[i-1] != uidList[i]) { - uidArr[di++] = uidList[i]; - } - } - } - } - // Process packages with valid entries. - if (mediaStatus) { - if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading packages"); - loadMediaPackages(processCids, uidArr, removeCids); - startCleaningPackages(); - } else { - if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading packages"); - unloadMediaPackages(processCids, uidArr, reportStatus); - } - } - - private void sendResourcesChangedBroadcast(boolean mediaStatus, - ArrayList<String> pkgList, int uidArr[], IIntentReceiver finishedReceiver) { - int size = pkgList.size(); - if (size > 0) { - // Send broadcasts here - Bundle extras = new Bundle(); - extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, - pkgList.toArray(new String[size])); - if (uidArr != null) { - extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidArr); - } - String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE - : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE; - sendPackageBroadcast(action, null, extras, null, finishedReceiver); - } - } - - /* - * Look at potentially valid container ids from processCids - * If package information doesn't match the one on record - * or package scanning fails, the cid is added to list of - * removeCids. We currently don't delete stale containers. - */ - private void loadMediaPackages(HashMap<SdInstallArgs, String> processCids, - int uidArr[], HashSet<String> removeCids) { - ArrayList<String> pkgList = new ArrayList<String>(); - Set<SdInstallArgs> keys = processCids.keySet(); - boolean doGc = false; - for (SdInstallArgs args : keys) { - String codePath = processCids.get(args); - if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading container : " - + args.cid); - int retCode = PackageManager.INSTALL_FAILED_CONTAINER_ERROR; - try { - // Make sure there are no container errors first. - if (args.doPreInstall(PackageManager.INSTALL_SUCCEEDED) - != PackageManager.INSTALL_SUCCEEDED) { - Slog.e(TAG, "Failed to mount cid : " + args.cid + - " when installing from sdcard"); - continue; - } - // Check code path here. - if (codePath == null || !codePath.equals(args.getCodePath())) { - Slog.e(TAG, "Container " + args.cid + " cachepath " + args.getCodePath()+ - " does not match one in settings " + codePath); - continue; - } - // Parse package - int parseFlags = PackageParser.PARSE_ON_SDCARD | mDefParseFlags; - doGc = true; - synchronized (mInstallLock) { - final PackageParser.Package pkg = scanPackageLI(new File(codePath), - parseFlags, 0, 0); - // Scan the package - if (pkg != null) { - synchronized (mPackages) { - retCode = PackageManager.INSTALL_SUCCEEDED; - pkgList.add(pkg.packageName); - // Post process args - args.doPostInstall(PackageManager.INSTALL_SUCCEEDED); - } - } else { - Slog.i(TAG, "Failed to install pkg from " + - codePath + " from sdcard"); - } - } - } finally { - if (retCode != PackageManager.INSTALL_SUCCEEDED) { - // Don't destroy container here. Wait till gc clears things up. - removeCids.add(args.cid); - } - } - } - synchronized (mPackages) { - // If the platform SDK has changed since the last time we booted, - // we need to re-grant app permission to catch any new ones that - // appear. This is really a hack, and means that apps can in some - // cases get permissions that the user didn't initially explicitly - // allow... it would be nice to have some better way to handle - // this situation. - final boolean regrantPermissions = mSettings.mExternalSdkPlatform - != mSdkVersion; - if (regrantPermissions) Slog.i(TAG, "Platform changed from " - + mSettings.mExternalSdkPlatform + " to " + mSdkVersion - + "; regranting permissions for external storage"); - mSettings.mExternalSdkPlatform = mSdkVersion; - - // Make sure group IDs have been assigned, and any permission - // changes in other apps are accounted for - updatePermissionsLP(null, null, true, regrantPermissions, regrantPermissions); - // Persist settings - mSettings.writeLP(); - } - // Send a broadcast to let everyone know we are done processing - if (pkgList.size() > 0) { - sendResourcesChangedBroadcast(true, pkgList, uidArr, null); - } - // Force gc to avoid any stale parser references that we might have. - if (doGc) { - Runtime.getRuntime().gc(); - } - // List stale containers and destroy stale temporary containers. - if (removeCids != null) { - for (String cid : removeCids) { - if (cid.startsWith(mTempContainerPrefix)) { - Log.i(TAG, "Destroying stale temporary container " + cid); - PackageHelper.destroySdDir(cid); - } else { - Log.w(TAG, "Container " + cid + " is stale"); - } - } - } - } - - /* - * Utility method to unload a list of specified containers - */ - private void unloadAllContainers(Set<SdInstallArgs> cidArgs) { - // Just unmount all valid containers. - for (SdInstallArgs arg : cidArgs) { - synchronized (mInstallLock) { - arg.doPostDeleteLI(false); - } - } - } - - /* - * Unload packages mounted on external media. This involves deleting - * package data from internal structures, sending broadcasts about - * diabled packages, gc'ing to free up references, unmounting all - * secure containers corresponding to packages on external media, and - * posting a UPDATED_MEDIA_STATUS message if status has been requested. - * Please note that we always have to post this message if status has - * been requested no matter what. - */ - private void unloadMediaPackages(HashMap<SdInstallArgs, String> processCids, - int uidArr[], final boolean reportStatus) { - if (DEBUG_SD_INSTALL) Log.i(TAG, "unloading media packages"); - ArrayList<String> pkgList = new ArrayList<String>(); - ArrayList<SdInstallArgs> failedList = new ArrayList<SdInstallArgs>(); - final Set<SdInstallArgs> keys = processCids.keySet(); - for (SdInstallArgs args : keys) { - String cid = args.cid; - String pkgName = args.getPackageName(); - if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to unload pkg : " + pkgName); - // Delete package internally - PackageRemovedInfo outInfo = new PackageRemovedInfo(); - synchronized (mInstallLock) { - boolean res = deletePackageLI(pkgName, false, - PackageManager.DONT_DELETE_DATA, outInfo, false); - if (res) { - pkgList.add(pkgName); - } else { - Slog.e(TAG, "Failed to delete pkg from sdcard : " + pkgName); - failedList.add(args); - } - } - } - - synchronized (mPackages) { - // We didn't update the settings after removing each package; - // write them now for all packages. - mSettings.writeLP(); - } - - // We have to absolutely send UPDATED_MEDIA_STATUS only - // after confirming that all the receivers processed the ordered - // broadcast when packages get disabled, force a gc to clean things up. - // and unload all the containers. - if (pkgList.size() > 0) { - sendResourcesChangedBroadcast(false, pkgList, uidArr, new IIntentReceiver.Stub() { - public void performReceive(Intent intent, int resultCode, String data, Bundle extras, - boolean ordered, boolean sticky) throws RemoteException { - Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, - reportStatus ? 1 : 0, 1, keys); - mHandler.sendMessage(msg); - } - }); - } else { - Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, - reportStatus ? 1 : 0, -1, keys); - mHandler.sendMessage(msg); - } - } - - public void movePackage(final String packageName, - final IPackageMoveObserver observer, final int flags) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.MOVE_PACKAGE, null); - int returnCode = PackageManager.MOVE_SUCCEEDED; - int currFlags = 0; - int newFlags = 0; - synchronized (mPackages) { - PackageParser.Package pkg = mPackages.get(packageName); - if (pkg == null) { - returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST; - } else { - // Disable moving fwd locked apps and system packages - if (pkg.applicationInfo != null && isSystemApp(pkg)) { - Slog.w(TAG, "Cannot move system application"); - returnCode = PackageManager.MOVE_FAILED_SYSTEM_PACKAGE; - } else if (pkg.applicationInfo != null && isForwardLocked(pkg)) { - Slog.w(TAG, "Cannot move forward locked app."); - returnCode = PackageManager.MOVE_FAILED_FORWARD_LOCKED; - } else if (pkg.mOperationPending) { - Slog.w(TAG, "Attempt to move package which has pending operations"); - returnCode = PackageManager.MOVE_FAILED_OPERATION_PENDING; - } else { - // Find install location first - if ((flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 && - (flags & PackageManager.MOVE_INTERNAL) != 0) { - Slog.w(TAG, "Ambigous flags specified for move location."); - returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION; - } else { - newFlags = (flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 ? - PackageManager.INSTALL_EXTERNAL : PackageManager.INSTALL_INTERNAL; - currFlags = isExternal(pkg) ? PackageManager.INSTALL_EXTERNAL - : PackageManager.INSTALL_INTERNAL; - if (newFlags == currFlags) { - Slog.w(TAG, "No move required. Trying to move to same location"); - returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION; - } - } - if (returnCode == PackageManager.MOVE_SUCCEEDED) { - pkg.mOperationPending = true; - } - } - } - if (returnCode != PackageManager.MOVE_SUCCEEDED) { + /* + * TODO this next block probably shouldn't be inside the lock. We + * can't guarantee these won't change after this is fired off + * anyway. + */ + if (returnCode != PackageManager.MOVE_SUCCEEDED) { processPendingMove(new MoveParams(null, observer, 0, packageName, null), returnCode); - } else { - Message msg = mHandler.obtainMessage(INIT_COPY); - InstallArgs srcArgs = createInstallArgs(currFlags, pkg.applicationInfo.sourceDir, + } else { + Message msg = mHandler.obtainMessage(INIT_COPY); + InstallArgs srcArgs = createInstallArgs(currFlags, pkg.applicationInfo.sourceDir, pkg.applicationInfo.publicSourceDir, pkg.applicationInfo.nativeLibraryDir); - MoveParams mp = new MoveParams(srcArgs, observer, newFlags, packageName, + MoveParams mp = new MoveParams(srcArgs, observer, newFlags, packageName, pkg.applicationInfo.dataDir); - msg.obj = mp; - mHandler.sendMessage(msg); - } - } - } + msg.obj = mp; + mHandler.sendMessage(msg); + } + } + } - private void processPendingMove(final MoveParams mp, final int currentStatus) { - // Queue up an async operation since the package deletion may take a little while. - mHandler.post(new Runnable() { - public void run() { - mHandler.removeCallbacks(this); - int returnCode = currentStatus; - if (currentStatus == PackageManager.MOVE_SUCCEEDED) { - int uidArr[] = null; - ArrayList<String> pkgList = null; - synchronized (mPackages) { - PackageParser.Package pkg = mPackages.get(mp.packageName); - if (pkg == null) { - Slog.w(TAG, " Package " + mp.packageName + - " doesn't exist. Aborting move"); - returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST; - } else if (!mp.srcArgs.getCodePath().equals(pkg.applicationInfo.sourceDir)) { - Slog.w(TAG, "Package " + mp.packageName + " code path changed from " + - mp.srcArgs.getCodePath() + " to " + pkg.applicationInfo.sourceDir + - " Aborting move and returning error"); - returnCode = PackageManager.MOVE_FAILED_INTERNAL_ERROR; - } else { - uidArr = new int[] { pkg.applicationInfo.uid }; - pkgList = new ArrayList<String>(); - pkgList.add(mp.packageName); - } - } - if (returnCode == PackageManager.MOVE_SUCCEEDED) { - // Send resources unavailable broadcast - sendResourcesChangedBroadcast(false, pkgList, uidArr, null); - // Update package code and resource paths - synchronized (mInstallLock) { - synchronized (mPackages) { - PackageParser.Package pkg = mPackages.get(mp.packageName); - // Recheck for package again. + private void processPendingMove(final MoveParams mp, final int currentStatus) { + // Queue up an async operation since the package deletion may take a + // little while. + mHandler.post(new Runnable() { + public void run() { + // TODO fix this; this does nothing. + mHandler.removeCallbacks(this); + int returnCode = currentStatus; + if (currentStatus == PackageManager.MOVE_SUCCEEDED) { + int uidArr[] = null; + ArrayList<String> pkgList = null; + synchronized (mPackages) { + PackageParser.Package pkg = mPackages.get(mp.packageName); + if (pkg == null) { + Slog.w(TAG, " Package " + mp.packageName + + " doesn't exist. Aborting move"); + returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST; + } else if (!mp.srcArgs.getCodePath().equals(pkg.applicationInfo.sourceDir)) { + Slog.w(TAG, "Package " + mp.packageName + " code path changed from " + + mp.srcArgs.getCodePath() + " to " + + pkg.applicationInfo.sourceDir + + " Aborting move and returning error"); + returnCode = PackageManager.MOVE_FAILED_INTERNAL_ERROR; + } else { + uidArr = new int[] { + pkg.applicationInfo.uid + }; + pkgList = new ArrayList<String>(); + pkgList.add(mp.packageName); + } + } + if (returnCode == PackageManager.MOVE_SUCCEEDED) { + // Send resources unavailable broadcast + sendResourcesChangedBroadcast(false, pkgList, uidArr, null); + // Update package code and resource paths + synchronized (mInstallLock) { + synchronized (mPackages) { + PackageParser.Package pkg = mPackages.get(mp.packageName); + // Recheck for package again. if (pkg == null) { Slog.w(TAG, " Package " + mp.packageName + " doesn't exist. Aborting move"); returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST; - } else if (!mp.srcArgs.getCodePath().equals(pkg.applicationInfo.sourceDir)) { - Slog.w(TAG, "Package " + mp.packageName + " code path changed from " + - mp.srcArgs.getCodePath() + " to " + pkg.applicationInfo.sourceDir + - " Aborting move and returning error"); - returnCode = PackageManager.MOVE_FAILED_INTERNAL_ERROR; - } else { - final String oldCodePath = pkg.mPath; - final String newCodePath = mp.targetArgs.getCodePath(); - final String newResPath = mp.targetArgs.getResourcePath(); - final String newNativePath = mp.targetArgs.getNativeLibraryPath(); + } else if (!mp.srcArgs.getCodePath().equals( + pkg.applicationInfo.sourceDir)) { + Slog.w(TAG, "Package " + mp.packageName + + " code path changed from " + mp.srcArgs.getCodePath() + + " to " + pkg.applicationInfo.sourceDir + + " Aborting move and returning error"); + returnCode = PackageManager.MOVE_FAILED_INTERNAL_ERROR; + } else { + final String oldCodePath = pkg.mPath; + final String newCodePath = mp.targetArgs.getCodePath(); + final String newResPath = mp.targetArgs.getResourcePath(); + final String newNativePath = mp.targetArgs + .getNativeLibraryPath(); if ((mp.flags & PackageManager.INSTALL_EXTERNAL) == 0) { if (mInstaller .unlinkNativeLibraryDirectory(pkg.applicationInfo.dataDir) < 0) { returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE; } else { - NativeLibraryHelper.copyNativeBinariesLI( - new File(newCodePath), new File(newNativePath)); + NativeLibraryHelper.copyNativeBinariesLI(new File( + newCodePath), new File(newNativePath)); } } else { if (mInstaller.linkNativeLibraryDirectory( @@ -10442,90 +7962,105 @@ class PackageManagerService extends IPackageManager.Stub { } if (returnCode == PackageManager.MOVE_SUCCEEDED) { - pkg.mScanPath = newCodePath; - pkg.applicationInfo.sourceDir = newCodePath; - pkg.applicationInfo.publicSourceDir = newResPath; - pkg.applicationInfo.nativeLibraryDir = newNativePath; - PackageSetting ps = (PackageSetting) pkg.mExtras; - ps.codePath = new File(pkg.applicationInfo.sourceDir); - ps.codePathString = ps.codePath.getPath(); - ps.resourcePath = new File(pkg.applicationInfo.publicSourceDir); - ps.resourcePathString = ps.resourcePath.getPath(); - ps.nativeLibraryPathString = newNativePath; - // Set the application info flag correctly. - if ((mp.flags & PackageManager.INSTALL_EXTERNAL) != 0) { - pkg.applicationInfo.flags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE; - } else { - pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_EXTERNAL_STORAGE; - } - ps.setFlags(pkg.applicationInfo.flags); - mAppDirs.remove(oldCodePath); - mAppDirs.put(newCodePath, pkg); - // Persist settings - mSettings.writeLP(); - } - } - } - } - // Send resources available broadcast - sendResourcesChangedBroadcast(true, pkgList, uidArr, null); - } - } - if (returnCode != PackageManager.MOVE_SUCCEEDED){ - // Clean up failed installation - if (mp.targetArgs != null) { - mp.targetArgs.doPostInstall(PackageManager.INSTALL_FAILED_INTERNAL_ERROR); - } - } else { - // Force a gc to clear things up. - Runtime.getRuntime().gc(); - // Delete older code - synchronized (mInstallLock) { - mp.srcArgs.doPostDeleteLI(true); - } - } + pkg.mScanPath = newCodePath; + pkg.applicationInfo.sourceDir = newCodePath; + pkg.applicationInfo.publicSourceDir = newResPath; + pkg.applicationInfo.nativeLibraryDir = newNativePath; + PackageSetting ps = (PackageSetting) pkg.mExtras; + ps.codePath = new File(pkg.applicationInfo.sourceDir); + ps.codePathString = ps.codePath.getPath(); + ps.resourcePath = new File( + pkg.applicationInfo.publicSourceDir); + ps.resourcePathString = ps.resourcePath.getPath(); + ps.nativeLibraryPathString = newNativePath; + // Set the application info flag + // correctly. + if ((mp.flags & PackageManager.INSTALL_EXTERNAL) != 0) { + pkg.applicationInfo.flags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE; + } else { + pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_EXTERNAL_STORAGE; + } + ps.setFlags(pkg.applicationInfo.flags); + mAppDirs.remove(oldCodePath); + mAppDirs.put(newCodePath, pkg); + // Persist settings + mSettings.writeLPr(); + } + } + } + } + // Send resources available broadcast + sendResourcesChangedBroadcast(true, pkgList, uidArr, null); + } + } + if (returnCode != PackageManager.MOVE_SUCCEEDED) { + // Clean up failed installation + if (mp.targetArgs != null) { + mp.targetArgs.doPostInstall(PackageManager.INSTALL_FAILED_INTERNAL_ERROR); + } + } else { + // Force a gc to clear things up. + Runtime.getRuntime().gc(); + // Delete older code + synchronized (mInstallLock) { + mp.srcArgs.doPostDeleteLI(true); + } + } - // Allow more operations on this file if we didn't fail because - // an operation was already pending for this package. - if (returnCode != PackageManager.MOVE_FAILED_OPERATION_PENDING) { - synchronized (mPackages) { - PackageParser.Package pkg = mPackages.get(mp.packageName); - if (pkg != null) { - pkg.mOperationPending = false; + // Allow more operations on this file if we didn't fail because + // an operation was already pending for this package. + if (returnCode != PackageManager.MOVE_FAILED_OPERATION_PENDING) { + synchronized (mPackages) { + PackageParser.Package pkg = mPackages.get(mp.packageName); + if (pkg != null) { + pkg.mOperationPending = false; } } - } + } - IPackageMoveObserver observer = mp.observer; - if (observer != null) { - try { - observer.packageMoved(mp.packageName, returnCode); - } catch (RemoteException e) { - Log.i(TAG, "Observer no longer exists."); - } - } - } - }); - } + IPackageMoveObserver observer = mp.observer; + if (observer != null) { + try { + observer.packageMoved(mp.packageName, returnCode); + } catch (RemoteException e) { + Log.i(TAG, "Observer no longer exists."); + } + } + } + }); + } - public boolean setInstallLocation(int loc) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.WRITE_SECURE_SETTINGS, null); - if (getInstallLocation() == loc) { - return true; - } - if (loc == PackageHelper.APP_INSTALL_AUTO || - loc == PackageHelper.APP_INSTALL_INTERNAL || - loc == PackageHelper.APP_INSTALL_EXTERNAL) { - android.provider.Settings.System.putInt(mContext.getContentResolver(), - android.provider.Settings.Secure.DEFAULT_INSTALL_LOCATION, loc); - return true; - } - return false; + public boolean setInstallLocation(int loc) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS, + null); + if (getInstallLocation() == loc) { + return true; + } + if (loc == PackageHelper.APP_INSTALL_AUTO || loc == PackageHelper.APP_INSTALL_INTERNAL + || loc == PackageHelper.APP_INSTALL_EXTERNAL) { + android.provider.Settings.System.putInt(mContext.getContentResolver(), + android.provider.Settings.Secure.DEFAULT_INSTALL_LOCATION, loc); + return true; + } + return false; } - public int getInstallLocation() { - return android.provider.Settings.System.getInt(mContext.getContentResolver(), - android.provider.Settings.Secure.DEFAULT_INSTALL_LOCATION, PackageHelper.APP_INSTALL_AUTO); - } + public int getInstallLocation() { + return android.provider.Settings.System.getInt(mContext.getContentResolver(), + android.provider.Settings.Secure.DEFAULT_INSTALL_LOCATION, + PackageHelper.APP_INSTALL_AUTO); + } + + public UserInfo createUser(String name, int flags) { + UserInfo userInfo = mUserManager.createUser(name, flags, getInstalledApplications(0)); + return userInfo; + } + + public boolean removeUser(int userId) { + if (userId == 0) { + return false; + } + mUserManager.removeUser(userId); + return true; + } } diff --git a/services/java/com/android/server/pm/PackageSetting.java b/services/java/com/android/server/pm/PackageSetting.java new file mode 100644 index 0000000..efdc2b3 --- /dev/null +++ b/services/java/com/android/server/pm/PackageSetting.java @@ -0,0 +1,55 @@ +/* + * 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.pm; + +import android.content.pm.PackageParser; + +import java.io.File; + +/** + * Settings data for a particular package we know about. + */ +final class PackageSetting extends PackageSettingBase { + int userId; + PackageParser.Package pkg; + SharedUserSetting sharedUser; + + PackageSetting(String name, String realName, File codePath, File resourcePath, + String nativeLibraryPathString, int pVersionCode, int pkgFlags) { + super(name, realName, codePath, resourcePath, nativeLibraryPathString, pVersionCode, + pkgFlags); + } + + /** + * New instance of PackageSetting replicating the original settings. + * Note that it keeps the same PackageParser.Package instance. + */ + PackageSetting(PackageSetting orig) { + super(orig); + + userId = orig.userId; + pkg = orig.pkg; + sharedUser = orig.sharedUser; + } + + @Override + public String toString() { + return "PackageSetting{" + + Integer.toHexString(System.identityHashCode(this)) + + " " + name + "/" + userId + "}"; + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/pm/PackageSettingBase.java b/services/java/com/android/server/pm/PackageSettingBase.java new file mode 100644 index 0000000..e2f83ad --- /dev/null +++ b/services/java/com/android/server/pm/PackageSettingBase.java @@ -0,0 +1,207 @@ +/* + * 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.pm; + +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; + + +import java.io.File; +import java.util.HashSet; + +/** + * Settings base class for pending and resolved classes. + */ +class PackageSettingBase extends GrantedPermissions { + /** + * Indicates the state of installation. Used by PackageManager to figure out + * incomplete installations. Say a package is being installed (the state is + * set to PKG_INSTALL_INCOMPLETE) and remains so till the package + * installation is successful or unsuccessful in which case the + * PackageManager will no longer maintain state information associated with + * the package. If some exception(like device freeze or battery being pulled + * out) occurs during installation of a package, the PackageManager needs + * this information to clean up the previously failed installation. + */ + static final int PKG_INSTALL_COMPLETE = 1; + static final int PKG_INSTALL_INCOMPLETE = 0; + + final String name; + final String realName; + File codePath; + String codePathString; + File resourcePath; + String resourcePathString; + String nativeLibraryPathString; + long timeStamp; + long firstInstallTime; + long lastUpdateTime; + int versionCode; + + boolean uidError; + + PackageSignatures signatures = new PackageSignatures(); + + boolean permissionsFixed; + boolean haveGids; + + // Whether this package is currently stopped, thus can not be + // started until explicitly launched by the user. + public boolean stopped; + + // Set to true if we have never launched this app. + public boolean notLaunched; + + /* Explicitly disabled components */ + HashSet<String> disabledComponents = new HashSet<String>(0); + /* Explicitly enabled components */ + HashSet<String> enabledComponents = new HashSet<String>(0); + int enabled = COMPONENT_ENABLED_STATE_DEFAULT; + int installStatus = PKG_INSTALL_COMPLETE; + + PackageSettingBase origPackage; + + /* package name of the app that installed this package */ + String installerPackageName; + PackageSettingBase(String name, String realName, File codePath, File resourcePath, + String nativeLibraryPathString, int pVersionCode, int pkgFlags) { + super(pkgFlags); + this.name = name; + this.realName = realName; + init(codePath, resourcePath, nativeLibraryPathString, pVersionCode); + } + + /** + * New instance of PackageSetting with one-level-deep cloning. + */ + @SuppressWarnings("unchecked") + PackageSettingBase(PackageSettingBase base) { + super(base); + + name = base.name; + realName = base.realName; + codePath = base.codePath; + codePathString = base.codePathString; + resourcePath = base.resourcePath; + resourcePathString = base.resourcePathString; + nativeLibraryPathString = base.nativeLibraryPathString; + timeStamp = base.timeStamp; + firstInstallTime = base.firstInstallTime; + lastUpdateTime = base.lastUpdateTime; + versionCode = base.versionCode; + + uidError = base.uidError; + + signatures = new PackageSignatures(base.signatures); + + permissionsFixed = base.permissionsFixed; + haveGids = base.haveGids; + stopped = base.stopped; + notLaunched = base.notLaunched; + + disabledComponents = (HashSet<String>) base.disabledComponents.clone(); + + enabledComponents = (HashSet<String>) base.enabledComponents.clone(); + + enabled = base.enabled; + installStatus = base.installStatus; + + origPackage = base.origPackage; + + installerPackageName = base.installerPackageName; + } + + void init(File codePath, File resourcePath, String nativeLibraryPathString, + int pVersionCode) { + this.codePath = codePath; + this.codePathString = codePath.toString(); + this.resourcePath = resourcePath; + this.resourcePathString = resourcePath.toString(); + this.nativeLibraryPathString = nativeLibraryPathString; + this.versionCode = pVersionCode; + } + + public void setInstallerPackageName(String packageName) { + installerPackageName = packageName; + } + + String getInstallerPackageName() { + return installerPackageName; + } + + public void setInstallStatus(int newStatus) { + installStatus = newStatus; + } + + public int getInstallStatus() { + return installStatus; + } + + public void setTimeStamp(long newStamp) { + timeStamp = newStamp; + } + + /** + * Make a shallow copy of this package settings. + */ + public void copyFrom(PackageSettingBase base) { + grantedPermissions = base.grantedPermissions; + gids = base.gids; + + timeStamp = base.timeStamp; + firstInstallTime = base.firstInstallTime; + lastUpdateTime = base.lastUpdateTime; + signatures = base.signatures; + permissionsFixed = base.permissionsFixed; + haveGids = base.haveGids; + stopped = base.stopped; + notLaunched = base.notLaunched; + disabledComponents = base.disabledComponents; + enabledComponents = base.enabledComponents; + enabled = base.enabled; + installStatus = base.installStatus; + } + + boolean enableComponentLPw(String componentClassName) { + boolean changed = disabledComponents.remove(componentClassName); + changed |= enabledComponents.add(componentClassName); + return changed; + } + + boolean disableComponentLPw(String componentClassName) { + boolean changed = enabledComponents.remove(componentClassName); + changed |= disabledComponents.add(componentClassName); + return changed; + } + + boolean restoreComponentLPw(String componentClassName) { + boolean changed = enabledComponents.remove(componentClassName); + changed |= disabledComponents.remove(componentClassName); + return changed; + } + + int getCurrentEnabledStateLPr(String componentName) { + if (enabledComponents.contains(componentName)) { + return COMPONENT_ENABLED_STATE_ENABLED; + } else if (disabledComponents.contains(componentName)) { + return COMPONENT_ENABLED_STATE_DISABLED; + } else { + return COMPONENT_ENABLED_STATE_DEFAULT; + } + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/pm/PackageSignatures.java b/services/java/com/android/server/pm/PackageSignatures.java new file mode 100644 index 0000000..a25ec6c --- /dev/null +++ b/services/java/com/android/server/pm/PackageSignatures.java @@ -0,0 +1,198 @@ +/* + * 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.pm; + +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import android.content.pm.Signature; +import android.util.Log; + +import java.io.IOException; +import java.util.ArrayList; + +class PackageSignatures { + Signature[] mSignatures; + + PackageSignatures(PackageSignatures orig) { + if (orig != null && orig.mSignatures != null) { + mSignatures = orig.mSignatures.clone(); + } + } + + PackageSignatures(Signature[] sigs) { + assignSignatures(sigs); + } + + PackageSignatures() { + } + + void writeXml(XmlSerializer serializer, String tagName, + ArrayList<Signature> pastSignatures) throws IOException { + if (mSignatures == null) { + return; + } + serializer.startTag(null, tagName); + serializer.attribute(null, "count", + Integer.toString(mSignatures.length)); + for (int i=0; i<mSignatures.length; i++) { + serializer.startTag(null, "cert"); + final Signature sig = mSignatures[i]; + final int sigHash = sig.hashCode(); + final int numPast = pastSignatures.size(); + int j; + for (j=0; j<numPast; j++) { + Signature pastSig = pastSignatures.get(j); + if (pastSig.hashCode() == sigHash && pastSig.equals(sig)) { + serializer.attribute(null, "index", Integer.toString(j)); + break; + } + } + if (j >= numPast) { + pastSignatures.add(sig); + serializer.attribute(null, "index", Integer.toString(numPast)); + serializer.attribute(null, "key", sig.toCharsString()); + } + serializer.endTag(null, "cert"); + } + serializer.endTag(null, tagName); + } + + void readXml(XmlPullParser parser, ArrayList<Signature> pastSignatures) + throws IOException, XmlPullParserException { + String countStr = parser.getAttributeValue(null, "count"); + if (countStr == null) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <signatures> has" + + " no count at " + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + } + final int count = Integer.parseInt(countStr); + mSignatures = new Signature[count]; + int pos = 0; + + int outerDepth = parser.getDepth(); + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG + || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("cert")) { + if (pos < count) { + String index = parser.getAttributeValue(null, "index"); + if (index != null) { + try { + int idx = Integer.parseInt(index); + String key = parser.getAttributeValue(null, "key"); + if (key == null) { + if (idx >= 0 && idx < pastSignatures.size()) { + Signature sig = pastSignatures.get(idx); + if (sig != null) { + mSignatures[pos] = pastSignatures.get(idx); + pos++; + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <cert> " + + "index " + index + " is not defined at " + + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <cert> " + + "index " + index + " is out of bounds at " + + parser.getPositionDescription()); + } + } else { + while (pastSignatures.size() <= idx) { + pastSignatures.add(null); + } + Signature sig = new Signature(key); + pastSignatures.set(idx, sig); + mSignatures[pos] = sig; + pos++; + } + } catch (NumberFormatException e) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <cert> " + + "index " + index + " is not a number at " + + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <cert> has" + + " no index at " + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: too " + + "many <cert> tags, expected " + count + + " at " + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <cert>: " + + parser.getName()); + } + XmlUtils.skipCurrentTag(parser); + } + + if (pos < count) { + // Should never happen -- there is an error in the written + // settings -- but if it does we don't want to generate + // a bad array. + Signature[] newSigs = new Signature[pos]; + System.arraycopy(mSignatures, 0, newSigs, 0, pos); + mSignatures = newSigs; + } + } + + void assignSignatures(Signature[] sigs) { + if (sigs == null) { + mSignatures = null; + return; + } + mSignatures = new Signature[sigs.length]; + for (int i=0; i<sigs.length; i++) { + mSignatures[i] = sigs[i]; + } + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(128); + buf.append("PackageSignatures{"); + buf.append(Integer.toHexString(System.identityHashCode(this))); + buf.append(" ["); + if (mSignatures != null) { + for (int i=0; i<mSignatures.length; i++) { + if (i > 0) buf.append(", "); + buf.append(Integer.toHexString( + System.identityHashCode(mSignatures[i]))); + } + } + buf.append("]}"); + return buf.toString(); + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/pm/PendingPackage.java b/services/java/com/android/server/pm/PendingPackage.java new file mode 100644 index 0000000..c17cc46 --- /dev/null +++ b/services/java/com/android/server/pm/PendingPackage.java @@ -0,0 +1,30 @@ +/* + * 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.pm; + +import java.io.File; + +final class PendingPackage extends PackageSettingBase { + final int sharedId; + + PendingPackage(String name, String realName, File codePath, File resourcePath, + String nativeLibraryPathString, int sharedId, int pVersionCode, int pkgFlags) { + super(name, realName, codePath, resourcePath, nativeLibraryPathString, pVersionCode, + pkgFlags); + this.sharedId = sharedId; + } +} diff --git a/services/java/com/android/server/pm/PreferredActivity.java b/services/java/com/android/server/pm/PreferredActivity.java new file mode 100644 index 0000000..b100eb1 --- /dev/null +++ b/services/java/com/android/server/pm/PreferredActivity.java @@ -0,0 +1,73 @@ +/* + * 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.pm; + +import com.android.internal.util.XmlUtils; +import com.android.server.PreferredComponent; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import android.content.ComponentName; +import android.content.IntentFilter; +import android.util.Log; + +import java.io.IOException; + +class PreferredActivity extends IntentFilter implements PreferredComponent.Callbacks { + private static final String TAG = "PreferredActivity"; + + private static final boolean DEBUG_FILTERS = false; + + final PreferredComponent mPref; + + PreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) { + super(filter); + mPref = new PreferredComponent(this, match, set, activity); + } + + PreferredActivity(XmlPullParser parser) throws XmlPullParserException, IOException { + mPref = new PreferredComponent(this, parser); + } + + public void writeToXml(XmlSerializer serializer) throws IOException { + mPref.writeToXml(serializer); + serializer.startTag(null, "filter"); + super.writeToXml(serializer); + serializer.endTag(null, "filter"); + } + + public boolean onReadTag(String tagName, XmlPullParser parser) throws XmlPullParserException, + IOException { + if (tagName.equals("filter")) { + if (DEBUG_FILTERS) { + Log.i(TAG, "Starting to parse filter..."); + } + readFromXml(parser); + if (DEBUG_FILTERS) { + Log.i(TAG, "Finished filter: depth=" + parser.getDepth() + " tag=" + + parser.getName()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <preferred-activities>: " + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + return true; + } +} diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java new file mode 100644 index 0000000..2720bf8 --- /dev/null +++ b/services/java/com/android/server/pm/Settings.java @@ -0,0 +1,2230 @@ +/* + * 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.pm; + +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; + +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.JournaledFile; +import com.android.internal.util.XmlUtils; +import com.android.server.IntentResolver; +import com.android.server.pm.PackageManagerService.DumpState; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.ComponentInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageParser; +import android.content.pm.PermissionInfo; +import android.content.pm.Signature; +import android.os.Binder; +import android.os.Environment; +import android.os.FileUtils; +import android.os.Process; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import android.util.Xml; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; + +/** + * Holds information about dynamic settings. + */ +final class Settings { + private static final String TAG = "PackageSettings"; + + private static final boolean DEBUG_STOPPED = false; + + private final File mSettingsFilename; + private final File mBackupSettingsFilename; + private final File mPackageListFilename; + private final File mStoppedPackagesFilename; + private final File mBackupStoppedPackagesFilename; + final HashMap<String, PackageSetting> mPackages = + new HashMap<String, PackageSetting>(); + // List of replaced system applications + final HashMap<String, PackageSetting> mDisabledSysPackages = + new HashMap<String, PackageSetting>(); + + // These are the last platform API version we were using for + // the apps installed on internal and external storage. It is + // used to grant newer permissions one time during a system upgrade. + int mInternalSdkPlatform; + int mExternalSdkPlatform; + + // The user's preferred activities associated with particular intent + // filters. + final IntentResolver<PreferredActivity, PreferredActivity> mPreferredActivities = + new IntentResolver<PreferredActivity, PreferredActivity>() { + @Override + protected String packageForFilter(PreferredActivity filter) { + return filter.mPref.mComponent.getPackageName(); + } + @Override + protected void dumpFilter(PrintWriter out, String prefix, + PreferredActivity filter) { + filter.mPref.dump(out, prefix, filter); + } + }; + final HashMap<String, SharedUserSetting> mSharedUsers = + new HashMap<String, SharedUserSetting>(); + private final ArrayList<Object> mUserIds = new ArrayList<Object>(); + private final SparseArray<Object> mOtherUserIds = + new SparseArray<Object>(); + + // For reading/writing settings file. + private final ArrayList<Signature> mPastSignatures = + new ArrayList<Signature>(); + + // Mapping from permission names to info about them. + final HashMap<String, BasePermission> mPermissions = + new HashMap<String, BasePermission>(); + + // Mapping from permission tree names to info about them. + final HashMap<String, BasePermission> mPermissionTrees = + new HashMap<String, BasePermission>(); + + // Packages that have been uninstalled and still need their external + // storage data deleted. + final ArrayList<String> mPackagesToBeCleaned = new ArrayList<String>(); + + // Packages that have been renamed since they were first installed. + // Keys are the new names of the packages, values are the original + // names. The packages appear everwhere else under their original + // names. + final HashMap<String, String> mRenamedPackages = new HashMap<String, String>(); + + final StringBuilder mReadMessages = new StringBuilder(); + + /** + * Used to track packages that have a shared user ID that hasn't been read + * in yet. + * <p> + * TODO: make this just a local variable that is passed in during package + * scanning to make it less confusing. + */ + private final ArrayList<PendingPackage> mPendingPackages = new ArrayList<PendingPackage>(); + + Settings() { + File dataDir = Environment.getDataDirectory(); + File systemDir = new File(dataDir, "system"); + systemDir.mkdirs(); + FileUtils.setPermissions(systemDir.toString(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG + |FileUtils.S_IROTH|FileUtils.S_IXOTH, + -1, -1); + mSettingsFilename = new File(systemDir, "packages.xml"); + mBackupSettingsFilename = new File(systemDir, "packages-backup.xml"); + mPackageListFilename = new File(systemDir, "packages.list"); + mStoppedPackagesFilename = new File(systemDir, "packages-stopped.xml"); + mBackupStoppedPackagesFilename = new File(systemDir, "packages-stopped-backup.xml"); + } + + PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage, + String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, + String nativeLibraryPathString, int pkgFlags, boolean create, boolean add) { + final String name = pkg.packageName; + PackageSetting p = getPackageLPw(name, origPackage, realName, sharedUser, codePath, + resourcePath, nativeLibraryPathString, pkg.mVersionCode, pkgFlags, create, add); + return p; + } + + PackageSetting peekPackageLPr(String name) { + return mPackages.get(name); + } + + void setInstallStatus(String pkgName, int status) { + PackageSetting p = mPackages.get(pkgName); + if(p != null) { + if(p.getInstallStatus() != status) { + p.setInstallStatus(status); + } + } + } + + void setInstallerPackageName(String pkgName, + String installerPkgName) { + PackageSetting p = mPackages.get(pkgName); + if(p != null) { + p.setInstallerPackageName(installerPkgName); + } + } + + SharedUserSetting getSharedUserLPw(String name, + int pkgFlags, boolean create) { + SharedUserSetting s = mSharedUsers.get(name); + if (s == null) { + if (!create) { + return null; + } + s = new SharedUserSetting(name, pkgFlags); + if (PackageManagerService.MULTIPLE_APPLICATION_UIDS) { + s.userId = newUserIdLPw(s); + } else { + s.userId = PackageManagerService.FIRST_APPLICATION_UID; + } + Log.i(PackageManagerService.TAG, "New shared user " + name + ": id=" + s.userId); + // < 0 means we couldn't assign a userid; fall out and return + // s, which is currently null + if (s.userId >= 0) { + mSharedUsers.put(name, s); + } + } + + return s; + } + + boolean disableSystemPackageLPw(String name) { + final PackageSetting p = mPackages.get(name); + if(p == null) { + Log.w(PackageManagerService.TAG, "Package:"+name+" is not an installed package"); + return false; + } + final PackageSetting dp = mDisabledSysPackages.get(name); + // always make sure the system package code and resource paths dont change + if (dp == null) { + if((p.pkg != null) && (p.pkg.applicationInfo != null)) { + p.pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; + } + mDisabledSysPackages.put(name, p); + + // a little trick... when we install the new package, we don't + // want to modify the existing PackageSetting for the built-in + // version. so at this point we need a new PackageSetting that + // is okay to muck with. + PackageSetting newp = new PackageSetting(p); + replacePackageLPw(name, newp); + return true; + } + return false; + } + + PackageSetting enableSystemPackageLPw(String name) { + PackageSetting p = mDisabledSysPackages.get(name); + if(p == null) { + Log.w(PackageManagerService.TAG, "Package:"+name+" is not disabled"); + return null; + } + // Reset flag in ApplicationInfo object + if((p.pkg != null) && (p.pkg.applicationInfo != null)) { + p.pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; + } + PackageSetting ret = addPackageLPw(name, p.realName, p.codePath, p.resourcePath, + p.nativeLibraryPathString, p.userId, p.versionCode, p.pkgFlags); + mDisabledSysPackages.remove(name); + return ret; + } + + PackageSetting addPackageLPw(String name, String realName, File codePath, File resourcePath, + String nativeLibraryPathString, int uid, int vc, int pkgFlags) { + PackageSetting p = mPackages.get(name); + if (p != null) { + if (p.userId == uid) { + return p; + } + PackageManagerService.reportSettingsProblem(Log.ERROR, + "Adding duplicate package, keeping first: " + name); + return null; + } + p = new PackageSetting(name, realName, codePath, resourcePath, nativeLibraryPathString, + vc, pkgFlags); + p.userId = uid; + if (addUserIdLPw(uid, p, name)) { + mPackages.put(name, p); + return p; + } + return null; + } + + SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags) { + SharedUserSetting s = mSharedUsers.get(name); + if (s != null) { + if (s.userId == uid) { + return s; + } + PackageManagerService.reportSettingsProblem(Log.ERROR, + "Adding duplicate shared user, keeping first: " + name); + return null; + } + s = new SharedUserSetting(name, pkgFlags); + s.userId = uid; + if (addUserIdLPw(uid, s, name)) { + mSharedUsers.put(name, s); + return s; + } + return null; + } + + // Transfer ownership of permissions from one package to another. + void transferPermissionsLPw(String origPkg, String newPkg) { + // Transfer ownership of permissions to the new package. + for (int i=0; i<2; i++) { + HashMap<String, BasePermission> permissions = + i == 0 ? mPermissionTrees : mPermissions; + for (BasePermission bp : permissions.values()) { + if (origPkg.equals(bp.sourcePackage)) { + if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, + "Moving permission " + bp.name + + " from pkg " + bp.sourcePackage + + " to " + newPkg); + bp.sourcePackage = newPkg; + bp.packageSetting = null; + bp.perm = null; + if (bp.pendingInfo != null) { + bp.pendingInfo.packageName = newPkg; + } + bp.uid = 0; + bp.gids = null; + } + } + } + } + + private PackageSetting getPackageLPw(String name, PackageSetting origPackage, + String realName, SharedUserSetting sharedUser, File codePath, File resourcePath, + String nativeLibraryPathString, int vc, int pkgFlags, boolean create, boolean add) { + PackageSetting p = mPackages.get(name); + if (p != null) { + if (!p.codePath.equals(codePath)) { + // Check to see if its a disabled system app + if ((p.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) { + // This is an updated system app with versions in both system + // and data partition. Just let the most recent version + // take precedence. + Slog.w(PackageManagerService.TAG, "Trying to update system app code path from " + + p.codePathString + " to " + codePath.toString()); + } else { + // Just a change in the code path is not an issue, but + // let's log a message about it. + Slog.i(PackageManagerService.TAG, "Package " + name + " codePath changed from " + p.codePath + + " to " + codePath + "; Retaining data and using new"); + /* + * Since we've changed paths, we need to prefer the new + * native library path over the one stored in the + * package settings since we might have moved from + * internal to external storage or vice versa. + */ + p.nativeLibraryPathString = nativeLibraryPathString; + } + } + if (p.sharedUser != sharedUser) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Package " + name + " shared user changed from " + + (p.sharedUser != null ? p.sharedUser.name : "<nothing>") + + " to " + + (sharedUser != null ? sharedUser.name : "<nothing>") + + "; replacing with new"); + p = null; + } else { + if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0) { + // If what we are scanning is a system package, then + // make it so, regardless of whether it was previously + // installed only in the data partition. + p.pkgFlags |= ApplicationInfo.FLAG_SYSTEM; + } + } + } + if (p == null) { + // Create a new PackageSettings entry. this can end up here because + // of code path mismatch or user id mismatch of an updated system partition + if (!create) { + return null; + } + if (origPackage != null) { + // We are consuming the data from an existing package. + p = new PackageSetting(origPackage.name, name, codePath, resourcePath, + nativeLibraryPathString, vc, pkgFlags); + if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package " + name + + " is adopting original package " + origPackage.name); + // Note that we will retain the new package's signature so + // that we can keep its data. + PackageSignatures s = p.signatures; + p.copyFrom(origPackage); + p.signatures = s; + p.sharedUser = origPackage.sharedUser; + p.userId = origPackage.userId; + p.origPackage = origPackage; + mRenamedPackages.put(name, origPackage.name); + name = origPackage.name; + // Update new package state. + p.setTimeStamp(codePath.lastModified()); + } else { + p = new PackageSetting(name, realName, codePath, resourcePath, + nativeLibraryPathString, vc, pkgFlags); + p.setTimeStamp(codePath.lastModified()); + p.sharedUser = sharedUser; + // If this is not a system app, it starts out stopped. + if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { + if (DEBUG_STOPPED) { + RuntimeException e = new RuntimeException("here"); + e.fillInStackTrace(); + Slog.i(PackageManagerService.TAG, "Stopping package " + name, e); + } + p.stopped = true; + p.notLaunched = true; + } + if (sharedUser != null) { + p.userId = sharedUser.userId; + } else if (PackageManagerService.MULTIPLE_APPLICATION_UIDS) { + // Clone the setting here for disabled system packages + PackageSetting dis = mDisabledSysPackages.get(name); + if (dis != null) { + // For disabled packages a new setting is created + // from the existing user id. This still has to be + // added to list of user id's + // Copy signatures from previous setting + if (dis.signatures.mSignatures != null) { + p.signatures.mSignatures = dis.signatures.mSignatures.clone(); + } + p.userId = dis.userId; + // Clone permissions + p.grantedPermissions = new HashSet<String>(dis.grantedPermissions); + // Clone component info + p.disabledComponents = new HashSet<String>(dis.disabledComponents); + p.enabledComponents = new HashSet<String>(dis.enabledComponents); + // Add new setting to list of user ids + addUserIdLPw(p.userId, p, name); + } else { + // Assign new user id + p.userId = newUserIdLPw(p); + } + } else { + p.userId = PackageManagerService.FIRST_APPLICATION_UID; + } + } + if (p.userId < 0) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Package " + name + " could not be assigned a valid uid"); + return null; + } + if (add) { + // Finish adding new package by adding it and updating shared + // user preferences + addPackageSettingLPw(p, name, sharedUser); + } + } + return p; + } + + void insertPackageSettingLPw(PackageSetting p, PackageParser.Package pkg) { + p.pkg = pkg; + pkg.mSetEnabled = p.enabled; + pkg.mSetStopped = p.stopped; + final String codePath = pkg.applicationInfo.sourceDir; + final String resourcePath = pkg.applicationInfo.publicSourceDir; + // Update code path if needed + if (!codePath.equalsIgnoreCase(p.codePathString)) { + Slog.w(PackageManagerService.TAG, "Code path for pkg : " + p.pkg.packageName + + " changing from " + p.codePathString + " to " + codePath); + p.codePath = new File(codePath); + p.codePathString = codePath; + } + //Update resource path if needed + if (!resourcePath.equalsIgnoreCase(p.resourcePathString)) { + Slog.w(PackageManagerService.TAG, "Resource path for pkg : " + p.pkg.packageName + + " changing from " + p.resourcePathString + " to " + resourcePath); + p.resourcePath = new File(resourcePath); + p.resourcePathString = resourcePath; + } + // Update the native library path if needed + final String nativeLibraryPath = pkg.applicationInfo.nativeLibraryDir; + if (nativeLibraryPath != null + && !nativeLibraryPath.equalsIgnoreCase(p.nativeLibraryPathString)) { + p.nativeLibraryPathString = nativeLibraryPath; + } + // Update version code if needed + if (pkg.mVersionCode != p.versionCode) { + p.versionCode = pkg.mVersionCode; + } + // Update signatures if needed. + if (p.signatures.mSignatures == null) { + p.signatures.assignSignatures(pkg.mSignatures); + } + // If this app defines a shared user id initialize + // the shared user signatures as well. + if (p.sharedUser != null && p.sharedUser.signatures.mSignatures == null) { + p.sharedUser.signatures.assignSignatures(pkg.mSignatures); + } + addPackageSettingLPw(p, pkg.packageName, p.sharedUser); + } + + // Utility method that adds a PackageSetting to mPackages and + // completes updating the shared user attributes + private void addPackageSettingLPw(PackageSetting p, String name, + SharedUserSetting sharedUser) { + mPackages.put(name, p); + if (sharedUser != null) { + if (p.sharedUser != null && p.sharedUser != sharedUser) { + PackageManagerService.reportSettingsProblem(Log.ERROR, + "Package " + p.name + " was user " + + p.sharedUser + " but is now " + sharedUser + + "; I am not changing its files so it will probably fail!"); + p.sharedUser.packages.remove(p); + } else if (p.userId != sharedUser.userId) { + PackageManagerService.reportSettingsProblem(Log.ERROR, + "Package " + p.name + " was user id " + p.userId + + " but is now user " + sharedUser + + " with id " + sharedUser.userId + + "; I am not changing its files so it will probably fail!"); + } + + sharedUser.packages.add(p); + p.sharedUser = sharedUser; + p.userId = sharedUser.userId; + } + } + + /* + * Update the shared user setting when a package using + * specifying the shared user id is removed. The gids + * associated with each permission of the deleted package + * are removed from the shared user's gid list only if its + * not in use by other permissions of packages in the + * shared user setting. + */ + void updateSharedUserPermsLPw(PackageSetting deletedPs, int[] globalGids) { + if ((deletedPs == null) || (deletedPs.pkg == null)) { + Slog.i(PackageManagerService.TAG, + "Trying to update info for null package. Just ignoring"); + return; + } + // No sharedUserId + if (deletedPs.sharedUser == null) { + return; + } + SharedUserSetting sus = deletedPs.sharedUser; + // Update permissions + for (String eachPerm : deletedPs.pkg.requestedPermissions) { + boolean used = false; + if (!sus.grantedPermissions.contains(eachPerm)) { + continue; + } + for (PackageSetting pkg:sus.packages) { + if (pkg.pkg != null && + !pkg.pkg.packageName.equals(deletedPs.pkg.packageName) && + pkg.pkg.requestedPermissions.contains(eachPerm)) { + used = true; + break; + } + } + if (!used) { + // can safely delete this permission from list + sus.grantedPermissions.remove(eachPerm); + } + } + // Update gids + int newGids[] = globalGids; + for (String eachPerm : sus.grantedPermissions) { + BasePermission bp = mPermissions.get(eachPerm); + if (bp != null) { + newGids = PackageManagerService.appendInts(newGids, bp.gids); + } + } + sus.gids = newGids; + } + + int removePackageLPw(String name) { + final PackageSetting p = mPackages.get(name); + if (p != null) { + mPackages.remove(name); + if (p.sharedUser != null) { + p.sharedUser.packages.remove(p); + if (p.sharedUser.packages.size() == 0) { + mSharedUsers.remove(p.sharedUser.name); + removeUserIdLPw(p.sharedUser.userId); + return p.sharedUser.userId; + } + } else { + removeUserIdLPw(p.userId); + return p.userId; + } + } + return -1; + } + + private void replacePackageLPw(String name, PackageSetting newp) { + final PackageSetting p = mPackages.get(name); + if (p != null) { + if (p.sharedUser != null) { + p.sharedUser.packages.remove(p); + p.sharedUser.packages.add(newp); + } else { + replaceUserIdLPw(p.userId, newp); + } + } + mPackages.put(name, newp); + } + + private boolean addUserIdLPw(int uid, Object obj, Object name) { + if (uid >= PackageManagerService.FIRST_APPLICATION_UID + PackageManagerService.MAX_APPLICATION_UIDS) { + return false; + } + + if (uid >= PackageManagerService.FIRST_APPLICATION_UID) { + int N = mUserIds.size(); + final int index = uid - PackageManagerService.FIRST_APPLICATION_UID; + while (index >= N) { + mUserIds.add(null); + N++; + } + if (mUserIds.get(index) != null) { + PackageManagerService.reportSettingsProblem(Log.ERROR, + "Adding duplicate user id: " + uid + + " name=" + name); + return false; + } + mUserIds.set(index, obj); + } else { + if (mOtherUserIds.get(uid) != null) { + PackageManagerService.reportSettingsProblem(Log.ERROR, + "Adding duplicate shared id: " + uid + + " name=" + name); + return false; + } + mOtherUserIds.put(uid, obj); + } + return true; + } + + public Object getUserIdLPr(int uid) { + if (uid >= PackageManagerService.FIRST_APPLICATION_UID) { + final int N = mUserIds.size(); + final int index = uid - PackageManagerService.FIRST_APPLICATION_UID; + return index < N ? mUserIds.get(index) : null; + } else { + return mOtherUserIds.get(uid); + } + } + + private void removeUserIdLPw(int uid) { + if (uid >= PackageManagerService.FIRST_APPLICATION_UID) { + final int N = mUserIds.size(); + final int index = uid - PackageManagerService.FIRST_APPLICATION_UID; + if (index < N) mUserIds.set(index, null); + } else { + mOtherUserIds.remove(uid); + } + } + + private void replaceUserIdLPw(int uid, Object obj) { + if (uid >= PackageManagerService.FIRST_APPLICATION_UID) { + final int N = mUserIds.size(); + final int index = uid - PackageManagerService.FIRST_APPLICATION_UID; + if (index < N) mUserIds.set(index, obj); + } else { + mOtherUserIds.put(uid, obj); + } + } + + void writeStoppedLPr() { + // Keep the old stopped packages around until we know the new ones have + // been successfully written. + if (mStoppedPackagesFilename.exists()) { + // Presence of backup settings file indicates that we failed + // to persist packages earlier. So preserve the older + // backup for future reference since the current packages + // might have been corrupted. + if (!mBackupStoppedPackagesFilename.exists()) { + if (!mStoppedPackagesFilename.renameTo(mBackupStoppedPackagesFilename)) { + Log.wtf(PackageManagerService.TAG, "Unable to backup package manager stopped packages, " + + "current changes will be lost at reboot"); + return; + } + } else { + mStoppedPackagesFilename.delete(); + Slog.w(PackageManagerService.TAG, "Preserving older stopped packages backup"); + } + } + + try { + final FileOutputStream fstr = new FileOutputStream(mStoppedPackagesFilename); + final BufferedOutputStream str = new BufferedOutputStream(fstr); + + //XmlSerializer serializer = XmlUtils.serializerInstance(); + final XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(str, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + serializer.startTag(null, "stopped-packages"); + + for (final PackageSetting pkg : mPackages.values()) { + if (pkg.stopped) { + serializer.startTag(null, "pkg"); + serializer.attribute(null, "name", pkg.name); + if (pkg.notLaunched) { + serializer.attribute(null, "nl", "1"); + } + serializer.endTag(null, "pkg"); + } + } + + serializer.endTag(null, "stopped-packages"); + + serializer.endDocument(); + + str.flush(); + FileUtils.sync(fstr); + str.close(); + + // New settings successfully written, old ones are no longer + // needed. + mBackupStoppedPackagesFilename.delete(); + FileUtils.setPermissions(mStoppedPackagesFilename.toString(), + FileUtils.S_IRUSR|FileUtils.S_IWUSR + |FileUtils.S_IRGRP|FileUtils.S_IWGRP + |FileUtils.S_IROTH, + -1, -1); + + // Done, all is good! + return; + } catch(java.io.IOException e) { + Log.wtf(PackageManagerService.TAG, "Unable to write package manager stopped packages, " + + " current changes will be lost at reboot", e); + } + + // Clean up partially written files + if (mStoppedPackagesFilename.exists()) { + if (!mStoppedPackagesFilename.delete()) { + Log.i(PackageManagerService.TAG, "Failed to clean up mangled file: " + mStoppedPackagesFilename); + } + } + } + + // Note: assumed "stopped" field is already cleared in all packages. + void readStoppedLPw() { + FileInputStream str = null; + if (mBackupStoppedPackagesFilename.exists()) { + try { + str = new FileInputStream(mBackupStoppedPackagesFilename); + mReadMessages.append("Reading from backup stopped packages file\n"); + PackageManagerService.reportSettingsProblem(Log.INFO, "Need to read from backup stopped packages file"); + if (mSettingsFilename.exists()) { + // If both the backup and normal file exist, we + // ignore the normal one since it might have been + // corrupted. + Slog.w(PackageManagerService.TAG, "Cleaning up stopped packages file " + + mStoppedPackagesFilename); + mStoppedPackagesFilename.delete(); + } + } catch (java.io.IOException e) { + // We'll try for the normal settings file. + } + } + + try { + if (str == null) { + if (!mStoppedPackagesFilename.exists()) { + mReadMessages.append("No stopped packages file found\n"); + PackageManagerService.reportSettingsProblem(Log.INFO, "No stopped packages file file; " + + "assuming all started"); + // At first boot, make sure no packages are stopped. + // We usually want to have third party apps initialize + // in the stopped state, but not at first boot. + for (PackageSetting pkg : mPackages.values()) { + pkg.stopped = false; + pkg.notLaunched = false; + } + return; + } + str = new FileInputStream(mStoppedPackagesFilename); + } + final XmlPullParser parser = Xml.newPullParser(); + parser.setInput(str, null); + + int type; + while ((type=parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + mReadMessages.append("No start tag found in stopped packages file\n"); + PackageManagerService.reportSettingsProblem(Log.WARN, + "No start tag found in package manager stopped packages"); + return; + } + + int outerDepth = parser.getDepth(); + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG + || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("pkg")) { + String name = parser.getAttributeValue(null, "name"); + PackageSetting ps = mPackages.get(name); + if (ps != null) { + ps.stopped = true; + if ("1".equals(parser.getAttributeValue(null, "nl"))) { + ps.notLaunched = true; + } + } else { + Slog.w(PackageManagerService.TAG, "No package known for stopped package: " + name); + } + XmlUtils.skipCurrentTag(parser); + } else { + Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + + str.close(); + + } catch(XmlPullParserException e) { + mReadMessages.append("Error reading: " + e.toString()); + PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading stopped packages: " + e); + Log.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", e); + + } catch(java.io.IOException e) { + mReadMessages.append("Error reading: " + e.toString()); + PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); + Log.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", e); + + } + } + + void writeLPr() { + //Debug.startMethodTracing("/data/system/packageprof", 8 * 1024 * 1024); + + // Keep the old settings around until we know the new ones have + // been successfully written. + if (mSettingsFilename.exists()) { + // Presence of backup settings file indicates that we failed + // to persist settings earlier. So preserve the older + // backup for future reference since the current settings + // might have been corrupted. + if (!mBackupSettingsFilename.exists()) { + if (!mSettingsFilename.renameTo(mBackupSettingsFilename)) { + Log.wtf(PackageManagerService.TAG, "Unable to backup package manager settings, " + + " current changes will be lost at reboot"); + return; + } + } else { + mSettingsFilename.delete(); + Slog.w(PackageManagerService.TAG, "Preserving older settings backup"); + } + } + + mPastSignatures.clear(); + + try { + FileOutputStream fstr = new FileOutputStream(mSettingsFilename); + BufferedOutputStream str = new BufferedOutputStream(fstr); + + //XmlSerializer serializer = XmlUtils.serializerInstance(); + XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(str, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + serializer.startTag(null, "packages"); + + serializer.startTag(null, "last-platform-version"); + serializer.attribute(null, "internal", Integer.toString(mInternalSdkPlatform)); + serializer.attribute(null, "external", Integer.toString(mExternalSdkPlatform)); + serializer.endTag(null, "last-platform-version"); + + serializer.startTag(null, "permission-trees"); + for (BasePermission bp : mPermissionTrees.values()) { + writePermissionLPr(serializer, bp); + } + serializer.endTag(null, "permission-trees"); + + serializer.startTag(null, "permissions"); + for (BasePermission bp : mPermissions.values()) { + writePermissionLPr(serializer, bp); + } + serializer.endTag(null, "permissions"); + + for (final PackageSetting pkg : mPackages.values()) { + writePackageLPr(serializer, pkg); + } + + for (final PackageSetting pkg : mDisabledSysPackages.values()) { + writeDisabledSysPackageLPr(serializer, pkg); + } + + serializer.startTag(null, "preferred-activities"); + for (final PreferredActivity pa : mPreferredActivities.filterSet()) { + serializer.startTag(null, "item"); + pa.writeToXml(serializer); + serializer.endTag(null, "item"); + } + serializer.endTag(null, "preferred-activities"); + + for (final SharedUserSetting usr : mSharedUsers.values()) { + serializer.startTag(null, "shared-user"); + serializer.attribute(null, "name", usr.name); + serializer.attribute(null, "userId", + Integer.toString(usr.userId)); + usr.signatures.writeXml(serializer, "sigs", mPastSignatures); + serializer.startTag(null, "perms"); + for (String name : usr.grantedPermissions) { + serializer.startTag(null, "item"); + serializer.attribute(null, "name", name); + serializer.endTag(null, "item"); + } + serializer.endTag(null, "perms"); + serializer.endTag(null, "shared-user"); + } + + if (mPackagesToBeCleaned.size() > 0) { + for (int i=0; i<mPackagesToBeCleaned.size(); i++) { + serializer.startTag(null, "cleaning-package"); + serializer.attribute(null, "name", mPackagesToBeCleaned.get(i)); + serializer.endTag(null, "cleaning-package"); + } + } + + if (mRenamedPackages.size() > 0) { + for (HashMap.Entry<String, String> e : mRenamedPackages.entrySet()) { + serializer.startTag(null, "renamed-package"); + serializer.attribute(null, "new", e.getKey()); + serializer.attribute(null, "old", e.getValue()); + serializer.endTag(null, "renamed-package"); + } + } + + serializer.endTag(null, "packages"); + + serializer.endDocument(); + + str.flush(); + FileUtils.sync(fstr); + str.close(); + + // New settings successfully written, old ones are no longer + // needed. + mBackupSettingsFilename.delete(); + FileUtils.setPermissions(mSettingsFilename.toString(), + FileUtils.S_IRUSR|FileUtils.S_IWUSR + |FileUtils.S_IRGRP|FileUtils.S_IWGRP + |FileUtils.S_IROTH, + -1, -1); + + // Write package list file now, use a JournaledFile. + // + File tempFile = new File(mPackageListFilename.toString() + ".tmp"); + JournaledFile journal = new JournaledFile(mPackageListFilename, tempFile); + + fstr = new FileOutputStream(journal.chooseForWrite()); + str = new BufferedOutputStream(fstr); + try { + StringBuilder sb = new StringBuilder(); + for (final PackageSetting pkg : mPackages.values()) { + ApplicationInfo ai = pkg.pkg.applicationInfo; + String dataPath = ai.dataDir; + boolean isDebug = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + + // Avoid any application that has a space in its path + // or that is handled by the system. + if (dataPath.indexOf(" ") >= 0 || ai.uid <= Process.FIRST_APPLICATION_UID) + continue; + + // we store on each line the following information for now: + // + // pkgName - package name + // userId - application-specific user id + // debugFlag - 0 or 1 if the package is debuggable. + // dataPath - path to package's data path + // + // NOTE: We prefer not to expose all ApplicationInfo flags for now. + // + // DO NOT MODIFY THIS FORMAT UNLESS YOU CAN ALSO MODIFY ITS USERS + // FROM NATIVE CODE. AT THE MOMENT, LOOK AT THE FOLLOWING SOURCES: + // system/core/run-as/run-as.c + // + sb.setLength(0); + sb.append(ai.packageName); + sb.append(" "); + sb.append((int)ai.uid); + sb.append(isDebug ? " 1 " : " 0 "); + sb.append(dataPath); + sb.append("\n"); + str.write(sb.toString().getBytes()); + } + str.flush(); + FileUtils.sync(fstr); + str.close(); + journal.commit(); + } + catch (Exception e) { + journal.rollback(); + } + + FileUtils.setPermissions(mPackageListFilename.toString(), + FileUtils.S_IRUSR|FileUtils.S_IWUSR + |FileUtils.S_IRGRP|FileUtils.S_IWGRP + |FileUtils.S_IROTH, + -1, -1); + + writeStoppedLPr(); + + return; + + } catch(XmlPullParserException e) { + Log.wtf(PackageManagerService.TAG, "Unable to write package manager settings, " + + "current changes will be lost at reboot", e); + } catch(java.io.IOException e) { + Log.wtf(PackageManagerService.TAG, "Unable to write package manager settings, " + + "current changes will be lost at reboot", e); + } + // Clean up partially written files + if (mSettingsFilename.exists()) { + if (!mSettingsFilename.delete()) { + Log.wtf(PackageManagerService.TAG, "Failed to clean up mangled file: " + mSettingsFilename); + } + } + //Debug.stopMethodTracing(); + } + + void writeDisabledSysPackageLPr(XmlSerializer serializer, final PackageSetting pkg) + throws java.io.IOException { + serializer.startTag(null, "updated-package"); + serializer.attribute(null, "name", pkg.name); + if (pkg.realName != null) { + serializer.attribute(null, "realName", pkg.realName); + } + serializer.attribute(null, "codePath", pkg.codePathString); + serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp)); + serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime)); + serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime)); + serializer.attribute(null, "version", String.valueOf(pkg.versionCode)); + if (!pkg.resourcePathString.equals(pkg.codePathString)) { + serializer.attribute(null, "resourcePath", pkg.resourcePathString); + } + if (pkg.nativeLibraryPathString != null) { + serializer.attribute(null, "nativeLibraryPath", pkg.nativeLibraryPathString); + } + if (pkg.sharedUser == null) { + serializer.attribute(null, "userId", Integer.toString(pkg.userId)); + } else { + serializer.attribute(null, "sharedUserId", Integer.toString(pkg.userId)); + } + serializer.startTag(null, "perms"); + if (pkg.sharedUser == null) { + // If this is a shared user, the permissions will + // be written there. We still need to write an + // empty permissions list so permissionsFixed will + // be set. + for (final String name : pkg.grantedPermissions) { + BasePermission bp = mPermissions.get(name); + if (bp != null) { + // We only need to write signature or system permissions but + // this wont + // match the semantics of grantedPermissions. So write all + // permissions. + serializer.startTag(null, "item"); + serializer.attribute(null, "name", name); + serializer.endTag(null, "item"); + } + } + } + serializer.endTag(null, "perms"); + serializer.endTag(null, "updated-package"); + } + + void writePackageLPr(XmlSerializer serializer, final PackageSetting pkg) + throws java.io.IOException { + serializer.startTag(null, "package"); + serializer.attribute(null, "name", pkg.name); + if (pkg.realName != null) { + serializer.attribute(null, "realName", pkg.realName); + } + serializer.attribute(null, "codePath", pkg.codePathString); + if (!pkg.resourcePathString.equals(pkg.codePathString)) { + serializer.attribute(null, "resourcePath", pkg.resourcePathString); + } + if (pkg.nativeLibraryPathString != null) { + serializer.attribute(null, "nativeLibraryPath", pkg.nativeLibraryPathString); + } + serializer.attribute(null, "flags", Integer.toString(pkg.pkgFlags)); + serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp)); + serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime)); + serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime)); + serializer.attribute(null, "version", String.valueOf(pkg.versionCode)); + if (pkg.sharedUser == null) { + serializer.attribute(null, "userId", Integer.toString(pkg.userId)); + } else { + serializer.attribute(null, "sharedUserId", Integer.toString(pkg.userId)); + } + if (pkg.uidError) { + serializer.attribute(null, "uidError", "true"); + } + if (pkg.enabled != COMPONENT_ENABLED_STATE_DEFAULT) { + serializer.attribute(null, "enabled", + pkg.enabled == COMPONENT_ENABLED_STATE_ENABLED ? "true" : "false"); + } + if (pkg.installStatus == PackageSettingBase.PKG_INSTALL_INCOMPLETE) { + serializer.attribute(null, "installStatus", "false"); + } + if (pkg.installerPackageName != null) { + serializer.attribute(null, "installer", pkg.installerPackageName); + } + pkg.signatures.writeXml(serializer, "sigs", mPastSignatures); + if ((pkg.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) { + serializer.startTag(null, "perms"); + if (pkg.sharedUser == null) { + // If this is a shared user, the permissions will + // be written there. We still need to write an + // empty permissions list so permissionsFixed will + // be set. + for (final String name : pkg.grantedPermissions) { + serializer.startTag(null, "item"); + serializer.attribute(null, "name", name); + serializer.endTag(null, "item"); + } + } + serializer.endTag(null, "perms"); + } + if (pkg.disabledComponents.size() > 0) { + serializer.startTag(null, "disabled-components"); + for (final String name : pkg.disabledComponents) { + serializer.startTag(null, "item"); + serializer.attribute(null, "name", name); + serializer.endTag(null, "item"); + } + serializer.endTag(null, "disabled-components"); + } + if (pkg.enabledComponents.size() > 0) { + serializer.startTag(null, "enabled-components"); + for (final String name : pkg.enabledComponents) { + serializer.startTag(null, "item"); + serializer.attribute(null, "name", name); + serializer.endTag(null, "item"); + } + serializer.endTag(null, "enabled-components"); + } + + serializer.endTag(null, "package"); + } + + void writePermissionLPr(XmlSerializer serializer, BasePermission bp) + throws XmlPullParserException, java.io.IOException { + if (bp.type != BasePermission.TYPE_BUILTIN && bp.sourcePackage != null) { + serializer.startTag(null, "item"); + serializer.attribute(null, "name", bp.name); + serializer.attribute(null, "package", bp.sourcePackage); + if (bp.protectionLevel != PermissionInfo.PROTECTION_NORMAL) { + serializer.attribute(null, "protection", Integer.toString(bp.protectionLevel)); + } + if (PackageManagerService.DEBUG_SETTINGS) + Log.v(PackageManagerService.TAG, "Writing perm: name=" + bp.name + " type=" + + bp.type); + if (bp.type == BasePermission.TYPE_DYNAMIC) { + final PermissionInfo pi = bp.perm != null ? bp.perm.info : bp.pendingInfo; + if (pi != null) { + serializer.attribute(null, "type", "dynamic"); + if (pi.icon != 0) { + serializer.attribute(null, "icon", Integer.toString(pi.icon)); + } + if (pi.nonLocalizedLabel != null) { + serializer.attribute(null, "label", pi.nonLocalizedLabel.toString()); + } + } + } + serializer.endTag(null, "item"); + } + } + + ArrayList<PackageSetting> getListOfIncompleteInstallPackagesLPr() { + final HashSet<String> kList = new HashSet<String>(mPackages.keySet()); + final Iterator<String> its = kList.iterator(); + final ArrayList<PackageSetting> ret = new ArrayList<PackageSetting>(); + while (its.hasNext()) { + final String key = its.next(); + final PackageSetting ps = mPackages.get(key); + if (ps.getInstallStatus() == PackageSettingBase.PKG_INSTALL_INCOMPLETE) { + ret.add(ps); + } + } + return ret; + } + + boolean readLPw() { + FileInputStream str = null; + if (mBackupSettingsFilename.exists()) { + try { + str = new FileInputStream(mBackupSettingsFilename); + mReadMessages.append("Reading from backup settings file\n"); + PackageManagerService.reportSettingsProblem(Log.INFO, + "Need to read from backup settings file"); + if (mSettingsFilename.exists()) { + // If both the backup and settings file exist, we + // ignore the settings since it might have been + // corrupted. + Slog.w(PackageManagerService.TAG, "Cleaning up settings file " + + mSettingsFilename); + mSettingsFilename.delete(); + } + } catch (java.io.IOException e) { + // We'll try for the normal settings file. + } + } + + mPendingPackages.clear(); + mPastSignatures.clear(); + + try { + if (str == null) { + if (!mSettingsFilename.exists()) { + mReadMessages.append("No settings file found\n"); + PackageManagerService.reportSettingsProblem(Log.INFO, + "No settings file; creating initial state"); + return false; + } + str = new FileInputStream(mSettingsFilename); + } + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(str, null); + + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + mReadMessages.append("No start tag found in settings file\n"); + PackageManagerService.reportSettingsProblem(Log.WARN, + "No start tag found in package manager settings"); + Log + .wtf(PackageManagerService.TAG, + "No start tag found in package manager settings"); + return false; + } + + int outerDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("package")) { + readPackageLPw(parser); + } else if (tagName.equals("permissions")) { + readPermissionsLPw(mPermissions, parser); + } else if (tagName.equals("permission-trees")) { + readPermissionsLPw(mPermissionTrees, parser); + } else if (tagName.equals("shared-user")) { + readSharedUserLPw(parser); + } else if (tagName.equals("preferred-packages")) { + // no longer used. + } else if (tagName.equals("preferred-activities")) { + readPreferredActivitiesLPw(parser); + } else if (tagName.equals("updated-package")) { + readDisabledSysPackageLPw(parser); + } else if (tagName.equals("cleaning-package")) { + String name = parser.getAttributeValue(null, "name"); + if (name != null) { + mPackagesToBeCleaned.add(name); + } + } else if (tagName.equals("renamed-package")) { + String nname = parser.getAttributeValue(null, "new"); + String oname = parser.getAttributeValue(null, "old"); + if (nname != null && oname != null) { + mRenamedPackages.put(nname, oname); + } + } else if (tagName.equals("last-platform-version")) { + mInternalSdkPlatform = mExternalSdkPlatform = 0; + try { + String internal = parser.getAttributeValue(null, "internal"); + if (internal != null) { + mInternalSdkPlatform = Integer.parseInt(internal); + } + String external = parser.getAttributeValue(null, "external"); + if (external != null) { + mExternalSdkPlatform = Integer.parseInt(external); + } + } catch (NumberFormatException e) { + } + } else { + Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + + str.close(); + + } catch (XmlPullParserException e) { + mReadMessages.append("Error reading: " + e.toString()); + PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); + Log.wtf(PackageManagerService.TAG, "Error reading package manager settings", e); + + } catch (java.io.IOException e) { + mReadMessages.append("Error reading: " + e.toString()); + PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); + Log.wtf(PackageManagerService.TAG, "Error reading package manager settings", e); + + } + + final int N = mPendingPackages.size(); + for (int i = 0; i < N; i++) { + final PendingPackage pp = mPendingPackages.get(i); + Object idObj = getUserIdLPr(pp.sharedId); + if (idObj != null && idObj instanceof SharedUserSetting) { + PackageSetting p = getPackageLPw(pp.name, null, pp.realName, + (SharedUserSetting) idObj, pp.codePath, pp.resourcePath, + pp.nativeLibraryPathString, pp.versionCode, pp.pkgFlags, true, true); + if (p == null) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unable to create application package for " + pp.name); + continue; + } + p.copyFrom(pp); + } else if (idObj != null) { + String msg = "Bad package setting: package " + pp.name + " has shared uid " + + pp.sharedId + " that is not a shared uid\n"; + mReadMessages.append(msg); + PackageManagerService.reportSettingsProblem(Log.ERROR, msg); + } else { + String msg = "Bad package setting: package " + pp.name + " has shared uid " + + pp.sharedId + " that is not defined\n"; + mReadMessages.append(msg); + PackageManagerService.reportSettingsProblem(Log.ERROR, msg); + } + } + mPendingPackages.clear(); + + /* + * Make sure all the updated system packages have their shared users + * associated with them. + */ + final Iterator<PackageSetting> disabledIt = mDisabledSysPackages.values().iterator(); + while (disabledIt.hasNext()) { + final PackageSetting disabledPs = disabledIt.next(); + final Object id = getUserIdLPr(disabledPs.userId); + if (id != null && id instanceof SharedUserSetting) { + disabledPs.sharedUser = (SharedUserSetting) id; + } + } + + readStoppedLPw(); + + mReadMessages.append("Read completed successfully: " + mPackages.size() + " packages, " + + mSharedUsers.size() + " shared uids\n"); + + return true; + } + + private int readInt(XmlPullParser parser, String ns, String name, int defValue) { + String v = parser.getAttributeValue(ns, name); + try { + if (v == null) { + return defValue; + } + return Integer.parseInt(v); + } catch (NumberFormatException e) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: attribute " + name + + " has bad integer value " + v + " at " + + parser.getPositionDescription()); + } + return defValue; + } + + private void readPermissionsLPw(HashMap<String, BasePermission> out, XmlPullParser parser) + throws IOException, XmlPullParserException { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + final String tagName = parser.getName(); + if (tagName.equals("item")) { + final String name = parser.getAttributeValue(null, "name"); + final String sourcePackage = parser.getAttributeValue(null, "package"); + final String ptype = parser.getAttributeValue(null, "type"); + if (name != null && sourcePackage != null) { + final boolean dynamic = "dynamic".equals(ptype); + final BasePermission bp = new BasePermission(name, sourcePackage, + dynamic ? BasePermission.TYPE_DYNAMIC : BasePermission.TYPE_NORMAL); + bp.protectionLevel = readInt(parser, null, "protection", + PermissionInfo.PROTECTION_NORMAL); + if (dynamic) { + PermissionInfo pi = new PermissionInfo(); + pi.packageName = sourcePackage.intern(); + pi.name = name.intern(); + pi.icon = readInt(parser, null, "icon", 0); + pi.nonLocalizedLabel = parser.getAttributeValue(null, "label"); + pi.protectionLevel = bp.protectionLevel; + bp.pendingInfo = pi; + } + out.put(bp.name, bp); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: permissions has" + " no name at " + + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element reading permissions: " + parser.getName() + " at " + + parser.getPositionDescription()); + } + XmlUtils.skipCurrentTag(parser); + } + } + + private void readDisabledSysPackageLPw(XmlPullParser parser) throws XmlPullParserException, + IOException { + String name = parser.getAttributeValue(null, "name"); + String realName = parser.getAttributeValue(null, "realName"); + String codePathStr = parser.getAttributeValue(null, "codePath"); + String resourcePathStr = parser.getAttributeValue(null, "resourcePath"); + String nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath"); + if (resourcePathStr == null) { + resourcePathStr = codePathStr; + } + String version = parser.getAttributeValue(null, "version"); + int versionCode = 0; + if (version != null) { + try { + versionCode = Integer.parseInt(version); + } catch (NumberFormatException e) { + } + } + + int pkgFlags = 0; + pkgFlags |= ApplicationInfo.FLAG_SYSTEM; + PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr), + new File(resourcePathStr), nativeLibraryPathStr, versionCode, pkgFlags); + String timeStampStr = parser.getAttributeValue(null, "ft"); + if (timeStampStr != null) { + try { + long timeStamp = Long.parseLong(timeStampStr, 16); + ps.setTimeStamp(timeStamp); + } catch (NumberFormatException e) { + } + } else { + timeStampStr = parser.getAttributeValue(null, "ts"); + if (timeStampStr != null) { + try { + long timeStamp = Long.parseLong(timeStampStr); + ps.setTimeStamp(timeStamp); + } catch (NumberFormatException e) { + } + } + } + timeStampStr = parser.getAttributeValue(null, "it"); + if (timeStampStr != null) { + try { + ps.firstInstallTime = Long.parseLong(timeStampStr, 16); + } catch (NumberFormatException e) { + } + } + timeStampStr = parser.getAttributeValue(null, "ut"); + if (timeStampStr != null) { + try { + ps.lastUpdateTime = Long.parseLong(timeStampStr, 16); + } catch (NumberFormatException e) { + } + } + String idStr = parser.getAttributeValue(null, "userId"); + ps.userId = idStr != null ? Integer.parseInt(idStr) : 0; + if (ps.userId <= 0) { + String sharedIdStr = parser.getAttributeValue(null, "sharedUserId"); + ps.userId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0; + } + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("perms")) { + readGrantedPermissionsLPw(parser, ps.grantedPermissions); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <updated-package>: " + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + mDisabledSysPackages.put(name, ps); + } + + private void readPackageLPw(XmlPullParser parser) throws XmlPullParserException, IOException { + String name = null; + String realName = null; + String idStr = null; + String sharedIdStr = null; + String codePathStr = null; + String resourcePathStr = null; + String nativeLibraryPathStr = null; + String systemStr = null; + String installerPackageName = null; + String uidError = null; + int pkgFlags = 0; + long timeStamp = 0; + long firstInstallTime = 0; + long lastUpdateTime = 0; + PackageSettingBase packageSetting = null; + String version = null; + int versionCode = 0; + try { + name = parser.getAttributeValue(null, "name"); + realName = parser.getAttributeValue(null, "realName"); + idStr = parser.getAttributeValue(null, "userId"); + uidError = parser.getAttributeValue(null, "uidError"); + sharedIdStr = parser.getAttributeValue(null, "sharedUserId"); + codePathStr = parser.getAttributeValue(null, "codePath"); + resourcePathStr = parser.getAttributeValue(null, "resourcePath"); + nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath"); + version = parser.getAttributeValue(null, "version"); + if (version != null) { + try { + versionCode = Integer.parseInt(version); + } catch (NumberFormatException e) { + } + } + installerPackageName = parser.getAttributeValue(null, "installer"); + + systemStr = parser.getAttributeValue(null, "flags"); + if (systemStr != null) { + try { + pkgFlags = Integer.parseInt(systemStr); + } catch (NumberFormatException e) { + } + } else { + // For backward compatibility + systemStr = parser.getAttributeValue(null, "system"); + if (systemStr != null) { + pkgFlags |= ("true".equalsIgnoreCase(systemStr)) ? ApplicationInfo.FLAG_SYSTEM + : 0; + } else { + // Old settings that don't specify system... just treat + // them as system, good enough. + pkgFlags |= ApplicationInfo.FLAG_SYSTEM; + } + } + String timeStampStr = parser.getAttributeValue(null, "ft"); + if (timeStampStr != null) { + try { + timeStamp = Long.parseLong(timeStampStr, 16); + } catch (NumberFormatException e) { + } + } else { + timeStampStr = parser.getAttributeValue(null, "ts"); + if (timeStampStr != null) { + try { + timeStamp = Long.parseLong(timeStampStr); + } catch (NumberFormatException e) { + } + } + } + timeStampStr = parser.getAttributeValue(null, "it"); + if (timeStampStr != null) { + try { + firstInstallTime = Long.parseLong(timeStampStr, 16); + } catch (NumberFormatException e) { + } + } + timeStampStr = parser.getAttributeValue(null, "ut"); + if (timeStampStr != null) { + try { + lastUpdateTime = Long.parseLong(timeStampStr, 16); + } catch (NumberFormatException e) { + } + } + if (PackageManagerService.DEBUG_SETTINGS) + Log.v(PackageManagerService.TAG, "Reading package: " + name + " userId=" + idStr + + " sharedUserId=" + sharedIdStr); + int userId = idStr != null ? Integer.parseInt(idStr) : 0; + if (resourcePathStr == null) { + resourcePathStr = codePathStr; + } + if (realName != null) { + realName = realName.intern(); + } + if (name == null) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <package> has no name at " + + parser.getPositionDescription()); + } else if (codePathStr == null) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <package> has no codePath at " + + parser.getPositionDescription()); + } else if (userId > 0) { + packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr), + new File(resourcePathStr), nativeLibraryPathStr, userId, versionCode, + pkgFlags); + if (PackageManagerService.DEBUG_SETTINGS) + Log.i(PackageManagerService.TAG, "Reading package " + name + ": userId=" + + userId + " pkg=" + packageSetting); + if (packageSetting == null) { + PackageManagerService.reportSettingsProblem(Log.ERROR, "Failure adding uid " + + userId + " while parsing settings at " + + parser.getPositionDescription()); + } else { + packageSetting.setTimeStamp(timeStamp); + packageSetting.firstInstallTime = firstInstallTime; + packageSetting.lastUpdateTime = lastUpdateTime; + } + } else if (sharedIdStr != null) { + userId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0; + if (userId > 0) { + packageSetting = new PendingPackage(name.intern(), realName, new File( + codePathStr), new File(resourcePathStr), nativeLibraryPathStr, userId, + versionCode, pkgFlags); + packageSetting.setTimeStamp(timeStamp); + packageSetting.firstInstallTime = firstInstallTime; + packageSetting.lastUpdateTime = lastUpdateTime; + mPendingPackages.add((PendingPackage) packageSetting); + if (PackageManagerService.DEBUG_SETTINGS) + Log.i(PackageManagerService.TAG, "Reading package " + name + + ": sharedUserId=" + userId + " pkg=" + packageSetting); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: package " + name + + " has bad sharedId " + sharedIdStr + " at " + + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: package " + name + " has bad userId " + + idStr + " at " + parser.getPositionDescription()); + } + } catch (NumberFormatException e) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: package " + name + " has bad userId " + + idStr + " at " + parser.getPositionDescription()); + } + if (packageSetting != null) { + packageSetting.uidError = "true".equals(uidError); + packageSetting.installerPackageName = installerPackageName; + packageSetting.nativeLibraryPathString = nativeLibraryPathStr; + final String enabledStr = parser.getAttributeValue(null, "enabled"); + if (enabledStr != null) { + if (enabledStr.equalsIgnoreCase("true")) { + packageSetting.enabled = COMPONENT_ENABLED_STATE_ENABLED; + } else if (enabledStr.equalsIgnoreCase("false")) { + packageSetting.enabled = COMPONENT_ENABLED_STATE_DISABLED; + } else if (enabledStr.equalsIgnoreCase("default")) { + packageSetting.enabled = COMPONENT_ENABLED_STATE_DEFAULT; + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: package " + name + + " has bad enabled value: " + idStr + " at " + + parser.getPositionDescription()); + } + } else { + packageSetting.enabled = COMPONENT_ENABLED_STATE_DEFAULT; + } + final String installStatusStr = parser.getAttributeValue(null, "installStatus"); + if (installStatusStr != null) { + if (installStatusStr.equalsIgnoreCase("false")) { + packageSetting.installStatus = PackageSettingBase.PKG_INSTALL_INCOMPLETE; + } else { + packageSetting.installStatus = PackageSettingBase.PKG_INSTALL_COMPLETE; + } + } + + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("disabled-components")) { + readDisabledComponentsLPw(packageSetting, parser); + } else if (tagName.equals("enabled-components")) { + readEnabledComponentsLPw(packageSetting, parser); + } else if (tagName.equals("sigs")) { + packageSetting.signatures.readXml(parser, mPastSignatures); + } else if (tagName.equals("perms")) { + readGrantedPermissionsLPw(parser, packageSetting.grantedPermissions); + packageSetting.permissionsFixed = true; + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <package>: " + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + } else { + XmlUtils.skipCurrentTag(parser); + } + } + + private void readDisabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser) + throws IOException, XmlPullParserException { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("item")) { + String name = parser.getAttributeValue(null, "name"); + if (name != null) { + packageSetting.disabledComponents.add(name.intern()); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <disabled-components> has" + + " no name at " + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <disabled-components>: " + parser.getName()); + } + XmlUtils.skipCurrentTag(parser); + } + } + + private void readEnabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser) + throws IOException, XmlPullParserException { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("item")) { + String name = parser.getAttributeValue(null, "name"); + if (name != null) { + packageSetting.enabledComponents.add(name.intern()); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <enabled-components> has" + + " no name at " + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <enabled-components>: " + parser.getName()); + } + XmlUtils.skipCurrentTag(parser); + } + } + + private void readSharedUserLPw(XmlPullParser parser) throws XmlPullParserException, IOException { + String name = null; + String idStr = null; + int pkgFlags = 0; + SharedUserSetting su = null; + try { + name = parser.getAttributeValue(null, "name"); + idStr = parser.getAttributeValue(null, "userId"); + int userId = idStr != null ? Integer.parseInt(idStr) : 0; + if ("true".equals(parser.getAttributeValue(null, "system"))) { + pkgFlags |= ApplicationInfo.FLAG_SYSTEM; + } + if (name == null) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <shared-user> has no name at " + + parser.getPositionDescription()); + } else if (userId == 0) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: shared-user " + name + + " has bad userId " + idStr + " at " + + parser.getPositionDescription()); + } else { + if ((su = addSharedUserLPw(name.intern(), userId, pkgFlags)) == null) { + PackageManagerService + .reportSettingsProblem(Log.ERROR, "Occurred while parsing settings at " + + parser.getPositionDescription()); + } + } + } catch (NumberFormatException e) { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: package " + name + " has bad userId " + + idStr + " at " + parser.getPositionDescription()); + } + ; + + if (su != null) { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("sigs")) { + su.signatures.readXml(parser, mPastSignatures); + } else if (tagName.equals("perms")) { + readGrantedPermissionsLPw(parser, su.grantedPermissions); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <shared-user>: " + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + + } else { + XmlUtils.skipCurrentTag(parser); + } + } + + private void readGrantedPermissionsLPw(XmlPullParser parser, HashSet<String> outPerms) + throws IOException, XmlPullParserException { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("item")) { + String name = parser.getAttributeValue(null, "name"); + if (name != null) { + outPerms.add(name.intern()); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <perms> has" + " no name at " + + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <perms>: " + parser.getName()); + } + XmlUtils.skipCurrentTag(parser); + } + } + + private void readPreferredActivitiesLPw(XmlPullParser parser) throws XmlPullParserException, + IOException { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("item")) { + PreferredActivity pa = new PreferredActivity(parser); + if (pa.mPref.getParseError() == null) { + mPreferredActivities.addFilter(pa); + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Error in package manager settings: <preferred-activity> " + + pa.mPref.getParseError() + " at " + + parser.getPositionDescription()); + } + } else { + PackageManagerService.reportSettingsProblem(Log.WARN, + "Unknown element under <preferred-activities>: " + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + } + + // Returns -1 if we could not find an available UserId to assign + private int newUserIdLPw(Object obj) { + // Let's be stupidly inefficient for now... + final int N = mUserIds.size(); + for (int i = 0; i < N; i++) { + if (mUserIds.get(i) == null) { + mUserIds.set(i, obj); + return PackageManagerService.FIRST_APPLICATION_UID + i; + } + } + + // None left? + if (N >= PackageManagerService.MAX_APPLICATION_UIDS) { + return -1; + } + + mUserIds.add(obj); + return PackageManagerService.FIRST_APPLICATION_UID + N; + } + + public PackageSetting getDisabledSystemPkgLPr(String name) { + PackageSetting ps = mDisabledSysPackages.get(name); + return ps; + } + + boolean isEnabledLPr(ComponentInfo componentInfo, int flags) { + if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { + return true; + } + final PackageSetting packageSettings = mPackages.get(componentInfo.packageName); + if (PackageManagerService.DEBUG_SETTINGS) { + Log.v(PackageManagerService.TAG, "isEnabledLock - packageName = " + componentInfo.packageName + + " componentName = " + componentInfo.name); + Log.v(PackageManagerService.TAG, "enabledComponents: " + + Arrays.toString(packageSettings.enabledComponents.toArray())); + Log.v(PackageManagerService.TAG, "disabledComponents: " + + Arrays.toString(packageSettings.disabledComponents.toArray())); + } + if (packageSettings == null) { + return false; + } + if (packageSettings.enabled == COMPONENT_ENABLED_STATE_DISABLED + || (packageSettings.pkg != null && !packageSettings.pkg.applicationInfo.enabled + && packageSettings.enabled == COMPONENT_ENABLED_STATE_DEFAULT)) { + return false; + } + if (packageSettings.enabledComponents.contains(componentInfo.name)) { + return true; + } + if (packageSettings.disabledComponents.contains(componentInfo.name)) { + return false; + } + return componentInfo.enabled; + } + + String getInstallerPackageNameLPr(String packageName) { + final PackageSetting pkg = mPackages.get(packageName); + if (pkg == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + return pkg.installerPackageName; + } + + int getApplicationEnabledSettingLPr(String packageName) { + final PackageSetting pkg = mPackages.get(packageName); + if (pkg == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + return pkg.enabled; + } + + int getComponentEnabledSettingLPr(ComponentName componentName) { + final String packageName = componentName.getPackageName(); + final PackageSetting pkg = mPackages.get(packageName); + if (pkg == null) { + throw new IllegalArgumentException("Unknown component: " + componentName); + } + final String classNameStr = componentName.getClassName(); + return pkg.getCurrentEnabledStateLPr(classNameStr); + } + + boolean setPackageStoppedStateLPw(String packageName, boolean stopped, + boolean allowedByPermission, int uid) { + final PackageSetting pkgSetting = mPackages.get(packageName); + if (pkgSetting == null) { + throw new IllegalArgumentException("Unknown package: " + packageName); + } + if (!allowedByPermission && (uid != pkgSetting.userId)) { + throw new SecurityException( + "Permission Denial: attempt to change stopped state from pid=" + + Binder.getCallingPid() + + ", uid=" + uid + ", package uid=" + pkgSetting.userId); + } + if (DEBUG_STOPPED) { + if (stopped) { + RuntimeException e = new RuntimeException("here"); + e.fillInStackTrace(); + Slog.i(TAG, "Stopping package " + packageName, e); + } + } + if (pkgSetting.stopped != stopped) { + pkgSetting.stopped = stopped; + pkgSetting.pkg.mSetStopped = stopped; + if (pkgSetting.notLaunched) { + if (pkgSetting.installerPackageName != null) { + PackageManagerService.sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, + pkgSetting.name, null, + pkgSetting.installerPackageName, null); + } + pkgSetting.notLaunched = false; + } + return true; + } + return false; + } + + void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState) { + final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + final Date date = new Date(); + boolean printedSomething = false; + for (final PackageSetting ps : mPackages.values()) { + if (packageName != null && !packageName.equals(ps.realName) + && !packageName.equals(ps.name)) { + continue; + } + + if (packageName != null) { + dumpState.setSharedUser(ps.sharedUser); + } + + if (!printedSomething) { + if (dumpState.onTitlePrinted()) + pw.println(" "); + pw.println("Packages:"); + printedSomething = true; + } + pw.print(" Package ["); + pw.print(ps.realName != null ? ps.realName : ps.name); + pw.print("] ("); + pw.print(Integer.toHexString(System.identityHashCode(ps))); + pw.println("):"); + + if (ps.realName != null) { + pw.print(" compat name="); + pw.println(ps.name); + } + + pw.print(" userId="); pw.print(ps.userId); + pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids)); + pw.print(" sharedUser="); pw.println(ps.sharedUser); + pw.print(" pkg="); pw.println(ps.pkg); + pw.print(" codePath="); pw.println(ps.codePathString); + pw.print(" resourcePath="); pw.println(ps.resourcePathString); + pw.print(" nativeLibraryPath="); pw.println(ps.nativeLibraryPathString); + pw.print(" versionCode="); pw.println(ps.versionCode); + if (ps.pkg != null) { + pw.print(" versionName="); pw.println(ps.pkg.mVersionName); + pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir); + pw.print(" targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion); + if (ps.pkg.mOperationPending) { + pw.println(" mOperationPending=true"); + } + pw.print(" supportsScreens=["); + boolean first = true; + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("small"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("medium"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("large"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("xlarge"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("resizeable"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("anyDensity"); + } + } + pw.println("]"); + pw.print(" timeStamp="); + date.setTime(ps.timeStamp); + pw.println(sdf.format(date)); + pw.print(" firstInstallTime="); + date.setTime(ps.firstInstallTime); + pw.println(sdf.format(date)); + pw.print(" lastUpdateTime="); + date.setTime(ps.lastUpdateTime); + pw.println(sdf.format(date)); + if (ps.installerPackageName != null) { + pw.print(" installerPackageName="); pw.println(ps.installerPackageName); + } + pw.print(" signatures="); pw.println(ps.signatures); + pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed); + pw.print(" haveGids="); pw.println(ps.haveGids); + pw.print(" pkgFlags=0x"); pw.print(Integer.toHexString(ps.pkgFlags)); + pw.print(" installStatus="); pw.print(ps.installStatus); + pw.print(" stopped="); pw.print(ps.stopped); + pw.print(" enabled="); pw.println(ps.enabled); + if (ps.disabledComponents.size() > 0) { + pw.println(" disabledComponents:"); + for (String s : ps.disabledComponents) { + pw.print(" "); pw.println(s); + } + } + if (ps.enabledComponents.size() > 0) { + pw.println(" enabledComponents:"); + for (String s : ps.enabledComponents) { + pw.print(" "); pw.println(s); + } + } + if (ps.grantedPermissions.size() > 0) { + pw.println(" grantedPermissions:"); + for (String s : ps.grantedPermissions) { + pw.print(" "); pw.println(s); + } + } + } + + printedSomething = false; + if (mRenamedPackages.size() > 0) { + for (final HashMap.Entry<String, String> e : mRenamedPackages.entrySet()) { + if (packageName != null && !packageName.equals(e.getKey()) + && !packageName.equals(e.getValue())) { + continue; + } + if (!printedSomething) { + if (dumpState.onTitlePrinted()) + pw.println(" "); + pw.println("Renamed packages:"); + printedSomething = true; + } + pw.print(" "); + pw.print(e.getKey()); + pw.print(" -> "); + pw.println(e.getValue()); + } + } + + printedSomething = false; + if (mDisabledSysPackages.size() > 0) { + for (final PackageSetting ps : mDisabledSysPackages.values()) { + if (packageName != null && !packageName.equals(ps.realName) + && !packageName.equals(ps.name)) { + continue; + } + if (!printedSomething) { + if (dumpState.onTitlePrinted()) + pw.println(" "); + pw.println("Hidden system packages:"); + printedSomething = true; + } + pw.print(" Package ["); + pw.print(ps.realName != null ? ps.realName : ps.name); + pw.print("] ("); + pw.print(Integer.toHexString(System.identityHashCode(ps))); + pw.println("):"); + if (ps.realName != null) { + pw.print(" compat name="); + pw.println(ps.name); + } + pw.print(" userId="); + pw.println(ps.userId); + pw.print(" sharedUser="); + pw.println(ps.sharedUser); + pw.print(" codePath="); + pw.println(ps.codePathString); + pw.print(" resourcePath="); + pw.println(ps.resourcePathString); + } + } + } + + void dumpPermissionsLPr(PrintWriter pw, String packageName, DumpState dumpState) { + boolean printedSomething = false; + for (BasePermission p : mPermissions.values()) { + if (packageName != null && !packageName.equals(p.sourcePackage)) { + continue; + } + if (!printedSomething) { + if (dumpState.onTitlePrinted()) + pw.println(" "); + pw.println("Permissions:"); + printedSomething = true; + } + pw.print(" Permission ["); pw.print(p.name); pw.print("] ("); + pw.print(Integer.toHexString(System.identityHashCode(p))); + pw.println("):"); + pw.print(" sourcePackage="); pw.println(p.sourcePackage); + pw.print(" uid="); pw.print(p.uid); + pw.print(" gids="); pw.print(PackageManagerService.arrayToString(p.gids)); + pw.print(" type="); pw.print(p.type); + pw.print(" prot="); pw.println(p.protectionLevel); + if (p.packageSetting != null) { + pw.print(" packageSetting="); pw.println(p.packageSetting); + } + if (p.perm != null) { + pw.print(" perm="); pw.println(p.perm); + } + } + } + + void dumpSharedUsersLPr(PrintWriter pw, String packageName, DumpState dumpState) { + boolean printedSomething = false; + for (SharedUserSetting su : mSharedUsers.values()) { + if (packageName != null && su != dumpState.getSharedUser()) { + continue; + } + if (!printedSomething) { + if (dumpState.onTitlePrinted()) + pw.println(" "); + pw.println("Shared users:"); + printedSomething = true; + } + pw.print(" SharedUser ["); + pw.print(su.name); + pw.print("] ("); + pw.print(Integer.toHexString(System.identityHashCode(su))); + pw.println("):"); + pw.print(" userId="); + pw.print(su.userId); + pw.print(" gids="); + pw.println(PackageManagerService.arrayToString(su.gids)); + pw.println(" grantedPermissions:"); + for (String s : su.grantedPermissions) { + pw.print(" "); + pw.println(s); + } + } + } + + void dumpReadMessagesLPr(PrintWriter pw, DumpState dumpState) { + pw.println("Settings parse messages:"); + pw.print(mReadMessages.toString()); + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/pm/SharedUserSetting.java b/services/java/com/android/server/pm/SharedUserSetting.java new file mode 100644 index 0000000..76826ea --- /dev/null +++ b/services/java/com/android/server/pm/SharedUserSetting.java @@ -0,0 +1,43 @@ +/* + * 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.pm; + +import java.util.HashSet; + +/** + * Settings data for a particular shared user ID we know about. + */ +final class SharedUserSetting extends GrantedPermissions { + final String name; + + int userId; + + final HashSet<PackageSetting> packages = new HashSet<PackageSetting>(); + + final PackageSignatures signatures = new PackageSignatures(); + + SharedUserSetting(String _name, int _pkgFlags) { + super(_pkgFlags); + name = _name; + } + + @Override + public String toString() { + return "SharedUserSetting{" + Integer.toHexString(System.identityHashCode(this)) + " " + + name + "/" + userId + "}"; + } +} diff --git a/services/java/com/android/server/pm/UserManager.java b/services/java/com/android/server/pm/UserManager.java new file mode 100644 index 0000000..76fa5ab --- /dev/null +++ b/services/java/com/android/server/pm/UserManager.java @@ -0,0 +1,401 @@ +/* + * 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.pm; + +import com.android.internal.util.FastXmlSerializer; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.os.Environment; +import android.os.FileUtils; +import android.os.SystemClock; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import android.util.Xml; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +public class UserManager { + private static final String TAG_NAME = "name"; + + private static final String ATTR_FLAGS = "flags"; + + private static final String ATTR_ID = "id"; + + private static final String TAG_USERS = "users"; + + private static final String TAG_USER = "user"; + + private static final String LOG_TAG = "UserManager"; + + private static final String USER_INFO_DIR = "system" + File.separator + "users"; + private static final String USER_LIST_FILENAME = "userlist.xml"; + + private SparseArray<UserInfo> mUsers; + + private final File mUsersDir; + private final File mUserListFile; + private int[] mUserIds; + + private Installer mInstaller; + private File mBaseUserPath; + + /** + * Available for testing purposes. + */ + UserManager(File dataDir, File baseUserPath) { + mUsersDir = new File(dataDir, USER_INFO_DIR); + mUsersDir.mkdirs(); + mBaseUserPath = baseUserPath; + FileUtils.setPermissions(mUsersDir.toString(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG + |FileUtils.S_IROTH|FileUtils.S_IXOTH, + -1, -1); + mUserListFile = new File(mUsersDir, USER_LIST_FILENAME); + readUserList(); + } + + public UserManager(Installer installer, File baseUserPath) { + this(Environment.getDataDirectory(), baseUserPath); + mInstaller = installer; + } + + public List<UserInfo> getUsers() { + ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size()); + for (int i = 0; i < mUsers.size(); i++) { + users.add(mUsers.valueAt(i)); + } + return users; + } + + /** + * Returns an array of user ids. This array is cached here for quick access, so do not modify or + * cache it elsewhere. + * @return the array of user ids. + */ + int[] getUserIds() { + return mUserIds; + } + + private void readUserList() { + mUsers = new SparseArray<UserInfo>(); + if (!mUserListFile.exists()) { + fallbackToSingleUser(); + return; + } + FileInputStream fis = null; + try { + fis = new FileInputStream(mUserListFile); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + Slog.e(LOG_TAG, "Unable to read user list"); + fallbackToSingleUser(); + return; + } + + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { + String id = parser.getAttributeValue(null, ATTR_ID); + UserInfo user = readUser(Integer.parseInt(id)); + if (user != null) { + mUsers.put(user.id, user); + } + } + } + updateUserIds(); + } catch (IOException ioe) { + fallbackToSingleUser(); + } catch (XmlPullParserException pe) { + fallbackToSingleUser(); + } + } + + private void fallbackToSingleUser() { + // Create the primary user + UserInfo primary = new UserInfo(0, "Primary", + UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); + mUsers.put(0, primary); + updateUserIds(); + + writeUserList(); + writeUser(primary); + } + + /* + * Writes the user file in this format: + * + * <user flags="20039023" id="0"> + * <name>Primary</name> + * </user> + */ + private void writeUser(UserInfo userInfo) { + try { + final File mUserFile = new File(mUsersDir, userInfo.id + ".xml"); + final FileOutputStream fos = new FileOutputStream(mUserFile); + final BufferedOutputStream bos = new BufferedOutputStream(fos); + + // XmlSerializer serializer = XmlUtils.serializerInstance(); + final XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(bos, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + serializer.startTag(null, TAG_USER); + serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id)); + serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags)); + + serializer.startTag(null, TAG_NAME); + serializer.text(userInfo.name); + serializer.endTag(null, TAG_NAME); + + serializer.endTag(null, TAG_USER); + + serializer.endDocument(); + } catch (IOException ioe) { + Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe); + } + } + + /* + * Writes the user list file in this format: + * + * <users> + * <user id="0"></user> + * <user id="2"></user> + * </users> + */ + private void writeUserList() { + try { + final FileOutputStream fos = new FileOutputStream(mUserListFile); + final BufferedOutputStream bos = new BufferedOutputStream(fos); + + // XmlSerializer serializer = XmlUtils.serializerInstance(); + final XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(bos, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + serializer.startTag(null, TAG_USERS); + + for (int i = 0; i < mUsers.size(); i++) { + UserInfo user = mUsers.valueAt(i); + serializer.startTag(null, TAG_USER); + serializer.attribute(null, ATTR_ID, Integer.toString(user.id)); + serializer.endTag(null, TAG_USER); + } + + serializer.endTag(null, TAG_USERS); + + serializer.endDocument(); + } catch (IOException ioe) { + Slog.e(LOG_TAG, "Error writing user list"); + } + } + + private UserInfo readUser(int id) { + int flags = 0; + String name = null; + + FileInputStream fis = null; + try { + File userFile = new File(mUsersDir, Integer.toString(id) + ".xml"); + fis = new FileInputStream(userFile); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + ; + } + + if (type != XmlPullParser.START_TAG) { + Slog.e(LOG_TAG, "Unable to read user " + id); + return null; + } + + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) { + String storedId = parser.getAttributeValue(null, ATTR_ID); + if (Integer.parseInt(storedId) != id) { + Slog.e(LOG_TAG, "User id does not match the file name"); + return null; + } + String flagString = parser.getAttributeValue(null, ATTR_FLAGS); + flags = Integer.parseInt(flagString); + + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + } + if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) { + type = parser.next(); + if (type == XmlPullParser.TEXT) { + name = parser.getText(); + } + } + } + fis.close(); + + UserInfo userInfo = new UserInfo(id, name, flags); + return userInfo; + + } catch (IOException ioe) { + } catch (XmlPullParserException pe) { + } + return null; + } + + public UserInfo createUser(String name, int flags, List<ApplicationInfo> apps) { + int userId = getNextAvailableId(); + UserInfo userInfo = new UserInfo(userId, name, flags); + File userPath = new File(mBaseUserPath, Integer.toString(userId)); + if (!createPackageFolders(userId, userPath, apps)) { + return null; + } + mUsers.put(userId, userInfo); + writeUserList(); + writeUser(userInfo); + updateUserIds(); + return userInfo; + } + + /** + * Removes a user and all data directories created for that user. This method should be called + * after the user's processes have been terminated. + * @param id the user's id + */ + public void removeUser(int id) { + // Remove from the list + UserInfo userInfo = mUsers.get(id); + if (userInfo != null) { + // Remove this user from the list + mUsers.remove(id); + // Remove user file + File userFile = new File(mUsersDir, id + ".xml"); + userFile.delete(); + // Update the user list + writeUserList(); + // Remove the data directories for all packages for this user + removePackageFolders(id); + updateUserIds(); + } + } + + public void installPackageForAllUsers(String packageName, int uid) { + for (int userId : mUserIds) { + // Don't do it for the primary user, it will become recursive. + if (userId == 0) + continue; + mInstaller.createUserData(packageName, PackageManager.getUid(userId, uid), + userId); + } + } + + public void clearUserDataForAllUsers(String packageName) { + for (int userId : mUserIds) { + // Don't do it for the primary user, it will become recursive. + if (userId == 0) + continue; + mInstaller.clearUserData(packageName, userId); + } + } + + public void removePackageForAllUsers(String packageName) { + for (int userId : mUserIds) { + // Don't do it for the primary user, it will become recursive. + if (userId == 0) + continue; + mInstaller.remove(packageName, userId); + } + } + + /** + * Caches the list of user ids in an array, adjusting the array size when necessary. + */ + private void updateUserIds() { + if (mUserIds == null || mUserIds.length != mUsers.size()) { + mUserIds = new int[mUsers.size()]; + } + for (int i = 0; i < mUsers.size(); i++) { + mUserIds[i] = mUsers.keyAt(i); + } + } + + /** + * Returns the next available user id, filling in any holes in the ids. + * @return + */ + private int getNextAvailableId() { + int i = 0; + while (i < Integer.MAX_VALUE) { + if (mUsers.indexOfKey(i) < 0) { + break; + } + i++; + } + return i; + } + + private boolean createPackageFolders(int id, File userPath, final List<ApplicationInfo> apps) { + // mInstaller may not be available for unit-tests. + if (mInstaller == null || apps == null) return true; + + final long startTime = SystemClock.elapsedRealtime(); + // Create the user path + userPath.mkdir(); + 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); + } + } + final long stopTime = SystemClock.elapsedRealtime(); + Log.i(LOG_TAG, + "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms"); + return true; + } + + private boolean removePackageFolders(int id) { + // mInstaller may not be available for unit-tests. + if (mInstaller == null) return true; + + mInstaller.removeUserDataDirs(id); + return true; + } +} diff --git a/services/java/com/android/server/wm/DimSurface.java b/services/java/com/android/server/wm/DimSurface.java index 084ac6f..220c7fb 100644 --- a/services/java/com/android/server/wm/DimSurface.java +++ b/services/java/com/android/server/wm/DimSurface.java @@ -89,7 +89,7 @@ class DimSurface { public void printTo(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("mDimSurface="); pw.println(mDimSurface); pw.print(prefix); pw.print("mDimShown="); pw.print(mDimShown); - pw.print(" mLayer="); pw.println(mLayer); + pw.print(" mLayer="); pw.print(mLayer); pw.print(" mDimColor=0x"); pw.println(Integer.toHexString(mDimColor)); pw.print(prefix); pw.print("mLastDimWidth="); pw.print(mLastDimWidth); pw.print(" mLastDimWidth="); pw.println(mLastDimWidth); diff --git a/services/java/com/android/server/wm/InputFilter.java b/services/java/com/android/server/wm/InputFilter.java new file mode 100644 index 0000000..8f0001a --- /dev/null +++ b/services/java/com/android/server/wm/InputFilter.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.view.InputEvent; +import android.view.InputEventConsistencyVerifier; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.WindowManagerPolicy; + +/** + * Filters input events before they are dispatched to the system. + * <p> + * At most one input filter can be installed by calling + * {@link WindowManagerService#setInputFilter}. When an input filter is installed, the + * system's behavior changes as follows: + * <ul> + * <li>Input events are first delivered to the {@link WindowManagerPolicy} + * interception methods before queueing as usual. This critical step takes care of managing + * the power state of the device and handling wake keys.</li> + * <li>Input events are then asynchronously delivered to the input filter's + * {@link #onInputEvent(InputEvent)} method instead of being enqueued for dispatch to + * applications as usual. The input filter only receives input events that were + * generated by input device; the input filter will not receive input events that were + * injected into the system by other means, such as by instrumentation.</li> + * <li>The input filter processes and optionally transforms the stream of events. For example, + * it may transform a sequence of motion events representing an accessibility gesture into + * a different sequence of motion events, key presses or other system-level interactions. + * The input filter can send events to be dispatched by calling + * {@link #sendInputEvent(InputEvent)} and passing appropriate policy flags for the + * input event.</li> + * </ul> + * </p> + * <h3>The importance of input event consistency</h3> + * <p> + * The input filter mechanism is very low-level. At a minimum, it needs to ensure that it + * sends an internally consistent stream of input events to the dispatcher. There are + * very important invariants to be maintained. + * </p><p> + * For example, if a key down is sent, a corresponding key up should also be sent eventually. + * Likewise, for touch events, each pointer must individually go down with + * {@link MotionEvent#ACTION_DOWN} or {@link MotionEvent#ACTION_POINTER_DOWN} and then + * individually go up with {@link MotionEvent#ACTION_POINTER_UP} or {@link MotionEvent#ACTION_UP} + * and the sequence of pointer ids used must be consistent throughout the gesture. + * </p><p> + * Sometimes a filter may wish to cancel a previously dispatched key or motion. It should + * use {@link KeyEvent#FLAG_CANCELED} or {@link MotionEvent#ACTION_CANCEL} accordingly. + * </p><p> + * The input filter must take into account the fact that the input events coming from different + * devices or even different sources all consist of distinct streams of input. + * Use {@link InputEvent#getDeviceId()} and {@link InputEvent#getSource()} to identify + * the source of the event and its semantics. There are be multiple sources of keys, + * touches and other input: they must be kept separate. + * </p> + * <h3>Policy flags</h3> + * <p> + * Input events received from the dispatcher and sent to the dispatcher have policy flags + * associated with them. Policy flags control some functions of the dispatcher. + * </p><p> + * The early policy interception decides whether an input event should be delivered + * to applications or dropped. The policy indicates its decision by setting the + * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} policy flag. The input filter may + * sometimes receive events that do not have this flag set. It should take note of + * the fact that the policy intends to drop the event, clean up its state, and + * then send appropriate cancelation events to the dispatcher if needed. + * </p><p> + * For example, suppose the input filter is processing a gesture and one of the touch events + * it receives does not have the {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag set. + * The input filter should clear its internal state about the gesture and then send key or + * motion events to the dispatcher to cancel any keys or pointers that are down. + * </p><p> + * Corollary: Events that set sent to the dispatcher should usually include the + * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag. Otherwise, they will be dropped! + * </p><p> + * It may be prudent to disable automatic key repeating for synthetically generated + * keys by setting the {@link WindowManagerPolicy#FLAG_DISABLE_KEY_REPEAT} policy flag. + * </p> + */ +public abstract class InputFilter { + private static final int MSG_INSTALL = 1; + private static final int MSG_UNINSTALL = 2; + private static final int MSG_INPUT_EVENT = 3; + + private final H mH; + private Host mHost; + + // Consistency verifiers for debugging purposes. + private final InputEventConsistencyVerifier mInboundInputEventConsistencyVerifier = + InputEventConsistencyVerifier.isInstrumentationEnabled() ? + new InputEventConsistencyVerifier(this, + InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT, + "InputFilter#InboundInputEventConsistencyVerifier") : null; + private final InputEventConsistencyVerifier mOutboundInputEventConsistencyVerifier = + InputEventConsistencyVerifier.isInstrumentationEnabled() ? + new InputEventConsistencyVerifier(this, + InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT, + "InputFilter#OutboundInputEventConsistencyVerifier") : null; + + /** + * Creates the input filter. + * + * @param looper The looper to run callbacks on. + */ + public InputFilter(Looper looper) { + mH = new H(looper); + } + + /** + * Called when the input filter is installed. + * This method is guaranteed to be non-reentrant. + * + * @param host The input filter host environment. + */ + final void install(Host host) { + mH.obtainMessage(MSG_INSTALL, host).sendToTarget(); + } + + /** + * Called when the input filter is uninstalled. + * This method is guaranteed to be non-reentrant. + */ + final void uninstall() { + mH.obtainMessage(MSG_UNINSTALL).sendToTarget(); + } + + /** + * Called to enqueue the input event for filtering. + * The event will be recycled after the input filter processes it. + * This method is guaranteed to be non-reentrant. + * + * @param event The input event to enqueue. + */ + final void filterInputEvent(InputEvent event, int policyFlags) { + mH.obtainMessage(MSG_INPUT_EVENT, policyFlags, 0, event).sendToTarget(); + } + + /** + * Sends an input event to the dispatcher. + * + * @param event The input event to publish. + * @param policyFlags The input event policy flags. + */ + public void sendInputEvent(InputEvent event, int policyFlags) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + if (mHost == null) { + throw new IllegalStateException("Cannot send input event because the input filter " + + "is not installed."); + } + if (mOutboundInputEventConsistencyVerifier != null) { + mOutboundInputEventConsistencyVerifier.onInputEvent(event, 0); + } + mHost.sendInputEvent(event, policyFlags); + } + + /** + * Called when an input event has been received from the dispatcher. + * <p> + * The default implementation sends the input event back to the dispatcher, unchanged. + * </p><p> + * The event will be recycled when this method returns. If you want to keep it around, + * make a copy! + * </p> + * + * @param event The input event that was received. + * @param policyFlags The input event policy flags. + */ + public void onInputEvent(InputEvent event, int policyFlags) { + sendInputEvent(event, policyFlags); + } + + /** + * Called when the filter is installed into the dispatch pipeline. + * <p> + * This method is called before the input filter receives any input events. + * The input filter should take this opportunity to prepare itself. + * </p> + */ + public void onInstalled() { + } + + /** + * Called when the filter is uninstalled from the dispatch pipeline. + * <p> + * This method is called after the input filter receives its last input event. + * The input filter should take this opportunity to clean up. + * </p> + */ + public void onUninstalled() { + } + + private final class H extends Handler { + public H(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_INSTALL: + mHost = (Host)msg.obj; + if (mInboundInputEventConsistencyVerifier != null) { + mInboundInputEventConsistencyVerifier.reset(); + } + if (mOutboundInputEventConsistencyVerifier != null) { + mOutboundInputEventConsistencyVerifier.reset(); + } + onInstalled(); + break; + + case MSG_UNINSTALL: + try { + onUninstalled(); + } finally { + mHost = null; + } + break; + + case MSG_INPUT_EVENT: { + final InputEvent event = (InputEvent)msg.obj; + try { + if (mInboundInputEventConsistencyVerifier != null) { + mInboundInputEventConsistencyVerifier.onInputEvent(event, 0); + } + onInputEvent(event, msg.arg1); + } finally { + event.recycle(); + } + break; + } + } + } + } + + interface Host { + public void sendInputEvent(InputEvent event, int policyFlags); + } +} diff --git a/services/java/com/android/server/wm/InputManager.java b/services/java/com/android/server/wm/InputManager.java index ca1da95..69bde41 100644 --- a/services/java/com/android/server/wm/InputManager.java +++ b/services/java/com/android/server/wm/InputManager.java @@ -23,12 +23,6 @@ import org.xmlpull.v1.XmlPullParser; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.content.res.XmlResourceParser; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; import android.os.Environment; import android.os.Looper; import android.os.MessageQueue; @@ -39,9 +33,11 @@ import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; import android.view.KeyEvent; +import android.view.PointerIcon; import android.view.Surface; import android.view.ViewConfiguration; import android.view.WindowManager; +import android.view.WindowManagerPolicy; import java.io.File; import java.io.FileNotFoundException; @@ -62,7 +58,8 @@ public class InputManager { private final Context mContext; private final WindowManagerService mWindowManagerService; - private static native void nativeInit(Callbacks callbacks, MessageQueue messageQueue); + private static native void nativeInit(Context context, + Callbacks callbacks, MessageQueue messageQueue); private static native void nativeStart(); private static native void nativeSetDisplaySize(int displayId, int width, int height); private static native void nativeSetDisplayOrientation(int displayId, int rotation); @@ -78,8 +75,10 @@ public class InputManager { private static native void nativeRegisterInputChannel(InputChannel inputChannel, InputWindowHandle inputWindowHandle, boolean monitor); private static native void nativeUnregisterInputChannel(InputChannel inputChannel); + private static native void nativeSetInputFilterEnabled(boolean enable); private static native int nativeInjectInputEvent(InputEvent event, - int injectorPid, int injectorUid, int syncMode, int timeoutMillis); + int injectorPid, int injectorUid, int syncMode, int timeoutMillis, + int policyFlags); private static native void nativeSetInputWindows(InputWindow[] windows); private static native void nativeSetInputDispatchMode(boolean enabled, boolean frozen); private static native void nativeSetSystemUiVisibility(int visibility); @@ -117,6 +116,11 @@ public class InputManager { /** The key is down but is a virtual key press that is being emulated by the system. */ public static final int KEY_STATE_VIRTUAL = 2; + // State for the currently installed input filter. + final Object mInputFilterLock = new Object(); + InputFilter mInputFilter; + InputFilterHost mInputFilterHost; + public InputManager(Context context, WindowManagerService windowManagerService) { this.mContext = context; this.mWindowManagerService = windowManagerService; @@ -125,7 +129,7 @@ public class InputManager { Looper looper = windowManagerService.mH.getLooper(); Slog.i(TAG, "Initializing input manager"); - nativeInit(mCallbacks, looper.getQueue()); + nativeInit(mContext, mCallbacks, looper.getQueue()); } public void start() { @@ -268,7 +272,42 @@ public class InputManager { nativeUnregisterInputChannel(inputChannel); } - + + /** + * Sets an input filter that will receive all input events before they are dispatched. + * The input filter may then reinterpret input events or inject new ones. + * + * To ensure consistency, the input dispatcher automatically drops all events + * in progress whenever an input filter is installed or uninstalled. After an input + * filter is uninstalled, it can no longer send input events unless it is reinstalled. + * Any events it attempts to send after it has been uninstalled will be dropped. + * + * @param filter The input filter, or null to remove the current filter. + */ + public void setInputFilter(InputFilter filter) { + synchronized (mInputFilterLock) { + final InputFilter oldFilter = mInputFilter; + if (oldFilter == filter) { + return; // nothing to do + } + + if (oldFilter != null) { + mInputFilter = null; + mInputFilterHost.disconnectLocked(); + mInputFilterHost = null; + oldFilter.uninstall(); + } + + if (filter != null) { + mInputFilter = filter; + mInputFilterHost = new InputFilterHost(); + filter.install(mInputFilterHost); + } + + nativeSetInputFilterEnabled(filter != null); + } + } + /** * Injects an input event into the event system on behalf of an application. * The synchronization mode determines whether the method blocks while waiting for @@ -304,9 +343,10 @@ public class InputManager { throw new IllegalArgumentException("timeoutMillis must be positive"); } - return nativeInjectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis); + return nativeInjectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis, + WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT); } - + /** * Gets information about the input device with the specified id. * @param id The device id. @@ -370,52 +410,31 @@ public class InputManager { } } - private static final class PointerIcon { - public Bitmap bitmap; - public float hotSpotX; - public float hotSpotY; - - public static PointerIcon load(Resources resources, int resourceId) { - PointerIcon icon = new PointerIcon(); + private final class InputFilterHost implements InputFilter.Host { + private boolean mDisconnected; - XmlResourceParser parser = resources.getXml(resourceId); - final int bitmapRes; - try { - XmlUtils.beginDocument(parser, "pointer-icon"); - - TypedArray a = resources.obtainAttributes( - parser, com.android.internal.R.styleable.PointerIcon); - bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0); - icon.hotSpotX = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0); - icon.hotSpotY = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0); - a.recycle(); - } catch (Exception ex) { - Slog.e(TAG, "Exception parsing pointer icon resource.", ex); - return null; - } finally { - parser.close(); - } + public void disconnectLocked() { + mDisconnected = true; + } - if (bitmapRes == 0) { - Slog.e(TAG, "<pointer-icon> is missing bitmap attribute"); - return null; + public void sendInputEvent(InputEvent event, int policyFlags) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); } - Drawable drawable = resources.getDrawable(bitmapRes); - if (!(drawable instanceof BitmapDrawable)) { - Slog.e(TAG, "<pointer-icon> bitmap attribute must refer to a bitmap drawable"); - return null; + synchronized (mInputFilterLock) { + if (!mDisconnected) { + nativeInjectInputEvent(event, 0, 0, INPUT_EVENT_INJECTION_SYNC_NONE, 0, + policyFlags | WindowManagerPolicy.FLAG_FILTERED); + } } - - icon.bitmap = ((BitmapDrawable)drawable).getBitmap(); - return icon; } } /* * Callbacks from native. */ - private class Callbacks { + private final class Callbacks { static final String TAG = "InputManager-Callbacks"; private static final boolean DEBUG_VIRTUAL_KEYS = false; @@ -443,7 +462,19 @@ public class InputManager { return mWindowManagerService.mInputMonitor.notifyANR( inputApplicationHandle, inputWindowHandle); } - + + @SuppressWarnings("unused") + final boolean filterInputEvent(InputEvent event, int policyFlags) { + synchronized (mInputFilterLock) { + if (mInputFilter != null) { + mInputFilter.filterInputEvent(event, policyFlags); + return false; + } + } + event.recycle(); + return true; + } + @SuppressWarnings("unused") public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) { return mWindowManagerService.mInputMonitor.interceptKeyBeforeQueueing( @@ -564,8 +595,7 @@ public class InputManager { @SuppressWarnings("unused") public PointerIcon getPointerIcon() { - return PointerIcon.load(mContext.getResources(), - com.android.internal.R.drawable.pointer_arrow_icon); + return PointerIcon.getDefaultIcon(mContext); } } } diff --git a/services/java/com/android/server/wm/InputMonitor.java b/services/java/com/android/server/wm/InputMonitor.java index 57f0799..a3e8be0 100644 --- a/services/java/com/android/server/wm/InputMonitor.java +++ b/services/java/com/android/server/wm/InputMonitor.java @@ -132,8 +132,8 @@ final class InputMonitor { // The drag window covers the entire display inputWindow.frameLeft = 0; inputWindow.frameTop = 0; - inputWindow.frameRight = mService.mDisplay.getWidth(); - inputWindow.frameBottom = mService.mDisplay.getHeight(); + inputWindow.frameRight = mService.mDisplay.getRealWidth(); + inputWindow.frameBottom = mService.mDisplay.getRealHeight(); // The drag window cannot receive new touches. inputWindow.touchableRegion.setEmpty(); diff --git a/services/java/com/android/server/wm/ViewServer.java b/services/java/com/android/server/wm/ViewServer.java index cebd5e7..9fb35b9 100644 --- a/services/java/com/android/server/wm/ViewServer.java +++ b/services/java/com/android/server/wm/ViewServer.java @@ -49,7 +49,7 @@ class ViewServer implements Runnable { // Debug facility private static final String LOG_TAG = "ViewServer"; - private static final String VALUE_PROTOCOL_VERSION = "3"; + private static final String VALUE_PROTOCOL_VERSION = "4"; private static final String VALUE_SERVER_VERSION = "4"; // Protocol commands diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 769e423..12a5000 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -63,6 +63,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.os.BatteryStats; @@ -391,9 +392,12 @@ public class WindowManagerService extends IWindowManager.Stub boolean mSystemBooted = false; int mInitialDisplayWidth = 0; int mInitialDisplayHeight = 0; + int mCurDisplayWidth = 0; + int mCurDisplayHeight = 0; int mRotation = 0; int mRequestedRotation = 0; int mForcedAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + boolean mAltOrientation = false; int mLastRotationFlags; ArrayList<IRotationWatcher> mRotationWatchers = new ArrayList<IRotationWatcher>(); @@ -581,7 +585,6 @@ public class WindowManagerService extends IWindowManager.Stub } final Configuration mTempConfiguration = new Configuration(); - int mScreenLayout = Configuration.SCREENLAYOUT_SIZE_UNDEFINED; // The frame use to limit the size of the app running in compatibility mode. Rect mCompatibleScreenFrame = new Rect(); @@ -1419,8 +1422,8 @@ public class WindowManagerService extends IWindowManager.Stub int adjustWallpaperWindowsLocked() { int changed = 0; - final int dw = mDisplay.getWidth(); - final int dh = mDisplay.getHeight(); + final int dw = mCurDisplayWidth; + final int dh = mCurDisplayHeight; // First find top-most window that has asked to be on top of the // wallpaper; all wallpapers go behind it. @@ -1838,8 +1841,8 @@ public class WindowManagerService extends IWindowManager.Stub } boolean updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) { - final int dw = mDisplay.getWidth(); - final int dh = mDisplay.getHeight(); + final int dw = mCurDisplayWidth; + final int dh = mCurDisplayHeight; boolean changed = false; @@ -1879,8 +1882,8 @@ public class WindowManagerService extends IWindowManager.Stub void updateWallpaperVisibilityLocked() { final boolean visible = isWallpaperVisible(mWallpaperTarget); - final int dw = mDisplay.getWidth(); - final int dh = mDisplay.getHeight(); + final int dw = mCurDisplayWidth; + final int dh = mCurDisplayHeight; int curTokenIndex = mWallpaperTokens.size(); while (curTokenIndex > 0) { @@ -2682,8 +2685,7 @@ public class WindowManagerService extends IWindowManager.Stub configChanged = updateOrientationFromAppTokensLocked(false); performLayoutAndPlaceSurfacesLocked(); if (displayed && win.mIsWallpaper) { - updateWallpaperOffsetLocked(win, mDisplay.getWidth(), - mDisplay.getHeight(), false); + updateWallpaperOffsetLocked(win, mCurDisplayWidth, mCurDisplayHeight, false); } if (win.mAppToken != null) { win.mAppToken.updateReportedVisibilityLocked(); @@ -4606,6 +4608,10 @@ public class WindowManagerService extends IWindowManager.Stub return mInputManager.monitorInput(inputChannelName); } + public void setInputFilter(InputFilter filter) { + mInputManager.setInputFilter(filter); + } + public InputDevice getInputDevice(int deviceId) { return mInputManager.getInputDevice(deviceId); } @@ -4755,8 +4761,8 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { long ident = Binder.clearCallingIdentity(); - dw = mPolicy.getNonDecorDisplayWidth(mDisplay.getWidth()); - dh = mPolicy.getNonDecorDisplayHeight(mDisplay.getHeight()); + dw = mPolicy.getNonDecorDisplayWidth(mCurDisplayWidth); + dh = mPolicy.getNonDecorDisplayHeight(mCurDisplayHeight); int aboveAppLayer = mPolicy.windowTypeToLayerLw( WindowManager.LayoutParams.TYPE_APPLICATION) * TYPE_LAYER_MULTIPLIER @@ -4950,7 +4956,52 @@ public class WindowManagerService extends IWindowManager.Stub rotation = mPolicy.rotationForOrientationLw(mForcedAppOrientation, mRotation, mDisplayEnabled); if (DEBUG_ORIENTATION) Slog.v(TAG, "new rotation is set to " + rotation); + + int desiredRotation = rotation; + int lockedRotation = mPolicy.getLockedRotationLw(); + if (lockedRotation >= 0 && rotation != lockedRotation) { + // We are locked in a rotation but something is requesting + // a different rotation... we will either keep the locked + // rotation if it results in the same orientation, or have to + // switch into an emulated orientation mode. + + // First, we know that our rotation is actually going to be + // the locked rotation. + rotation = lockedRotation; + + // Now the difference between the desired and lockedRotation + // may mean that the orientation is different... if that is + // not the case, we can just make the desired rotation be the + // same as the new locked rotation. + switch (lockedRotation) { + case Surface.ROTATION_0: + if (rotation == Surface.ROTATION_180) { + desiredRotation = lockedRotation; + } + break; + case Surface.ROTATION_90: + if (rotation == Surface.ROTATION_270) { + desiredRotation = lockedRotation; + } + break; + case Surface.ROTATION_180: + if (rotation == Surface.ROTATION_0) { + desiredRotation = lockedRotation; + } + break; + case Surface.ROTATION_270: + if (rotation == Surface.ROTATION_90) { + desiredRotation = lockedRotation; + } + break; + } + } + changed = mDisplayEnabled && mRotation != rotation; + if (mAltOrientation != (rotation != desiredRotation)) { + changed = true; + mAltOrientation = rotation != desiredRotation; + } if (changed) { if (DEBUG_ORIENTATION) Slog.v(TAG, @@ -4996,6 +5047,7 @@ public class WindowManagerService extends IWindowManager.Stub Surface.setOrientation(0, rotation, animFlags); } } + for (int i=mWindows.size()-1; i>=0; i--) { WindowState w = mWindows.get(i); if (w.mSurface != null) { @@ -5436,8 +5488,32 @@ public class WindowManagerService extends IWindowManager.Stub // Use the effective "visual" dimensions based on current rotation final boolean rotated = (mRotation == Surface.ROTATION_90 || mRotation == Surface.ROTATION_270); - final int dw = rotated ? mInitialDisplayHeight : mInitialDisplayWidth; - final int dh = rotated ? mInitialDisplayWidth : mInitialDisplayHeight; + final int realdw = rotated ? mInitialDisplayHeight : mInitialDisplayWidth; + final int realdh = rotated ? mInitialDisplayWidth : mInitialDisplayHeight; + + if (mAltOrientation) { + mCurDisplayWidth = realdw; + mCurDisplayHeight = realdh; + if (realdw > realdh) { + // Turn landscape into portrait. + int maxw = (int)(realdh/1.3f); + if (maxw < realdw) { + mCurDisplayWidth = maxw; + } + } else { + // Turn portrait into landscape. + int maxh = (int)(realdw/1.3f); + if (maxh < realdh) { + mCurDisplayHeight = maxh; + } + } + } else { + mCurDisplayWidth = realdw; + mCurDisplayHeight = realdh; + } + + final int dw = mCurDisplayWidth; + final int dh = mCurDisplayHeight; int orientation = Configuration.ORIENTATION_SQUARE; if (dw < dh) { @@ -5448,66 +5524,70 @@ public class WindowManagerService extends IWindowManager.Stub config.orientation = orientation; DisplayMetrics dm = new DisplayMetrics(); - mDisplay.getMetrics(dm); - dm.realWidthPixels = mPolicy.getNonDecorDisplayWidth(dm.realWidthPixels); - dm.realHeightPixels = mPolicy.getNonDecorDisplayHeight(dm.realHeightPixels); + mDisplay.getRealMetrics(dm); + + // Override display width and height with what we are computing, + // to be sure they remain consistent. + dm.widthPixels = mPolicy.getNonDecorDisplayWidth(dw); + dm.heightPixels = mPolicy.getNonDecorDisplayHeight(dh); + mCompatibleScreenScale = CompatibilityInfo.updateCompatibleScreenFrame( dm, mCompatibleScreenFrame, null); - if (mScreenLayout == Configuration.SCREENLAYOUT_SIZE_UNDEFINED) { - // Note we only do this once because at this point we don't - // expect the screen to change in this way at runtime, and want - // to avoid all of this computation for every config change. - int longSize = dw; - int shortSize = dh; - if (longSize < shortSize) { - int tmp = longSize; - longSize = shortSize; - shortSize = tmp; - } - longSize = (int)(longSize/dm.density); - shortSize = (int)(shortSize/dm.density); - - // These semi-magic numbers define our compatibility modes for - // applications with different screens. These are guarantees to - // app developers about the space they can expect for a particular - // configuration. DO NOT CHANGE! - if (longSize < 470) { - // This is shorter than an HVGA normal density screen (which - // is 480 pixels on its long side). - mScreenLayout = Configuration.SCREENLAYOUT_SIZE_SMALL - | Configuration.SCREENLAYOUT_LONG_NO; + config.screenWidthDp = (int)(dm.widthPixels / dm.density); + config.screenHeightDp = (int)(dm.heightPixels / dm.density); + + // Compute the screen layout size class. + int screenLayout; + int longSize = dw; + int shortSize = dh; + if (longSize < shortSize) { + int tmp = longSize; + longSize = shortSize; + shortSize = tmp; + } + longSize = (int)(longSize/dm.density); + shortSize = (int)(shortSize/dm.density); + + // These semi-magic numbers define our compatibility modes for + // applications with different screens. These are guarantees to + // app developers about the space they can expect for a particular + // configuration. DO NOT CHANGE! + if (longSize < 470) { + // This is shorter than an HVGA normal density screen (which + // is 480 pixels on its long side). + screenLayout = Configuration.SCREENLAYOUT_SIZE_SMALL + | Configuration.SCREENLAYOUT_LONG_NO; + } else { + // What size is this screen screen? + if (longSize >= 960 && shortSize >= 720) { + // 1.5xVGA or larger screens at medium density are the point + // at which we consider it to be an extra large screen. + screenLayout = Configuration.SCREENLAYOUT_SIZE_XLARGE; + } else if (longSize >= 640 && shortSize >= 480) { + // VGA or larger screens at medium density are the point + // at which we consider it to be a large screen. + screenLayout = Configuration.SCREENLAYOUT_SIZE_LARGE; } else { - // What size is this screen screen? - if (longSize >= 960 && shortSize >= 720) { - // 1.5xVGA or larger screens at medium density are the point - // at which we consider it to be an extra large screen. - mScreenLayout = Configuration.SCREENLAYOUT_SIZE_XLARGE; - } else if (longSize >= 640 && shortSize >= 480) { - // VGA or larger screens at medium density are the point - // at which we consider it to be a large screen. - mScreenLayout = Configuration.SCREENLAYOUT_SIZE_LARGE; - } else { - mScreenLayout = Configuration.SCREENLAYOUT_SIZE_NORMAL; - } - - // If this screen is wider than normal HVGA, or taller - // than FWVGA, then for old apps we want to run in size - // compatibility mode. - if (shortSize > 321 || longSize > 570) { - mScreenLayout |= Configuration.SCREENLAYOUT_COMPAT_NEEDED; - } + screenLayout = Configuration.SCREENLAYOUT_SIZE_NORMAL; + } - // Is this a long screen? - if (((longSize*3)/5) >= (shortSize-1)) { - // Anything wider than WVGA (5:3) is considering to be long. - mScreenLayout |= Configuration.SCREENLAYOUT_LONG_YES; - } else { - mScreenLayout |= Configuration.SCREENLAYOUT_LONG_NO; - } + // If this screen is wider than normal HVGA, or taller + // than FWVGA, then for old apps we want to run in size + // compatibility mode. + if (shortSize > 321 || longSize > 570) { + screenLayout |= Configuration.SCREENLAYOUT_COMPAT_NEEDED; + } + + // Is this a long screen? + if (((longSize*3)/5) >= (shortSize-1)) { + // Anything wider than WVGA (5:3) is considering to be long. + screenLayout |= Configuration.SCREENLAYOUT_LONG_YES; + } else { + screenLayout |= Configuration.SCREENLAYOUT_LONG_NO; } } - config.screenLayout = mScreenLayout; + config.screenLayout = screenLayout; // Determine whether a hard keyboard is available and enabled. boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS; @@ -5846,9 +5926,10 @@ public class WindowManagerService extends IWindowManager.Stub } WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); mDisplay = wm.getDefaultDisplay(); - mInitialDisplayWidth = mDisplay.getWidth(); - mInitialDisplayHeight = mDisplay.getHeight(); - mInputManager.setDisplaySize(0, mDisplay.getRealWidth(), mDisplay.getRealHeight()); + mInitialDisplayWidth = mCurDisplayWidth = mDisplay.getRealWidth(); + mInitialDisplayHeight = mCurDisplayHeight = mDisplay.getRealHeight(); + mInputManager.setDisplaySize(0, mDisplay.getRawWidth(), mDisplay.getRawHeight()); + mPolicy.setInitialDisplaySize(mInitialDisplayWidth, mInitialDisplayHeight); } try { @@ -6342,6 +6423,21 @@ public class WindowManagerService extends IWindowManager.Stub return false; } + public void getDisplaySize(Point size) { + synchronized(mWindowMap) { + size.x = mCurDisplayWidth; + size.y = mCurDisplayHeight; + } + } + + public int getMaximumSizeDimension() { + synchronized(mWindowMap) { + // Do this based on the raw screen size, until we are smarter. + return mInitialDisplayWidth > mInitialDisplayHeight + ? mInitialDisplayWidth : mInitialDisplayHeight; + } + } + // ------------------------------------------------------------- // Internals // ------------------------------------------------------------- @@ -6584,8 +6680,8 @@ public class WindowManagerService extends IWindowManager.Stub mLayoutNeeded = false; - final int dw = mDisplay.getWidth(); - final int dh = mDisplay.getHeight(); + final int dw = mCurDisplayWidth; + final int dh = mCurDisplayHeight; final int innerDw = mPolicy.getNonDecorDisplayWidth(dw); final int innerDh = mPolicy.getNonDecorDisplayHeight(dh); @@ -6709,8 +6805,8 @@ public class WindowManagerService extends IWindowManager.Stub } final long currentTime = SystemClock.uptimeMillis(); - final int dw = mDisplay.getWidth(); - final int dh = mDisplay.getHeight(); + final int dw = mCurDisplayWidth; + final int dh = mCurDisplayHeight; final int innerDw = mPolicy.getNonDecorDisplayWidth(dw); final int innerDh = mPolicy.getNonDecorDisplayHeight(dh); @@ -8707,24 +8803,25 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mAppsFreezingScreen="); pw.print(mAppsFreezingScreen); pw.print(" mWaitingForConfig="); pw.println(mWaitingForConfig); pw.print(" mRotation="); pw.print(mRotation); - pw.print(", mForcedAppOrientation="); pw.print(mForcedAppOrientation); - pw.print(", mRequestedRotation="); pw.println(mRequestedRotation); + pw.print(" mForcedAppOrientation="); pw.print(mForcedAppOrientation); + pw.print(" mRequestedRotation="); pw.print(mRequestedRotation); + pw.print(" mAltOrientation="); pw.println(mAltOrientation); pw.print(" mDeferredRotation="); pw.print(mDeferredRotation); - pw.print(", mDeferredRotationAnimFlags="); pw.print(mDeferredRotationAnimFlags); + pw.print(", mDeferredRotationAnimFlags="); pw.println(mDeferredRotationAnimFlags); pw.print(" mAnimationPending="); pw.print(mAnimationPending); pw.print(" mWindowAnimationScale="); pw.print(mWindowAnimationScale); pw.print(" mTransitionWindowAnimationScale="); pw.println(mTransitionAnimationScale); pw.print(" mNextAppTransition=0x"); pw.print(Integer.toHexString(mNextAppTransition)); - pw.print(", mAppTransitionReady="); pw.print(mAppTransitionReady); - pw.print(", mAppTransitionRunning="); pw.print(mAppTransitionRunning); - pw.print(", mAppTransitionTimeout="); pw.println( mAppTransitionTimeout); + pw.print(" mAppTransitionReady="); pw.println(mAppTransitionReady); + pw.print(" mAppTransitionRunning="); pw.print(mAppTransitionRunning); + pw.print(" mAppTransitionTimeout="); pw.println( mAppTransitionTimeout); if (mNextAppTransitionPackage != null) { pw.print(" mNextAppTransitionPackage="); pw.print(mNextAppTransitionPackage); - pw.print(", mNextAppTransitionEnter=0x"); + pw.print(" mNextAppTransitionEnter=0x"); pw.print(Integer.toHexString(mNextAppTransitionEnter)); - pw.print(", mNextAppTransitionExit=0x"); + pw.print(" mNextAppTransitionExit=0x"); pw.print(Integer.toHexString(mNextAppTransitionExit)); } pw.print(" mStartingIconInTransition="); pw.print(mStartingIconInTransition); @@ -8742,8 +8839,13 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mToBottomApps="); pw.println(mToBottomApps); } if (mDisplay != null) { - pw.print(" DisplayWidth="); pw.print(mDisplay.getWidth()); - pw.print(" DisplayHeight="); pw.println(mDisplay.getHeight()); + pw.print(" Display: init="); pw.print(mInitialDisplayWidth); pw.print("x"); + pw.print(mInitialDisplayHeight); pw.print(" cur="); + pw.print(mCurDisplayWidth); pw.print("x"); pw.print(mCurDisplayHeight); + pw.print(" real="); pw.print(mDisplay.getRealWidth()); + pw.print("x"); pw.print(mDisplay.getRealHeight()); + pw.print(" raw="); pw.print(mDisplay.getRawWidth()); + pw.print("x"); pw.println(mDisplay.getRawHeight()); } else { pw.println(" NO DISPLAY"); } diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index 72049ec..2014e9d 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -483,8 +483,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { } if (mIsWallpaper && (fw != frame.width() || fh != frame.height())) { - mService.updateWallpaperOffsetLocked(this, mService.mDisplay.getWidth(), - mService.mDisplay.getHeight(), false); + mService.updateWallpaperOffsetLocked(this, mService.mDisplay.getRealWidth(), + mService.mDisplay.getRealHeight(), false); } if (WindowManagerService.localLOGV) { diff --git a/services/jni/Android.mk b/services/jni/Android.mk index be37d5d..4e93fe2 100644 --- a/services/jni/Android.mk +++ b/services/jni/Android.mk @@ -34,7 +34,7 @@ LOCAL_SHARED_LIBRARIES := \ libui \ libinput \ libskia \ - libsurfaceflinger_client \ + libgui \ libusbhost ifeq ($(TARGET_SIMULATOR),true) diff --git a/services/jni/com_android_server_AlarmManagerService.cpp b/services/jni/com_android_server_AlarmManagerService.cpp index aa8c9b3..c9a702a 100644 --- a/services/jni/com_android_server_AlarmManagerService.cpp +++ b/services/jni/com_android_server_AlarmManagerService.cpp @@ -2,16 +2,16 @@ ** ** Copyright 2006, The Android Open Source Project ** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at +** 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 +** 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 +** 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. */ @@ -84,7 +84,7 @@ static void android_server_AlarmManagerService_set(JNIEnv* env, jobject obj, jin struct timespec ts; ts.tv_sec = seconds; ts.tv_nsec = nanoseconds; - + int result = ioctl(fd, ANDROID_ALARM_SET(type), &ts); if (result < 0) { @@ -97,18 +97,18 @@ static jint android_server_AlarmManagerService_waitForAlarm(JNIEnv* env, jobject { #ifdef HAVE_ANDROID_OS int result = 0; - + do { result = ioctl(fd, ANDROID_ALARM_WAIT); } while (result < 0 && errno == EINTR); - + if (result < 0) { LOGE("Unable to wait on alarm: %s\n", strerror(errno)); return 0; } - + return result; #endif } @@ -124,14 +124,6 @@ static JNINativeMethod sMethods[] = { int register_android_server_AlarmManagerService(JNIEnv* env) { - jclass clazz = env->FindClass("com/android/server/AlarmManagerService"); - - if (clazz == NULL) - { - LOGE("Can't find com/android/server/AlarmManagerService"); - return -1; - } - return jniRegisterNativeMethods(env, "com/android/server/AlarmManagerService", sMethods, NELEM(sMethods)); } diff --git a/services/jni/com_android_server_InputApplication.cpp b/services/jni/com_android_server_InputApplication.cpp index e64ec4e..1f80242 100644 --- a/services/jni/com_android_server_InputApplication.cpp +++ b/services/jni/com_android_server_InputApplication.cpp @@ -26,8 +26,6 @@ namespace android { static struct { - jclass clazz; - jfieldID inputApplicationHandle; jfieldID name; jfieldID dispatchingTimeoutNanos; @@ -69,25 +67,25 @@ void android_server_InputApplication_toNative( #define FIND_CLASS(var, className) \ var = env->FindClass(className); \ - LOG_FATAL_IF(! var, "Unable to find class " className); \ - var = jclass(env->NewGlobalRef(var)); + LOG_FATAL_IF(! var, "Unable to find class " className); #define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ LOG_FATAL_IF(! var, "Unable to find field " fieldName); int register_android_server_InputApplication(JNIEnv* env) { - FIND_CLASS(gInputApplicationClassInfo.clazz, "com/android/server/wm/InputApplication"); + jclass clazz; + FIND_CLASS(clazz, "com/android/server/wm/InputApplication"); GET_FIELD_ID(gInputApplicationClassInfo.inputApplicationHandle, - gInputApplicationClassInfo.clazz, + clazz, "inputApplicationHandle", "Lcom/android/server/wm/InputApplicationHandle;"); - GET_FIELD_ID(gInputApplicationClassInfo.name, gInputApplicationClassInfo.clazz, + GET_FIELD_ID(gInputApplicationClassInfo.name, clazz, "name", "Ljava/lang/String;"); GET_FIELD_ID(gInputApplicationClassInfo.dispatchingTimeoutNanos, - gInputApplicationClassInfo.clazz, + clazz, "dispatchingTimeoutNanos", "J"); return 0; } diff --git a/services/jni/com_android_server_InputApplicationHandle.cpp b/services/jni/com_android_server_InputApplicationHandle.cpp index 3a1214f..9516964 100644 --- a/services/jni/com_android_server_InputApplicationHandle.cpp +++ b/services/jni/com_android_server_InputApplicationHandle.cpp @@ -26,8 +26,6 @@ namespace android { static struct { - jclass clazz; - jfieldID ptr; } gInputApplicationHandleClassInfo; @@ -98,8 +96,7 @@ static JNINativeMethod gInputApplicationHandleMethods[] = { #define FIND_CLASS(var, className) \ var = env->FindClass(className); \ - LOG_FATAL_IF(! var, "Unable to find class " className); \ - var = jclass(env->NewGlobalRef(var)); + LOG_FATAL_IF(! var, "Unable to find class " className); #define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ @@ -110,9 +107,10 @@ int register_android_server_InputApplicationHandle(JNIEnv* env) { gInputApplicationHandleMethods, NELEM(gInputApplicationHandleMethods)); LOG_FATAL_IF(res < 0, "Unable to register native methods."); - FIND_CLASS(gInputApplicationHandleClassInfo.clazz, "com/android/server/wm/InputApplicationHandle"); + jclass clazz; + FIND_CLASS(clazz, "com/android/server/wm/InputApplicationHandle"); - GET_FIELD_ID(gInputApplicationHandleClassInfo.ptr, gInputApplicationHandleClassInfo.clazz, + GET_FIELD_ID(gInputApplicationHandleClassInfo.ptr, clazz, "ptr", "I"); return 0; diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp index 80dddc2..1f10d9c 100644 --- a/services/jni/com_android_server_InputManager.cpp +++ b/services/jni/com_android_server_InputManager.cpp @@ -36,11 +36,13 @@ #include <input/InputManager.h> #include <input/PointerController.h> +#include <input/SpriteController.h> #include <android_os_MessageQueue.h> #include <android_view_KeyEvent.h> #include <android_view_MotionEvent.h> #include <android_view_InputChannel.h> +#include <android_view_PointerIcon.h> #include <android/graphics/GraphicsJNI.h> #include "com_android_server_PowerManagerService.h" @@ -52,12 +54,11 @@ namespace android { static struct { - jclass clazz; - jmethodID notifyConfigurationChanged; jmethodID notifyLidSwitchChanged; jmethodID notifyInputChannelBroken; jmethodID notifyANR; + jmethodID filterInputEvent; jmethodID interceptKeyBeforeQueueing; jmethodID interceptMotionBeforeQueueingWhenScreenOff; jmethodID interceptKeyBeforeDispatching; @@ -95,21 +96,11 @@ static struct { } gInputDeviceClassInfo; static struct { - jclass clazz; - jfieldID touchscreen; jfieldID keyboard; jfieldID navigation; } gConfigurationClassInfo; -static struct { - jclass clazz; - - jfieldID bitmap; - jfieldID hotSpotX; - jfieldID hotSpotY; -} gPointerIconClassInfo; - // --- Global functions --- @@ -131,17 +122,30 @@ static jobject getInputWindowHandleObjLocalRef(JNIEnv* env, getInputWindowHandleObjLocalRef(env); } +static void loadSystemIconAsSprite(JNIEnv* env, jobject contextObj, int32_t style, + SpriteIcon* outSpriteIcon) { + PointerIcon pointerIcon; + status_t status = android_view_PointerIcon_loadSystemIcon(env, + contextObj, style, &pointerIcon); + if (!status) { + pointerIcon.bitmap.copyTo(&outSpriteIcon->bitmap, SkBitmap::kARGB_8888_Config); + outSpriteIcon->hotSpotX = pointerIcon.hotSpotX; + outSpriteIcon->hotSpotY = pointerIcon.hotSpotY; + } +} + // --- NativeInputManager --- class NativeInputManager : public virtual RefBase, public virtual InputReaderPolicyInterface, - public virtual InputDispatcherPolicyInterface { + public virtual InputDispatcherPolicyInterface, + public virtual PointerControllerPolicyInterface { protected: virtual ~NativeInputManager(); public: - NativeInputManager(jobject callbacksObj, const sp<Looper>& looper); + NativeInputManager(jobject contextObj, jobject callbacksObj, const sp<Looper>& looper); inline sp<InputManager> getInputManager() const { return mInputManager; } @@ -180,6 +184,7 @@ public: virtual nsecs_t getKeyRepeatTimeout(); virtual nsecs_t getKeyRepeatDelay(); virtual int32_t getMaxEventsPerSecond(); + virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags); virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags); virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags); virtual bool interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle, @@ -190,9 +195,14 @@ public: virtual bool checkInjectEventsPermissionNonReentrant( int32_t injectorPid, int32_t injectorUid); + /* --- PointerControllerPolicyInterface implementation --- */ + + virtual void loadPointerResources(PointerResources* outResources); + private: sp<InputManager> mInputManager; + jobject mContextObj; jobject mCallbacksObj; sp<Looper> mLooper; @@ -217,12 +227,16 @@ private: // System UI visibility. int32_t systemUiVisibility; + // Sprite controller singleton, created on first use. + sp<SpriteController> spriteController; + // Pointer controller singleton, created and destroyed as needed. wp<PointerController> pointerController; } mLocked; - void updateInactivityFadeDelayLocked(const sp<PointerController>& controller); + void updateInactivityTimeoutLocked(const sp<PointerController>& controller); void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags); + void ensureSpriteControllerLocked(); // Power manager interactions. bool isScreenOn(); @@ -237,13 +251,15 @@ private: -NativeInputManager::NativeInputManager(jobject callbacksObj, const sp<Looper>& looper) : +NativeInputManager::NativeInputManager(jobject contextObj, + jobject callbacksObj, const sp<Looper>& looper) : mLooper(looper), mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), mVirtualKeyQuietTime(-1), mKeyRepeatTimeout(-1), mKeyRepeatDelay(-1), mMaxEventsPerSecond(-1) { JNIEnv* env = jniEnv(); + mContextObj = env->NewGlobalRef(contextObj); mCallbacksObj = env->NewGlobalRef(callbacksObj); { @@ -262,6 +278,7 @@ NativeInputManager::NativeInputManager(jobject callbacksObj, const sp<Looper>& l NativeInputManager::~NativeInputManager() { JNIEnv* env = jniEnv(); + env->DeleteGlobalRef(mContextObj); env->DeleteGlobalRef(mCallbacksObj); } @@ -285,9 +302,13 @@ bool NativeInputManager::checkAndClearExceptionFromCallback(JNIEnv* env, const c void NativeInputManager::setDisplaySize(int32_t displayId, int32_t width, int32_t height) { if (displayId == 0) { - AutoMutex _l(mLock); + { // acquire lock + AutoMutex _l(mLock); + + if (mLocked.displayWidth == width && mLocked.displayHeight == height) { + return; + } - if (mLocked.displayWidth != width || mLocked.displayHeight != height) { mLocked.displayWidth = width; mLocked.displayHeight = height; @@ -295,7 +316,7 @@ void NativeInputManager::setDisplaySize(int32_t displayId, int32_t width, int32_ if (controller != NULL) { controller->setDisplaySize(width, height); } - } + } // release lock } } @@ -423,38 +444,46 @@ sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32 sp<PointerController> controller = mLocked.pointerController.promote(); if (controller == NULL) { - JNIEnv* env = jniEnv(); - jint layer = env->CallIntMethod(mCallbacksObj, gCallbacksClassInfo.getPointerLayer); - if (checkAndClearExceptionFromCallback(env, "getPointerLayer")) { - layer = -1; - } + ensureSpriteControllerLocked(); - controller = new PointerController(mLooper, layer); + controller = new PointerController(this, mLooper, mLocked.spriteController); mLocked.pointerController = controller; controller->setDisplaySize(mLocked.displayWidth, mLocked.displayHeight); controller->setDisplayOrientation(mLocked.displayOrientation); - jobject iconObj = env->CallObjectMethod(mCallbacksObj, gCallbacksClassInfo.getPointerIcon); - if (!checkAndClearExceptionFromCallback(env, "getPointerIcon") && iconObj) { - jfloat iconHotSpotX = env->GetFloatField(iconObj, gPointerIconClassInfo.hotSpotX); - jfloat iconHotSpotY = env->GetFloatField(iconObj, gPointerIconClassInfo.hotSpotY); - jobject iconBitmapObj = env->GetObjectField(iconObj, gPointerIconClassInfo.bitmap); - if (iconBitmapObj) { - SkBitmap* iconBitmap = GraphicsJNI::getNativeBitmap(env, iconBitmapObj); - if (iconBitmap) { - controller->setPointerIcon(iconBitmap, iconHotSpotX, iconHotSpotY); - } - env->DeleteLocalRef(iconBitmapObj); + JNIEnv* env = jniEnv(); + jobject pointerIconObj = env->CallObjectMethod(mCallbacksObj, + gCallbacksClassInfo.getPointerIcon); + if (!checkAndClearExceptionFromCallback(env, "getPointerIcon")) { + PointerIcon pointerIcon; + status_t status = android_view_PointerIcon_load(env, pointerIconObj, + mContextObj, &pointerIcon); + if (!status && !pointerIcon.isNullIcon()) { + controller->setPointerIcon(SpriteIcon(pointerIcon.bitmap, + pointerIcon.hotSpotX, pointerIcon.hotSpotY)); + } else { + controller->setPointerIcon(SpriteIcon()); } - env->DeleteLocalRef(iconObj); + env->DeleteLocalRef(pointerIconObj); } - updateInactivityFadeDelayLocked(controller); + updateInactivityTimeoutLocked(controller); } return controller; } +void NativeInputManager::ensureSpriteControllerLocked() { + if (mLocked.spriteController == NULL) { + JNIEnv* env = jniEnv(); + jint layer = env->CallIntMethod(mCallbacksObj, gCallbacksClassInfo.getPointerLayer); + if (checkAndClearExceptionFromCallback(env, "getPointerLayer")) { + layer = -1; + } + mLocked.spriteController = new SpriteController(mLooper, layer); + } +} + void NativeInputManager::notifySwitch(nsecs_t when, int32_t switchCode, int32_t switchValue, uint32_t policyFlags) { #if DEBUG_INPUT_DISPATCHER_POLICY @@ -606,6 +635,7 @@ void NativeInputManager::setFocusedApplication(JNIEnv* env, jobject applicationO android_server_InputApplication_toNative(env, applicationObj, &application); if (application.inputApplicationHandle != NULL) { mInputManager->getDispatcher()->setFocusedApplication(&application); + return; } } mInputManager->getDispatcher()->setFocusedApplication(NULL); @@ -623,16 +653,16 @@ void NativeInputManager::setSystemUiVisibility(int32_t visibility) { sp<PointerController> controller = mLocked.pointerController.promote(); if (controller != NULL) { - updateInactivityFadeDelayLocked(controller); + updateInactivityTimeoutLocked(controller); } } } -void NativeInputManager::updateInactivityFadeDelayLocked(const sp<PointerController>& controller) { +void NativeInputManager::updateInactivityTimeoutLocked(const sp<PointerController>& controller) { bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN; - controller->setInactivityFadeDelay(lightsOut - ? PointerController::INACTIVITY_FADE_DELAY_SHORT - : PointerController::INACTIVITY_FADE_DELAY_NORMAL); + controller->setInactivityTimeout(lightsOut + ? PointerController::INACTIVITY_TIMEOUT_SHORT + : PointerController::INACTIVITY_TIMEOUT_NORMAL); } bool NativeInputManager::isScreenOn() { @@ -643,6 +673,38 @@ bool NativeInputManager::isScreenBright() { return android_server_PowerManagerService_isScreenBright(); } +bool NativeInputManager::filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) { + jobject inputEventObj; + + JNIEnv* env = jniEnv(); + switch (inputEvent->getType()) { + case AINPUT_EVENT_TYPE_KEY: + inputEventObj = android_view_KeyEvent_fromNative(env, + static_cast<const KeyEvent*>(inputEvent)); + break; + case AINPUT_EVENT_TYPE_MOTION: + inputEventObj = android_view_MotionEvent_obtainAsCopy(env, + static_cast<const MotionEvent*>(inputEvent)); + break; + default: + return true; // dispatch the event normally + } + + if (!inputEventObj) { + LOGE("Failed to obtain input event object for filterInputEvent."); + return true; // dispatch the event normally + } + + // The callee is responsible for recycling the event. + jboolean pass = env->CallBooleanMethod(mCallbacksObj, gCallbacksClassInfo.filterInputEvent, + inputEventObj, policyFlags); + if (checkAndClearExceptionFromCallback(env, "filterInputEvent")) { + pass = true; + } + env->DeleteLocalRef(inputEventObj); + return pass; +} + void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) { // Policy: @@ -794,7 +856,9 @@ bool NativeInputManager::dispatchUnhandledKey(const sp<InputWindowHandle>& input jobject fallbackKeyEventObj = env->CallObjectMethod(mCallbacksObj, gCallbacksClassInfo.dispatchUnhandledKey, inputWindowHandleObj, keyEventObj, policyFlags); - checkAndClearExceptionFromCallback(env, "dispatchUnhandledKey"); + if (checkAndClearExceptionFromCallback(env, "dispatchUnhandledKey")) { + fallbackKeyEventObj = NULL; + } android_view_KeyEvent_recycle(env, keyEventObj); env->DeleteLocalRef(keyEventObj); @@ -825,10 +889,23 @@ bool NativeInputManager::checkInjectEventsPermissionNonReentrant( JNIEnv* env = jniEnv(); jboolean result = env->CallBooleanMethod(mCallbacksObj, gCallbacksClassInfo.checkInjectEventsPermission, injectorPid, injectorUid); - checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission"); + if (checkAndClearExceptionFromCallback(env, "checkInjectEventsPermission")) { + result = false; + } return result; } +void NativeInputManager::loadPointerResources(PointerResources* outResources) { + JNIEnv* env = jniEnv(); + + loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_HOVER, + &outResources->spotHover); + loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_TOUCH, + &outResources->spotTouch); + loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_ANCHOR, + &outResources->spotAnchor); +} + // ---------------------------------------------------------------------------- @@ -844,10 +921,10 @@ static bool checkInputManagerUnitialized(JNIEnv* env) { } static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz, - jobject callbacks, jobject messageQueueObj) { + jobject contextObj, jobject callbacksObj, jobject messageQueueObj) { if (gNativeInputManager == NULL) { sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj); - gNativeInputManager = new NativeInputManager(callbacks, looper); + gNativeInputManager = new NativeInputManager(contextObj, callbacksObj, looper); } else { LOGE("Input manager already initialized."); jniThrowRuntimeException(env, "Input manager already initialized."); @@ -1006,9 +1083,18 @@ static void android_server_InputManager_nativeUnregisterInputChannel(JNIEnv* env } } +static void android_server_InputManager_nativeSetInputFilterEnabled(JNIEnv* env, jclass clazz, + jboolean enabled) { + if (checkInputManagerUnitialized(env)) { + return; + } + + gNativeInputManager->getInputManager()->getDispatcher()->setInputFilterEnabled(enabled); +} + static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jclass clazz, jobject inputEventObj, jint injectorPid, jint injectorUid, - jint syncMode, jint timeoutMillis) { + jint syncMode, jint timeoutMillis, jint policyFlags) { if (checkInputManagerUnitialized(env)) { return INPUT_EVENT_INJECTION_FAILED; } @@ -1022,17 +1108,18 @@ static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jcla } return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent( - & keyEvent, injectorPid, injectorUid, syncMode, timeoutMillis); + & keyEvent, injectorPid, injectorUid, syncMode, timeoutMillis, + uint32_t(policyFlags)); } else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) { - MotionEvent motionEvent; - status_t status = android_view_MotionEvent_toNative(env, inputEventObj, & motionEvent); - if (status) { + const MotionEvent* motionEvent = android_view_MotionEvent_getNativePtr(env, inputEventObj); + if (!motionEvent) { jniThrowRuntimeException(env, "Could not read contents of MotionEvent object."); return INPUT_EVENT_INJECTION_FAILED; } return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent( - & motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis); + motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis, + uint32_t(policyFlags)); } else { jniThrowRuntimeException(env, "Invalid input event type."); return INPUT_EVENT_INJECTION_FAILED; @@ -1181,7 +1268,8 @@ static jstring android_server_InputManager_nativeDump(JNIEnv* env, jclass clazz) static JNINativeMethod gInputManagerMethods[] = { /* name, signature, funcPtr */ - { "nativeInit", "(Lcom/android/server/wm/InputManager$Callbacks;Landroid/os/MessageQueue;)V", + { "nativeInit", "(Landroid/content/Context;" + "Lcom/android/server/wm/InputManager$Callbacks;Landroid/os/MessageQueue;)V", (void*) android_server_InputManager_nativeInit }, { "nativeStart", "()V", (void*) android_server_InputManager_nativeStart }, @@ -1202,7 +1290,9 @@ static JNINativeMethod gInputManagerMethods[] = { (void*) android_server_InputManager_nativeRegisterInputChannel }, { "nativeUnregisterInputChannel", "(Landroid/view/InputChannel;)V", (void*) android_server_InputManager_nativeUnregisterInputChannel }, - { "nativeInjectInputEvent", "(Landroid/view/InputEvent;IIII)I", + { "nativeSetInputFilterEnabled", "(Z)V", + (void*) android_server_InputManager_nativeSetInputFilterEnabled }, + { "nativeInjectInputEvent", "(Landroid/view/InputEvent;IIIII)I", (void*) android_server_InputManager_nativeInjectInputEvent }, { "nativeSetInputWindows", "([Lcom/android/server/wm/InputWindow;)V", (void*) android_server_InputManager_nativeSetInputWindows }, @@ -1226,8 +1316,7 @@ static JNINativeMethod gInputManagerMethods[] = { #define FIND_CLASS(var, className) \ var = env->FindClass(className); \ - LOG_FATAL_IF(! var, "Unable to find class " className); \ - var = jclass(env->NewGlobalRef(var)); + LOG_FATAL_IF(! var, "Unable to find class " className); #define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \ var = env->GetMethodID(clazz, methodName, methodDescriptor); \ @@ -1244,77 +1333,85 @@ int register_android_server_InputManager(JNIEnv* env) { // Callbacks - FIND_CLASS(gCallbacksClassInfo.clazz, "com/android/server/wm/InputManager$Callbacks"); + jclass clazz; + FIND_CLASS(clazz, "com/android/server/wm/InputManager$Callbacks"); - GET_METHOD_ID(gCallbacksClassInfo.notifyConfigurationChanged, gCallbacksClassInfo.clazz, + GET_METHOD_ID(gCallbacksClassInfo.notifyConfigurationChanged, clazz, "notifyConfigurationChanged", "(J)V"); - GET_METHOD_ID(gCallbacksClassInfo.notifyLidSwitchChanged, gCallbacksClassInfo.clazz, + GET_METHOD_ID(gCallbacksClassInfo.notifyLidSwitchChanged, clazz, "notifyLidSwitchChanged", "(JZ)V"); - GET_METHOD_ID(gCallbacksClassInfo.notifyInputChannelBroken, gCallbacksClassInfo.clazz, + GET_METHOD_ID(gCallbacksClassInfo.notifyInputChannelBroken, clazz, "notifyInputChannelBroken", "(Lcom/android/server/wm/InputWindowHandle;)V"); - GET_METHOD_ID(gCallbacksClassInfo.notifyANR, gCallbacksClassInfo.clazz, + GET_METHOD_ID(gCallbacksClassInfo.notifyANR, clazz, "notifyANR", "(Lcom/android/server/wm/InputApplicationHandle;Lcom/android/server/wm/InputWindowHandle;)J"); - GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeQueueing, gCallbacksClassInfo.clazz, + GET_METHOD_ID(gCallbacksClassInfo.filterInputEvent, clazz, + "filterInputEvent", "(Landroid/view/InputEvent;I)Z"); + + GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeQueueing, clazz, "interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;IZ)I"); GET_METHOD_ID(gCallbacksClassInfo.interceptMotionBeforeQueueingWhenScreenOff, - gCallbacksClassInfo.clazz, + clazz, "interceptMotionBeforeQueueingWhenScreenOff", "(I)I"); - GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeDispatching, gCallbacksClassInfo.clazz, + GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeDispatching, clazz, "interceptKeyBeforeDispatching", "(Lcom/android/server/wm/InputWindowHandle;Landroid/view/KeyEvent;I)Z"); - GET_METHOD_ID(gCallbacksClassInfo.dispatchUnhandledKey, gCallbacksClassInfo.clazz, + GET_METHOD_ID(gCallbacksClassInfo.dispatchUnhandledKey, clazz, "dispatchUnhandledKey", "(Lcom/android/server/wm/InputWindowHandle;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;"); - GET_METHOD_ID(gCallbacksClassInfo.checkInjectEventsPermission, gCallbacksClassInfo.clazz, + GET_METHOD_ID(gCallbacksClassInfo.checkInjectEventsPermission, clazz, "checkInjectEventsPermission", "(II)Z"); - GET_METHOD_ID(gCallbacksClassInfo.filterTouchEvents, gCallbacksClassInfo.clazz, + GET_METHOD_ID(gCallbacksClassInfo.filterTouchEvents, clazz, "filterTouchEvents", "()Z"); - GET_METHOD_ID(gCallbacksClassInfo.filterJumpyTouchEvents, gCallbacksClassInfo.clazz, + GET_METHOD_ID(gCallbacksClassInfo.filterJumpyTouchEvents, clazz, "filterJumpyTouchEvents", "()Z"); - GET_METHOD_ID(gCallbacksClassInfo.getVirtualKeyQuietTimeMillis, gCallbacksClassInfo.clazz, + GET_METHOD_ID(gCallbacksClassInfo.getVirtualKeyQuietTimeMillis, clazz, "getVirtualKeyQuietTimeMillis", "()I"); - GET_METHOD_ID(gCallbacksClassInfo.getExcludedDeviceNames, gCallbacksClassInfo.clazz, + GET_METHOD_ID(gCallbacksClassInfo.getExcludedDeviceNames, clazz, "getExcludedDeviceNames", "()[Ljava/lang/String;"); - GET_METHOD_ID(gCallbacksClassInfo.getKeyRepeatTimeout, gCallbacksClassInfo.clazz, + GET_METHOD_ID(gCallbacksClassInfo.getKeyRepeatTimeout, clazz, "getKeyRepeatTimeout", "()I"); - GET_METHOD_ID(gCallbacksClassInfo.getKeyRepeatDelay, gCallbacksClassInfo.clazz, + GET_METHOD_ID(gCallbacksClassInfo.getKeyRepeatDelay, clazz, "getKeyRepeatDelay", "()I"); - GET_METHOD_ID(gCallbacksClassInfo.getMaxEventsPerSecond, gCallbacksClassInfo.clazz, + GET_METHOD_ID(gCallbacksClassInfo.getMaxEventsPerSecond, clazz, "getMaxEventsPerSecond", "()I"); - GET_METHOD_ID(gCallbacksClassInfo.getPointerLayer, gCallbacksClassInfo.clazz, + GET_METHOD_ID(gCallbacksClassInfo.getPointerLayer, clazz, "getPointerLayer", "()I"); - GET_METHOD_ID(gCallbacksClassInfo.getPointerIcon, gCallbacksClassInfo.clazz, - "getPointerIcon", "()Lcom/android/server/wm/InputManager$PointerIcon;"); + GET_METHOD_ID(gCallbacksClassInfo.getPointerIcon, clazz, + "getPointerIcon", "()Landroid/view/PointerIcon;"); // KeyEvent FIND_CLASS(gKeyEventClassInfo.clazz, "android/view/KeyEvent"); + gKeyEventClassInfo.clazz = jclass(env->NewGlobalRef(gKeyEventClassInfo.clazz)); + // MotionEvent FIND_CLASS(gMotionEventClassInfo.clazz, "android/view/MotionEvent"); + gMotionEventClassInfo.clazz = jclass(env->NewGlobalRef(gMotionEventClassInfo.clazz)); // InputDevice FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice"); + gInputDeviceClassInfo.clazz = jclass(env->NewGlobalRef(gInputDeviceClassInfo.clazz)); GET_METHOD_ID(gInputDeviceClassInfo.ctor, gInputDeviceClassInfo.clazz, "<init>", "()V"); @@ -1336,30 +1433,17 @@ int register_android_server_InputManager(JNIEnv* env) { // Configuration - FIND_CLASS(gConfigurationClassInfo.clazz, "android/content/res/Configuration"); + FIND_CLASS(clazz, "android/content/res/Configuration"); - GET_FIELD_ID(gConfigurationClassInfo.touchscreen, gConfigurationClassInfo.clazz, + GET_FIELD_ID(gConfigurationClassInfo.touchscreen, clazz, "touchscreen", "I"); - GET_FIELD_ID(gConfigurationClassInfo.keyboard, gConfigurationClassInfo.clazz, + GET_FIELD_ID(gConfigurationClassInfo.keyboard, clazz, "keyboard", "I"); - GET_FIELD_ID(gConfigurationClassInfo.navigation, gConfigurationClassInfo.clazz, + GET_FIELD_ID(gConfigurationClassInfo.navigation, clazz, "navigation", "I"); - // PointerIcon - - FIND_CLASS(gPointerIconClassInfo.clazz, "com/android/server/wm/InputManager$PointerIcon"); - - GET_FIELD_ID(gPointerIconClassInfo.bitmap, gPointerIconClassInfo.clazz, - "bitmap", "Landroid/graphics/Bitmap;"); - - GET_FIELD_ID(gPointerIconClassInfo.hotSpotX, gPointerIconClassInfo.clazz, - "hotSpotX", "F"); - - GET_FIELD_ID(gPointerIconClassInfo.hotSpotY, gPointerIconClassInfo.clazz, - "hotSpotY", "F"); - return 0; } diff --git a/services/jni/com_android_server_InputWindow.cpp b/services/jni/com_android_server_InputWindow.cpp index d36c010..012ce21 100644 --- a/services/jni/com_android_server_InputWindow.cpp +++ b/services/jni/com_android_server_InputWindow.cpp @@ -28,8 +28,6 @@ namespace android { static struct { - jclass clazz; - jfieldID inputWindowHandle; jfieldID inputChannel; jfieldID name; @@ -139,74 +137,74 @@ void android_server_InputWindow_toNative( #define FIND_CLASS(var, className) \ var = env->FindClass(className); \ - LOG_FATAL_IF(! var, "Unable to find class " className); \ - var = jclass(env->NewGlobalRef(var)); + LOG_FATAL_IF(! var, "Unable to find class " className); #define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ LOG_FATAL_IF(! var, "Unable to find field " fieldName); int register_android_server_InputWindow(JNIEnv* env) { - FIND_CLASS(gInputWindowClassInfo.clazz, "com/android/server/wm/InputWindow"); + jclass clazz; + FIND_CLASS(clazz, "com/android/server/wm/InputWindow"); - GET_FIELD_ID(gInputWindowClassInfo.inputWindowHandle, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.inputWindowHandle, clazz, "inputWindowHandle", "Lcom/android/server/wm/InputWindowHandle;"); - GET_FIELD_ID(gInputWindowClassInfo.inputChannel, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.inputChannel, clazz, "inputChannel", "Landroid/view/InputChannel;"); - GET_FIELD_ID(gInputWindowClassInfo.name, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.name, clazz, "name", "Ljava/lang/String;"); - GET_FIELD_ID(gInputWindowClassInfo.layoutParamsFlags, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.layoutParamsFlags, clazz, "layoutParamsFlags", "I"); - GET_FIELD_ID(gInputWindowClassInfo.layoutParamsType, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.layoutParamsType, clazz, "layoutParamsType", "I"); - GET_FIELD_ID(gInputWindowClassInfo.dispatchingTimeoutNanos, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.dispatchingTimeoutNanos, clazz, "dispatchingTimeoutNanos", "J"); - GET_FIELD_ID(gInputWindowClassInfo.frameLeft, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.frameLeft, clazz, "frameLeft", "I"); - GET_FIELD_ID(gInputWindowClassInfo.frameTop, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.frameTop, clazz, "frameTop", "I"); - GET_FIELD_ID(gInputWindowClassInfo.frameRight, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.frameRight, clazz, "frameRight", "I"); - GET_FIELD_ID(gInputWindowClassInfo.frameBottom, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.frameBottom, clazz, "frameBottom", "I"); - GET_FIELD_ID(gInputWindowClassInfo.scaleFactor, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.scaleFactor, clazz, "scaleFactor", "F"); - GET_FIELD_ID(gInputWindowClassInfo.touchableRegion, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.touchableRegion, clazz, "touchableRegion", "Landroid/graphics/Region;"); - GET_FIELD_ID(gInputWindowClassInfo.visible, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.visible, clazz, "visible", "Z"); - GET_FIELD_ID(gInputWindowClassInfo.canReceiveKeys, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.canReceiveKeys, clazz, "canReceiveKeys", "Z"); - GET_FIELD_ID(gInputWindowClassInfo.hasFocus, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.hasFocus, clazz, "hasFocus", "Z"); - GET_FIELD_ID(gInputWindowClassInfo.hasWallpaper, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.hasWallpaper, clazz, "hasWallpaper", "Z"); - GET_FIELD_ID(gInputWindowClassInfo.paused, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.paused, clazz, "paused", "Z"); - GET_FIELD_ID(gInputWindowClassInfo.layer, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.layer, clazz, "layer", "I"); - GET_FIELD_ID(gInputWindowClassInfo.ownerPid, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.ownerPid, clazz, "ownerPid", "I"); - GET_FIELD_ID(gInputWindowClassInfo.ownerUid, gInputWindowClassInfo.clazz, + GET_FIELD_ID(gInputWindowClassInfo.ownerUid, clazz, "ownerUid", "I"); return 0; } diff --git a/services/jni/com_android_server_InputWindowHandle.cpp b/services/jni/com_android_server_InputWindowHandle.cpp index 5b74e43..aaf679c 100644 --- a/services/jni/com_android_server_InputWindowHandle.cpp +++ b/services/jni/com_android_server_InputWindowHandle.cpp @@ -27,8 +27,6 @@ namespace android { static struct { - jclass clazz; - jfieldID ptr; jfieldID inputApplicationHandle; } gInputWindowHandleClassInfo; @@ -108,8 +106,7 @@ static JNINativeMethod gInputWindowHandleMethods[] = { #define FIND_CLASS(var, className) \ var = env->FindClass(className); \ - LOG_FATAL_IF(! var, "Unable to find class " className); \ - var = jclass(env->NewGlobalRef(var)); + LOG_FATAL_IF(! var, "Unable to find class " className); #define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ @@ -120,13 +117,14 @@ int register_android_server_InputWindowHandle(JNIEnv* env) { gInputWindowHandleMethods, NELEM(gInputWindowHandleMethods)); LOG_FATAL_IF(res < 0, "Unable to register native methods."); - FIND_CLASS(gInputWindowHandleClassInfo.clazz, "com/android/server/wm/InputWindowHandle"); + jclass clazz; + FIND_CLASS(clazz, "com/android/server/wm/InputWindowHandle"); - GET_FIELD_ID(gInputWindowHandleClassInfo.ptr, gInputWindowHandleClassInfo.clazz, + GET_FIELD_ID(gInputWindowHandleClassInfo.ptr, clazz, "ptr", "I"); GET_FIELD_ID(gInputWindowHandleClassInfo.inputApplicationHandle, - gInputWindowHandleClassInfo.clazz, + clazz, "inputApplicationHandle", "Lcom/android/server/wm/InputApplicationHandle;"); return 0; diff --git a/services/jni/com_android_server_PowerManagerService.cpp b/services/jni/com_android_server_PowerManagerService.cpp index 705be60..a389c11 100644 --- a/services/jni/com_android_server_PowerManagerService.cpp +++ b/services/jni/com_android_server_PowerManagerService.cpp @@ -35,8 +35,6 @@ namespace android { // ---------------------------------------------------------------------------- static struct { - jclass clazz; - jmethodID goToSleep; jmethodID userActivity; } gPowerManagerServiceClassInfo; @@ -144,8 +142,7 @@ static JNINativeMethod gPowerManagerServiceMethods[] = { #define FIND_CLASS(var, className) \ var = env->FindClass(className); \ - LOG_FATAL_IF(! var, "Unable to find class " className); \ - var = jclass(env->NewGlobalRef(var)); + LOG_FATAL_IF(! var, "Unable to find class " className); #define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \ var = env->GetMethodID(clazz, methodName, methodDescriptor); \ @@ -162,12 +159,13 @@ int register_android_server_PowerManagerService(JNIEnv* env) { // Callbacks - FIND_CLASS(gPowerManagerServiceClassInfo.clazz, "com/android/server/PowerManagerService"); + jclass clazz; + FIND_CLASS(clazz, "com/android/server/PowerManagerService"); - GET_METHOD_ID(gPowerManagerServiceClassInfo.goToSleep, gPowerManagerServiceClassInfo.clazz, + GET_METHOD_ID(gPowerManagerServiceClassInfo.goToSleep, clazz, "goToSleep", "(J)V"); - GET_METHOD_ID(gPowerManagerServiceClassInfo.userActivity, gPowerManagerServiceClassInfo.clazz, + GET_METHOD_ID(gPowerManagerServiceClassInfo.userActivity, clazz, "userActivity", "(JZIZ)V"); // Initialize diff --git a/services/jni/com_android_server_UsbService.cpp b/services/jni/com_android_server_UsbService.cpp index 6aeede2..9cd04f6 100644 --- a/services/jni/com_android_server_UsbService.cpp +++ b/services/jni/com_android_server_UsbService.cpp @@ -37,13 +37,6 @@ namespace android { -static struct file_descriptor_offsets_t -{ - jclass mClass; - jmethodID mConstructor; - jfieldID mDescriptor; -} gFileDescriptorOffsets; - static struct parcel_file_descriptor_offsets_t { jclass mClass; @@ -167,11 +160,8 @@ static jobject android_server_UsbService_openDevice(JNIEnv *env, jobject thiz, j int newFD = dup(fd); usb_device_close(device); - jobject fileDescriptor = env->NewObject(gFileDescriptorOffsets.mClass, - gFileDescriptorOffsets.mConstructor); - if (fileDescriptor != NULL) { - env->SetIntField(fileDescriptor, gFileDescriptorOffsets.mDescriptor, newFD); - } else { + jobject fileDescriptor = jniCreateFileDescriptor(env, newFD); + if (fileDescriptor == NULL) { return NULL; } return env->NewObject(gParcelFileDescriptorOffsets.mClass, @@ -221,11 +211,8 @@ static jobject android_server_UsbService_openAccessory(JNIEnv *env, jobject thiz LOGE("could not open %s", DRIVER_NAME); return NULL; } - jobject fileDescriptor = env->NewObject(gFileDescriptorOffsets.mClass, - gFileDescriptorOffsets.mConstructor); - if (fileDescriptor != NULL) { - env->SetIntField(fileDescriptor, gFileDescriptorOffsets.mDescriptor, fd); - } else { + jobject fileDescriptor = jniCreateFileDescriptor(env, fd); + if (fileDescriptor == NULL) { return NULL; } return env->NewObject(gParcelFileDescriptorOffsets.mClass, @@ -260,15 +247,7 @@ int register_android_server_UsbService(JNIEnv *env) return -1; } - clazz = env->FindClass("java/io/FileDescriptor"); - LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor"); - gFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz); - gFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "()V"); - gFileDescriptorOffsets.mDescriptor = env->GetFieldID(clazz, "descriptor", "I"); - LOG_FATAL_IF(gFileDescriptorOffsets.mDescriptor == NULL, - "Unable to find descriptor field in java.io.FileDescriptor"); - - clazz = env->FindClass("android/os/ParcelFileDescriptor"); + clazz = env->FindClass("android/os/ParcelFileDescriptor"); LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor"); gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz); gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V"); diff --git a/services/sensorservice/Android.mk b/services/sensorservice/Android.mk index 7e17fdd..c50e4a1 100644 --- a/services/sensorservice/Android.mk +++ b/services/sensorservice/Android.mk @@ -27,7 +27,7 @@ LOCAL_SHARED_LIBRARIES := \ libui \ libgui -LOCAL_PRELINK_MODULE := false + LOCAL_MODULE:= libsensorservice diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk index 8a00a2e..9daaad8 100644 --- a/services/surfaceflinger/Android.mk +++ b/services/surfaceflinger/Android.mk @@ -41,7 +41,7 @@ LOCAL_SHARED_LIBRARIES := \ libGLESv1_CM \ libbinder \ libui \ - libsurfaceflinger_client + libgui LOCAL_C_INCLUDES := \ $(call include-path-for, corecg graphics) diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp index 64cff96..a774841 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp +++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp @@ -93,7 +93,11 @@ int DisplayHardware::getWidth() const { return mWidth; } int DisplayHardware::getHeight() const { return mHeight; } PixelFormat DisplayHardware::getFormat() const { return mFormat; } uint32_t DisplayHardware::getMaxTextureSize() const { return mMaxTextureSize; } -uint32_t DisplayHardware::getMaxViewportDims() const { return mMaxViewportDims; } + +uint32_t DisplayHardware::getMaxViewportDims() const { + return mMaxViewportDims[0] < mMaxViewportDims[1] ? + mMaxViewportDims[0] : mMaxViewportDims[1]; +} void DisplayHardware::init(uint32_t dpy) { @@ -228,7 +232,7 @@ void DisplayHardware::init(uint32_t dpy) eglQueryString(display, EGL_EXTENSIONS)); glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); - glGetIntegerv(GL_MAX_VIEWPORT_DIMS, &mMaxViewportDims); + glGetIntegerv(GL_MAX_VIEWPORT_DIMS, mMaxViewportDims); #ifdef EGL_ANDROID_swap_rectangle @@ -260,7 +264,7 @@ void DisplayHardware::init(uint32_t dpy) LOGI("version : %s", extensions.getVersion()); LOGI("extensions: %s", extensions.getExtension()); LOGI("GL_MAX_TEXTURE_SIZE = %d", mMaxTextureSize); - LOGI("GL_MAX_VIEWPORT_DIMS = %d", mMaxViewportDims); + LOGI("GL_MAX_VIEWPORT_DIMS = %d x %d", mMaxViewportDims[0], mMaxViewportDims[1]); LOGI("flags = %08x", mFlags); // Unbind the context from this thread diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.h b/services/surfaceflinger/DisplayHardware/DisplayHardware.h index ee7a2af..cdf89fd 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardware.h +++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.h @@ -108,7 +108,7 @@ private: PixelFormat mFormat; uint32_t mFlags; mutable uint32_t mPageFlipCount; - GLint mMaxViewportDims; + GLint mMaxViewportDims[2]; GLint mMaxTextureSize; HWComposer* mHwc; diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp index 90865da..59b7e5a 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp +++ b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp @@ -38,23 +38,10 @@ #include "SurfaceFlinger.h" // ---------------------------------------------------------------------------- -// the sim build doesn't have gettid - -#ifndef HAVE_GETTID -# define gettid getpid -#endif - -// ---------------------------------------------------------------------------- namespace android { -static char const * kSleepFileName = "/sys/power/wait_for_fb_sleep"; -static char const * kWakeFileName = "/sys/power/wait_for_fb_wake"; -static char const * const kOldSleepFileName = "/sys/android_power/wait_for_fb_sleep"; -static char const * const kOldWakeFileName = "/sys/android_power/wait_for_fb_wake"; - -// This dir exists if the framebuffer console is present, either built into -// the kernel or loaded as a module. -static char const * const kFbconSysDir = "/sys/class/graphics/fbcon"; +static char const * const kSleepFileName = "/sys/power/wait_for_fb_sleep"; +static char const * const kWakeFileName = "/sys/power/wait_for_fb_wake"; // ---------------------------------------------------------------------------- @@ -122,237 +109,13 @@ status_t DisplayHardwareBase::DisplayEventThread::releaseScreen() const status_t DisplayHardwareBase::DisplayEventThread::readyToRun() { - if (access(kSleepFileName, R_OK) || access(kWakeFileName, R_OK)) { - if (access(kOldSleepFileName, R_OK) || access(kOldWakeFileName, R_OK)) { - LOGE("Couldn't open %s or %s", kSleepFileName, kWakeFileName); - return NO_INIT; - } - kSleepFileName = kOldSleepFileName; - kWakeFileName = kOldWakeFileName; - } return NO_ERROR; } status_t DisplayHardwareBase::DisplayEventThread::initCheck() const { - return (((access(kSleepFileName, R_OK) == 0 && - access(kWakeFileName, R_OK) == 0) || - (access(kOldSleepFileName, R_OK) == 0 && - access(kOldWakeFileName, R_OK) == 0)) && - access(kFbconSysDir, F_OK) != 0) ? NO_ERROR : NO_INIT; -} - -// ---------------------------------------------------------------------------- - -pid_t DisplayHardwareBase::ConsoleManagerThread::sSignalCatcherPid = 0; - -DisplayHardwareBase::ConsoleManagerThread::ConsoleManagerThread( - const sp<SurfaceFlinger>& flinger) - : DisplayEventThreadBase(flinger), consoleFd(-1) -{ - sSignalCatcherPid = 0; - - // create a new console - char const * const ttydev = "/dev/tty0"; - int fd = open(ttydev, O_RDWR | O_SYNC); - if (fd<0) { - LOGE("Can't open %s", ttydev); - this->consoleFd = -errno; - return; - } - - // to make sure that we are in text mode - int res = ioctl(fd, KDSETMODE, (void*) KD_TEXT); - if (res<0) { - LOGE("ioctl(%d, KDSETMODE, ...) failed, res %d (%s)", - fd, res, strerror(errno)); - } - - // get the current console - struct vt_stat vs; - res = ioctl(fd, VT_GETSTATE, &vs); - if (res<0) { - LOGE("ioctl(%d, VT_GETSTATE, ...) failed, res %d (%s)", - fd, res, strerror(errno)); - this->consoleFd = -errno; - return; - } - - // switch to console 7 (which is what X normaly uses) - int vtnum = 7; - do { - res = ioctl(fd, VT_ACTIVATE, (void*)vtnum); - } while(res < 0 && errno == EINTR); - if (res<0) { - LOGE("ioctl(%d, VT_ACTIVATE, ...) failed, %d (%s) for %d", - fd, errno, strerror(errno), vtnum); - this->consoleFd = -errno; - return; - } - - do { - res = ioctl(fd, VT_WAITACTIVE, (void*)vtnum); - } while(res < 0 && errno == EINTR); - if (res<0) { - LOGE("ioctl(%d, VT_WAITACTIVE, ...) failed, %d %d %s for %d", - fd, res, errno, strerror(errno), vtnum); - this->consoleFd = -errno; - return; - } - - // open the new console - close(fd); - fd = open(ttydev, O_RDWR | O_SYNC); - if (fd<0) { - LOGE("Can't open new console %s", ttydev); - this->consoleFd = -errno; - return; - } - - /* disable console line buffer, echo, ... */ - struct termios ttyarg; - ioctl(fd, TCGETS , &ttyarg); - ttyarg.c_iflag = 0; - ttyarg.c_lflag = 0; - ioctl(fd, TCSETS , &ttyarg); - - // set up signals so we're notified when the console changes - // we can't use SIGUSR1 because it's used by the java-vm - vm.mode = VT_PROCESS; - vm.waitv = 0; - vm.relsig = SIGUSR2; - vm.acqsig = SIGUNUSED; - vm.frsig = 0; - - struct sigaction act; - sigemptyset(&act.sa_mask); - act.sa_handler = sigHandler; - act.sa_flags = 0; - sigaction(vm.relsig, &act, NULL); - - sigemptyset(&act.sa_mask); - act.sa_handler = sigHandler; - act.sa_flags = 0; - sigaction(vm.acqsig, &act, NULL); - - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, vm.relsig); - sigaddset(&mask, vm.acqsig); - sigprocmask(SIG_BLOCK, &mask, NULL); - - // switch to graphic mode - res = ioctl(fd, KDSETMODE, (void*)KD_GRAPHICS); - LOGW_IF(res<0, - "ioctl(%d, KDSETMODE, KD_GRAPHICS) failed, res %d", fd, res); - - this->prev_vt_num = vs.v_active; - this->vt_num = vtnum; - this->consoleFd = fd; -} - -DisplayHardwareBase::ConsoleManagerThread::~ConsoleManagerThread() -{ - if (this->consoleFd >= 0) { - int fd = this->consoleFd; - int prev_vt_num = this->prev_vt_num; - int res; - ioctl(fd, KDSETMODE, (void*)KD_TEXT); - do { - res = ioctl(fd, VT_ACTIVATE, (void*)prev_vt_num); - } while(res < 0 && errno == EINTR); - do { - res = ioctl(fd, VT_WAITACTIVE, (void*)prev_vt_num); - } while(res < 0 && errno == EINTR); - close(fd); - char const * const ttydev = "/dev/tty0"; - fd = open(ttydev, O_RDWR | O_SYNC); - ioctl(fd, VT_DISALLOCATE, 0); - close(fd); - } -} - -status_t DisplayHardwareBase::ConsoleManagerThread::readyToRun() -{ - if (this->consoleFd >= 0) { - sSignalCatcherPid = gettid(); - - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, vm.relsig); - sigaddset(&mask, vm.acqsig); - sigprocmask(SIG_BLOCK, &mask, NULL); - - int res = ioctl(this->consoleFd, VT_SETMODE, &vm); - if (res<0) { - LOGE("ioctl(%d, VT_SETMODE, ...) failed, %d (%s)", - this->consoleFd, errno, strerror(errno)); - } - return NO_ERROR; - } - return this->consoleFd; -} - -void DisplayHardwareBase::ConsoleManagerThread::requestExit() -{ - Thread::requestExit(); - if (sSignalCatcherPid != 0) { - // wake the thread up - kill(sSignalCatcherPid, SIGINT); - // wait for it... - } -} - -void DisplayHardwareBase::ConsoleManagerThread::sigHandler(int sig) -{ - // resend the signal to our signal catcher thread - LOGW("received signal %d in thread %d, resending to %d", - sig, gettid(), sSignalCatcherPid); - - // we absolutely need the delays below because without them - // our main thread never gets a chance to handle the signal. - usleep(10000); - kill(sSignalCatcherPid, sig); - usleep(10000); -} - -status_t DisplayHardwareBase::ConsoleManagerThread::releaseScreen() const -{ - int fd = this->consoleFd; - int err = ioctl(fd, VT_RELDISP, (void*)1); - LOGE_IF(err<0, "ioctl(%d, VT_RELDISP, 1) failed %d (%s)", - fd, errno, strerror(errno)); - return (err<0) ? (-errno) : status_t(NO_ERROR); -} - -bool DisplayHardwareBase::ConsoleManagerThread::threadLoop() -{ - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, vm.relsig); - sigaddset(&mask, vm.acqsig); - - int sig = 0; - sigwait(&mask, &sig); - - if (sig == vm.relsig) { - sp<SurfaceFlinger> flinger = mFlinger.promote(); - //LOGD("About to give-up screen, flinger = %p", flinger.get()); - if (flinger != 0) - flinger->screenReleased(0); - } else if (sig == vm.acqsig) { - sp<SurfaceFlinger> flinger = mFlinger.promote(); - //LOGD("Screen about to return, flinger = %p", flinger.get()); - if (flinger != 0) - flinger->screenAcquired(0); - } - - return true; -} - -status_t DisplayHardwareBase::ConsoleManagerThread::initCheck() const -{ - return consoleFd >= 0 ? NO_ERROR : NO_INIT; + return ((access(kSleepFileName, R_OK) == 0 && + access(kWakeFileName, R_OK) == 0)) ? NO_ERROR : NO_INIT; } // ---------------------------------------------------------------------------- @@ -362,10 +125,6 @@ DisplayHardwareBase::DisplayHardwareBase(const sp<SurfaceFlinger>& flinger, : mCanDraw(true), mScreenAcquired(true) { mDisplayEventThread = new DisplayEventThread(flinger); - if (mDisplayEventThread->initCheck() != NO_ERROR) { - // fall-back on the console - mDisplayEventThread = new ConsoleManagerThread(flinger); - } } DisplayHardwareBase::~DisplayHardwareBase() diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h index fa6a0c4..30eb258 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h +++ b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h @@ -73,24 +73,6 @@ private: virtual status_t initCheck() const; }; - class ConsoleManagerThread : public DisplayEventThreadBase - { - int consoleFd; - int vt_num; - int prev_vt_num; - vt_mode vm; - static void sigHandler(int sig); - static pid_t sSignalCatcherPid; - public: - ConsoleManagerThread(const sp<SurfaceFlinger>& flinger); - virtual ~ConsoleManagerThread(); - virtual bool threadLoop(); - virtual status_t readyToRun(); - virtual void requestExit(); - virtual status_t releaseScreen() const; - virtual status_t initCheck() const; - }; - sp<DisplayEventThreadBase> mDisplayEventThread; mutable int mCanDraw; mutable int mScreenAcquired; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 99d904d..b2f95cd 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -52,6 +52,7 @@ template <typename T> inline T min(T a, T b) { Layer::Layer(SurfaceFlinger* flinger, DisplayID display, const sp<Client>& client) : LayerBaseClient(flinger, display, client), + mFormat(PIXEL_FORMAT_NONE), mGLExtensions(GLExtensions::getInstance()), mNeedsBlending(true), mNeedsDithering(false), @@ -59,7 +60,8 @@ Layer::Layer(SurfaceFlinger* flinger, mProtectedByApp(false), mTextureManager(), mBufferManager(mTextureManager), - mWidth(0), mHeight(0), mNeedsScaling(false), mFixedSize(false) + mWidth(0), mHeight(0), + mNeedsScaling(false), mFixedSize(false) { } diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index a9fa1ef..e8f0328 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -395,7 +395,7 @@ bool SurfaceFlinger::threadLoop() if (LIKELY(mTransactionCount == 0)) { // if we're in a global transaction, don't do anything. const uint32_t mask = eTransactionNeeded | eTraversalNeeded; - uint32_t transactionFlags = getTransactionFlags(mask); + uint32_t transactionFlags = peekTransactionFlags(mask); if (LIKELY(transactionFlags)) { handleTransaction(transactionFlags); } @@ -490,7 +490,17 @@ void SurfaceFlinger::handleTransaction(uint32_t transactionFlags) Mutex::Autolock _l(mStateLock); const nsecs_t now = systemTime(); mDebugInTransaction = now; + + // Here we're guaranteed that some transaction flags are set + // so we can call handleTransactionLocked() unconditionally. + // We call getTransactionFlags(), which will also clear the flags, + // with mStateLock held to guarantee that mCurrentState won't change + // until the transaction is commited. + + const uint32_t mask = eTransactionNeeded | eTraversalNeeded; + transactionFlags = getTransactionFlags(mask); handleTransactionLocked(transactionFlags, ditchedLayers); + mLastTransactionTime = systemTime() - now; mDebugInTransaction = 0; invalidateHwcGeometry(); @@ -600,7 +610,7 @@ sp<FreezeLock> SurfaceFlinger::getFreezeLock() const } void SurfaceFlinger::computeVisibleRegions( - LayerVector& currentLayers, Region& dirtyRegion, Region& opaqueRegion) + const LayerVector& currentLayers, Region& dirtyRegion, Region& opaqueRegion) { const GraphicPlane& plane(graphicPlane(0)); const Transform& planeTransform(plane.transform()); @@ -735,8 +745,7 @@ void SurfaceFlinger::commitTransaction() void SurfaceFlinger::handlePageFlip() { bool visibleRegions = mVisibleRegionsDirty; - LayerVector& currentLayers( - const_cast<LayerVector&>(mDrawingState.layersSortedByZ)); + const LayerVector& currentLayers(mDrawingState.layersSortedByZ); visibleRegions |= lockPageFlip(currentLayers); const DisplayHardware& hw = graphicPlane(0).displayHardware(); @@ -748,9 +757,8 @@ void SurfaceFlinger::handlePageFlip() /* * rebuild the visible layer list */ + const size_t count = currentLayers.size(); mVisibleLayersSortedByZ.clear(); - const LayerVector& currentLayers(mDrawingState.layersSortedByZ); - size_t count = currentLayers.size(); mVisibleLayersSortedByZ.setCapacity(count); for (size_t i=0 ; i<count ; i++) { if (!currentLayers[i]->visibleRegionScreen.isEmpty()) @@ -1096,15 +1104,15 @@ status_t SurfaceFlinger::addLayer_l(const sp<LayerBase>& layer) ssize_t SurfaceFlinger::addClientLayer(const sp<Client>& client, const sp<LayerBaseClient>& lbc) { - Mutex::Autolock _l(mStateLock); - // attach this layer to the client - ssize_t name = client->attachLayer(lbc); + size_t name = client->attachLayer(lbc); + + Mutex::Autolock _l(mStateLock); // add this layer to the current state list addLayer_l(lbc); - return name; + return ssize_t(name); } status_t SurfaceFlinger::removeLayer(const sp<LayerBase>& layer) @@ -1155,6 +1163,11 @@ status_t SurfaceFlinger::invalidateLayerVisibility(const sp<LayerBase>& layer) return NO_ERROR; } +uint32_t SurfaceFlinger::peekTransactionFlags(uint32_t flags) +{ + return android_atomic_release_load(&mTransactionFlags); +} + uint32_t SurfaceFlinger::getTransactionFlags(uint32_t flags) { return android_atomic_and(~flags, &mTransactionFlags) & flags; @@ -1243,8 +1256,10 @@ int SurfaceFlinger::setOrientation(DisplayID dpy, return orientation; } -sp<ISurface> SurfaceFlinger::createSurface(const sp<Client>& client, int pid, - const String8& name, ISurfaceComposerClient::surface_data_t* params, +sp<ISurface> SurfaceFlinger::createSurface( + ISurfaceComposerClient::surface_data_t* params, + const String8& name, + const sp<Client>& client, DisplayID d, uint32_t w, uint32_t h, PixelFormat format, uint32_t flags) { @@ -2381,15 +2396,17 @@ status_t Client::initCheck() const { return NO_ERROR; } -ssize_t Client::attachLayer(const sp<LayerBaseClient>& layer) +size_t Client::attachLayer(const sp<LayerBaseClient>& layer) { - int32_t name = android_atomic_inc(&mNameGenerator); + Mutex::Autolock _l(mLock); + size_t name = mNameGenerator++; mLayers.add(name, layer); return name; } void Client::detachLayer(const LayerBaseClient* layer) { + Mutex::Autolock _l(mLock); // we do a linear search here, because this doesn't happen often const size_t count = mLayers.size(); for (size_t i=0 ; i<count ; i++) { @@ -2399,9 +2416,11 @@ void Client::detachLayer(const LayerBaseClient* layer) } } } -sp<LayerBaseClient> Client::getLayerUser(int32_t i) const { +sp<LayerBaseClient> Client::getLayerUser(int32_t i) const +{ + Mutex::Autolock _l(mLock); sp<LayerBaseClient> lbc; - const wp<LayerBaseClient>& layer(mLayers.valueFor(i)); + wp<LayerBaseClient> layer(mLayers.valueFor(i)); if (layer != 0) { lbc = layer.promote(); LOGE_IF(lbc==0, "getLayerUser(name=%d) is dead", int(i)); @@ -2416,12 +2435,12 @@ ssize_t Client::getTokenForSurface(const sp<ISurface>& sur) const { return -1; } sp<ISurface> Client::createSurface( - ISurfaceComposerClient::surface_data_t* params, int pid, + ISurfaceComposerClient::surface_data_t* params, const String8& name, DisplayID display, uint32_t w, uint32_t h, PixelFormat format, uint32_t flags) { - return mFlinger->createSurface(this, pid, name, params, + return mFlinger->createSurface(params, name, this, display, w, h, format, flags); } status_t Client::destroySurface(SurfaceID sid) { @@ -2515,7 +2534,7 @@ ssize_t UserClient::getTokenForSurface(const sp<ISurface>& sur) const } break; } - if (++name >= SharedBufferStack::NUM_LAYERS_MAX) + if (++name >= int32_t(SharedBufferStack::NUM_LAYERS_MAX)) name = NO_MEMORY; } while(name >= 0); @@ -2525,7 +2544,7 @@ ssize_t UserClient::getTokenForSurface(const sp<ISurface>& sur) const } sp<ISurface> UserClient::createSurface( - ISurfaceComposerClient::surface_data_t* params, int pid, + ISurfaceComposerClient::surface_data_t* params, const String8& name, DisplayID display, uint32_t w, uint32_t h, PixelFormat format, uint32_t flags) { @@ -2555,22 +2574,9 @@ sp<GraphicBuffer> GraphicBufferAlloc::createGraphicBuffer(uint32_t w, uint32_t h LOGE("createGraphicBuffer: unable to create GraphicBuffer"); return 0; } - Mutex::Autolock _l(mLock); - mBuffers.add(graphicBuffer); return graphicBuffer; } -void GraphicBufferAlloc::freeAllGraphicBuffersExcept(int bufIdx) { - Mutex::Autolock _l(mLock); - if (0 <= bufIdx && bufIdx < mBuffers.size()) { - sp<GraphicBuffer> b(mBuffers[bufIdx]); - mBuffers.clear(); - mBuffers.add(b); - } else { - mBuffers.clear(); - } -} - // --------------------------------------------------------------------------- GraphicPlane::GraphicPlane() diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index 9566819..1e16943 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -65,7 +65,7 @@ public: status_t initCheck() const; // protected by SurfaceFlinger::mStateLock - ssize_t attachLayer(const sp<LayerBaseClient>& layer); + size_t attachLayer(const sp<LayerBaseClient>& layer); void detachLayer(const LayerBaseClient* layer); sp<LayerBaseClient> getLayerUser(int32_t i) const; @@ -75,15 +75,21 @@ private: virtual sp<IMemoryHeap> getControlBlock() const; virtual ssize_t getTokenForSurface(const sp<ISurface>& sur) const; virtual sp<ISurface> createSurface( - surface_data_t* params, int pid, const String8& name, + surface_data_t* params, const String8& name, DisplayID display, uint32_t w, uint32_t h,PixelFormat format, uint32_t flags); virtual status_t destroySurface(SurfaceID surfaceId); virtual status_t setState(int32_t count, const layer_state_t* states); - DefaultKeyedVector< size_t, wp<LayerBaseClient> > mLayers; + // constant sp<SurfaceFlinger> mFlinger; - int32_t mNameGenerator; + + // protected by mLock + DefaultKeyedVector< size_t, wp<LayerBaseClient> > mLayers; + size_t mNameGenerator; + + // thread-safe + mutable Mutex mLock; }; class UserClient : public BnSurfaceComposerClient @@ -107,7 +113,7 @@ private: virtual sp<IMemoryHeap> getControlBlock() const; virtual ssize_t getTokenForSurface(const sp<ISurface>& sur) const; virtual sp<ISurface> createSurface( - surface_data_t* params, int pid, const String8& name, + surface_data_t* params, const String8& name, DisplayID display, uint32_t w, uint32_t h,PixelFormat format, uint32_t flags); virtual status_t destroySurface(SurfaceID surfaceId); @@ -125,14 +131,8 @@ class GraphicBufferAlloc : public BnGraphicBufferAlloc public: GraphicBufferAlloc(); virtual ~GraphicBufferAlloc(); - virtual sp<GraphicBuffer> createGraphicBuffer(uint32_t w, uint32_t h, PixelFormat format, uint32_t usage); - virtual void freeAllGraphicBuffersExcept(int bufIdx); - -private: - Vector<sp<GraphicBuffer> > mBuffers; - Mutex mLock; }; // --------------------------------------------------------------------------- @@ -238,9 +238,10 @@ private: friend class Layer; friend class LayerDim; - sp<ISurface> createSurface(const sp<Client>& client, - int pid, const String8& name, + sp<ISurface> createSurface( ISurfaceComposerClient::surface_data_t* params, + const String8& name, + const sp<Client>& client, DisplayID display, uint32_t w, uint32_t h, PixelFormat format, uint32_t flags); @@ -304,7 +305,7 @@ private: Vector< sp<LayerBase> >& ditchedLayers); void computeVisibleRegions( - LayerVector& currentLayers, + const LayerVector& currentLayers, Region& dirtyRegion, Region& wormholeRegion); @@ -324,6 +325,7 @@ private: status_t purgatorizeLayer_l(const sp<LayerBase>& layer); uint32_t getTransactionFlags(uint32_t flags); + uint32_t peekTransactionFlags(uint32_t flags); uint32_t setTransactionFlags(uint32_t flags); void commitTransaction(); @@ -371,7 +373,6 @@ private: // access must be protected by mStateLock mutable Mutex mStateLock; State mCurrentState; - State mDrawingState; volatile int32_t mTransactionFlags; volatile int32_t mTransactionCount; Condition mTransactionCV; @@ -395,6 +396,7 @@ private: // Can only accessed from the main thread, these members // don't need synchronization + State mDrawingState; Region mDirtyRegion; Region mDirtyRegionRemovedLayer; Region mInvalidRegion; diff --git a/services/surfaceflinger/TextureManager.cpp b/services/surfaceflinger/TextureManager.cpp index 9e24f90..bb63c37 100644 --- a/services/surfaceflinger/TextureManager.cpp +++ b/services/surfaceflinger/TextureManager.cpp @@ -144,7 +144,7 @@ status_t TextureManager::initEglImage(Image* pImage, } // construct an EGL_NATIVE_BUFFER_ANDROID - android_native_buffer_t* clientBuf = buffer->getNativeBuffer(); + ANativeWindowBuffer* clientBuf = buffer->getNativeBuffer(); // create the new EGLImageKHR const EGLint attrs[] = { diff --git a/services/surfaceflinger/tests/resize/Android.mk b/services/surfaceflinger/tests/resize/Android.mk index 24c2d01..d81679e 100644 --- a/services/surfaceflinger/tests/resize/Android.mk +++ b/services/surfaceflinger/tests/resize/Android.mk @@ -8,7 +8,7 @@ LOCAL_SHARED_LIBRARIES := \ libcutils \ libutils \ libui \ - libsurfaceflinger_client + libgui LOCAL_MODULE:= test-resize diff --git a/services/surfaceflinger/tests/resize/resize.cpp b/services/surfaceflinger/tests/resize/resize.cpp index 0ccca77..18c54b3 100644 --- a/services/surfaceflinger/tests/resize/resize.cpp +++ b/services/surfaceflinger/tests/resize/resize.cpp @@ -29,13 +29,6 @@ using namespace android; namespace android { -class Test { -public: - static const sp<ISurface>& getISurface(const sp<Surface>& s) { - return s->getISurface(); - } -}; -}; int main(int argc, char** argv) { diff --git a/services/surfaceflinger/tests/screencap/Android.mk b/services/surfaceflinger/tests/screencap/Android.mk index 1cfb471..5cdd1a8 100644 --- a/services/surfaceflinger/tests/screencap/Android.mk +++ b/services/surfaceflinger/tests/screencap/Android.mk @@ -10,7 +10,7 @@ LOCAL_SHARED_LIBRARIES := \ libbinder \ libskia \ libui \ - libsurfaceflinger_client + libgui LOCAL_MODULE:= test-screencap diff --git a/services/surfaceflinger/tests/surface/Android.mk b/services/surfaceflinger/tests/surface/Android.mk index ce0e807..c59060e 100644 --- a/services/surfaceflinger/tests/surface/Android.mk +++ b/services/surfaceflinger/tests/surface/Android.mk @@ -9,7 +9,7 @@ LOCAL_SHARED_LIBRARIES := \ libutils \ libbinder \ libui \ - libsurfaceflinger_client + libgui LOCAL_MODULE:= test-surface diff --git a/services/surfaceflinger/tests/surface/surface.cpp b/services/surfaceflinger/tests/surface/surface.cpp index 67ecf7e..5265f91 100644 --- a/services/surfaceflinger/tests/surface/surface.cpp +++ b/services/surfaceflinger/tests/surface/surface.cpp @@ -53,7 +53,7 @@ int main(int argc, char** argv) printf("window=%p\n", window); int err = native_window_set_buffer_count(window, 8); - android_native_buffer_t* buffer; + ANativeWindowBuffer* buffer; for (int i=0 ; i<8 ; i++) { window->dequeueBuffer(window, &buffer); diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index 186b349..295f324 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -7,7 +7,9 @@ LOCAL_MODULE_TAGS := tests # Include all test java files. LOCAL_SRC_FILES := $(call all-java-files-under, src) -LOCAL_STATIC_JAVA_LIBRARIES := easymocklib +LOCAL_STATIC_JAVA_LIBRARIES := \ + easymocklib \ + guava LOCAL_JAVA_LIBRARIES := android.test.runner services diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index f115f42..f8d1426 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -15,11 +15,15 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.frameworks.servicestests"> + package="com.android.frameworks.servicestests"> <uses-permission android:name="android.permission.READ_LOGS" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <uses-permission android:name="android.permission.READ_PHONE_STATE" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.BROADCAST_STICKY" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <application> <uses-library android:name="android.test.runner" /> diff --git a/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java new file mode 100644 index 0000000..f20d5e5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/ThrottleServiceTest.java @@ -0,0 +1,420 @@ +/* + * 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 org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.eq; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.isA; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.reset; +import static org.easymock.EasyMock.verify; + +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.AbstractFuture; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.INetworkManagementEventObserver; +import android.net.NetworkStats; +import android.net.ThrottleManager; +import android.os.IBinder; +import android.os.INetworkManagementService; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.provider.Settings; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.Suppress; +import android.text.format.DateUtils; +import android.util.Log; +import android.util.TrustedTime; + +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Future; + +/** + * Tests for {@link ThrottleService}. + */ +public class ThrottleServiceTest extends AndroidTestCase { + private static final String TAG = "ThrottleServiceTest"; + + private static final long MB_IN_BYTES = 1024 * 1024; + + private static final int TEST_KBITPS = 222; + private static final int TEST_RESET_DAY = 11; + + private static final String TEST_IFACE = "test0"; + + private WatchingContext mWatchingContext; + private INetworkManagementService mMockNMService; + private TrustedTime mMockTime; + + private ThrottleService mThrottleService; + + @Override + public void setUp() throws Exception { + super.setUp(); + + mWatchingContext = new WatchingContext(getContext()); + + mMockNMService = createMock(INetworkManagementService.class); + mMockTime = createMock(TrustedTime.class); + + mThrottleService = new ThrottleService( + mWatchingContext, mMockNMService, mMockTime, TEST_IFACE); + } + + @Override + public void tearDown() throws Exception { + mWatchingContext = null; + mMockNMService = null; + + mThrottleService.shutdown(); + mThrottleService = null; + + clearThrottlePolicy(); + + super.tearDown(); + } + + public void testNoPolicyNotThrottled() throws Exception { + expectTimeCurrent(); + expectSystemReady(); + + // provide stats without policy, verify not throttled + expectGetInterfaceCounter(1 * MB_IN_BYTES, 2 * MB_IN_BYTES); + expectSetInterfaceThrottle(-1, -1); + + replay(mMockTime, mMockNMService); + systemReady(); + verify(mMockTime, mMockNMService); + } + + public void testUnderLimitNotThrottled() throws Exception { + setThrottlePolicy(200 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY); + + expectTimeCurrent(); + expectSystemReady(); + + // provide stats under limits, and verify not throttled + expectGetInterfaceCounter(1 * MB_IN_BYTES, 2 * MB_IN_BYTES); + expectSetInterfaceThrottle(-1, -1); + + replay(mMockTime, mMockNMService); + systemReady(); + verify(mMockTime, mMockNMService); + } + + public void testOverLimitThrottled() throws Exception { + setThrottlePolicy(200 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY); + + expectTimeCurrent(); + expectSystemReady(); + + // provide stats over limits, and verify throttled + expectGetInterfaceCounter(500 * MB_IN_BYTES, 600 * MB_IN_BYTES); + expectSetInterfaceThrottle(TEST_KBITPS, TEST_KBITPS); + + replay(mMockTime, mMockNMService); + systemReady(); + verify(mMockTime, mMockNMService); + } + + public void testUnderThenOverLimitThrottled() throws Exception { + setThrottlePolicy(201 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY); + + expectTimeCurrent(); + expectSystemReady(); + + // provide stats right under 201MB limit, verify not throttled + expectGetInterfaceCounter(100 * MB_IN_BYTES, 100 * MB_IN_BYTES); + expectSetInterfaceThrottle(-1, -1); + + replay(mMockTime, mMockNMService); + systemReady(); + verify(mMockTime, mMockNMService); + reset(mMockTime, mMockNMService); + + expectTimeCurrent(); + + // adjust usage to bump over limit, verify throttle kicks in + expectGetInterfaceCounter(105 * MB_IN_BYTES, 100 * MB_IN_BYTES); + expectSetInterfaceThrottle(TEST_KBITPS, TEST_KBITPS); + + // and kick poll event which should throttle + replay(mMockTime, mMockNMService); + forceServicePoll(); + verify(mMockTime, mMockNMService); + } + + public void testUpdatedPolicyThrottled() throws Exception { + setThrottlePolicy(500 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY); + + expectTimeCurrent(); + expectSystemReady(); + + // provide stats under limit, verify not throttled + expectGetInterfaceCounter(50 * MB_IN_BYTES, 50 * MB_IN_BYTES); + expectSetInterfaceThrottle(-1, -1); + + replay(mMockTime, mMockNMService); + systemReady(); + verify(mMockTime, mMockNMService); + reset(mMockTime, mMockNMService); + + expectTimeCurrent(); + + // provide same stats, but verify that modified policy will throttle + expectGetInterfaceCounter(50 * MB_IN_BYTES, 50 * MB_IN_BYTES); + expectSetInterfaceThrottle(TEST_KBITPS, TEST_KBITPS); + + replay(mMockTime, mMockNMService); + + // now adjust policy to bump usage over limit + setThrottlePolicy(5 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY); + + // and wait for policy updated broadcast + mWatchingContext.nextBroadcastIntent(ThrottleManager.POLICY_CHANGED_ACTION).get(); + + verify(mMockTime, mMockNMService); + } + + public void testWithPolicyOverLimitThrottledAndRemovedAfterCycle() throws Exception { + setThrottlePolicy(90 * MB_IN_BYTES, TEST_KBITPS, TEST_RESET_DAY); + + final long baseTime = System.currentTimeMillis(); + + expectTime(baseTime); + expectSystemReady(); + + // provide stats over limit, verify throttle kicks in + expectGetInterfaceCounter(50 * MB_IN_BYTES, 50 * MB_IN_BYTES); + expectSetInterfaceThrottle(TEST_KBITPS, TEST_KBITPS); + + replay(mMockTime, mMockNMService); + systemReady(); + verify(mMockTime, mMockNMService); + reset(mMockTime, mMockNMService); + + // pretend that time has jumped forward two months + expectTime(baseTime + DateUtils.WEEK_IN_MILLIS * 8); + + // provide slightly updated stats, but verify throttle is removed + expectGetInterfaceCounter(60 * MB_IN_BYTES, 60 * MB_IN_BYTES); + expectSetInterfaceThrottle(-1, -1); + + // and kick poll event which should throttle + replay(mMockTime, mMockNMService); + forceServiceReset(); + verify(mMockTime, mMockNMService); + } + + @Suppress + public void testReturnStats() throws Exception { + final IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + final INetworkManagementService nmService = INetworkManagementService.Stub.asInterface(b); + + // test is currently no-op, just exercises stats apis + Log.d(TAG, nmService.getNetworkStatsSummary().toString()); + Log.d(TAG, nmService.getNetworkStatsDetail().toString()); + } + + /** + * Persist the given {@link ThrottleService} policy into {@link Settings}. + */ + public void setThrottlePolicy(long thresholdBytes, int valueKbitps, int resetDay) { + final ContentResolver resolver = getContext().getContentResolver(); + Settings.Secure.putLong(resolver, Settings.Secure.THROTTLE_THRESHOLD_BYTES, thresholdBytes); + Settings.Secure.putInt(resolver, Settings.Secure.THROTTLE_VALUE_KBITSPS, valueKbitps); + Settings.Secure.putInt(resolver, Settings.Secure.THROTTLE_RESET_DAY, resetDay); + } + + /** + * Clear any {@link ThrottleService} policy from {@link Settings}. + */ + public void clearThrottlePolicy() { + final ContentResolver resolver = getContext().getContentResolver(); + Settings.Secure.putString(resolver, Settings.Secure.THROTTLE_THRESHOLD_BYTES, null); + Settings.Secure.putString(resolver, Settings.Secure.THROTTLE_VALUE_KBITSPS, null); + Settings.Secure.putString(resolver, Settings.Secure.THROTTLE_RESET_DAY, null); + } + + /** + * Expect any {@link TrustedTime} mock calls, and respond with + * {@link System#currentTimeMillis()}. + */ + public void expectTimeCurrent() throws Exception { + expectTime(System.currentTimeMillis()); + } + + /** + * Expect any {@link TrustedTime} mock calls, and respond with the given + * time in response to {@link TrustedTime#currentTimeMillis()}. + */ + public void expectTime(long currentTime) throws Exception { + expect(mMockTime.forceRefresh()).andReturn(false).anyTimes(); + expect(mMockTime.hasCache()).andReturn(true).anyTimes(); + expect(mMockTime.currentTimeMillis()).andReturn(currentTime).anyTimes(); + expect(mMockTime.getCacheAge()).andReturn(0L).anyTimes(); + expect(mMockTime.getCacheCertainty()).andReturn(0L).anyTimes(); + } + + /** + * Expect {@link ThrottleService#systemReady()} generated calls, such as + * connecting with {@link NetworkManagementService} mock. + */ + public void expectSystemReady() throws Exception { + mMockNMService.registerObserver(isA(INetworkManagementEventObserver.class)); + expectLastCall().atLeastOnce(); + } + + /** + * Expect {@link NetworkManagementService#getNetworkStatsSummary()} mock + * calls, responding with the given counter values. + */ + public void expectGetInterfaceCounter(long rx, long tx) throws Exception { + // TODO: provide elapsedRealtime mock to match TimeAuthority + final NetworkStats.Builder stats = new NetworkStats.Builder( + SystemClock.elapsedRealtime(), 1); + stats.addEntry(TEST_IFACE, NetworkStats.UID_ALL, rx, tx); + + expect(mMockNMService.getNetworkStatsSummary()).andReturn(stats.build()).atLeastOnce(); + } + + /** + * Expect {@link NetworkManagementService#setInterfaceThrottle} mock call + * with the specified parameters. + */ + public void expectSetInterfaceThrottle(int rx, int tx) throws Exception { + mMockNMService.setInterfaceThrottle(isA(String.class), eq(rx), eq(tx)); + expectLastCall().atLeastOnce(); + } + + /** + * Dispatch {@link ThrottleService#systemReady()} and block until finished. + */ + public void systemReady() throws Exception { + final Future<Intent> policyChanged = mWatchingContext.nextBroadcastIntent( + ThrottleManager.POLICY_CHANGED_ACTION); + final Future<Intent> pollAction = mWatchingContext.nextBroadcastIntent( + ThrottleManager.THROTTLE_POLL_ACTION); + + mThrottleService.systemReady(); + + // wait for everything to settle; for policy to update and for first poll + policyChanged.get(); + pollAction.get(); + } + + /** + * Dispatch {@link ThrottleService#dispatchPoll()} and block until finished. + */ + public void forceServicePoll() throws Exception { + // during systemReady() service already pushed a sticky broadcast, so we + // need to skip the immediate and wait for the updated sticky. + final Future<Intent> pollAction = mWatchingContext.nextBroadcastIntent( + ThrottleManager.THROTTLE_POLL_ACTION); + + mThrottleService.dispatchPoll(); + + pollAction.get(); + } + + /** + * Dispatch {@link ThrottleService#dispatchReset()} and block until finished. + */ + public void forceServiceReset() throws Exception { + // during systemReady() service already pushed a sticky broadcast, so we + // need to skip the immediate and wait for the updated sticky. + final Future<Intent> pollAction = mWatchingContext.nextBroadcastIntent( + ThrottleManager.THROTTLE_POLL_ACTION); + + mThrottleService.dispatchReset(); + + pollAction.get(); + } + + + /** + * {@link ContextWrapper} that can attach listeners for upcoming + * {@link Context#sendBroadcast(Intent)}. + */ + private static class WatchingContext extends ContextWrapper { + private List<LocalBroadcastReceiver> mReceivers = Lists.newArrayList(); + + public class LocalBroadcastReceiver extends AbstractFuture<Intent> { + private IntentFilter mFilter; + + public LocalBroadcastReceiver(IntentFilter filter) { + mFilter = filter; + } + + public boolean dispatchBroadcast(Intent intent) { + if (mFilter.match(getContentResolver(), intent, false, TAG) > 0) { + set(intent); + return true; + } else { + return false; + } + } + } + + public WatchingContext(Context base) { + super(base); + } + + public Future<Intent> nextBroadcastIntent(String action) { + return nextBroadcastIntent(new IntentFilter(action)); + } + + public Future<Intent> nextBroadcastIntent(IntentFilter filter) { + final LocalBroadcastReceiver receiver = new LocalBroadcastReceiver(filter); + synchronized (mReceivers) { + mReceivers.add(receiver); + } + return receiver; + } + + @Override + public void sendBroadcast(Intent intent) { + synchronized (mReceivers) { + final Iterator<LocalBroadcastReceiver> i = mReceivers.iterator(); + while (i.hasNext()) { + final LocalBroadcastReceiver receiver = i.next(); + if (receiver.dispatchBroadcast(intent)) { + i.remove(); + } + } + } + } + + @Override + public void sendStickyBroadcast(Intent intent) { + sendBroadcast(intent); + } + + @Override + public void removeStickyBroadcast(Intent intent) { + // ignored + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java b/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java index 71e8e2a..60677df 100755 --- a/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java +++ b/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java @@ -15,17 +15,25 @@ */ package com.android.server.location; -import java.util.ArrayList; -import java.util.List; -import java.util.Timer; - import android.location.Country; import android.location.CountryListener; import android.location.Location; import android.location.LocationListener; +import android.location.LocationManager; import android.test.AndroidTestCase; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Timer; + public class LocationBasedCountryDetectorTest extends AndroidTestCase { + private static final List<String> sEnabledProviders = Arrays.asList( + LocationManager.GPS_PROVIDER, LocationManager.PASSIVE_PROVIDER); private class TestCountryDetector extends LocationBasedCountryDetector { public static final int TOTAL_PROVIDERS = 2; protected Object countryFoundLocker = new Object(); @@ -33,7 +41,7 @@ public class LocationBasedCountryDetectorTest extends AndroidTestCase { private final Location mLocation; private final String mCountry; private final long mQueryLocationTimeout; - private List<LocationListener> mListeners; + private Map<String, LocationListener> mListeners; public TestCountryDetector(String country, String provider) { this(country, provider, 1000 * 60 * 5); @@ -44,7 +52,7 @@ public class LocationBasedCountryDetectorTest extends AndroidTestCase { mCountry = country; mLocation = new Location(provider); mQueryLocationTimeout = queryLocationTimeout; - mListeners = new ArrayList<LocationListener>(); + mListeners = new HashMap<String, LocationListener>(); } @Override @@ -69,16 +77,40 @@ public class LocationBasedCountryDetectorTest extends AndroidTestCase { return mLocation; } + private Set<String> mAcceptableProviders; + + public void setAcceptableProvider(Set<String> acceptableProviders) { + mAcceptableProviders = acceptableProviders; + } + + @Override + protected boolean isAcceptableProvider(String provider) { + if (mAcceptableProviders != null) { + return mAcceptableProviders.contains(provider); + } else { + return true; + } + } + @Override - protected void registerEnabledProviders(List<LocationListener> listeners) { - mListeners.addAll(listeners); + protected void registerListener(String provider, LocationListener listener) { + assertNotNull(provider); + mListeners.put(provider, listener); } @Override - protected void unregisterProviders(List<LocationListener> listeners) { - for (LocationListener listener : mLocationListeners) { - assertTrue(mListeners.remove(listener)); + protected void unregisterListener(LocationListener listener) { + for (Entry<String, LocationListener> entry : mListeners.entrySet()) { + if (entry.getValue().equals(listener)) { + mListeners.remove(entry.getKey()); + return; + } } + fail("Not registered"); + } + + public Map<String, LocationListener> getListeners() { + return mListeners; } @Override @@ -87,8 +119,8 @@ public class LocationBasedCountryDetectorTest extends AndroidTestCase { } @Override - protected int getTotalEnabledProviders() { - return TOTAL_PROVIDERS; + protected List<String> getEnabledProviders() { + return sEnabledProviders; } public void notifyLocationFound() { @@ -140,16 +172,39 @@ public class LocationBasedCountryDetectorTest extends AndroidTestCase { } public void testFindingCountry() { + testFindingCountryCommon(null); + } + + public void testFindingCountryWithAcceptableProvider() { + testFindingCountryCommon(new HashSet<String>(Arrays.asList("passive"))); + } + + private void testFindingCountryCommon(Set<String> acceptableProviders) { final String country = "us"; final String provider = "Good"; CountryListenerImpl countryListener = new CountryListenerImpl(); TestCountryDetector detector = new TestCountryDetector(country, provider); + + if (acceptableProviders != null) { + detector.setAcceptableProvider(acceptableProviders); + } + detector.setCountryListener(countryListener); detector.detectCountry(); - assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + + if (acceptableProviders != null) { + assertEquals(acceptableProviders.size(), detector.getListenersCount()); + Map<String, LocationListener> listeners = detector.getListeners(); + for (String acceptableProvider : acceptableProviders) { + assertTrue(listeners.containsKey(acceptableProvider)); + } + } else { + assertEquals(TestCountryDetector.TOTAL_PROVIDERS, detector.getListenersCount()); + } + detector.notifyLocationFound(); // All listeners should be unregistered - assertEquals(detector.getListenersCount(), 0); + assertEquals(0, detector.getListenersCount()); assertNull(detector.getTimer()); Thread queryThread = waitForQueryThreadLaunched(detector); detector.notifyCountryFound(); @@ -168,10 +223,10 @@ public class LocationBasedCountryDetectorTest extends AndroidTestCase { TestCountryDetector detector = new TestCountryDetector(country, provider); detector.setCountryListener(countryListener); detector.detectCountry(); - assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + assertEquals(TestCountryDetector.TOTAL_PROVIDERS, detector.getListenersCount()); detector.notifyLocationFound(); // All listeners should be unregistered - assertEquals(detector.getListenersCount(), 0); + assertEquals(0, detector.getListenersCount()); // The time should be stopped assertNull(detector.getTimer()); Thread queryThread = waitForQueryThreadLaunched(detector); @@ -193,10 +248,10 @@ public class LocationBasedCountryDetectorTest extends AndroidTestCase { TestCountryDetector detector = new TestCountryDetector(country, provider); detector.setCountryListener(countryListener); detector.detectCountry(); - assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + assertEquals(TestCountryDetector.TOTAL_PROVIDERS, detector.getListenersCount()); detector.stop(); // All listeners should be unregistered - assertEquals(detector.getListenersCount(), 0); + assertEquals(0, detector.getListenersCount()); // The time should be stopped assertNull(detector.getTimer()); // QueryThread should still be NULL @@ -217,10 +272,10 @@ public class LocationBasedCountryDetectorTest extends AndroidTestCase { CountryListenerImpl countryListener = new CountryListenerImpl(); detector.setCountryListener(countryListener); detector.detectCountry(); - assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + assertEquals(TestCountryDetector.TOTAL_PROVIDERS, detector.getListenersCount()); waitForTimerReset(detector); // All listeners should be unregistered - assertEquals(detector.getListenersCount(), 0); + assertEquals(0, detector.getListenersCount()); // QueryThread should still be NULL assertNull(detector.getQueryThread()); assertTrue(countryListener.notified()); @@ -248,10 +303,10 @@ public class LocationBasedCountryDetectorTest extends AndroidTestCase { CountryListenerImpl countryListener = new CountryListenerImpl(); detector.setCountryListener(countryListener); detector.detectCountry(); - assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + assertEquals(TestCountryDetector.TOTAL_PROVIDERS, detector.getListenersCount()); detector.notifyLocationFound(); // All listeners should be unregistered - assertEquals(detector.getListenersCount(), 0); + assertEquals(0, detector.getListenersCount()); assertNull(detector.getTimer()); Thread queryThread = waitForQueryThreadLaunched(detector); detector.notifyCountryFound(); @@ -272,10 +327,10 @@ public class LocationBasedCountryDetectorTest extends AndroidTestCase { CountryListenerImpl countryListener = new CountryListenerImpl(); detector.setCountryListener(countryListener); detector.detectCountry(); - assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS); + assertEquals(TestCountryDetector.TOTAL_PROVIDERS, detector.getListenersCount()); waitForTimerReset(detector); // All listeners should be unregistered - assertEquals(detector.getListenersCount(), 0); + assertEquals(0, detector.getListenersCount()); Thread queryThread = waitForQueryThreadLaunched(detector); detector.notifyCountryFound(); // Wait for query thread ending diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java new file mode 100644 index 0000000..e8188e7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -0,0 +1,107 @@ +/* + * 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.pm; + +import com.android.server.pm.UserManager; + +import android.content.pm.UserInfo; +import android.os.Debug; +import android.os.Environment; +import android.test.AndroidTestCase; + +import java.util.List; + +/** Test {@link UserManager} functionality. */ +public class UserManagerTest extends AndroidTestCase { + + UserManager mUserManager = null; + + @Override + public void setUp() throws Exception { + mUserManager = new UserManager(Environment.getExternalStorageDirectory(), + Environment.getExternalStorageDirectory()); + } + + @Override + public void tearDown() throws Exception { + List<UserInfo> users = mUserManager.getUsers(); + // Remove all except the primary user + for (UserInfo user : users) { + if (!user.isPrimary()) { + mUserManager.removeUser(user.id); + } + } + } + + public void testHasPrimary() throws Exception { + assertTrue(findUser(0)); + } + + public void testAddUser() throws Exception { + final UserManager details = mUserManager; + + UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null); + assertTrue(userInfo != null); + + List<UserInfo> list = details.getUsers(); + boolean found = false; + for (UserInfo user : list) { + if (user.id == userInfo.id && user.name.equals("Guest 1") + && user.isGuest() + && !user.isAdmin() + && !user.isPrimary()) { + found = true; + } + } + assertTrue(found); + } + + public void testAdd2Users() throws Exception { + final UserManager details = mUserManager; + + UserInfo user1 = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null); + UserInfo user2 = details.createUser("User 2", UserInfo.FLAG_ADMIN, null); + + assertTrue(user1 != null); + assertTrue(user2 != null); + + assertTrue(findUser(0)); + assertTrue(findUser(user1.id)); + assertTrue(findUser(user2.id)); + } + + public void testRemoveUser() throws Exception { + final UserManager details = mUserManager; + + UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null); + + details.removeUser(userInfo.id); + + assertFalse(findUser(userInfo.id)); + } + + private boolean findUser(int id) { + List<UserInfo> list = mUserManager.getUsers(); + + for (UserInfo user : list) { + if (user.id == id) { + return true; + } + } + return false; + } +} |