summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
Diffstat (limited to 'services')
-rw-r--r--services/audioflinger/A2dpAudioInterface.cpp102
-rw-r--r--services/audioflinger/A2dpAudioInterface.h3
-rw-r--r--services/audioflinger/AudioFlinger.cpp73
-rw-r--r--services/audioflinger/AudioFlinger.h6
-rw-r--r--services/audioflinger/AudioPolicyManagerBase.cpp112
-rw-r--r--services/audioflinger/AudioPolicyService.cpp26
-rw-r--r--services/audioflinger/AudioPolicyService.h5
-rw-r--r--services/camera/libcameraservice/Android.mk3
-rw-r--r--services/camera/libcameraservice/CameraHardwareStub.cpp4
-rw-r--r--services/camera/libcameraservice/CameraHardwareStub.h2
-rw-r--r--services/camera/libcameraservice/CameraService.cpp322
-rw-r--r--services/camera/libcameraservice/CameraService.h27
-rw-r--r--services/input/Android.mk57
-rw-r--r--services/input/EventHub.cpp1095
-rw-r--r--services/input/EventHub.h327
-rw-r--r--services/input/InputApplication.h51
-rw-r--r--services/input/InputDispatcher.cpp3862
-rw-r--r--services/input/InputDispatcher.h1001
-rw-r--r--services/input/InputManager.cpp93
-rw-r--r--services/input/InputManager.h109
-rw-r--r--services/input/InputReader.cpp3708
-rw-r--r--services/input/InputReader.h973
-rw-r--r--services/input/InputWindow.cpp46
-rw-r--r--services/input/InputWindow.h159
-rw-r--r--services/input/PointerController.cpp415
-rw-r--r--services/input/PointerController.h129
-rw-r--r--services/input/tests/Android.mk50
-rw-r--r--services/input/tests/InputDispatcher_test.cpp230
-rw-r--r--services/input/tests/InputReader_test.cpp3696
-rw-r--r--services/java/com/android/server/AccessibilityManagerService.java71
-rw-r--r--services/java/com/android/server/AlarmManagerService.java3
-rw-r--r--services/java/com/android/server/AppWidgetService.java257
-rw-r--r--services/java/com/android/server/BackupManagerService.java232
-rw-r--r--services/java/com/android/server/BatteryService.java183
-rw-r--r--services/java/com/android/server/ClipboardService.java224
-rw-r--r--services/java/com/android/server/ConnectivityService.java1000
-rw-r--r--services/java/com/android/server/CountryDetectorService.java204
-rw-r--r--services/java/com/android/server/DemoDataSet.java140
-rw-r--r--services/java/com/android/server/DevicePolicyManagerService.java1160
-rw-r--r--services/java/com/android/server/DeviceStorageMonitorService.java12
-rw-r--r--services/java/com/android/server/DockObserver.java10
-rw-r--r--services/java/com/android/server/DropBoxManagerService.java28
-rw-r--r--services/java/com/android/server/HeadsetObserver.java183
-rw-r--r--services/java/com/android/server/InputApplication.java13
-rw-r--r--services/java/com/android/server/InputApplicationHandle.java45
-rw-r--r--services/java/com/android/server/InputManager.java223
-rw-r--r--services/java/com/android/server/InputMethodManagerService.java1310
-rw-r--r--services/java/com/android/server/InputWindow.java47
-rw-r--r--services/java/com/android/server/InputWindowHandle.java51
-rw-r--r--services/java/com/android/server/LightsService.java4
-rw-r--r--services/java/com/android/server/LocationManagerService.java7
-rw-r--r--services/java/com/android/server/MountService.java164
-rw-r--r--services/java/com/android/server/NetworkManagementService.java34
-rw-r--r--services/java/com/android/server/NetworkTimeUpdateService.java297
-rwxr-xr-xservices/java/com/android/server/NotificationManagerService.java175
-rw-r--r--services/java/com/android/server/PackageManagerService.java220
-rw-r--r--services/java/com/android/server/PowerManagerService.java101
-rw-r--r--services/java/com/android/server/ProcessStats.java11
-rw-r--r--services/java/com/android/server/SamplingProfilerService.java117
-rw-r--r--services/java/com/android/server/ScreenRotationAnimation.java420
-rw-r--r--services/java/com/android/server/ShutdownActivity.java11
-rw-r--r--services/java/com/android/server/StatusBarManagerService.java242
-rw-r--r--services/java/com/android/server/StrictModeFlash.java114
-rw-r--r--services/java/com/android/server/SystemBackupAgent.java15
-rw-r--r--services/java/com/android/server/SystemServer.java173
-rw-r--r--services/java/com/android/server/TelephonyRegistry.java246
-rw-r--r--services/java/com/android/server/ThrottleService.java234
-rw-r--r--services/java/com/android/server/UsbObserver.java207
-rw-r--r--services/java/com/android/server/UsbService.java252
-rwxr-xr-xservices/java/com/android/server/VibratorService.java5
-rw-r--r--services/java/com/android/server/WifiService.java2059
-rw-r--r--services/java/com/android/server/WifiStateTracker.java0
-rw-r--r--services/java/com/android/server/WifiWatchdogService.java34
-rw-r--r--services/java/com/android/server/WindowManagerService.java2390
-rw-r--r--services/java/com/android/server/WiredAccessoryObserver.java299
-rwxr-xr-xservices/java/com/android/server/am/ActivityManagerService.java837
-rw-r--r--services/java/com/android/server/am/ActivityRecord.java72
-rw-r--r--services/java/com/android/server/am/ActivityStack.java552
-rw-r--r--services/java/com/android/server/am/BaseErrorDialog.java2
-rw-r--r--services/java/com/android/server/am/BatteryStatsService.java39
-rw-r--r--services/java/com/android/server/am/LaunchWarningWindow.java8
-rw-r--r--services/java/com/android/server/am/PendingIntentRecord.java39
-rw-r--r--services/java/com/android/server/am/TaskRecord.java14
-rw-r--r--services/java/com/android/server/am/UriPermissionOwner.java2
-rw-r--r--services/java/com/android/server/connectivity/Tethering.java268
-rwxr-xr-xservices/java/com/android/server/location/ComprehensiveCountryDetector.java359
-rw-r--r--services/java/com/android/server/location/CountryDetectorBase.java72
-rwxr-xr-xservices/java/com/android/server/location/LocationBasedCountryDetector.java235
-rw-r--r--services/jni/Android.mk31
-rw-r--r--services/jni/com_android_server_InputApplication.cpp95
-rw-r--r--services/jni/com_android_server_InputApplication.h32
-rw-r--r--services/jni/com_android_server_InputApplicationHandle.cpp121
-rw-r--r--services/jni/com_android_server_InputApplicationHandle.h44
-rw-r--r--services/jni/com_android_server_InputManager.cpp903
-rw-r--r--services/jni/com_android_server_InputWindow.cpp208
-rw-r--r--services/jni/com_android_server_InputWindow.h32
-rw-r--r--services/jni/com_android_server_InputWindowHandle.cpp135
-rw-r--r--services/jni/com_android_server_InputWindowHandle.h45
-rw-r--r--services/jni/com_android_server_VibratorService.cpp6
-rw-r--r--services/jni/onload.cpp8
-rw-r--r--services/surfaceflinger/Android.mk4
-rw-r--r--services/surfaceflinger/BlurFilter.cpp376
-rw-r--r--services/surfaceflinger/BlurFilter.h35
-rw-r--r--services/surfaceflinger/DisplayHardware/DisplayHardware.cpp44
-rw-r--r--services/surfaceflinger/DisplayHardware/DisplayHardware.h15
-rw-r--r--services/surfaceflinger/DisplayHardware/HWComposer.cpp132
-rw-r--r--services/surfaceflinger/DisplayHardware/HWComposer.h75
-rw-r--r--services/surfaceflinger/Layer.cpp289
-rw-r--r--services/surfaceflinger/Layer.h17
-rw-r--r--services/surfaceflinger/LayerBase.cpp57
-rw-r--r--services/surfaceflinger/LayerBase.h20
-rw-r--r--services/surfaceflinger/LayerBlur.cpp251
-rw-r--r--services/surfaceflinger/LayerBlur.h65
-rw-r--r--services/surfaceflinger/LayerBuffer.cpp701
-rw-r--r--services/surfaceflinger/LayerBuffer.h224
-rw-r--r--services/surfaceflinger/LayerDim.cpp37
-rw-r--r--services/surfaceflinger/LayerDim.h7
-rw-r--r--services/surfaceflinger/SurfaceFlinger.cpp362
-rw-r--r--services/surfaceflinger/SurfaceFlinger.h55
-rw-r--r--services/surfaceflinger/tests/overlays/Android.mk17
-rw-r--r--services/surfaceflinger/tests/overlays/overlays.cpp59
-rw-r--r--services/surfaceflinger/tests/resize/resize.cpp3
-rw-r--r--services/surfaceflinger/tests/surface/surface.cpp3
-rw-r--r--services/tests/servicestests/Android.mk2
-rw-r--r--services/tests/servicestests/AndroidManifest.xml13
-rw-r--r--services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java725
-rw-r--r--services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java257
-rw-r--r--services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java109
-rw-r--r--services/tests/servicestests/src/com/android/server/MockAccessibilityService.java256
-rw-r--r--services/tests/servicestests/src/com/android/server/location/ComprehensiveCountryDetectorTest.java299
-rwxr-xr-xservices/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java324
131 files changed, 31474 insertions, 7431 deletions
diff --git a/services/audioflinger/A2dpAudioInterface.cpp b/services/audioflinger/A2dpAudioInterface.cpp
index 995e31c..d926cb1 100644
--- a/services/audioflinger/A2dpAudioInterface.cpp
+++ b/services/audioflinger/A2dpAudioInterface.cpp
@@ -23,10 +23,13 @@
#include "A2dpAudioInterface.h"
#include "audio/liba2dp.h"
-
+#include <hardware_legacy/power.h>
namespace android {
+static const char *sA2dpWakeLock = "A2dpOutputStream";
+#define MAX_WRITE_RETRIES 5
+
// ----------------------------------------------------------------------------
//AudioHardwareInterface* A2dpAudioInterface::createA2dpInterface()
@@ -257,52 +260,74 @@ status_t A2dpAudioInterface::A2dpAudioStreamOut::set(
if (pRate) *pRate = lRate;
mDevice = device;
+ mBufferDurationUs = ((bufferSize() * 1000 )/ frameSize() / sampleRate()) * 1000;
return NO_ERROR;
}
A2dpAudioInterface::A2dpAudioStreamOut::~A2dpAudioStreamOut()
{
LOGV("A2dpAudioStreamOut destructor");
- standby();
close();
LOGV("A2dpAudioStreamOut destructor returning from close()");
}
ssize_t A2dpAudioInterface::A2dpAudioStreamOut::write(const void* buffer, size_t bytes)
{
- Mutex::Autolock lock(mLock);
-
- size_t remaining = bytes;
status_t status = -1;
+ {
+ Mutex::Autolock lock(mLock);
- if (!mBluetoothEnabled || mClosing || mSuspended) {
- LOGV("A2dpAudioStreamOut::write(), but bluetooth disabled \
- mBluetoothEnabled %d, mClosing %d, mSuspended %d",
- mBluetoothEnabled, mClosing, mSuspended);
- goto Error;
- }
-
- status = init();
- if (status < 0)
- goto Error;
+ size_t remaining = bytes;
- while (remaining > 0) {
- status = a2dp_write(mData, buffer, remaining);
- if (status <= 0) {
- LOGE("a2dp_write failed err: %d\n", status);
+ if (!mBluetoothEnabled || mClosing || mSuspended) {
+ LOGV("A2dpAudioStreamOut::write(), but bluetooth disabled \
+ mBluetoothEnabled %d, mClosing %d, mSuspended %d",
+ mBluetoothEnabled, mClosing, mSuspended);
goto Error;
}
- remaining -= status;
- buffer = ((char *)buffer) + status;
- }
- mStandby = false;
+ if (mStandby) {
+ acquire_wake_lock (PARTIAL_WAKE_LOCK, sA2dpWakeLock);
+ mStandby = false;
+ mLastWriteTime = systemTime();
+ }
+
+ status = init();
+ if (status < 0)
+ goto Error;
+
+ int retries = MAX_WRITE_RETRIES;
+ while (remaining > 0 && retries) {
+ status = a2dp_write(mData, buffer, remaining);
+ if (status < 0) {
+ LOGE("a2dp_write failed err: %d\n", status);
+ goto Error;
+ }
+ if (status == 0) {
+ retries--;
+ }
+ remaining -= status;
+ buffer = (char *)buffer + status;
+ }
- return bytes;
+ // if A2DP sink runs abnormally fast, sleep a little so that audioflinger mixer thread
+ // does no spin and starve other threads.
+ // NOTE: It is likely that the A2DP headset is being disconnected
+ nsecs_t now = systemTime();
+ if ((uint32_t)ns2us(now - mLastWriteTime) < (mBufferDurationUs >> 2)) {
+ LOGV("A2DP sink runs too fast");
+ usleep(mBufferDurationUs - (uint32_t)ns2us(now - mLastWriteTime));
+ }
+ mLastWriteTime = now;
+ return bytes;
+ }
Error:
+
+ standby();
+
// Simulate audio output timing in case of error
- usleep(((bytes * 1000 )/ frameSize() / sampleRate()) * 1000);
+ usleep(mBufferDurationUs);
return status;
}
@@ -324,19 +349,22 @@ status_t A2dpAudioInterface::A2dpAudioStreamOut::init()
status_t A2dpAudioInterface::A2dpAudioStreamOut::standby()
{
- int result = 0;
-
- if (mClosing) {
- LOGV("Ignore standby, closing");
- return result;
- }
-
Mutex::Autolock lock(mLock);
+ return standby_l();
+}
+
+status_t A2dpAudioInterface::A2dpAudioStreamOut::standby_l()
+{
+ int result = NO_ERROR;
if (!mStandby) {
- result = a2dp_stop(mData);
- if (result == 0)
- mStandby = true;
+ LOGV_IF(mClosing || !mBluetoothEnabled, "Standby skip stop: closing %d enabled %d",
+ mClosing, mBluetoothEnabled);
+ if (!mClosing && mBluetoothEnabled) {
+ result = a2dp_stop(mData);
+ }
+ release_wake_lock(sA2dpWakeLock);
+ mStandby = true;
}
return result;
@@ -362,6 +390,9 @@ status_t A2dpAudioInterface::A2dpAudioStreamOut::setParameters(const String8& ke
key = String8("closing");
if (param.get(key, value) == NO_ERROR) {
mClosing = (value == "true");
+ if (mClosing) {
+ standby();
+ }
param.remove(key);
}
key = AudioParameter::keyRouting;
@@ -444,6 +475,7 @@ status_t A2dpAudioInterface::A2dpAudioStreamOut::close()
status_t A2dpAudioInterface::A2dpAudioStreamOut::close_l()
{
+ standby_l();
if (mData) {
LOGV("A2dpAudioStreamOut::close_l() calling a2dp_cleanup(mData)");
a2dp_cleanup(mData);
diff --git a/services/audioflinger/A2dpAudioInterface.h b/services/audioflinger/A2dpAudioInterface.h
index 48154f9..dbe2c6a 100644
--- a/services/audioflinger/A2dpAudioInterface.h
+++ b/services/audioflinger/A2dpAudioInterface.h
@@ -103,6 +103,7 @@ private:
status_t setAddress(const char* address);
status_t setBluetoothEnabled(bool enabled);
status_t setSuspended(bool onOff);
+ status_t standby_l();
private:
int mFd;
@@ -116,6 +117,8 @@ private:
uint32_t mDevice;
bool mClosing;
bool mSuspended;
+ nsecs_t mLastWriteTime;
+ uint32_t mBufferDurationUs;
};
friend class A2dpAudioStreamOut;
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 5935bf9..4ec16c1 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -131,18 +131,21 @@ AudioFlinger::AudioFlinger()
: BnAudioFlinger(),
mAudioHardware(0), mMasterVolume(1.0f), mMasterMute(false), mNextUniqueId(1)
{
+ Mutex::Autolock _l(mLock);
+
mHardwareStatus = AUDIO_HW_IDLE;
mAudioHardware = AudioHardwareInterface::create();
mHardwareStatus = AUDIO_HW_INIT;
if (mAudioHardware->initCheck() == NO_ERROR) {
- // open 16-bit output stream for s/w mixer
+ AutoMutex lock(mHardwareLock);
mMode = AudioSystem::MODE_NORMAL;
- setMode(mMode);
-
- setMasterVolume(1.0f);
- setMasterMute(false);
+ mHardwareStatus = AUDIO_HW_SET_MODE;
+ mAudioHardware->setMode(mMode);
+ mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
+ mAudioHardware->setMasterVolume(1.0f);
+ mHardwareStatus = AUDIO_HW_IDLE;
} else {
LOGE("Couldn't even initialize the stubbed audio hardware!");
}
@@ -343,7 +346,7 @@ sp<IAudioTrack> AudioFlinger::createTrack(
lSessionId = *sessionId;
} else {
// if no audio session id is provided, create one here
- lSessionId = nextUniqueId();
+ lSessionId = nextUniqueId_l();
if (sessionId != NULL) {
*sessionId = lSessionId;
}
@@ -440,13 +443,16 @@ status_t AudioFlinger::setMasterVolume(float value)
}
// when hw supports master volume, don't scale in sw mixer
- AutoMutex lock(mHardwareLock);
- mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
- if (mAudioHardware->setMasterVolume(value) == NO_ERROR) {
- value = 1.0f;
+ { // scope for the lock
+ AutoMutex lock(mHardwareLock);
+ mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
+ if (mAudioHardware->setMasterVolume(value) == NO_ERROR) {
+ value = 1.0f;
+ }
+ mHardwareStatus = AUDIO_HW_IDLE;
}
- mHardwareStatus = AUDIO_HW_IDLE;
+ Mutex::Autolock _l(mLock);
mMasterVolume = value;
for (uint32_t i = 0; i < mPlaybackThreads.size(); i++)
mPlaybackThreads.valueAt(i)->setMasterVolume(value);
@@ -517,6 +523,7 @@ status_t AudioFlinger::setMasterMute(bool muted)
return PERMISSION_DENIED;
}
+ Mutex::Autolock _l(mLock);
mMasterMute = muted;
for (uint32_t i = 0; i < mPlaybackThreads.size(); i++)
mPlaybackThreads.valueAt(i)->setMasterMute(muted);
@@ -579,6 +586,7 @@ status_t AudioFlinger::setStreamMute(int stream, bool muted)
return BAD_VALUE;
}
+ AutoMutex lock(mLock);
mStreamTypes[stream].mute = muted;
for (uint32_t i = 0; i < mPlaybackThreads.size(); i++)
mPlaybackThreads.valueAt(i)->setStreamMute(stream, muted);
@@ -616,17 +624,6 @@ bool AudioFlinger::streamMute(int stream) const
return mStreamTypes[stream].mute;
}
-bool AudioFlinger::isStreamActive(int stream) const
-{
- Mutex::Autolock _l(mLock);
- for (uint32_t i = 0; i < mPlaybackThreads.size(); i++) {
- if (mPlaybackThreads.valueAt(i)->isStreamActive(stream)) {
- return true;
- }
- }
- return false;
-}
-
status_t AudioFlinger::setParameters(int ioHandle, const String8& keyValuePairs)
{
status_t result;
@@ -1283,20 +1280,6 @@ bool AudioFlinger::PlaybackThread::streamMute(int stream) const
return mStreamTypes[stream].mute;
}
-bool AudioFlinger::PlaybackThread::isStreamActive(int stream) const
-{
- Mutex::Autolock _l(mLock);
- size_t count = mActiveTracks.size();
- for (size_t i = 0 ; i < count ; ++i) {
- sp<Track> t = mActiveTracks[i].promote();
- if (t == 0) continue;
- Track* const track = t.get();
- if (t->type() == stream)
- return true;
- }
- return false;
-}
-
// addTrack_l() must be called with ThreadBase::mLock held
status_t AudioFlinger::PlaybackThread::addTrack_l(const sp<Track>& track)
{
@@ -3699,7 +3682,7 @@ sp<IAudioRecord> AudioFlinger::openRecord(
if (sessionId != NULL && *sessionId != AudioSystem::SESSION_OUTPUT_MIX) {
lSessionId = *sessionId;
} else {
- lSessionId = nextUniqueId();
+ lSessionId = nextUniqueId_l();
if (sessionId != NULL) {
*sessionId = lSessionId;
}
@@ -4300,7 +4283,7 @@ int AudioFlinger::openOutput(uint32_t *pDevices,
mHardwareStatus = AUDIO_HW_IDLE;
if (output != 0) {
- int id = nextUniqueId();
+ int id = nextUniqueId_l();
if ((flags & AudioSystem::OUTPUT_FLAG_DIRECT) ||
(format != AudioSystem::PCM_16_BIT) ||
(channels != AudioSystem::CHANNEL_OUT_STEREO)) {
@@ -4348,7 +4331,7 @@ int AudioFlinger::openDuplicateOutput(int output1, int output2)
return 0;
}
- int id = nextUniqueId();
+ int id = nextUniqueId_l();
DuplicatingThread *thread = new DuplicatingThread(this, thread1, id);
thread->addOutputTrack(thread2);
mPlaybackThreads.add(id, thread);
@@ -4473,7 +4456,7 @@ int AudioFlinger::openInput(uint32_t *pDevices,
}
if (input != 0) {
- int id = nextUniqueId();
+ int id = nextUniqueId_l();
// Start record thread
thread = new RecordThread(this, input, reqSamplingRate, reqChannels, id);
mRecordThreads.add(id, thread);
@@ -4543,7 +4526,8 @@ status_t AudioFlinger::setStreamOutput(uint32_t stream, int output)
int AudioFlinger::newAudioSessionId()
{
- return nextUniqueId();
+ AutoMutex _l(mLock);
+ return nextUniqueId_l();
}
// checkPlaybackThread_l() must be called with AudioFlinger::mLock held
@@ -4578,9 +4562,10 @@ AudioFlinger::RecordThread *AudioFlinger::checkRecordThread_l(int input) const
return thread;
}
-int AudioFlinger::nextUniqueId()
+// nextUniqueId_l() must be called with AudioFlinger::mLock held
+int AudioFlinger::nextUniqueId_l()
{
- return android_atomic_inc(&mNextUniqueId);
+ return mNextUniqueId++;
}
// ----------------------------------------------------------------------------
@@ -4967,7 +4952,7 @@ sp<AudioFlinger::EffectHandle> AudioFlinger::PlaybackThread::createEffect_l(
LOGV("createEffect_l() got effect %p on chain %p", effect == 0 ? 0 : effect.get(), chain.get());
if (effect == 0) {
- int id = mAudioFlinger->nextUniqueId();
+ int id = mAudioFlinger->nextUniqueId_l();
// Check CPU and memory usage
lStatus = AudioSystem::registerEffect(desc, mId, chain->strategy(), sessionId, id);
if (lStatus != NO_ERROR) {
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 5917632..81f2eb4 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -107,8 +107,6 @@ public:
virtual status_t setMicMute(bool state);
virtual bool getMicMute() const;
- virtual bool isStreamActive(int stream) const;
-
virtual status_t setParameters(int ioHandle, const String8& keyValuePairs);
virtual String8 getParameters(int ioHandle, const String8& keys);
@@ -579,8 +577,6 @@ private:
virtual float streamVolume(int stream) const;
virtual bool streamMute(int stream) const;
- bool isStreamActive(int stream) const;
-
sp<Track> createTrack_l(
const sp<AudioFlinger::Client>& client,
int streamType,
@@ -785,7 +781,7 @@ private:
float streamVolumeInternal(int stream) const { return mStreamTypes[stream].volume; }
void audioConfigChanged_l(int event, int ioHandle, void *param2);
- int nextUniqueId();
+ int nextUniqueId_l();
status_t moveEffectChain_l(int session,
AudioFlinger::PlaybackThread *srcThread,
AudioFlinger::PlaybackThread *dstThread,
diff --git a/services/audioflinger/AudioPolicyManagerBase.cpp b/services/audioflinger/AudioPolicyManagerBase.cpp
index 4612af1..afa9acc 100644
--- a/services/audioflinger/AudioPolicyManagerBase.cpp
+++ b/services/audioflinger/AudioPolicyManagerBase.cpp
@@ -312,8 +312,7 @@ void AudioPolicyManagerBase::setPhoneState(int state)
// Flag that ringtone volume must be limited to music volume until we exit MODE_RINGTONE
if (state == AudioSystem::MODE_RINGTONE &&
- (hwOutputDesc->mRefCount[AudioSystem::MUSIC] ||
- (systemTime() - mMusicStopTime) < seconds(SONIFICATION_HEADSET_MUSIC_DELAY))) {
+ isStreamActive(AudioSystem::MUSIC, SONIFICATION_HEADSET_MUSIC_DELAY)) {
mLimitRingtoneVolume = true;
} else {
mLimitRingtoneVolume = false;
@@ -343,7 +342,9 @@ void AudioPolicyManagerBase::setForceUse(AudioSystem::force_use usage, AudioSyst
break;
case AudioSystem::FOR_MEDIA:
if (config != AudioSystem::FORCE_HEADPHONES && config != AudioSystem::FORCE_BT_A2DP &&
- config != AudioSystem::FORCE_WIRED_ACCESSORY && config != AudioSystem::FORCE_NONE) {
+ config != AudioSystem::FORCE_WIRED_ACCESSORY &&
+ config != AudioSystem::FORCE_ANALOG_DOCK &&
+ config != AudioSystem::FORCE_DIGITAL_DOCK && config != AudioSystem::FORCE_NONE) {
LOGW("setForceUse() invalid config %d for FOR_MEDIA", config);
return;
}
@@ -359,7 +360,10 @@ void AudioPolicyManagerBase::setForceUse(AudioSystem::force_use usage, AudioSyst
break;
case AudioSystem::FOR_DOCK:
if (config != AudioSystem::FORCE_NONE && config != AudioSystem::FORCE_BT_CAR_DOCK &&
- config != AudioSystem::FORCE_BT_DESK_DOCK && config != AudioSystem::FORCE_WIRED_ACCESSORY) {
+ config != AudioSystem::FORCE_BT_DESK_DOCK &&
+ config != AudioSystem::FORCE_WIRED_ACCESSORY &&
+ config != AudioSystem::FORCE_ANALOG_DOCK &&
+ config != AudioSystem::FORCE_DIGITAL_DOCK) {
LOGW("setForceUse() invalid config %d for FOR_DOCK", config);
}
forceVolumeReeval = true;
@@ -473,6 +477,7 @@ audio_io_handle_t AudioPolicyManagerBase::getOutput(AudioSystem::stream_type str
outputDesc->mLatency = 0;
outputDesc->mFlags = (AudioSystem::output_flags)(flags | AudioSystem::OUTPUT_FLAG_DIRECT);
outputDesc->mRefCount[stream] = 0;
+ outputDesc->mStopTime[stream] = 0;
output = mpClientInterface->openOutput(&outputDesc->mDevice,
&outputDesc->mSamplingRate,
&outputDesc->mFormat,
@@ -601,10 +606,8 @@ status_t AudioPolicyManagerBase::stopOutput(audio_io_handle_t output,
if (outputDesc->mRefCount[stream] > 0) {
// decrement usage count of this stream on the output
outputDesc->changeRefCount(stream, -1);
- // store time at which the last music track was stopped - see computeVolume()
- if (stream == AudioSystem::MUSIC) {
- mMusicStopTime = systemTime();
- }
+ // store time at which the stream was stopped - see isStreamActive()
+ outputDesc->mStopTime[stream] = systemTime();
setOutputDevice(output, getNewDevice(output));
@@ -914,6 +917,19 @@ status_t AudioPolicyManagerBase::unregisterEffect(int id)
return NO_ERROR;
}
+bool AudioPolicyManagerBase::isStreamActive(int stream, uint32_t inPastMs) const
+{
+ nsecs_t sysTime = systemTime();
+ for (size_t i = 0; i < mOutputs.size(); i++) {
+ if (mOutputs.valueAt(i)->mRefCount[stream] != 0 ||
+ ns2ms(sysTime - mOutputs.valueAt(i)->mStopTime[stream]) < inPastMs) {
+ return true;
+ }
+ }
+ return false;
+}
+
+
status_t AudioPolicyManagerBase::dump(int fd)
{
const size_t SIZE = 256;
@@ -1004,7 +1020,7 @@ AudioPolicyManagerBase::AudioPolicyManagerBase(AudioPolicyClientInterface *clien
Thread(false),
#endif //AUDIO_POLICY_TEST
mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0),
- mMusicStopTime(0), mLimitRingtoneVolume(false), mLastVoiceVolume(-1.0f),
+ mLimitRingtoneVolume(false), mLastVoiceVolume(-1.0f),
mTotalEffectsCpuLoad(0), mTotalEffectsMemory(0),
mA2dpSuspended(false)
{
@@ -1047,25 +1063,27 @@ AudioPolicyManagerBase::AudioPolicyManagerBase(AudioPolicyClientInterface *clien
updateDeviceForStrategy();
#ifdef AUDIO_POLICY_TEST
- AudioParameter outputCmd = AudioParameter();
- outputCmd.addInt(String8("set_id"), 0);
- mpClientInterface->setParameters(mHardwareOutput, outputCmd.toString());
-
- mTestDevice = AudioSystem::DEVICE_OUT_SPEAKER;
- mTestSamplingRate = 44100;
- mTestFormat = AudioSystem::PCM_16_BIT;
- mTestChannels = AudioSystem::CHANNEL_OUT_STEREO;
- mTestLatencyMs = 0;
- mCurOutput = 0;
- mDirectOutput = false;
- for (int i = 0; i < NUM_TEST_OUTPUTS; i++) {
- mTestOutputs[i] = 0;
- }
+ if (mHardwareOutput != 0) {
+ AudioParameter outputCmd = AudioParameter();
+ outputCmd.addInt(String8("set_id"), 0);
+ mpClientInterface->setParameters(mHardwareOutput, outputCmd.toString());
+
+ mTestDevice = AudioSystem::DEVICE_OUT_SPEAKER;
+ mTestSamplingRate = 44100;
+ mTestFormat = AudioSystem::PCM_16_BIT;
+ mTestChannels = AudioSystem::CHANNEL_OUT_STEREO;
+ mTestLatencyMs = 0;
+ mCurOutput = 0;
+ mDirectOutput = false;
+ for (int i = 0; i < NUM_TEST_OUTPUTS; i++) {
+ mTestOutputs[i] = 0;
+ }
- const size_t SIZE = 256;
- char buffer[SIZE];
- snprintf(buffer, SIZE, "AudioPolicyManagerTest");
- run(buffer, ANDROID_PRIORITY_AUDIO);
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+ snprintf(buffer, SIZE, "AudioPolicyManagerTest");
+ run(buffer, ANDROID_PRIORITY_AUDIO);
+ }
#endif //AUDIO_POLICY_TEST
}
@@ -1086,6 +1104,11 @@ AudioPolicyManagerBase::~AudioPolicyManagerBase()
mInputs.clear();
}
+status_t AudioPolicyManagerBase::initCheck()
+{
+ return (mHardwareOutput == 0) ? NO_INIT : NO_ERROR;
+}
+
#ifdef AUDIO_POLICY_TEST
bool AudioPolicyManagerBase::threadLoop()
{
@@ -1347,6 +1370,7 @@ status_t AudioPolicyManagerBase::handleA2dpDisconnection(AudioSystem::audio_devi
void AudioPolicyManagerBase::closeA2dpOutputs()
{
+
LOGV("setDeviceConnectionState() closing A2DP and duplicated output!");
if (mDuplicatedOutput != 0) {
@@ -1581,6 +1605,12 @@ uint32_t AudioPolicyManagerBase::getDeviceForStrategy(routing_strategy strategy,
if (device) break;
device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET;
if (device) break;
+ device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_AUX_DIGITAL;
+ if (device) break;
+ device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET;
+ if (device) break;
+ device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET;
+ if (device) break;
#ifdef WITH_A2DP
// when not in a phone call, phone strategy should route STREAM_VOICE_CALL to A2DP
if (!isInCall()) {
@@ -1597,10 +1627,12 @@ uint32_t AudioPolicyManagerBase::getDeviceForStrategy(routing_strategy strategy,
break;
case AudioSystem::FORCE_SPEAKER:
- if (!isInCall() || strategy != STRATEGY_DTMF) {
- device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
- if (device) break;
- }
+ device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_AUX_DIGITAL;
+ if (device) break;
+ device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET;
+ if (device) break;
+ device = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET;
+ if (device) break;
#ifdef WITH_A2DP
// when not in a phone call, phone strategy should route STREAM_VOICE_CALL to
// A2DP speaker when forcing to speaker output
@@ -1633,12 +1665,18 @@ uint32_t AudioPolicyManagerBase::getDeviceForStrategy(routing_strategy strategy,
// FALL THROUGH
case STRATEGY_MEDIA: {
- uint32_t device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_AUX_DIGITAL;
+ uint32_t device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE;
if (device2 == 0) {
- device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADPHONE;
+ device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET;
}
if (device2 == 0) {
- device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_WIRED_HEADSET;
+ device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_AUX_DIGITAL;
+ }
+ if (device2 == 0) {
+ device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET;
+ }
+ if (device2 == 0) {
+ device2 = mAvailableOutputDevices & AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET;
}
#ifdef WITH_A2DP
if (mA2dpOutput != 0) {
@@ -1805,7 +1843,9 @@ float AudioPolicyManagerBase::computeVolume(int stream, int index, audio_io_hand
(AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP |
AudioSystem::DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
AudioSystem::DEVICE_OUT_WIRED_HEADSET |
- AudioSystem::DEVICE_OUT_WIRED_HEADPHONE)) &&
+ AudioSystem::DEVICE_OUT_WIRED_HEADPHONE |
+ AudioSystem::DEVICE_OUT_ANLG_DOCK_HEADSET |
+ AudioSystem::DEVICE_OUT_DGTL_DOCK_HEADSET)) &&
((getStrategy((AudioSystem::stream_type)stream) == STRATEGY_SONIFICATION) ||
(stream == AudioSystem::SYSTEM)) &&
streamDesc.mCanBeMuted) {
@@ -2007,6 +2047,7 @@ AudioPolicyManagerBase::AudioOutputDescriptor::AudioOutputDescriptor()
mRefCount[i] = 0;
mCurVolume[i] = -1.0;
mMuteCount[i] = 0;
+ mStopTime[i] = 0;
}
}
@@ -2057,7 +2098,6 @@ uint32_t AudioPolicyManagerBase::AudioOutputDescriptor::strategyRefCount(routing
return refCount;
}
-
status_t AudioPolicyManagerBase::AudioOutputDescriptor::dump(int fd)
{
const size_t SIZE = 256;
diff --git a/services/audioflinger/AudioPolicyService.cpp b/services/audioflinger/AudioPolicyService.cpp
index f24e08e..b04672d 100644
--- a/services/audioflinger/AudioPolicyService.cpp
+++ b/services/audioflinger/AudioPolicyService.cpp
@@ -68,6 +68,8 @@ AudioPolicyService::AudioPolicyService()
{
char value[PROPERTY_VALUE_MAX];
+ Mutex::Autolock _l(mLock);
+
// start tone playback thread
mTonePlaybackThread = new AudioCommandThread(String8(""));
// start audio commands thread
@@ -88,9 +90,18 @@ AudioPolicyService::AudioPolicyService()
}
#endif
- // load properties
- property_get("ro.camera.sound.forced", value, "0");
- mpPolicyManager->setSystemProperty("ro.camera.sound.forced", value);
+ if ((mpPolicyManager != NULL) && (mpPolicyManager->initCheck() != NO_ERROR)) {
+ delete mpPolicyManager;
+ mpPolicyManager = NULL;
+ }
+
+ if (mpPolicyManager == NULL) {
+ LOGE("Could not create AudioPolicyManager");
+ } else {
+ // load properties
+ property_get("ro.camera.sound.forced", value, "0");
+ mpPolicyManager->setSystemProperty("ro.camera.sound.forced", value);
+ }
}
AudioPolicyService::~AudioPolicyService()
@@ -383,6 +394,15 @@ status_t AudioPolicyService::unregisterEffect(int id)
return mpPolicyManager->unregisterEffect(id);
}
+bool AudioPolicyService::isStreamActive(int stream, uint32_t inPastMs) const
+{
+ if (mpPolicyManager == NULL) {
+ return 0;
+ }
+ Mutex::Autolock _l(mLock);
+ return mpPolicyManager->isStreamActive(stream, inPastMs);
+}
+
void AudioPolicyService::binderDied(const wp<IBinder>& who) {
LOGW("binderDied() %p, tid %d, calling tid %d", who.unsafe_get(), gettid(),
IPCThreadState::self()->getCallingPid());
diff --git a/services/audioflinger/AudioPolicyService.h b/services/audioflinger/AudioPolicyService.h
index 558f455..54af1f1 100644
--- a/services/audioflinger/AudioPolicyService.h
+++ b/services/audioflinger/AudioPolicyService.h
@@ -88,6 +88,7 @@ public:
int session,
int id);
virtual status_t unregisterEffect(int id);
+ virtual bool isStreamActive(int stream, uint32_t inPastMs = 0) const;
virtual status_t onTransact(
uint32_t code,
@@ -230,8 +231,8 @@ private:
status_t dumpPermissionDenial(int fd);
- Mutex mLock; // prevents concurrent access to AudioPolicy manager functions changing device
- // connection stated our routing
+ mutable Mutex mLock; // prevents concurrent access to AudioPolicy manager functions changing
+ // device connection state or routing
AudioPolicyInterface* mpPolicyManager; // the platform specific policy manager
sp <AudioCommandThread> mAudioCommandThread; // audio commands thread
sp <AudioCommandThread> mTonePlaybackThread; // tone playback thread
diff --git a/services/camera/libcameraservice/Android.mk b/services/camera/libcameraservice/Android.mk
index 87975af..b52fc69 100644
--- a/services/camera/libcameraservice/Android.mk
+++ b/services/camera/libcameraservice/Android.mk
@@ -49,7 +49,8 @@ LOCAL_SHARED_LIBRARIES:= \
libcutils \
libmedia \
libcamera_client \
- libsurfaceflinger_client
+ libsurfaceflinger_client \
+ libgui
LOCAL_MODULE:= libcameraservice
diff --git a/services/camera/libcameraservice/CameraHardwareStub.cpp b/services/camera/libcameraservice/CameraHardwareStub.cpp
index b3e0ee6..07b5a37 100644
--- a/services/camera/libcameraservice/CameraHardwareStub.cpp
+++ b/services/camera/libcameraservice/CameraHardwareStub.cpp
@@ -101,9 +101,9 @@ CameraHardwareStub::~CameraHardwareStub()
mFakeCamera = 0; // paranoia
}
-sp<IMemoryHeap> CameraHardwareStub::getPreviewHeap() const
+status_t CameraHardwareStub::setPreviewWindow(const sp<ANativeWindow>& buf)
{
- return mPreviewHeap;
+ return NO_ERROR;
}
sp<IMemoryHeap> CameraHardwareStub::getRawHeap() const
diff --git a/services/camera/libcameraservice/CameraHardwareStub.h b/services/camera/libcameraservice/CameraHardwareStub.h
index d3427ba..9b66a76 100644
--- a/services/camera/libcameraservice/CameraHardwareStub.h
+++ b/services/camera/libcameraservice/CameraHardwareStub.h
@@ -29,7 +29,7 @@ namespace android {
class CameraHardwareStub : public CameraHardwareInterface {
public:
- virtual sp<IMemoryHeap> getPreviewHeap() const;
+ virtual status_t setPreviewWindow(const sp<ANativeWindow>& buf);
virtual sp<IMemoryHeap> getRawHeap() const;
virtual void setCallbacks(notify_callback notify_cb,
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index a64ddcf..3d8ca7a 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -26,11 +26,12 @@
#include <binder/MemoryBase.h>
#include <binder/MemoryHeapBase.h>
#include <cutils/atomic.h>
+#include <cutils/properties.h>
+#include <gui/SurfaceTextureClient.h>
#include <hardware/hardware.h>
#include <media/AudioSystem.h>
#include <media/mediaplayer.h>
#include <surfaceflinger/ISurface.h>
-#include <ui/Overlay.h>
#include <utils/Errors.h>
#include <utils/Log.h>
#include <utils/String16.h>
@@ -305,9 +306,9 @@ CameraService::Client::Client(const sp<CameraService>& cameraService,
mCameraId = cameraId;
mCameraFacing = cameraFacing;
mClientPid = clientPid;
- mUseOverlay = mHardware->useOverlay();
mMsgEnabled = 0;
-
+ mSurface = 0;
+ mPreviewWindow = 0;
mHardware->setCallbacks(notifyCallback,
dataCallback,
dataCallbackTimestamp,
@@ -317,42 +318,21 @@ CameraService::Client::Client(const sp<CameraService>& cameraService,
enableMsgType(CAMERA_MSG_ERROR |
CAMERA_MSG_ZOOM |
CAMERA_MSG_FOCUS);
- mOverlayW = 0;
- mOverlayH = 0;
// Callback is disabled by default
mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP;
mOrientation = getOrientation(0, mCameraFacing == CAMERA_FACING_FRONT);
- mOrientationChanged = false;
+ mPlayShutterSound = true;
cameraService->setCameraBusy(cameraId);
cameraService->loadSound();
LOG1("Client::Client X (pid %d)", callingPid);
}
-static void *unregister_surface(void *arg) {
- ISurface *surface = (ISurface *)arg;
- surface->unregisterBuffers();
- IPCThreadState::self()->flushCommands();
- return NULL;
-}
-
// tear down the client
CameraService::Client::~Client() {
int callingPid = getCallingPid();
LOG1("Client::~Client E (pid %d, this %p)", callingPid, this);
- if (mSurface != 0 && !mUseOverlay) {
- pthread_t thr;
- // We unregister the buffers in a different thread because binder does
- // not let us make sychronous transactions in a binder destructor (that
- // is, upon our reaching a refcount of zero.)
- pthread_create(&thr,
- NULL, // attr
- unregister_surface,
- mSurface.get());
- pthread_join(thr, NULL);
- }
-
// set mClientPid to let disconnet() tear down the hardware
mClientPid = callingPid;
disconnect();
@@ -466,9 +446,11 @@ void CameraService::Client::disconnect() {
mHardware->cancelPicture();
// Release the hardware resources.
mHardware->release();
- // Release the held overlay resources.
- if (mUseOverlay) {
- mOverlayRef = 0;
+
+ // Release the held ANativeWindow resources.
+ if (mPreviewWindow != 0) {
+ mPreviewWindow = 0;
+ mHardware->setPreviewWindow(mPreviewWindow);
}
mHardware.clear();
@@ -480,8 +462,8 @@ void CameraService::Client::disconnect() {
// ----------------------------------------------------------------------------
-// set the ISurface that the preview will use
-status_t CameraService::Client::setPreviewDisplay(const sp<ISurface>& surface) {
+// set the Surface that the preview will use
+status_t CameraService::Client::setPreviewDisplay(const sp<Surface>& surface) {
LOG1("setPreviewDisplay(%p) (pid %d)", surface.get(), getCallingPid());
Mutex::Autolock lock(mLock);
status_t result = checkPidAndHardware();
@@ -491,100 +473,64 @@ status_t CameraService::Client::setPreviewDisplay(const sp<ISurface>& surface) {
// return if no change in surface.
// asBinder() is safe on NULL (returns NULL)
- if (surface->asBinder() == mSurface->asBinder()) {
+ if (getISurface(surface)->asBinder() == mSurface) {
return result;
}
if (mSurface != 0) {
LOG1("clearing old preview surface %p", mSurface.get());
- if (mUseOverlay) {
- // Force the destruction of any previous overlay
- sp<Overlay> dummy;
- mHardware->setOverlay(dummy);
- mOverlayRef = 0;
- } else {
- mSurface->unregisterBuffers();
- }
}
- mSurface = surface;
- mOverlayRef = 0;
- // If preview has been already started, set overlay or register preview
+ mSurface = getISurface(surface)->asBinder();
+ mPreviewWindow = surface;
+
+ // If preview has been already started, register preview
// buffers now.
if (mHardware->previewEnabled()) {
- if (mUseOverlay) {
- result = setOverlay();
- } else if (mSurface != 0) {
- result = registerPreviewBuffers();
+ if (mPreviewWindow != 0) {
+ native_window_set_buffers_transform(mPreviewWindow.get(),
+ mOrientation);
+ result = mHardware->setPreviewWindow(mPreviewWindow);
}
}
return result;
}
-status_t CameraService::Client::registerPreviewBuffers() {
- int w, h;
- CameraParameters params(mHardware->getParameters());
- params.getPreviewSize(&w, &h);
-
- // FIXME: don't use a hardcoded format here.
- ISurface::BufferHeap buffers(w, h, w, h,
- HAL_PIXEL_FORMAT_YCrCb_420_SP,
- mOrientation,
- 0,
- mHardware->getPreviewHeap());
+// set the SurfaceTexture that the preview will use
+status_t CameraService::Client::setPreviewTexture(
+ const sp<ISurfaceTexture>& surfaceTexture) {
+ LOG1("setPreviewTexture(%p) (pid %d)", surfaceTexture.get(),
+ getCallingPid());
+ Mutex::Autolock lock(mLock);
+ status_t result = checkPidAndHardware();
+ if (result != NO_ERROR) return result;
- status_t result = mSurface->registerBuffers(buffers);
- if (result != NO_ERROR) {
- LOGE("registerBuffers failed with status %d", result);
+ // return if no change in surface.
+ // asBinder() is safe on NULL (returns NULL)
+ if (surfaceTexture->asBinder() == mSurface) {
+ return result;
}
- return result;
-}
-
-status_t CameraService::Client::setOverlay() {
- int w, h;
- CameraParameters params(mHardware->getParameters());
- params.getPreviewSize(&w, &h);
- if (w != mOverlayW || h != mOverlayH || mOrientationChanged) {
- // Force the destruction of any previous overlay
- sp<Overlay> dummy;
- mHardware->setOverlay(dummy);
- mOverlayRef = 0;
- mOrientationChanged = false;
+ if (mSurface != 0) {
+ LOG1("clearing old preview surface %p", mSurface.get());
}
-
- status_t result = NO_ERROR;
- if (mSurface == 0) {
- result = mHardware->setOverlay(NULL);
+ mSurface = surfaceTexture->asBinder();
+ if (surfaceTexture != 0) {
+ mPreviewWindow = new SurfaceTextureClient(surfaceTexture);
} else {
- if (mOverlayRef == 0) {
- // FIXME:
- // Surfaceflinger may hold onto the previous overlay reference for some
- // time after we try to destroy it. retry a few times. In the future, we
- // should make the destroy call block, or possibly specify that we can
- // wait in the createOverlay call if the previous overlay is in the
- // process of being destroyed.
- for (int retry = 0; retry < 50; ++retry) {
- mOverlayRef = mSurface->createOverlay(w, h, OVERLAY_FORMAT_DEFAULT,
- mOrientation);
- if (mOverlayRef != 0) break;
- LOGW("Overlay create failed - retrying");
- usleep(20000);
- }
- if (mOverlayRef == 0) {
- LOGE("Overlay Creation Failed!");
- return -EINVAL;
- }
- result = mHardware->setOverlay(new Overlay(mOverlayRef));
- }
- }
- if (result != NO_ERROR) {
- LOGE("mHardware->setOverlay() failed with status %d\n", result);
- return result;
+ mPreviewWindow = 0;
}
- mOverlayW = w;
- mOverlayH = h;
+ // If preview has been already started, set overlay or register preview
+ // buffers now.
+ if (mHardware->previewEnabled()) {
+ // XXX: What if the new preview window is 0?
+ if (mPreviewWindow != 0) {
+ native_window_set_buffers_transform(mPreviewWindow.get(),
+ mOrientation);
+ result = mHardware->setPreviewWindow(mPreviewWindow);
+ }
+ }
return result;
}
@@ -597,16 +543,10 @@ void CameraService::Client::setPreviewCallbackFlag(int callback_flag) {
if (checkPidAndHardware() != NO_ERROR) return;
mPreviewCallbackFlag = callback_flag;
-
- // If we don't use overlay, we always need the preview frame for display.
- // If we do use overlay, we only need the preview frame if the user
- // wants the data.
- if (mUseOverlay) {
- if(mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ENABLE_MASK) {
- enableMsgType(CAMERA_MSG_PREVIEW_FRAME);
- } else {
- disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
- }
+ if (mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ENABLE_MASK) {
+ enableMsgType(CAMERA_MSG_PREVIEW_FRAME);
+ } else {
+ disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
}
}
@@ -631,14 +571,14 @@ status_t CameraService::Client::startCameraMode(camera_mode mode) {
switch(mode) {
case CAMERA_PREVIEW_MODE:
- if (mSurface == 0) {
+ if (mSurface == 0 && mPreviewWindow == 0) {
LOG1("mSurface is not set yet.");
// still able to start preview in this case.
}
return startPreviewMode();
case CAMERA_RECORDING_MODE:
- if (mSurface == 0) {
- LOGE("mSurface must be set before startRecordingMode.");
+ if (mSurface == 0 && mPreviewWindow == 0) {
+ LOGE("mSurface or mPreviewWindow must be set before startRecordingMode.");
return INVALID_OPERATION;
}
return startRecordingMode();
@@ -656,25 +596,13 @@ status_t CameraService::Client::startPreviewMode() {
return NO_ERROR;
}
- if (mUseOverlay) {
- // If preview display has been set, set overlay now.
- if (mSurface != 0) {
- result = setOverlay();
- }
- if (result != NO_ERROR) return result;
- result = mHardware->startPreview();
- } else {
- enableMsgType(CAMERA_MSG_PREVIEW_FRAME);
- result = mHardware->startPreview();
- if (result != NO_ERROR) return result;
- // If preview display has been set, register preview buffers now.
- if (mSurface != 0) {
- // Unregister here because the surface may be previously registered
- // with the raw (snapshot) heap.
- mSurface->unregisterBuffers();
- result = registerPreviewBuffers();
- }
+ if (mPreviewWindow != 0) {
+ native_window_set_buffers_transform(mPreviewWindow.get(),
+ mOrientation);
}
+ mHardware->setPreviewWindow(mPreviewWindow);
+ result = mHardware->startPreview();
+
return result;
}
@@ -711,13 +639,10 @@ void CameraService::Client::stopPreview() {
Mutex::Autolock lock(mLock);
if (checkPidAndHardware() != NO_ERROR) return;
+
disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
mHardware->stopPreview();
- if (mSurface != 0 && !mUseOverlay) {
- mSurface->unregisterBuffers();
- }
-
mPreviewBuffer.clear();
}
@@ -741,6 +666,30 @@ void CameraService::Client::releaseRecordingFrame(const sp<IMemory>& mem) {
mHardware->releaseRecordingFrame(mem);
}
+int32_t CameraService::Client::getNumberOfVideoBuffers() const {
+ LOG1("getNumberOfVideoBuffers");
+ Mutex::Autolock lock(mLock);
+ if (checkPidAndHardware() != NO_ERROR) return 0;
+ return mHardware->getNumberOfVideoBuffers();
+}
+
+sp<IMemory> CameraService::Client::getVideoBuffer(int32_t index) const {
+ LOG1("getVideoBuffer: %d", index);
+ Mutex::Autolock lock(mLock);
+ if (checkPidAndHardware() != NO_ERROR) return 0;
+ return mHardware->getVideoBuffer(index);
+}
+
+status_t CameraService::Client::storeMetaDataInBuffers(bool enabled)
+{
+ LOG1("storeMetaDataInBuffers: %s", enabled? "true": "false");
+ Mutex::Autolock lock(mLock);
+ if (checkPidAndHardware() != NO_ERROR) {
+ return UNKNOWN_ERROR;
+ }
+ return mHardware->storeMetaDataInBuffers(enabled);
+}
+
bool CameraService::Client::previewEnabled() {
LOG1("previewEnabled (pid %d)", getCallingPid());
@@ -815,6 +764,35 @@ String8 CameraService::Client::getParameters() const {
return params;
}
+// enable shutter sound
+status_t CameraService::Client::enableShutterSound(bool enable) {
+ LOG1("enableShutterSound (pid %d)", getCallingPid());
+
+ status_t result = checkPidAndHardware();
+ if (result != NO_ERROR) return result;
+
+ if (enable) {
+ mPlayShutterSound = true;
+ return OK;
+ }
+
+ // Disabling shutter sound may not be allowed. In that case only
+ // allow the mediaserver process to disable the sound.
+ char value[PROPERTY_VALUE_MAX];
+ property_get("ro.camera.sound.forced", value, "0");
+ if (strcmp(value, "0") != 0) {
+ // Disabling shutter sound is not allowed. Deny if the current
+ // process is not mediaserver.
+ if (getCallingPid() != getpid()) {
+ LOGE("Failed to disable shutter sound. Permission denied (pid %d)", getCallingPid());
+ return PERMISSION_DENIED;
+ }
+ }
+
+ mPlayShutterSound = false;
+ return OK;
+}
+
status_t CameraService::Client::sendCommand(int32_t cmd, int32_t arg1, int32_t arg2) {
LOG1("sendCommand (pid %d)", getCallingPid());
int orientation;
@@ -833,9 +811,22 @@ status_t CameraService::Client::sendCommand(int32_t cmd, int32_t arg1, int32_t a
if (mOrientation != orientation) {
mOrientation = orientation;
- if (mOverlayRef != 0) mOrientationChanged = true;
}
return OK;
+ } else if (cmd == CAMERA_CMD_ENABLE_SHUTTER_SOUND) {
+ switch (arg1) {
+ case 0:
+ enableShutterSound(false);
+ break;
+ case 1:
+ enableShutterSound(true);
+ break;
+ default:
+ return BAD_VALUE;
+ }
+ return OK;
+ } else if (cmd == CAMERA_CMD_PLAY_RECORDING_SOUND) {
+ mCameraService->playSound(SOUND_RECORDING);
}
return mHardware->sendCommand(cmd, arg1, arg2);
@@ -996,11 +987,8 @@ void CameraService::Client::dataCallbackTimestamp(nsecs_t timestamp,
// "size" is the width and height of yuv picture for registerBuffer.
// If it is NULL, use the picture size from parameters.
void CameraService::Client::handleShutter(image_rect_type *size) {
- mCameraService->playSound(SOUND_SHUTTER);
-
- // Screen goes black after the buffer is unregistered.
- if (mSurface != 0 && !mUseOverlay) {
- mSurface->unregisterBuffers();
+ if (mPlayShutterSound) {
+ mCameraService->playSound(SOUND_SHUTTER);
}
sp<ICameraClient> c = mCameraClient;
@@ -1011,29 +999,6 @@ void CameraService::Client::handleShutter(image_rect_type *size) {
}
disableMsgType(CAMERA_MSG_SHUTTER);
- // It takes some time before yuvPicture callback to be called.
- // Register the buffer for raw image here to reduce latency.
- if (mSurface != 0 && !mUseOverlay) {
- int w, h;
- CameraParameters params(mHardware->getParameters());
- if (size == NULL) {
- params.getPictureSize(&w, &h);
- } else {
- w = size->width;
- h = size->height;
- w &= ~1;
- h &= ~1;
- LOG1("Snapshot image width=%d, height=%d", w, h);
- }
- // FIXME: don't use hardcoded format constants here
- ISurface::BufferHeap buffers(w, h, w, h,
- HAL_PIXEL_FORMAT_YCrCb_420_SP, mOrientation, 0,
- mHardware->getRawHeap());
-
- mSurface->registerBuffers(buffers);
- IPCThreadState::self()->flushCommands();
- }
-
mLock.unlock();
}
@@ -1043,12 +1008,6 @@ void CameraService::Client::handlePreviewData(const sp<IMemory>& mem) {
size_t size;
sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);
- if (!mUseOverlay) {
- if (mSurface != 0) {
- mSurface->postBuffer(offset);
- }
- }
-
// local copy of the callback flags
int flags = mPreviewCallbackFlag;
@@ -1069,9 +1028,7 @@ void CameraService::Client::handlePreviewData(const sp<IMemory>& mem) {
mPreviewCallbackFlag &= ~(FRAME_CALLBACK_FLAG_ONE_SHOT_MASK |
FRAME_CALLBACK_FLAG_COPY_OUT_MASK |
FRAME_CALLBACK_FLAG_ENABLE_MASK);
- if (mUseOverlay) {
- disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
- }
+ disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
}
if (c != 0) {
@@ -1108,11 +1065,6 @@ void CameraService::Client::handleRawPicture(const sp<IMemory>& mem) {
size_t size;
sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);
- // Put the YUV version of the snapshot in the preview display.
- if (mSurface != 0 && !mUseOverlay) {
- mSurface->postBuffer(offset);
- }
-
sp<ICameraClient> c = mCameraClient;
mLock.unlock();
if (c != 0) {
@@ -1292,4 +1244,12 @@ status_t CameraService::dump(int fd, const Vector<String16>& args) {
return NO_ERROR;
}
+sp<ISurface> CameraService::getISurface(const sp<Surface>& surface) {
+ if (surface != 0) {
+ return surface->getISurface();
+ } else {
+ return sp<ISurface>(0);
+ }
+}
+
}; // namespace android
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index f09773d..ccb9cf7 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -79,6 +79,12 @@ private:
sp<MediaPlayer> mSoundPlayer[NUM_SOUNDS];
int mSoundRef; // reference count (release all MediaPlayer when 0)
+ // Used by Client objects to extract the ISurface from a Surface object.
+ // This is used because making Client a friend class of Surface would
+ // require including this header in Surface.h since Client is a nested
+ // class.
+ static sp<ISurface> getISurface(const sp<Surface>& surface);
+
class Client : public BnCamera
{
public:
@@ -87,11 +93,15 @@ private:
virtual status_t connect(const sp<ICameraClient>& client);
virtual status_t lock();
virtual status_t unlock();
- virtual status_t setPreviewDisplay(const sp<ISurface>& surface);
+ virtual status_t setPreviewDisplay(const sp<Surface>& surface);
+ virtual status_t setPreviewTexture(const sp<ISurfaceTexture>& surfaceTexture);
virtual void setPreviewCallbackFlag(int flag);
virtual status_t startPreview();
virtual void stopPreview();
virtual bool previewEnabled();
+ virtual int32_t getNumberOfVideoBuffers() const;
+ virtual sp<IMemory> getVideoBuffer(int32_t index) const;
+ virtual status_t storeMetaDataInBuffers(bool enabled);
virtual status_t startRecording();
virtual void stopRecording();
virtual bool recordingEnabled();
@@ -121,7 +131,6 @@ private:
// these are internal functions used to set up preview buffers
status_t registerPreviewBuffers();
- status_t setOverlay();
// camera operation mode
enum camera_mode {
@@ -133,6 +142,9 @@ private:
status_t startPreviewMode();
status_t startRecordingMode();
+ // internal function used by sendCommand to enable/disable shutter sound.
+ status_t enableShutterSound(bool enable);
+
// these are static callback functions
static void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2, void* user);
static void dataCallback(int32_t msgType, const sp<IMemory>& dataPtr, void* user);
@@ -163,18 +175,15 @@ private:
int mCameraFacing; // immutable after constructor
pid_t mClientPid;
sp<CameraHardwareInterface> mHardware; // cleared after disconnect()
- bool mUseOverlay; // immutable after constructor
- sp<OverlayRef> mOverlayRef;
- int mOverlayW;
- int mOverlayH;
int mPreviewCallbackFlag;
int mOrientation; // Current display orientation
- // True if display orientation has been changed. This is only used in overlay.
- int mOrientationChanged;
+ bool mPlayShutterSound;
// Ensures atomicity among the public methods
mutable Mutex mLock;
- sp<ISurface> mSurface;
+ // This is a binder of Surface or SurfaceTexture.
+ sp<IBinder> mSurface;
+ sp<ANativeWindow> mPreviewWindow;
// If the user want us to return a copy of the preview frame (instead
// of the original one), we allocate mPreviewBuffer and reuse it if possible.
diff --git a/services/input/Android.mk b/services/input/Android.mk
new file mode 100644
index 0000000..d7b61fc
--- /dev/null
+++ b/services/input/Android.mk
@@ -0,0 +1,57 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ EventHub.cpp \
+ InputDispatcher.cpp \
+ InputManager.cpp \
+ InputReader.cpp \
+ InputWindow.cpp \
+ PointerController.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libcutils \
+ libutils \
+ libhardware \
+ libhardware_legacy \
+ libsurfaceflinger_client \
+ libskia \
+ libui
+
+LOCAL_C_INCLUDES := \
+ external/skia/include/core
+
+LOCAL_MODULE:= libinput
+
+LOCAL_MODULE_TAGS := optional
+
+ifeq ($(TARGET_SIMULATOR),true)
+ LOCAL_LDLIBS += -lpthread
+endif
+
+include $(BUILD_SHARED_LIBRARY)
+
+
+# Include subdirectory makefiles
+# ============================================================
+
+# If we're building with ONE_SHOT_MAKEFILE (mm, mmm), then what the framework
+# team really wants is to build the stuff defined by this makefile.
+ifeq (,$(ONE_SHOT_MAKEFILE))
+include $(call first-makefiles-under,$(LOCAL_PATH))
+endif
diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp
new file mode 100644
index 0000000..41dbe2f
--- /dev/null
+++ b/services/input/EventHub.cpp
@@ -0,0 +1,1095 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Handle events, like key input and vsync.
+//
+// The goal is to provide an optimized solution for Linux, not an
+// implementation that works well across all platforms. We expect
+// events to arrive on file descriptors, so that we can use a select()
+// select() call to sleep.
+//
+// We can't select() on anything but network sockets in Windows, so we
+// provide an alternative implementation of waitEvent for that platform.
+//
+#define LOG_TAG "EventHub"
+
+//#define LOG_NDEBUG 0
+
+#include "EventHub.h"
+
+#include <hardware_legacy/power.h>
+
+#include <cutils/properties.h>
+#include <utils/Log.h>
+#include <utils/Timers.h>
+#include <utils/threads.h>
+#include <utils/Errors.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <memory.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <ui/KeyLayoutMap.h>
+#include <ui/KeyCharacterMap.h>
+#include <ui/VirtualKeyMap.h>
+
+#include <string.h>
+#include <stdint.h>
+#include <dirent.h>
+#ifdef HAVE_INOTIFY
+# include <sys/inotify.h>
+#endif
+#ifdef HAVE_ANDROID_OS
+# include <sys/limits.h> /* not part of Linux */
+#endif
+#include <sys/poll.h>
+#include <sys/ioctl.h>
+
+/* this macro is used to tell if "bit" is set in "array"
+ * it selects a byte from the array, and does a boolean AND
+ * operation with a byte that only has the relevant bit set.
+ * eg. to check for the 12th bit, we do (array[1] & 1<<4)
+ */
+#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8)))
+
+/* this macro computes the number of bytes needed to represent a bit array of the specified size */
+#define sizeof_bit_array(bits) ((bits + 7) / 8)
+
+// Fd at index 0 is always reserved for inotify
+#define FIRST_ACTUAL_DEVICE_INDEX 1
+
+#define INDENT " "
+#define INDENT2 " "
+#define INDENT3 " "
+
+namespace android {
+
+static const char *WAKE_LOCK_ID = "KeyEvents";
+static const char *DEVICE_PATH = "/dev/input";
+
+/* return the larger integer */
+static inline int max(int v1, int v2)
+{
+ return (v1 > v2) ? v1 : v2;
+}
+
+static inline const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
+// --- EventHub::Device ---
+
+EventHub::Device::Device(int fd, int32_t id, const String8& path,
+ const InputDeviceIdentifier& identifier) :
+ next(NULL),
+ fd(fd), id(id), path(path), identifier(identifier),
+ classes(0), keyBitmask(NULL), configuration(NULL), virtualKeyMap(NULL) {
+}
+
+EventHub::Device::~Device() {
+ close();
+ delete[] keyBitmask;
+ delete configuration;
+ delete virtualKeyMap;
+}
+
+void EventHub::Device::close() {
+ if (fd >= 0) {
+ ::close(fd);
+ fd = -1;
+ }
+}
+
+
+// --- EventHub ---
+
+EventHub::EventHub(void) :
+ mError(NO_INIT), mBuiltInKeyboardId(-1), mNextDeviceId(1),
+ mOpeningDevices(0), mClosingDevices(0),
+ mOpened(false), mNeedToSendFinishedDeviceScan(false),
+ mInputBufferIndex(0), mInputBufferCount(0), mInputFdIndex(0) {
+ acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
+#ifdef EV_SW
+ memset(mSwitches, 0, sizeof(mSwitches));
+#endif
+}
+
+EventHub::~EventHub(void) {
+ release_wake_lock(WAKE_LOCK_ID);
+ // we should free stuff here...
+}
+
+status_t EventHub::errorCheck() const {
+ return mError;
+}
+
+String8 EventHub::getDeviceName(int32_t deviceId) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device == NULL) return String8();
+ return device->identifier.name;
+}
+
+uint32_t EventHub::getDeviceClasses(int32_t deviceId) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device == NULL) return 0;
+ return device->classes;
+}
+
+void EventHub::getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && device->configuration) {
+ *outConfiguration = *device->configuration;
+ } else {
+ outConfiguration->clear();
+ }
+}
+
+status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
+ RawAbsoluteAxisInfo* outAxisInfo) const {
+ outAxisInfo->clear();
+
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device == NULL) return -1;
+
+ struct input_absinfo info;
+
+ if(ioctl(device->fd, EVIOCGABS(axis), &info)) {
+ LOGW("Error reading absolute controller %d for device %s fd %d\n",
+ axis, device->identifier.name.string(), device->fd);
+ return -errno;
+ }
+
+ if (info.minimum != info.maximum) {
+ outAxisInfo->valid = true;
+ outAxisInfo->minValue = info.minimum;
+ outAxisInfo->maxValue = info.maximum;
+ outAxisInfo->flat = info.flat;
+ outAxisInfo->fuzz = info.fuzz;
+ }
+ return OK;
+}
+
+int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const {
+ if (scanCode >= 0 && scanCode <= KEY_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device != NULL) {
+ return getScanCodeStateLocked(device, scanCode);
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getScanCodeStateLocked(Device* device, int32_t scanCode) const {
+ uint8_t key_bitmask[sizeof_bit_array(KEY_MAX + 1)];
+ memset(key_bitmask, 0, sizeof(key_bitmask));
+ if (ioctl(device->fd,
+ EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) {
+ return test_bit(scanCode, key_bitmask) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
+ }
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device != NULL) {
+ return getKeyCodeStateLocked(device, keyCode);
+ }
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getKeyCodeStateLocked(Device* device, int32_t keyCode) const {
+ if (!device->keyMap.haveKeyLayout()) {
+ return AKEY_STATE_UNKNOWN;
+ }
+
+ Vector<int32_t> scanCodes;
+ device->keyMap.keyLayoutMap->findScanCodes(keyCode, &scanCodes);
+
+ uint8_t key_bitmask[sizeof_bit_array(KEY_MAX + 1)];
+ memset(key_bitmask, 0, sizeof(key_bitmask));
+ if (ioctl(device->fd, EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) {
+ #if 0
+ for (size_t i=0; i<=KEY_MAX; i++) {
+ LOGI("(Scan code %d: down=%d)", i, test_bit(i, key_bitmask));
+ }
+ #endif
+ const size_t N = scanCodes.size();
+ for (size_t i=0; i<N && i<=KEY_MAX; i++) {
+ int32_t sc = scanCodes.itemAt(i);
+ //LOGI("Code %d: down=%d", sc, test_bit(sc, key_bitmask));
+ if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, key_bitmask)) {
+ return AKEY_STATE_DOWN;
+ }
+ }
+ return AKEY_STATE_UP;
+ }
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const {
+#ifdef EV_SW
+ if (sw >= 0 && sw <= SW_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device != NULL) {
+ return getSwitchStateLocked(device, sw);
+ }
+ }
+#endif
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getSwitchStateLocked(Device* device, int32_t sw) const {
+ uint8_t sw_bitmask[sizeof_bit_array(SW_MAX + 1)];
+ memset(sw_bitmask, 0, sizeof(sw_bitmask));
+ if (ioctl(device->fd,
+ EVIOCGSW(sizeof(sw_bitmask)), sw_bitmask) >= 0) {
+ return test_bit(sw, sw_bitmask) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
+ }
+ return AKEY_STATE_UNKNOWN;
+}
+
+bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) const {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device != NULL) {
+ return markSupportedKeyCodesLocked(device, numCodes, keyCodes, outFlags);
+ }
+ return false;
+}
+
+bool EventHub::markSupportedKeyCodesLocked(Device* device, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) const {
+ if (!device->keyMap.haveKeyLayout() || !device->keyBitmask) {
+ return false;
+ }
+
+ Vector<int32_t> scanCodes;
+ for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) {
+ scanCodes.clear();
+
+ status_t err = device->keyMap.keyLayoutMap->findScanCodes(keyCodes[codeIndex], &scanCodes);
+ if (! err) {
+ // check the possible scan codes identified by the layout map against the
+ // map of codes actually emitted by the driver
+ for (size_t sc = 0; sc < scanCodes.size(); sc++) {
+ if (test_bit(scanCodes[sc], device->keyBitmask)) {
+ outFlags[codeIndex] = 1;
+ break;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+status_t EventHub::scancodeToKeycode(int32_t deviceId, int scancode,
+ int32_t* outKeycode, uint32_t* outFlags) const
+{
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+
+ if (device && device->keyMap.haveKeyLayout()) {
+ status_t err = device->keyMap.keyLayoutMap->map(scancode, outKeycode, outFlags);
+ if (err == NO_ERROR) {
+ return NO_ERROR;
+ }
+ }
+
+ if (mBuiltInKeyboardId != -1) {
+ device = getDeviceLocked(mBuiltInKeyboardId);
+
+ if (device && device->keyMap.haveKeyLayout()) {
+ status_t err = device->keyMap.keyLayoutMap->map(scancode, outKeycode, outFlags);
+ if (err == NO_ERROR) {
+ return NO_ERROR;
+ }
+ }
+ }
+
+ *outKeycode = 0;
+ *outFlags = 0;
+ return NAME_NOT_FOUND;
+}
+
+void EventHub::addExcludedDevice(const char* deviceName)
+{
+ AutoMutex _l(mLock);
+
+ String8 name(deviceName);
+ mExcludedDevices.push_back(name);
+}
+
+bool EventHub::hasLed(int32_t deviceId, int32_t led) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device) {
+ uint8_t bitmask[sizeof_bit_array(LED_MAX + 1)];
+ memset(bitmask, 0, sizeof(bitmask));
+ if (ioctl(device->fd, EVIOCGBIT(EV_LED, sizeof(bitmask)), bitmask) >= 0) {
+ if (test_bit(led, bitmask)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void EventHub::setLedState(int32_t deviceId, int32_t led, bool on) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device) {
+ struct input_event ev;
+ ev.time.tv_sec = 0;
+ ev.time.tv_usec = 0;
+ ev.type = EV_LED;
+ ev.code = led;
+ ev.value = on ? 1 : 0;
+
+ ssize_t nWrite;
+ do {
+ nWrite = write(device->fd, &ev, sizeof(struct input_event));
+ } while (nWrite == -1 && errno == EINTR);
+ }
+}
+
+void EventHub::getVirtualKeyDefinitions(int32_t deviceId,
+ Vector<VirtualKeyDefinition>& outVirtualKeys) const {
+ outVirtualKeys.clear();
+
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && device->virtualKeyMap) {
+ outVirtualKeys.appendVector(device->virtualKeyMap->getVirtualKeys());
+ }
+}
+
+EventHub::Device* EventHub::getDeviceLocked(int32_t deviceId) const {
+ if (deviceId == 0) {
+ deviceId = mBuiltInKeyboardId;
+ }
+
+ size_t numDevices = mDevices.size();
+ for (size_t i = FIRST_ACTUAL_DEVICE_INDEX; i < numDevices; i++) {
+ Device* device = mDevices[i];
+ if (device->id == deviceId) {
+ return device;
+ }
+ }
+ return NULL;
+}
+
+bool EventHub::getEvent(RawEvent* outEvent) {
+ outEvent->deviceId = 0;
+ outEvent->type = 0;
+ outEvent->scanCode = 0;
+ outEvent->keyCode = 0;
+ outEvent->flags = 0;
+ outEvent->value = 0;
+ outEvent->when = 0;
+
+ // Note that we only allow one caller to getEvent(), so don't need
+ // to do locking here... only when adding/removing devices.
+
+ if (!mOpened) {
+ mError = openPlatformInput() ? NO_ERROR : UNKNOWN_ERROR;
+ mOpened = true;
+ mNeedToSendFinishedDeviceScan = true;
+ }
+
+ for (;;) {
+ // Report any devices that had last been added/removed.
+ if (mClosingDevices != NULL) {
+ Device* device = mClosingDevices;
+ LOGV("Reporting device closed: id=%d, name=%s\n",
+ device->id, device->path.string());
+ mClosingDevices = device->next;
+ if (device->id == mBuiltInKeyboardId) {
+ outEvent->deviceId = 0;
+ } else {
+ outEvent->deviceId = device->id;
+ }
+ outEvent->type = DEVICE_REMOVED;
+ outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC);
+ delete device;
+ mNeedToSendFinishedDeviceScan = true;
+ return true;
+ }
+
+ if (mOpeningDevices != NULL) {
+ Device* device = mOpeningDevices;
+ LOGV("Reporting device opened: id=%d, name=%s\n",
+ device->id, device->path.string());
+ mOpeningDevices = device->next;
+ if (device->id == mBuiltInKeyboardId) {
+ outEvent->deviceId = 0;
+ } else {
+ outEvent->deviceId = device->id;
+ }
+ outEvent->type = DEVICE_ADDED;
+ outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC);
+ mNeedToSendFinishedDeviceScan = true;
+ return true;
+ }
+
+ if (mNeedToSendFinishedDeviceScan) {
+ mNeedToSendFinishedDeviceScan = false;
+ outEvent->type = FINISHED_DEVICE_SCAN;
+ outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC);
+ return true;
+ }
+
+ // Grab the next input event.
+ for (;;) {
+ // Consume buffered input events, if any.
+ if (mInputBufferIndex < mInputBufferCount) {
+ const struct input_event& iev = mInputBufferData[mInputBufferIndex++];
+ const Device* device = mDevices[mInputFdIndex];
+
+ LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, v=%d", device->path.string(),
+ (int) iev.time.tv_sec, (int) iev.time.tv_usec, iev.type, iev.code, iev.value);
+ if (device->id == mBuiltInKeyboardId) {
+ outEvent->deviceId = 0;
+ } else {
+ outEvent->deviceId = device->id;
+ }
+ outEvent->type = iev.type;
+ outEvent->scanCode = iev.code;
+ outEvent->flags = 0;
+ if (iev.type == EV_KEY) {
+ outEvent->keyCode = AKEYCODE_UNKNOWN;
+ if (device->keyMap.haveKeyLayout()) {
+ status_t err = device->keyMap.keyLayoutMap->map(iev.code,
+ &outEvent->keyCode, &outEvent->flags);
+ LOGV("iev.code=%d keyCode=%d flags=0x%08x err=%d\n",
+ iev.code, outEvent->keyCode, outEvent->flags, err);
+ }
+ } else {
+ outEvent->keyCode = iev.code;
+ }
+ outEvent->value = iev.value;
+
+ // Use an event timestamp in the same timebase as
+ // java.lang.System.nanoTime() and android.os.SystemClock.uptimeMillis()
+ // as expected by the rest of the system.
+ outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC);
+ return true;
+ }
+
+ // Finish reading all events from devices identified in previous poll().
+ // This code assumes that mInputDeviceIndex is initially 0 and that the
+ // revents member of pollfd is initialized to 0 when the device is first added.
+ // Since mFds[0] is used for inotify, we process regular events starting at index 1.
+ mInputFdIndex += 1;
+ if (mInputFdIndex >= mFds.size()) {
+ break;
+ }
+
+ const struct pollfd& pfd = mFds[mInputFdIndex];
+ if (pfd.revents & POLLIN) {
+ int32_t readSize = read(pfd.fd, mInputBufferData,
+ sizeof(struct input_event) * INPUT_BUFFER_SIZE);
+ if (readSize < 0) {
+ if (errno != EAGAIN && errno != EINTR) {
+ LOGW("could not get event (errno=%d)", errno);
+ }
+ } else if ((readSize % sizeof(struct input_event)) != 0) {
+ LOGE("could not get event (wrong size: %d)", readSize);
+ } else {
+ mInputBufferCount = size_t(readSize) / sizeof(struct input_event);
+ mInputBufferIndex = 0;
+ }
+ }
+ }
+
+#if HAVE_INOTIFY
+ // readNotify() will modify mFDs and mFDCount, so this must be done after
+ // processing all other events.
+ if(mFds[0].revents & POLLIN) {
+ readNotify(mFds[0].fd);
+ mFds.editItemAt(0).revents = 0;
+ continue; // report added or removed devices immediately
+ }
+#endif
+
+ mInputFdIndex = 0;
+
+ // Poll for events. Mind the wake lock dance!
+ // We hold a wake lock at all times except during poll(). This works due to some
+ // subtle choreography. When a device driver has pending (unread) events, it acquires
+ // a kernel wake lock. However, once the last pending event has been read, the device
+ // driver will release the kernel wake lock. To prevent the system from going to sleep
+ // when this happens, the EventHub holds onto its own user wake lock while the client
+ // is processing events. Thus the system can only sleep if there are no events
+ // pending or currently being processed.
+ release_wake_lock(WAKE_LOCK_ID);
+
+ int pollResult = poll(mFds.editArray(), mFds.size(), -1);
+
+ acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
+
+ if (pollResult <= 0) {
+ if (errno != EINTR) {
+ LOGW("poll failed (errno=%d)\n", errno);
+ usleep(100000);
+ }
+ }
+ }
+}
+
+/*
+ * Open the platform-specific input device.
+ */
+bool EventHub::openPlatformInput(void) {
+ /*
+ * Open platform-specific input device(s).
+ */
+ int res, fd;
+
+#ifdef HAVE_INOTIFY
+ fd = inotify_init();
+ res = inotify_add_watch(fd, DEVICE_PATH, IN_DELETE | IN_CREATE);
+ if(res < 0) {
+ LOGE("could not add watch for %s, %s\n", DEVICE_PATH, strerror(errno));
+ }
+#else
+ /*
+ * The code in EventHub::getEvent assumes that mFDs[0] is an inotify fd.
+ * We allocate space for it and set it to something invalid.
+ */
+ fd = -1;
+#endif
+
+ // Reserve fd index 0 for inotify.
+ struct pollfd pollfd;
+ pollfd.fd = fd;
+ pollfd.events = POLLIN;
+ pollfd.revents = 0;
+ mFds.push(pollfd);
+ mDevices.push(NULL);
+
+ res = scanDir(DEVICE_PATH);
+ if(res < 0) {
+ LOGE("scan dir failed for %s\n", DEVICE_PATH);
+ }
+
+ return true;
+}
+
+// ----------------------------------------------------------------------------
+
+static bool containsNonZeroByte(const uint8_t* array, uint32_t startIndex, uint32_t endIndex) {
+ const uint8_t* end = array + endIndex;
+ array += startIndex;
+ while (array != end) {
+ if (*(array++) != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static const int32_t GAMEPAD_KEYCODES[] = {
+ AKEYCODE_BUTTON_A, AKEYCODE_BUTTON_B, AKEYCODE_BUTTON_C,
+ AKEYCODE_BUTTON_X, AKEYCODE_BUTTON_Y, AKEYCODE_BUTTON_Z,
+ AKEYCODE_BUTTON_L1, AKEYCODE_BUTTON_R1,
+ AKEYCODE_BUTTON_L2, AKEYCODE_BUTTON_R2,
+ AKEYCODE_BUTTON_THUMBL, AKEYCODE_BUTTON_THUMBR,
+ AKEYCODE_BUTTON_START, AKEYCODE_BUTTON_SELECT, AKEYCODE_BUTTON_MODE
+};
+
+int EventHub::openDevice(const char *devicePath) {
+ char buffer[80];
+
+ LOGV("Opening device: %s", devicePath);
+
+ AutoMutex _l(mLock);
+
+ int fd = open(devicePath, O_RDWR);
+ if(fd < 0) {
+ LOGE("could not open %s, %s\n", devicePath, strerror(errno));
+ return -1;
+ }
+
+ InputDeviceIdentifier identifier;
+
+ // Get device name.
+ if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {
+ //fprintf(stderr, "could not get device name for %s, %s\n", devicePath, strerror(errno));
+ } else {
+ buffer[sizeof(buffer) - 1] = '\0';
+ identifier.name.setTo(buffer);
+ }
+
+ // Check to see if the device is on our excluded list
+ List<String8>::iterator iter = mExcludedDevices.begin();
+ List<String8>::iterator end = mExcludedDevices.end();
+ for ( ; iter != end; iter++) {
+ const char* test = *iter;
+ if (identifier.name == test) {
+ LOGI("ignoring event id %s driver %s\n", devicePath, test);
+ close(fd);
+ return -1;
+ }
+ }
+
+ // Get device driver version.
+ int driverVersion;
+ if(ioctl(fd, EVIOCGVERSION, &driverVersion)) {
+ LOGE("could not get driver version for %s, %s\n", devicePath, strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ // Get device identifier.
+ struct input_id inputId;
+ if(ioctl(fd, EVIOCGID, &inputId)) {
+ LOGE("could not get device input id for %s, %s\n", devicePath, strerror(errno));
+ close(fd);
+ return -1;
+ }
+ identifier.bus = inputId.bustype;
+ identifier.product = inputId.product;
+ identifier.vendor = inputId.vendor;
+ identifier.version = inputId.version;
+
+ // Get device physical location.
+ if(ioctl(fd, EVIOCGPHYS(sizeof(buffer) - 1), &buffer) < 1) {
+ //fprintf(stderr, "could not get location for %s, %s\n", devicePath, strerror(errno));
+ } else {
+ buffer[sizeof(buffer) - 1] = '\0';
+ identifier.location.setTo(buffer);
+ }
+
+ // Get device unique id.
+ if(ioctl(fd, EVIOCGUNIQ(sizeof(buffer) - 1), &buffer) < 1) {
+ //fprintf(stderr, "could not get idstring for %s, %s\n", devicePath, strerror(errno));
+ } else {
+ buffer[sizeof(buffer) - 1] = '\0';
+ identifier.uniqueId.setTo(buffer);
+ }
+
+ // Make file descriptor non-blocking for use with poll().
+ if (fcntl(fd, F_SETFL, O_NONBLOCK)) {
+ LOGE("Error %d making device file descriptor non-blocking.", errno);
+ close(fd);
+ return -1;
+ }
+
+ // Allocate device. (The device object takes ownership of the fd at this point.)
+ int32_t deviceId = mNextDeviceId++;
+ Device* device = new Device(fd, deviceId, String8(devicePath), identifier);
+
+#if 0
+ LOGI("add device %d: %s\n", deviceId, devicePath);
+ LOGI(" bus: %04x\n"
+ " vendor %04x\n"
+ " product %04x\n"
+ " version %04x\n",
+ identifier.bus, identifier.vendor, identifier.product, identifier.version);
+ LOGI(" name: \"%s\"\n", identifier.name.string());
+ LOGI(" location: \"%s\"\n", identifier.location.string());
+ LOGI(" unique id: \"%s\"\n", identifier.uniqueId.string());
+ LOGI(" driver: v%d.%d.%d\n",
+ driverVersion >> 16, (driverVersion >> 8) & 0xff, driverVersion & 0xff);
+#endif
+
+ // Load the configuration file for the device.
+ loadConfiguration(device);
+
+ // Figure out the kinds of events the device reports.
+
+ uint8_t key_bitmask[sizeof_bit_array(KEY_MAX + 1)];
+ memset(key_bitmask, 0, sizeof(key_bitmask));
+
+ LOGV("Getting keys...");
+ if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), key_bitmask) >= 0) {
+ //LOGI("MAP\n");
+ //for (int i = 0; i < sizeof(key_bitmask); i++) {
+ // LOGI("%d: 0x%02x\n", i, key_bitmask[i]);
+ //}
+
+ // See if this is a keyboard. Ignore everything in the button range except for
+ // gamepads which are also considered keyboards.
+ if (containsNonZeroByte(key_bitmask, 0, sizeof_bit_array(BTN_MISC))
+ || containsNonZeroByte(key_bitmask, sizeof_bit_array(BTN_GAMEPAD),
+ sizeof_bit_array(BTN_DIGI))
+ || containsNonZeroByte(key_bitmask, sizeof_bit_array(KEY_OK),
+ sizeof_bit_array(KEY_MAX + 1))) {
+ device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;
+
+ device->keyBitmask = new uint8_t[sizeof(key_bitmask)];
+ if (device->keyBitmask != NULL) {
+ memcpy(device->keyBitmask, key_bitmask, sizeof(key_bitmask));
+ } else {
+ delete device;
+ LOGE("out of memory allocating key bitmask");
+ return -1;
+ }
+ }
+ }
+
+ // See if this is a cursor device such as a trackball or mouse.
+ if (test_bit(BTN_MOUSE, key_bitmask)) {
+ uint8_t rel_bitmask[sizeof_bit_array(REL_MAX + 1)];
+ memset(rel_bitmask, 0, sizeof(rel_bitmask));
+ LOGV("Getting relative controllers...");
+ if (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(rel_bitmask)), rel_bitmask) >= 0) {
+ if (test_bit(REL_X, rel_bitmask) && test_bit(REL_Y, rel_bitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_CURSOR;
+ }
+ }
+ }
+
+ // See if this is a touch pad.
+ uint8_t abs_bitmask[sizeof_bit_array(ABS_MAX + 1)];
+ memset(abs_bitmask, 0, sizeof(abs_bitmask));
+ LOGV("Getting absolute controllers...");
+ if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_bitmask)), abs_bitmask) >= 0) {
+ // Is this a new modern multi-touch driver?
+ if (test_bit(ABS_MT_POSITION_X, abs_bitmask)
+ && test_bit(ABS_MT_POSITION_Y, abs_bitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT;
+
+ // Is this an old style single-touch driver?
+ } else if (test_bit(BTN_TOUCH, key_bitmask)
+ && test_bit(ABS_X, abs_bitmask) && test_bit(ABS_Y, abs_bitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_TOUCH;
+ }
+ }
+
+#ifdef EV_SW
+ // figure out the switches this device reports
+ uint8_t sw_bitmask[sizeof_bit_array(SW_MAX + 1)];
+ memset(sw_bitmask, 0, sizeof(sw_bitmask));
+ bool hasSwitches = false;
+ if (ioctl(fd, EVIOCGBIT(EV_SW, sizeof(sw_bitmask)), sw_bitmask) >= 0) {
+ for (int i=0; i<EV_SW; i++) {
+ //LOGI("Device %d sw %d: has=%d", device->id, i, test_bit(i, sw_bitmask));
+ if (test_bit(i, sw_bitmask)) {
+ hasSwitches = true;
+ if (mSwitches[i] == 0) {
+ mSwitches[i] = device->id;
+ }
+ }
+ }
+ }
+ if (hasSwitches) {
+ device->classes |= INPUT_DEVICE_CLASS_SWITCH;
+ }
+#endif
+
+ if ((device->classes & INPUT_DEVICE_CLASS_TOUCH)) {
+ // Load the virtual keys for the touch screen, if any.
+ // We do this now so that we can make sure to load the keymap if necessary.
+ status_t status = loadVirtualKeyMap(device);
+ if (!status) {
+ device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;
+ }
+ }
+
+ if ((device->classes & INPUT_DEVICE_CLASS_KEYBOARD) != 0) {
+ // Load the keymap for the device.
+ status_t status = loadKeyMap(device);
+
+ // Set system properties for the keyboard.
+ setKeyboardProperties(device, false);
+
+ // Register the keyboard as a built-in keyboard if it is eligible.
+ if (!status
+ && mBuiltInKeyboardId == -1
+ && isEligibleBuiltInKeyboard(device->identifier,
+ device->configuration, &device->keyMap)) {
+ mBuiltInKeyboardId = device->id;
+ setKeyboardProperties(device, true);
+ }
+
+ // 'Q' key support = cheap test of whether this is an alpha-capable kbd
+ if (hasKeycodeLocked(device, AKEYCODE_Q)) {
+ device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY;
+ }
+
+ // See if this device has a DPAD.
+ if (hasKeycodeLocked(device, AKEYCODE_DPAD_UP) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_DOWN) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_LEFT) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_RIGHT) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_CENTER)) {
+ device->classes |= INPUT_DEVICE_CLASS_DPAD;
+ }
+
+ // See if this device has a gamepad.
+ for (size_t i = 0; i < sizeof(GAMEPAD_KEYCODES)/sizeof(GAMEPAD_KEYCODES[0]); i++) {
+ if (hasKeycodeLocked(device, GAMEPAD_KEYCODES[i])) {
+ device->classes |= INPUT_DEVICE_CLASS_GAMEPAD;
+ break;
+ }
+ }
+ }
+
+ // If the device isn't recognized as something we handle, don't monitor it.
+ if (device->classes == 0) {
+ LOGV("Dropping device: id=%d, path='%s', name='%s'",
+ deviceId, devicePath, device->identifier.name.string());
+ delete device;
+ return -1;
+ }
+
+ LOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=0x%x, "
+ "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s",
+ deviceId, fd, devicePath, device->identifier.name.string(),
+ device->classes,
+ device->configurationFile.string(),
+ device->keyMap.keyLayoutFile.string(),
+ device->keyMap.keyCharacterMapFile.string(),
+ toString(mBuiltInKeyboardId == deviceId));
+
+ struct pollfd pollfd;
+ pollfd.fd = fd;
+ pollfd.events = POLLIN;
+ pollfd.revents = 0;
+ mFds.push(pollfd);
+ mDevices.push(device);
+
+ device->next = mOpeningDevices;
+ mOpeningDevices = device;
+ return 0;
+}
+
+void EventHub::loadConfiguration(Device* device) {
+ device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier(
+ device->identifier, INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION);
+ if (device->configurationFile.isEmpty()) {
+ LOGD("No input device configuration file found for device '%s'.",
+ device->identifier.name.string());
+ } else {
+ status_t status = PropertyMap::load(device->configurationFile,
+ &device->configuration);
+ if (status) {
+ LOGE("Error loading input device configuration file for device '%s'. "
+ "Using default configuration.",
+ device->identifier.name.string());
+ }
+ }
+}
+
+status_t EventHub::loadVirtualKeyMap(Device* device) {
+ // The virtual key map is supplied by the kernel as a system board property file.
+ String8 path;
+ path.append("/sys/board_properties/virtualkeys.");
+ path.append(device->identifier.name);
+ if (access(path.string(), R_OK)) {
+ return NAME_NOT_FOUND;
+ }
+ return VirtualKeyMap::load(path, &device->virtualKeyMap);
+}
+
+status_t EventHub::loadKeyMap(Device* device) {
+ return device->keyMap.load(device->identifier, device->configuration);
+}
+
+void EventHub::setKeyboardProperties(Device* device, bool builtInKeyboard) {
+ int32_t id = builtInKeyboard ? 0 : device->id;
+ android::setKeyboardProperties(id, device->identifier,
+ device->keyMap.keyLayoutFile, device->keyMap.keyCharacterMapFile);
+}
+
+void EventHub::clearKeyboardProperties(Device* device, bool builtInKeyboard) {
+ int32_t id = builtInKeyboard ? 0 : device->id;
+ android::clearKeyboardProperties(id);
+}
+
+bool EventHub::hasKeycodeLocked(Device* device, int keycode) const {
+ if (!device->keyMap.haveKeyLayout() || !device->keyBitmask) {
+ return false;
+ }
+
+ Vector<int32_t> scanCodes;
+ device->keyMap.keyLayoutMap->findScanCodes(keycode, &scanCodes);
+ const size_t N = scanCodes.size();
+ for (size_t i=0; i<N && i<=KEY_MAX; i++) {
+ int32_t sc = scanCodes.itemAt(i);
+ if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, device->keyBitmask)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int EventHub::closeDevice(const char *devicePath) {
+ AutoMutex _l(mLock);
+
+ for (size_t i = FIRST_ACTUAL_DEVICE_INDEX; i < mDevices.size(); i++) {
+ Device* device = mDevices[i];
+ if (device->path == devicePath) {
+ LOGI("Removed device: path=%s name=%s id=%d fd=%d classes=0x%x\n",
+ device->path.string(), device->identifier.name.string(), device->id,
+ device->fd, device->classes);
+
+#ifdef EV_SW
+ for (int j=0; j<EV_SW; j++) {
+ if (mSwitches[j] == device->id) {
+ mSwitches[j] = 0;
+ }
+ }
+#endif
+
+ if (device->id == mBuiltInKeyboardId) {
+ LOGW("built-in keyboard device %s (id=%d) is closing! the apps will not like this",
+ device->path.string(), mBuiltInKeyboardId);
+ mBuiltInKeyboardId = -1;
+ clearKeyboardProperties(device, true);
+ }
+ clearKeyboardProperties(device, false);
+
+ mFds.removeAt(i);
+ mDevices.removeAt(i);
+ device->close();
+
+ device->next = mClosingDevices;
+ mClosingDevices = device;
+ return 0;
+ }
+ }
+ LOGE("remove device: %s not found\n", devicePath);
+ return -1;
+}
+
+int EventHub::readNotify(int nfd) {
+#ifdef HAVE_INOTIFY
+ int res;
+ char devname[PATH_MAX];
+ char *filename;
+ char event_buf[512];
+ int event_size;
+ int event_pos = 0;
+ struct inotify_event *event;
+
+ LOGV("EventHub::readNotify nfd: %d\n", nfd);
+ res = read(nfd, event_buf, sizeof(event_buf));
+ if(res < (int)sizeof(*event)) {
+ if(errno == EINTR)
+ return 0;
+ LOGW("could not get event, %s\n", strerror(errno));
+ return 1;
+ }
+ //printf("got %d bytes of event information\n", res);
+
+ strcpy(devname, DEVICE_PATH);
+ filename = devname + strlen(devname);
+ *filename++ = '/';
+
+ while(res >= (int)sizeof(*event)) {
+ event = (struct inotify_event *)(event_buf + event_pos);
+ //printf("%d: %08x \"%s\"\n", event->wd, event->mask, event->len ? event->name : "");
+ if(event->len) {
+ strcpy(filename, event->name);
+ if(event->mask & IN_CREATE) {
+ openDevice(devname);
+ }
+ else {
+ closeDevice(devname);
+ }
+ }
+ event_size = sizeof(*event) + event->len;
+ res -= event_size;
+ event_pos += event_size;
+ }
+#endif
+ return 0;
+}
+
+int EventHub::scanDir(const char *dirname)
+{
+ char devname[PATH_MAX];
+ char *filename;
+ DIR *dir;
+ struct dirent *de;
+ dir = opendir(dirname);
+ if(dir == NULL)
+ return -1;
+ strcpy(devname, dirname);
+ filename = devname + strlen(devname);
+ *filename++ = '/';
+ while((de = readdir(dir))) {
+ if(de->d_name[0] == '.' &&
+ (de->d_name[1] == '\0' ||
+ (de->d_name[1] == '.' && de->d_name[2] == '\0')))
+ continue;
+ strcpy(filename, de->d_name);
+ openDevice(devname);
+ }
+ closedir(dir);
+ return 0;
+}
+
+void EventHub::dump(String8& dump) {
+ dump.append("Event Hub State:\n");
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ dump.appendFormat(INDENT "BuiltInKeyboardId: %d\n", mBuiltInKeyboardId);
+
+ dump.append(INDENT "Devices:\n");
+
+ for (size_t i = FIRST_ACTUAL_DEVICE_INDEX; i < mDevices.size(); i++) {
+ const Device* device = mDevices[i];
+ if (device) {
+ if (mBuiltInKeyboardId == device->id) {
+ dump.appendFormat(INDENT2 "%d: %s (aka device 0 - built-in keyboard)\n",
+ device->id, device->identifier.name.string());
+ } else {
+ dump.appendFormat(INDENT2 "%d: %s\n", device->id,
+ device->identifier.name.string());
+ }
+ dump.appendFormat(INDENT3 "Classes: 0x%08x\n", device->classes);
+ dump.appendFormat(INDENT3 "Path: %s\n", device->path.string());
+ dump.appendFormat(INDENT3 "Location: %s\n", device->identifier.location.string());
+ dump.appendFormat(INDENT3 "UniqueId: %s\n", device->identifier.uniqueId.string());
+ dump.appendFormat(INDENT3 "Identifier: bus=0x%04x, vendor=0x%04x, "
+ "product=0x%04x, version=0x%04x\n",
+ device->identifier.bus, device->identifier.vendor,
+ device->identifier.product, device->identifier.version);
+ dump.appendFormat(INDENT3 "KeyLayoutFile: %s\n",
+ device->keyMap.keyLayoutFile.string());
+ dump.appendFormat(INDENT3 "KeyCharacterMapFile: %s\n",
+ device->keyMap.keyCharacterMapFile.string());
+ dump.appendFormat(INDENT3 "ConfigurationFile: %s\n",
+ device->configurationFile.string());
+ }
+ }
+ } // release lock
+}
+
+}; // namespace android
diff --git a/services/input/EventHub.h b/services/input/EventHub.h
new file mode 100644
index 0000000..0ee0b9b
--- /dev/null
+++ b/services/input/EventHub.h
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+#ifndef _RUNTIME_EVENT_HUB_H
+#define _RUNTIME_EVENT_HUB_H
+
+#include <ui/Input.h>
+#include <ui/Keyboard.h>
+#include <ui/KeyLayoutMap.h>
+#include <ui/KeyCharacterMap.h>
+#include <ui/VirtualKeyMap.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+#include <utils/Log.h>
+#include <utils/threads.h>
+#include <utils/List.h>
+#include <utils/Errors.h>
+#include <utils/PropertyMap.h>
+#include <utils/Vector.h>
+
+#include <linux/input.h>
+
+/* These constants are not defined in linux/input.h but they are part of the multitouch
+ * input protocol. */
+
+#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
+#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
+#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */
+#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */
+#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */
+#define ABS_MT_POSITION_X 0x35 /* Center X ellipse position */
+#define ABS_MT_POSITION_Y 0x36 /* Center Y ellipse position */
+#define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device (finger, pen, ...) */
+#define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */
+#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */
+#define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */
+
+#define MT_TOOL_FINGER 0 /* Identifies a finger */
+#define MT_TOOL_PEN 1 /* Identifies a pen */
+
+#define SYN_MT_REPORT 2
+
+/* Convenience constants. */
+
+#define BTN_FIRST 0x100 // first button scancode
+#define BTN_LAST 0x15f // last button scancode
+
+struct pollfd;
+
+namespace android {
+
+/*
+ * A raw event as retrieved from the EventHub.
+ */
+struct RawEvent {
+ nsecs_t when;
+ int32_t deviceId;
+ int32_t type;
+ int32_t scanCode;
+ int32_t keyCode;
+ int32_t value;
+ uint32_t flags;
+};
+
+/* Describes an absolute axis. */
+struct RawAbsoluteAxisInfo {
+ bool valid; // true if the information is valid, false otherwise
+
+ int32_t minValue; // minimum value
+ int32_t maxValue; // maximum value
+ int32_t flat; // center flat position, eg. flat == 8 means center is between -8 and 8
+ int32_t fuzz; // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise
+
+ inline int32_t getRange() { return maxValue - minValue; }
+
+ inline void clear() {
+ valid = false;
+ minValue = 0;
+ maxValue = 0;
+ flat = 0;
+ fuzz = 0;
+ }
+};
+
+/*
+ * Input device classes.
+ */
+enum {
+ /* The input device is a keyboard. */
+ INPUT_DEVICE_CLASS_KEYBOARD = 0x00000001,
+
+ /* The input device is an alpha-numeric keyboard (not just a dial pad). */
+ INPUT_DEVICE_CLASS_ALPHAKEY = 0x00000002,
+
+ /* The input device is a touchscreen or a touchpad (either single-touch or multi-touch). */
+ INPUT_DEVICE_CLASS_TOUCH = 0x00000004,
+
+ /* The input device is a cursor device such as a trackball or mouse. */
+ INPUT_DEVICE_CLASS_CURSOR = 0x00000008,
+
+ /* The input device is a multi-touch touchscreen. */
+ INPUT_DEVICE_CLASS_TOUCH_MT = 0x00000010,
+
+ /* The input device is a directional pad (implies keyboard, has DPAD keys). */
+ INPUT_DEVICE_CLASS_DPAD = 0x00000020,
+
+ /* The input device is a gamepad (implies keyboard, has BUTTON keys). */
+ INPUT_DEVICE_CLASS_GAMEPAD = 0x00000040,
+
+ /* The input device has switches. */
+ INPUT_DEVICE_CLASS_SWITCH = 0x00000080,
+};
+
+/*
+ * Grand Central Station for events.
+ *
+ * The event hub aggregates input events received across all known input
+ * devices on the system, including devices that may be emulated by the simulator
+ * environment. In addition, the event hub generates fake input events to indicate
+ * when devices are added or removed.
+ *
+ * The event hub provides a stream of input events (via the getEvent function).
+ * It also supports querying the current actual state of input devices such as identifying
+ * which keys are currently down. Finally, the event hub keeps track of the capabilities of
+ * individual input devices, such as their class and the set of key codes that they support.
+ */
+class EventHubInterface : public virtual RefBase {
+protected:
+ EventHubInterface() { }
+ virtual ~EventHubInterface() { }
+
+public:
+ // Synthetic raw event type codes produced when devices are added or removed.
+ enum {
+ // Sent when a device is added.
+ DEVICE_ADDED = 0x10000000,
+ // Sent when a device is removed.
+ DEVICE_REMOVED = 0x20000000,
+ // Sent when all added/removed devices from the most recent scan have been reported.
+ // This event is always sent at least once.
+ FINISHED_DEVICE_SCAN = 0x30000000,
+ };
+
+ virtual uint32_t getDeviceClasses(int32_t deviceId) const = 0;
+
+ virtual String8 getDeviceName(int32_t deviceId) const = 0;
+
+ virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const = 0;
+
+ virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+ RawAbsoluteAxisInfo* outAxisInfo) const = 0;
+
+ virtual status_t scancodeToKeycode(int32_t deviceId, int scancode,
+ int32_t* outKeycode, uint32_t* outFlags) const = 0;
+
+ // exclude a particular device from opening
+ // this can be used to ignore input devices for sensors
+ virtual void addExcludedDevice(const char* deviceName) = 0;
+
+ /*
+ * Wait for the next event to become available and return it.
+ * After returning, the EventHub holds onto a wake lock until the next call to getEvent.
+ * This ensures that the device will not go to sleep while the event is being processed.
+ * If the device needs to remain awake longer than that, then the caller is responsible
+ * for taking care of it (say, by poking the power manager user activity timer).
+ */
+ virtual bool getEvent(RawEvent* outEvent) = 0;
+
+ /*
+ * Query current input state.
+ */
+ virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0;
+ virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0;
+ virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0;
+
+ /*
+ * Examine key input devices for specific framework keycode support
+ */
+ virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
+ uint8_t* outFlags) const = 0;
+
+ virtual bool hasLed(int32_t deviceId, int32_t led) const = 0;
+ virtual void setLedState(int32_t deviceId, int32_t led, bool on) = 0;
+
+ virtual void getVirtualKeyDefinitions(int32_t deviceId,
+ Vector<VirtualKeyDefinition>& outVirtualKeys) const = 0;
+
+ virtual void dump(String8& dump) = 0;
+};
+
+class EventHub : public EventHubInterface
+{
+public:
+ EventHub();
+
+ status_t errorCheck() const;
+
+ virtual uint32_t getDeviceClasses(int32_t deviceId) const;
+
+ virtual String8 getDeviceName(int32_t deviceId) const;
+
+ virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const;
+
+ virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+ RawAbsoluteAxisInfo* outAxisInfo) const;
+
+ virtual status_t scancodeToKeycode(int32_t deviceId, int scancode,
+ int32_t* outKeycode, uint32_t* outFlags) const;
+
+ virtual void addExcludedDevice(const char* deviceName);
+
+ virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const;
+ virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const;
+ virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const;
+
+ virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) const;
+
+ virtual bool getEvent(RawEvent* outEvent);
+
+ virtual bool hasLed(int32_t deviceId, int32_t led) const;
+ virtual void setLedState(int32_t deviceId, int32_t led, bool on);
+
+ virtual void getVirtualKeyDefinitions(int32_t deviceId,
+ Vector<VirtualKeyDefinition>& outVirtualKeys) const;
+
+ virtual void dump(String8& dump);
+
+protected:
+ virtual ~EventHub();
+
+private:
+ bool openPlatformInput(void);
+
+ int openDevice(const char *devicePath);
+ int closeDevice(const char *devicePath);
+ int scanDir(const char *dirname);
+ int readNotify(int nfd);
+
+ status_t mError;
+
+ struct Device {
+ Device* next;
+
+ int fd;
+ const int32_t id;
+ const String8 path;
+ const InputDeviceIdentifier identifier;
+
+ uint32_t classes;
+ uint8_t* keyBitmask;
+ String8 configurationFile;
+ PropertyMap* configuration;
+ VirtualKeyMap* virtualKeyMap;
+ KeyMap keyMap;
+
+ Device(int fd, int32_t id, const String8& path, const InputDeviceIdentifier& identifier);
+ ~Device();
+
+ void close();
+ };
+
+ Device* getDeviceLocked(int32_t deviceId) const;
+ bool hasKeycodeLocked(Device* device, int keycode) const;
+
+ int32_t getScanCodeStateLocked(Device* device, int32_t scanCode) const;
+ int32_t getKeyCodeStateLocked(Device* device, int32_t keyCode) const;
+ int32_t getSwitchStateLocked(Device* device, int32_t sw) const;
+ bool markSupportedKeyCodesLocked(Device* device, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) const;
+
+ void loadConfiguration(Device* device);
+ status_t loadVirtualKeyMap(Device* device);
+ status_t loadKeyMap(Device* device);
+ void setKeyboardProperties(Device* device, bool builtInKeyboard);
+ void clearKeyboardProperties(Device* device, bool builtInKeyboard);
+
+ // Protect all internal state.
+ mutable Mutex mLock;
+
+ // The actual id of the built-in keyboard, or -1 if none.
+ // EventHub remaps the built-in keyboard to id 0 externally as required by the API.
+ int32_t mBuiltInKeyboardId;
+
+ int32_t mNextDeviceId;
+
+ // Parallel arrays of fds and devices.
+ // First index is reserved for inotify.
+ Vector<struct pollfd> mFds;
+ Vector<Device*> mDevices;
+
+ Device *mOpeningDevices;
+ Device *mClosingDevices;
+
+ bool mOpened;
+ bool mNeedToSendFinishedDeviceScan;
+ List<String8> mExcludedDevices;
+
+ // device ids that report particular switches.
+#ifdef EV_SW
+ int32_t mSwitches[SW_MAX + 1];
+#endif
+
+ static const int INPUT_BUFFER_SIZE = 64;
+ struct input_event mInputBufferData[INPUT_BUFFER_SIZE];
+ size_t mInputBufferIndex;
+ size_t mInputBufferCount;
+ size_t mInputFdIndex;
+};
+
+}; // namespace android
+
+#endif // _RUNTIME_EVENT_HUB_H
diff --git a/services/input/InputApplication.h b/services/input/InputApplication.h
new file mode 100644
index 0000000..cc80062
--- /dev/null
+++ b/services/input/InputApplication.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_APPLICATION_H
+#define _UI_INPUT_APPLICATION_H
+
+#include <ui/Input.h>
+
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+#include <utils/String8.h>
+
+namespace android {
+
+/*
+ * A handle to an application that can receive input.
+ * Used by the native input dispatcher to indirectly refer to the window manager objects
+ * that describe an application.
+ */
+class InputApplicationHandle : public RefBase {
+protected:
+ InputApplicationHandle() { }
+ virtual ~InputApplicationHandle() { }
+};
+
+
+/*
+ * An input application describes properties of an application that can receive input.
+ */
+struct InputApplication {
+ sp<InputApplicationHandle> inputApplicationHandle;
+ String8 name;
+ nsecs_t dispatchingTimeout;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_APPLICATION_H
diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp
new file mode 100644
index 0000000..cbfdd75
--- /dev/null
+++ b/services/input/InputDispatcher.cpp
@@ -0,0 +1,3862 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputDispatcher"
+
+//#define LOG_NDEBUG 0
+
+// Log detailed debug messages about each inbound event notification to the dispatcher.
+#define DEBUG_INBOUND_EVENT_DETAILS 0
+
+// Log detailed debug messages about each outbound event processed by the dispatcher.
+#define DEBUG_OUTBOUND_EVENT_DETAILS 0
+
+// Log debug messages about batching.
+#define DEBUG_BATCHING 0
+
+// Log debug messages about the dispatch cycle.
+#define DEBUG_DISPATCH_CYCLE 0
+
+// Log debug messages about registrations.
+#define DEBUG_REGISTRATION 0
+
+// Log debug messages about performance statistics.
+#define DEBUG_PERFORMANCE_STATISTICS 0
+
+// Log debug messages about input event injection.
+#define DEBUG_INJECTION 0
+
+// Log debug messages about input event throttling.
+#define DEBUG_THROTTLING 0
+
+// Log debug messages about input focus tracking.
+#define DEBUG_FOCUS 0
+
+// Log debug messages about the app switch latency optimization.
+#define DEBUG_APP_SWITCH 0
+
+#include "InputDispatcher.h"
+
+#include <cutils/log.h>
+#include <ui/PowerManager.h>
+
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+
+#define INDENT " "
+#define INDENT2 " "
+
+namespace android {
+
+// Default input dispatching timeout if there is no focused application or paused window
+// from which to determine an appropriate dispatching timeout.
+const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec
+
+// Amount of time to allow for all pending events to be processed when an app switch
+// key is on the way. This is used to preempt input dispatch and drop input events
+// when an application takes too long to respond and the user has pressed an app switch key.
+const nsecs_t APP_SWITCH_TIMEOUT = 500 * 1000000LL; // 0.5sec
+
+// Amount of time to allow for an event to be dispatched (measured since its eventTime)
+// before considering it stale and dropping it.
+const nsecs_t STALE_EVENT_TIMEOUT = 10000 * 1000000LL; // 10sec
+
+
+static inline nsecs_t now() {
+ return systemTime(SYSTEM_TIME_MONOTONIC);
+}
+
+static inline const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
+static inline int32_t getMotionEventActionPointerIndex(int32_t action) {
+ return (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
+ >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+}
+
+static bool isValidKeyAction(int32_t action) {
+ switch (action) {
+ case AKEY_EVENT_ACTION_DOWN:
+ case AKEY_EVENT_ACTION_UP:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool validateKeyEvent(int32_t action) {
+ if (! isValidKeyAction(action)) {
+ LOGE("Key event has invalid action code 0x%x", action);
+ return false;
+ }
+ return true;
+}
+
+static bool isValidMotionAction(int32_t action, size_t pointerCount) {
+ switch (action & AMOTION_EVENT_ACTION_MASK) {
+ case AMOTION_EVENT_ACTION_DOWN:
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_CANCEL:
+ case AMOTION_EVENT_ACTION_MOVE:
+ case AMOTION_EVENT_ACTION_OUTSIDE:
+ return true;
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ case AMOTION_EVENT_ACTION_POINTER_UP: {
+ int32_t index = getMotionEventActionPointerIndex(action);
+ return index >= 0 && size_t(index) < pointerCount;
+ }
+ default:
+ return false;
+ }
+}
+
+static bool validateMotionEvent(int32_t action, size_t pointerCount,
+ const int32_t* pointerIds) {
+ if (! isValidMotionAction(action, pointerCount)) {
+ LOGE("Motion event has invalid action code 0x%x", action);
+ return false;
+ }
+ if (pointerCount < 1 || pointerCount > MAX_POINTERS) {
+ LOGE("Motion event has invalid pointer count %d; value must be between 1 and %d.",
+ pointerCount, MAX_POINTERS);
+ return false;
+ }
+ BitSet32 pointerIdBits;
+ for (size_t i = 0; i < pointerCount; i++) {
+ int32_t id = pointerIds[i];
+ if (id < 0 || id > MAX_POINTER_ID) {
+ LOGE("Motion event has invalid pointer id %d; value must be between 0 and %d",
+ id, MAX_POINTER_ID);
+ return false;
+ }
+ if (pointerIdBits.hasBit(id)) {
+ LOGE("Motion event has duplicate pointer id %d", id);
+ return false;
+ }
+ pointerIdBits.markBit(id);
+ }
+ return true;
+}
+
+static void dumpRegion(String8& dump, const SkRegion& region) {
+ if (region.isEmpty()) {
+ dump.append("<empty>");
+ return;
+ }
+
+ bool first = true;
+ for (SkRegion::Iterator it(region); !it.done(); it.next()) {
+ if (first) {
+ first = false;
+ } else {
+ dump.append("|");
+ }
+ const SkIRect& rect = it.rect();
+ dump.appendFormat("[%d,%d][%d,%d]", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+ }
+}
+
+
+// --- InputDispatcher ---
+
+InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
+ mPolicy(policy),
+ mPendingEvent(NULL), mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX),
+ mNextUnblockedEvent(NULL),
+ mDispatchEnabled(true), mDispatchFrozen(false),
+ mFocusedWindow(NULL),
+ mFocusedApplication(NULL),
+ mCurrentInputTargetsValid(false),
+ mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) {
+ mLooper = new Looper(false);
+
+ mInboundQueue.headSentinel.refCount = -1;
+ mInboundQueue.headSentinel.type = EventEntry::TYPE_SENTINEL;
+ mInboundQueue.headSentinel.eventTime = LONG_LONG_MIN;
+
+ mInboundQueue.tailSentinel.refCount = -1;
+ mInboundQueue.tailSentinel.type = EventEntry::TYPE_SENTINEL;
+ mInboundQueue.tailSentinel.eventTime = LONG_LONG_MAX;
+
+ mKeyRepeatState.lastKeyEntry = NULL;
+
+ int32_t maxEventsPerSecond = policy->getMaxEventsPerSecond();
+ mThrottleState.minTimeBetweenEvents = 1000000000LL / maxEventsPerSecond;
+ mThrottleState.lastDeviceId = -1;
+
+#if DEBUG_THROTTLING
+ mThrottleState.originalSampleCount = 0;
+ LOGD("Throttling - Max events per second = %d", maxEventsPerSecond);
+#endif
+}
+
+InputDispatcher::~InputDispatcher() {
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ resetKeyRepeatLocked();
+ releasePendingEventLocked();
+ drainInboundQueueLocked();
+ }
+
+ while (mConnectionsByReceiveFd.size() != 0) {
+ unregisterInputChannel(mConnectionsByReceiveFd.valueAt(0)->inputChannel);
+ }
+}
+
+void InputDispatcher::dispatchOnce() {
+ nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout();
+ nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay();
+
+ nsecs_t nextWakeupTime = LONG_LONG_MAX;
+ { // acquire lock
+ AutoMutex _l(mLock);
+ dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime);
+
+ if (runCommandsLockedInterruptible()) {
+ nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
+ }
+ } // release lock
+
+ // Wait for callback or timeout or wake. (make sure we round up, not down)
+ nsecs_t currentTime = now();
+ int32_t timeoutMillis;
+ if (nextWakeupTime > currentTime) {
+ uint64_t timeout = uint64_t(nextWakeupTime - currentTime);
+ timeout = (timeout + 999999LL) / 1000000LL;
+ timeoutMillis = timeout > INT_MAX ? -1 : int32_t(timeout);
+ } else {
+ timeoutMillis = 0;
+ }
+
+ mLooper->pollOnce(timeoutMillis);
+}
+
+void InputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout,
+ nsecs_t keyRepeatDelay, nsecs_t* nextWakeupTime) {
+ nsecs_t currentTime = now();
+
+ // Reset the key repeat timer whenever we disallow key events, even if the next event
+ // is not a key. This is to ensure that we abort a key repeat if the device is just coming
+ // out of sleep.
+ if (keyRepeatTimeout < 0) {
+ resetKeyRepeatLocked();
+ }
+
+ // If dispatching is frozen, do not process timeouts or try to deliver any new events.
+ if (mDispatchFrozen) {
+#if DEBUG_FOCUS
+ LOGD("Dispatch frozen. Waiting some more.");
+#endif
+ return;
+ }
+
+ // Optimize latency of app switches.
+ // Essentially we start a short timeout when an app switch key (HOME / ENDCALL) has
+ // been pressed. When it expires, we preempt dispatch and drop all other pending events.
+ bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
+ if (mAppSwitchDueTime < *nextWakeupTime) {
+ *nextWakeupTime = mAppSwitchDueTime;
+ }
+
+ // Ready to start a new event.
+ // If we don't already have a pending event, go grab one.
+ if (! mPendingEvent) {
+ if (mInboundQueue.isEmpty()) {
+ if (isAppSwitchDue) {
+ // The inbound queue is empty so the app switch key we were waiting
+ // for will never arrive. Stop waiting for it.
+ resetPendingAppSwitchLocked(false);
+ isAppSwitchDue = false;
+ }
+
+ // Synthesize a key repeat if appropriate.
+ if (mKeyRepeatState.lastKeyEntry) {
+ if (currentTime >= mKeyRepeatState.nextRepeatTime) {
+ mPendingEvent = synthesizeKeyRepeatLocked(currentTime, keyRepeatDelay);
+ } else {
+ if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) {
+ *nextWakeupTime = mKeyRepeatState.nextRepeatTime;
+ }
+ }
+ }
+ if (! mPendingEvent) {
+ return;
+ }
+ } else {
+ // Inbound queue has at least one entry.
+ EventEntry* entry = mInboundQueue.headSentinel.next;
+
+ // Throttle the entry if it is a move event and there are no
+ // other events behind it in the queue. Due to movement batching, additional
+ // samples may be appended to this event by the time the throttling timeout
+ // expires.
+ // TODO Make this smarter and consider throttling per device independently.
+ if (entry->type == EventEntry::TYPE_MOTION
+ && !isAppSwitchDue
+ && mDispatchEnabled
+ && (entry->policyFlags & POLICY_FLAG_PASS_TO_USER)
+ && !entry->isInjected()) {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+ int32_t deviceId = motionEntry->deviceId;
+ uint32_t source = motionEntry->source;
+ if (! isAppSwitchDue
+ && motionEntry->next == & mInboundQueue.tailSentinel // exactly one event
+ && motionEntry->action == AMOTION_EVENT_ACTION_MOVE
+ && deviceId == mThrottleState.lastDeviceId
+ && source == mThrottleState.lastSource) {
+ nsecs_t nextTime = mThrottleState.lastEventTime
+ + mThrottleState.minTimeBetweenEvents;
+ if (currentTime < nextTime) {
+ // Throttle it!
+#if DEBUG_THROTTLING
+ LOGD("Throttling - Delaying motion event for "
+ "device %d, source 0x%08x by up to %0.3fms.",
+ deviceId, source, (nextTime - currentTime) * 0.000001);
+#endif
+ if (nextTime < *nextWakeupTime) {
+ *nextWakeupTime = nextTime;
+ }
+ if (mThrottleState.originalSampleCount == 0) {
+ mThrottleState.originalSampleCount =
+ motionEntry->countSamples();
+ }
+ return;
+ }
+ }
+
+#if DEBUG_THROTTLING
+ if (mThrottleState.originalSampleCount != 0) {
+ uint32_t count = motionEntry->countSamples();
+ LOGD("Throttling - Motion event sample count grew by %d from %d to %d.",
+ count - mThrottleState.originalSampleCount,
+ mThrottleState.originalSampleCount, count);
+ mThrottleState.originalSampleCount = 0;
+ }
+#endif
+
+ mThrottleState.lastEventTime = entry->eventTime < currentTime
+ ? entry->eventTime : currentTime;
+ mThrottleState.lastDeviceId = deviceId;
+ mThrottleState.lastSource = source;
+ }
+
+ mInboundQueue.dequeue(entry);
+ mPendingEvent = entry;
+ }
+
+ // Poke user activity for this event.
+ if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
+ pokeUserActivityLocked(mPendingEvent);
+ }
+ }
+
+ // Now we have an event to dispatch.
+ // All events are eventually dequeued and processed this way, even if we intend to drop them.
+ assert(mPendingEvent != NULL);
+ bool done = false;
+ DropReason dropReason = DROP_REASON_NOT_DROPPED;
+ if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
+ dropReason = DROP_REASON_POLICY;
+ } else if (!mDispatchEnabled) {
+ dropReason = DROP_REASON_DISABLED;
+ }
+
+ if (mNextUnblockedEvent == mPendingEvent) {
+ mNextUnblockedEvent = NULL;
+ }
+
+ switch (mPendingEvent->type) {
+ case EventEntry::TYPE_CONFIGURATION_CHANGED: {
+ ConfigurationChangedEntry* typedEntry =
+ static_cast<ConfigurationChangedEntry*>(mPendingEvent);
+ done = dispatchConfigurationChangedLocked(currentTime, typedEntry);
+ dropReason = DROP_REASON_NOT_DROPPED; // configuration changes are never dropped
+ break;
+ }
+
+ case EventEntry::TYPE_KEY: {
+ KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
+ if (isAppSwitchDue) {
+ if (isAppSwitchKeyEventLocked(typedEntry)) {
+ resetPendingAppSwitchLocked(true);
+ isAppSwitchDue = false;
+ } else if (dropReason == DROP_REASON_NOT_DROPPED) {
+ dropReason = DROP_REASON_APP_SWITCH;
+ }
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED
+ && isStaleEventLocked(currentTime, typedEntry)) {
+ dropReason = DROP_REASON_STALE;
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
+ dropReason = DROP_REASON_BLOCKED;
+ }
+ done = dispatchKeyLocked(currentTime, typedEntry, keyRepeatTimeout,
+ &dropReason, nextWakeupTime);
+ break;
+ }
+
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
+ if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {
+ dropReason = DROP_REASON_APP_SWITCH;
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED
+ && isStaleEventLocked(currentTime, typedEntry)) {
+ dropReason = DROP_REASON_STALE;
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
+ dropReason = DROP_REASON_BLOCKED;
+ }
+ done = dispatchMotionLocked(currentTime, typedEntry,
+ &dropReason, nextWakeupTime);
+ break;
+ }
+
+ default:
+ assert(false);
+ break;
+ }
+
+ if (done) {
+ if (dropReason != DROP_REASON_NOT_DROPPED) {
+ dropInboundEventLocked(mPendingEvent, dropReason);
+ }
+
+ releasePendingEventLocked();
+ *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
+ }
+}
+
+bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
+ bool needWake = mInboundQueue.isEmpty();
+ mInboundQueue.enqueueAtTail(entry);
+
+ switch (entry->type) {
+ case EventEntry::TYPE_KEY: {
+ // Optimize app switch latency.
+ // If the application takes too long to catch up then we drop all events preceding
+ // the app switch key.
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
+ if (isAppSwitchKeyEventLocked(keyEntry)) {
+ if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {
+ mAppSwitchSawKeyDown = true;
+ } else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {
+ if (mAppSwitchSawKeyDown) {
+#if DEBUG_APP_SWITCH
+ LOGD("App switch is pending!");
+#endif
+ mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;
+ mAppSwitchSawKeyDown = false;
+ needWake = true;
+ }
+ }
+ }
+ break;
+ }
+
+ case EventEntry::TYPE_MOTION: {
+ // Optimize case where the current application is unresponsive and the user
+ // decides to touch a window in a different application.
+ // If the application takes too long to catch up then we drop all events preceding
+ // the touch into the other window.
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+ if (motionEntry->action == AMOTION_EVENT_ACTION_DOWN
+ && (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER)
+ && mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
+ && mInputTargetWaitApplication != NULL) {
+ int32_t x = int32_t(motionEntry->firstSample.pointerCoords[0].x);
+ int32_t y = int32_t(motionEntry->firstSample.pointerCoords[0].y);
+ const InputWindow* touchedWindow = findTouchedWindowAtLocked(x, y);
+ if (touchedWindow
+ && touchedWindow->inputWindowHandle != NULL
+ && touchedWindow->inputWindowHandle->getInputApplicationHandle()
+ != mInputTargetWaitApplication) {
+ // User touched a different application than the one we are waiting on.
+ // Flag the event, and start pruning the input queue.
+ mNextUnblockedEvent = motionEntry;
+ needWake = true;
+ }
+ }
+ break;
+ }
+ }
+
+ return needWake;
+}
+
+const InputWindow* InputDispatcher::findTouchedWindowAtLocked(int32_t x, int32_t y) {
+ // Traverse windows from front to back to find touched window.
+ size_t numWindows = mWindows.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ const InputWindow* window = & mWindows.editItemAt(i);
+ int32_t flags = window->layoutParamsFlags;
+
+ if (window->visible) {
+ if (!(flags & InputWindow::FLAG_NOT_TOUCHABLE)) {
+ bool isTouchModal = (flags & (InputWindow::FLAG_NOT_FOCUSABLE
+ | InputWindow::FLAG_NOT_TOUCH_MODAL)) == 0;
+ if (isTouchModal || window->touchableRegionContainsPoint(x, y)) {
+ // Found window.
+ return window;
+ }
+ }
+ }
+
+ if (flags & InputWindow::FLAG_SYSTEM_ERROR) {
+ // Error window is on top but not visible, so touch is dropped.
+ return NULL;
+ }
+ }
+ return NULL;
+}
+
+void InputDispatcher::dropInboundEventLocked(EventEntry* entry, DropReason dropReason) {
+ const char* reason;
+ switch (dropReason) {
+ case DROP_REASON_POLICY:
+#if DEBUG_INBOUND_EVENT_DETAILS
+ LOGD("Dropped event because policy consumed it.");
+#endif
+ reason = "inbound event was dropped because the policy consumed it";
+ break;
+ case DROP_REASON_DISABLED:
+ LOGI("Dropped event because input dispatch is disabled.");
+ reason = "inbound event was dropped because input dispatch is disabled";
+ break;
+ case DROP_REASON_APP_SWITCH:
+ LOGI("Dropped event because of pending overdue app switch.");
+ reason = "inbound event was dropped because of pending overdue app switch";
+ break;
+ case DROP_REASON_BLOCKED:
+ LOGI("Dropped event because the current application is not responding and the user "
+ "has started interating with a different application.");
+ reason = "inbound event was dropped because the current application is not responding "
+ "and the user has started interating with a different application";
+ break;
+ case DROP_REASON_STALE:
+ LOGI("Dropped event because it is stale.");
+ reason = "inbound event was dropped because it is stale";
+ break;
+ default:
+ assert(false);
+ return;
+ }
+
+ switch (entry->type) {
+ case EventEntry::TYPE_KEY:
+ synthesizeCancelationEventsForAllConnectionsLocked(
+ InputState::CANCEL_NON_POINTER_EVENTS, reason);
+ break;
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+ if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) {
+ synthesizeCancelationEventsForAllConnectionsLocked(
+ InputState::CANCEL_POINTER_EVENTS, reason);
+ } else {
+ synthesizeCancelationEventsForAllConnectionsLocked(
+ InputState::CANCEL_NON_POINTER_EVENTS, reason);
+ }
+ break;
+ }
+ }
+}
+
+bool InputDispatcher::isAppSwitchKeyCode(int32_t keyCode) {
+ return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL;
+}
+
+bool InputDispatcher::isAppSwitchKeyEventLocked(KeyEntry* keyEntry) {
+ return ! (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED)
+ && isAppSwitchKeyCode(keyEntry->keyCode)
+ && (keyEntry->policyFlags & POLICY_FLAG_TRUSTED)
+ && (keyEntry->policyFlags & POLICY_FLAG_PASS_TO_USER);
+}
+
+bool InputDispatcher::isAppSwitchPendingLocked() {
+ return mAppSwitchDueTime != LONG_LONG_MAX;
+}
+
+void InputDispatcher::resetPendingAppSwitchLocked(bool handled) {
+ mAppSwitchDueTime = LONG_LONG_MAX;
+
+#if DEBUG_APP_SWITCH
+ if (handled) {
+ LOGD("App switch has arrived.");
+ } else {
+ LOGD("App switch was abandoned.");
+ }
+#endif
+}
+
+bool InputDispatcher::isStaleEventLocked(nsecs_t currentTime, EventEntry* entry) {
+ return currentTime - entry->eventTime >= STALE_EVENT_TIMEOUT;
+}
+
+bool InputDispatcher::runCommandsLockedInterruptible() {
+ if (mCommandQueue.isEmpty()) {
+ return false;
+ }
+
+ do {
+ CommandEntry* commandEntry = mCommandQueue.dequeueAtHead();
+
+ Command command = commandEntry->command;
+ (this->*command)(commandEntry); // commands are implicitly 'LockedInterruptible'
+
+ commandEntry->connection.clear();
+ mAllocator.releaseCommandEntry(commandEntry);
+ } while (! mCommandQueue.isEmpty());
+ return true;
+}
+
+InputDispatcher::CommandEntry* InputDispatcher::postCommandLocked(Command command) {
+ CommandEntry* commandEntry = mAllocator.obtainCommandEntry(command);
+ mCommandQueue.enqueueAtTail(commandEntry);
+ return commandEntry;
+}
+
+void InputDispatcher::drainInboundQueueLocked() {
+ while (! mInboundQueue.isEmpty()) {
+ EventEntry* entry = mInboundQueue.dequeueAtHead();
+ releaseInboundEventLocked(entry);
+ }
+}
+
+void InputDispatcher::releasePendingEventLocked() {
+ if (mPendingEvent) {
+ releaseInboundEventLocked(mPendingEvent);
+ mPendingEvent = NULL;
+ }
+}
+
+void InputDispatcher::releaseInboundEventLocked(EventEntry* entry) {
+ InjectionState* injectionState = entry->injectionState;
+ if (injectionState && injectionState->injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+#if DEBUG_DISPATCH_CYCLE
+ LOGD("Injected inbound event was dropped.");
+#endif
+ setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
+ }
+ mAllocator.releaseEventEntry(entry);
+}
+
+void InputDispatcher::resetKeyRepeatLocked() {
+ if (mKeyRepeatState.lastKeyEntry) {
+ mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry);
+ mKeyRepeatState.lastKeyEntry = NULL;
+ }
+}
+
+InputDispatcher::KeyEntry* InputDispatcher::synthesizeKeyRepeatLocked(
+ nsecs_t currentTime, nsecs_t keyRepeatDelay) {
+ KeyEntry* entry = mKeyRepeatState.lastKeyEntry;
+
+ // Reuse the repeated key entry if it is otherwise unreferenced.
+ uint32_t policyFlags = (entry->policyFlags & POLICY_FLAG_RAW_MASK)
+ | POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_TRUSTED;
+ if (entry->refCount == 1) {
+ mAllocator.recycleKeyEntry(entry);
+ entry->eventTime = currentTime;
+ entry->policyFlags = policyFlags;
+ entry->repeatCount += 1;
+ } else {
+ KeyEntry* newEntry = mAllocator.obtainKeyEntry(currentTime,
+ entry->deviceId, entry->source, policyFlags,
+ entry->action, entry->flags, entry->keyCode, entry->scanCode,
+ entry->metaState, entry->repeatCount + 1, entry->downTime);
+
+ mKeyRepeatState.lastKeyEntry = newEntry;
+ mAllocator.releaseKeyEntry(entry);
+
+ entry = newEntry;
+ }
+ entry->syntheticRepeat = true;
+
+ // Increment reference count since we keep a reference to the event in
+ // mKeyRepeatState.lastKeyEntry in addition to the one we return.
+ entry->refCount += 1;
+
+ if (entry->repeatCount == 1) {
+ entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS;
+ }
+
+ mKeyRepeatState.nextRepeatTime = currentTime + keyRepeatDelay;
+ return entry;
+}
+
+bool InputDispatcher::dispatchConfigurationChangedLocked(
+ nsecs_t currentTime, ConfigurationChangedEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ LOGD("dispatchConfigurationChanged - eventTime=%lld", entry->eventTime);
+#endif
+
+ // Reset key repeating in case a keyboard device was added or removed or something.
+ resetKeyRepeatLocked();
+
+ // Enqueue a command to run outside the lock to tell the policy that the configuration changed.
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doNotifyConfigurationChangedInterruptible);
+ commandEntry->eventTime = entry->eventTime;
+ return true;
+}
+
+bool InputDispatcher::dispatchKeyLocked(
+ nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout,
+ DropReason* dropReason, nsecs_t* nextWakeupTime) {
+ // Preprocessing.
+ if (! entry->dispatchInProgress) {
+ if (entry->repeatCount == 0
+ && entry->action == AKEY_EVENT_ACTION_DOWN
+ && (entry->policyFlags & POLICY_FLAG_TRUSTED)
+ && !entry->isInjected()) {
+ if (mKeyRepeatState.lastKeyEntry
+ && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) {
+ // We have seen two identical key downs in a row which indicates that the device
+ // driver is automatically generating key repeats itself. We take note of the
+ // repeat here, but we disable our own next key repeat timer since it is clear that
+ // we will not need to synthesize key repeats ourselves.
+ entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1;
+ resetKeyRepeatLocked();
+ mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves
+ } else {
+ // Not a repeat. Save key down state in case we do see a repeat later.
+ resetKeyRepeatLocked();
+ mKeyRepeatState.nextRepeatTime = entry->eventTime + keyRepeatTimeout;
+ }
+ mKeyRepeatState.lastKeyEntry = entry;
+ entry->refCount += 1;
+ } else if (! entry->syntheticRepeat) {
+ resetKeyRepeatLocked();
+ }
+
+ entry->dispatchInProgress = true;
+ resetTargetsLocked();
+
+ logOutboundKeyDetailsLocked("dispatchKey - ", entry);
+ }
+
+ // Give the policy a chance to intercept the key.
+ if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
+ if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
+ if (mFocusedWindow) {
+ commandEntry->inputWindowHandle = mFocusedWindow->inputWindowHandle;
+ }
+ commandEntry->keyEntry = entry;
+ entry->refCount += 1;
+ return false; // wait for the command to run
+ } else {
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+ }
+ } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
+ if (*dropReason == DROP_REASON_NOT_DROPPED) {
+ *dropReason = DROP_REASON_POLICY;
+ }
+ }
+
+ // Clean up if dropping the event.
+ if (*dropReason != DROP_REASON_NOT_DROPPED) {
+ resetTargetsLocked();
+ setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
+ ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
+ return true;
+ }
+
+ // Identify targets.
+ if (! mCurrentInputTargetsValid) {
+ int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
+ entry, nextWakeupTime);
+ if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+ return false;
+ }
+
+ setInjectionResultLocked(entry, injectionResult);
+ if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+ return true;
+ }
+
+ addMonitoringTargetsLocked();
+ commitTargetsLocked();
+ }
+
+ // Dispatch the key.
+ dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
+ return true;
+}
+
+void InputDispatcher::logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ LOGD("%seventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, "
+ "action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, "
+ "repeatCount=%d, downTime=%lld",
+ prefix,
+ entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
+ entry->action, entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
+ entry->repeatCount, entry->downTime);
+#endif
+}
+
+bool InputDispatcher::dispatchMotionLocked(
+ nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
+ // Preprocessing.
+ if (! entry->dispatchInProgress) {
+ entry->dispatchInProgress = true;
+ resetTargetsLocked();
+
+ logOutboundMotionDetailsLocked("dispatchMotion - ", entry);
+ }
+
+ // Clean up if dropping the event.
+ if (*dropReason != DROP_REASON_NOT_DROPPED) {
+ resetTargetsLocked();
+ setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
+ ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
+ return true;
+ }
+
+ bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
+
+ // Identify targets.
+ if (! mCurrentInputTargetsValid) {
+ int32_t injectionResult;
+ if (isPointerEvent) {
+ // Pointer event. (eg. touchscreen)
+ injectionResult = findTouchedWindowTargetsLocked(currentTime,
+ entry, nextWakeupTime);
+ } else {
+ // Non touch event. (eg. trackball)
+ injectionResult = findFocusedWindowTargetsLocked(currentTime,
+ entry, nextWakeupTime);
+ }
+ if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+ return false;
+ }
+
+ setInjectionResultLocked(entry, injectionResult);
+ if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+ return true;
+ }
+
+ addMonitoringTargetsLocked();
+ commitTargetsLocked();
+ }
+
+ // Dispatch the motion.
+ dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
+ return true;
+}
+
+
+void InputDispatcher::logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ LOGD("%seventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, "
+ "action=0x%x, flags=0x%x, "
+ "metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%lld",
+ prefix,
+ entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
+ entry->action, entry->flags,
+ entry->metaState, entry->edgeFlags, entry->xPrecision, entry->yPrecision,
+ entry->downTime);
+
+ // Print the most recent sample that we have available, this may change due to batching.
+ size_t sampleCount = 1;
+ const MotionSample* sample = & entry->firstSample;
+ for (; sample->next != NULL; sample = sample->next) {
+ sampleCount += 1;
+ }
+ for (uint32_t i = 0; i < entry->pointerCount; i++) {
+ LOGD(" Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, "
+ "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
+ "orientation=%f",
+ i, entry->pointerIds[i],
+ sample->pointerCoords[i].x, sample->pointerCoords[i].y,
+ sample->pointerCoords[i].pressure, sample->pointerCoords[i].size,
+ sample->pointerCoords[i].touchMajor, sample->pointerCoords[i].touchMinor,
+ sample->pointerCoords[i].toolMajor, sample->pointerCoords[i].toolMinor,
+ sample->pointerCoords[i].orientation);
+ }
+
+ // Keep in mind that due to batching, it is possible for the number of samples actually
+ // dispatched to change before the application finally consumed them.
+ if (entry->action == AMOTION_EVENT_ACTION_MOVE) {
+ LOGD(" ... Total movement samples currently batched %d ...", sampleCount);
+ }
+#endif
+}
+
+void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime,
+ EventEntry* eventEntry, bool resumeWithAppendedMotionSample) {
+#if DEBUG_DISPATCH_CYCLE
+ LOGD("dispatchEventToCurrentInputTargets - "
+ "resumeWithAppendedMotionSample=%s",
+ toString(resumeWithAppendedMotionSample));
+#endif
+
+ assert(eventEntry->dispatchInProgress); // should already have been set to true
+
+ pokeUserActivityLocked(eventEntry);
+
+ for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {
+ const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i);
+
+ ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+ prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget,
+ resumeWithAppendedMotionSample);
+ } else {
+#if DEBUG_FOCUS
+ LOGD("Dropping event delivery to target with channel '%s' because it "
+ "is no longer registered with the input dispatcher.",
+ inputTarget.inputChannel->getName().string());
+#endif
+ }
+ }
+}
+
+void InputDispatcher::resetTargetsLocked() {
+ mCurrentInputTargetsValid = false;
+ mCurrentInputTargets.clear();
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
+ mInputTargetWaitApplication.clear();
+}
+
+void InputDispatcher::commitTargetsLocked() {
+ mCurrentInputTargetsValid = true;
+}
+
+int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
+ const EventEntry* entry, const InputApplication* application, const InputWindow* window,
+ nsecs_t* nextWakeupTime) {
+ if (application == NULL && window == NULL) {
+ if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
+#if DEBUG_FOCUS
+ LOGD("Waiting for system to become ready for input.");
+#endif
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
+ mInputTargetWaitStartTime = currentTime;
+ mInputTargetWaitTimeoutTime = LONG_LONG_MAX;
+ mInputTargetWaitTimeoutExpired = false;
+ mInputTargetWaitApplication.clear();
+ }
+ } else {
+ if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
+#if DEBUG_FOCUS
+ LOGD("Waiting for application to become ready for input: %s",
+ getApplicationWindowLabelLocked(application, window).string());
+#endif
+ nsecs_t timeout = window ? window->dispatchingTimeout :
+ application ? application->dispatchingTimeout : DEFAULT_INPUT_DISPATCHING_TIMEOUT;
+
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
+ mInputTargetWaitStartTime = currentTime;
+ mInputTargetWaitTimeoutTime = currentTime + timeout;
+ mInputTargetWaitTimeoutExpired = false;
+ mInputTargetWaitApplication.clear();
+
+ if (window && window->inputWindowHandle != NULL) {
+ mInputTargetWaitApplication =
+ window->inputWindowHandle->getInputApplicationHandle();
+ }
+ if (mInputTargetWaitApplication == NULL && application) {
+ mInputTargetWaitApplication = application->inputApplicationHandle;
+ }
+ }
+ }
+
+ if (mInputTargetWaitTimeoutExpired) {
+ return INPUT_EVENT_INJECTION_TIMED_OUT;
+ }
+
+ if (currentTime >= mInputTargetWaitTimeoutTime) {
+ onANRLocked(currentTime, application, window, entry->eventTime, mInputTargetWaitStartTime);
+
+ // Force poll loop to wake up immediately on next iteration once we get the
+ // ANR response back from the policy.
+ *nextWakeupTime = LONG_LONG_MIN;
+ return INPUT_EVENT_INJECTION_PENDING;
+ } else {
+ // Force poll loop to wake up when timeout is due.
+ if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
+ *nextWakeupTime = mInputTargetWaitTimeoutTime;
+ }
+ return INPUT_EVENT_INJECTION_PENDING;
+ }
+}
+
+void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout,
+ const sp<InputChannel>& inputChannel) {
+ if (newTimeout > 0) {
+ // Extend the timeout.
+ mInputTargetWaitTimeoutTime = now() + newTimeout;
+ } else {
+ // Give up.
+ mInputTargetWaitTimeoutExpired = true;
+
+ // Release the touch targets.
+ mTouchState.reset();
+
+ // Input state will not be realistic. Mark it out of sync.
+ if (inputChannel.get()) {
+ ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+ if (connection->status == Connection::STATUS_NORMAL) {
+ synthesizeCancelationEventsForConnectionLocked(
+ connection, InputState::CANCEL_ALL_EVENTS,
+ "application not responding");
+ }
+ }
+ }
+ }
+}
+
+nsecs_t InputDispatcher::getTimeSpentWaitingForApplicationLocked(
+ nsecs_t currentTime) {
+ if (mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
+ return currentTime - mInputTargetWaitStartTime;
+ }
+ return 0;
+}
+
+void InputDispatcher::resetANRTimeoutsLocked() {
+#if DEBUG_FOCUS
+ LOGD("Resetting ANR timeouts.");
+#endif
+
+ // Reset input target wait timeout.
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
+}
+
+int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
+ const EventEntry* entry, nsecs_t* nextWakeupTime) {
+ mCurrentInputTargets.clear();
+
+ int32_t injectionResult;
+
+ // If there is no currently focused window and no focused application
+ // then drop the event.
+ if (! mFocusedWindow) {
+ if (mFocusedApplication) {
+#if DEBUG_FOCUS
+ LOGD("Waiting because there is no focused window but there is a "
+ "focused application that may eventually add a window: %s.",
+ getApplicationWindowLabelLocked(mFocusedApplication, NULL).string());
+#endif
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplication, NULL, nextWakeupTime);
+ goto Unresponsive;
+ }
+
+ LOGI("Dropping event because there is no focused window or focused application.");
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+
+ // Check permissions.
+ if (! checkInjectionPermission(mFocusedWindow, entry->injectionState)) {
+ injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+ goto Failed;
+ }
+
+ // If the currently focused window is paused then keep waiting.
+ if (mFocusedWindow->paused) {
+#if DEBUG_FOCUS
+ LOGD("Waiting because focused window is paused.");
+#endif
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplication, mFocusedWindow, nextWakeupTime);
+ goto Unresponsive;
+ }
+
+ // If the currently focused window is still working on previous events then keep waiting.
+ if (! isWindowFinishedWithPreviousInputLocked(mFocusedWindow)) {
+#if DEBUG_FOCUS
+ LOGD("Waiting because focused window still processing previous input.");
+#endif
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplication, mFocusedWindow, nextWakeupTime);
+ goto Unresponsive;
+ }
+
+ // Success! Output targets.
+ injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+ addWindowTargetLocked(mFocusedWindow, InputTarget::FLAG_FOREGROUND, BitSet32(0));
+
+ // Done.
+Failed:
+Unresponsive:
+ nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
+ updateDispatchStatisticsLocked(currentTime, entry,
+ injectionResult, timeSpentWaitingForApplication);
+#if DEBUG_FOCUS
+ LOGD("findFocusedWindow finished: injectionResult=%d, "
+ "timeSpendWaitingForApplication=%0.1fms",
+ injectionResult, timeSpentWaitingForApplication / 1000000.0);
+#endif
+ return injectionResult;
+}
+
+int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
+ const MotionEntry* entry, nsecs_t* nextWakeupTime) {
+ enum InjectionPermission {
+ INJECTION_PERMISSION_UNKNOWN,
+ INJECTION_PERMISSION_GRANTED,
+ INJECTION_PERMISSION_DENIED
+ };
+
+ mCurrentInputTargets.clear();
+
+ nsecs_t startTime = now();
+
+ // For security reasons, we defer updating the touch state until we are sure that
+ // event injection will be allowed.
+ //
+ // FIXME In the original code, screenWasOff could never be set to true.
+ // The reason is that the POLICY_FLAG_WOKE_HERE
+ // and POLICY_FLAG_BRIGHT_HERE flags were set only when preprocessing raw
+ // EV_KEY, EV_REL and EV_ABS events. As it happens, the touch event was
+ // actually enqueued using the policyFlags that appeared in the final EV_SYN
+ // events upon which no preprocessing took place. So policyFlags was always 0.
+ // In the new native input dispatcher we're a bit more careful about event
+ // preprocessing so the touches we receive can actually have non-zero policyFlags.
+ // Unfortunately we obtain undesirable behavior.
+ //
+ // Here's what happens:
+ //
+ // When the device dims in anticipation of going to sleep, touches
+ // in windows which have FLAG_TOUCHABLE_WHEN_WAKING cause
+ // the device to brighten and reset the user activity timer.
+ // Touches on other windows (such as the launcher window)
+ // are dropped. Then after a moment, the device goes to sleep. Oops.
+ //
+ // Also notice how screenWasOff was being initialized using POLICY_FLAG_BRIGHT_HERE
+ // instead of POLICY_FLAG_WOKE_HERE...
+ //
+ bool screenWasOff = false; // original policy: policyFlags & POLICY_FLAG_BRIGHT_HERE;
+
+ int32_t action = entry->action;
+ int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
+
+ // Update the touch state as needed based on the properties of the touch event.
+ int32_t injectionResult = INPUT_EVENT_INJECTION_PENDING;
+ InjectionPermission injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+ bool isSplit, wrongDevice;
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+ mTempTouchState.reset();
+ mTempTouchState.down = true;
+ mTempTouchState.deviceId = entry->deviceId;
+ mTempTouchState.source = entry->source;
+ isSplit = false;
+ wrongDevice = false;
+ } else {
+ mTempTouchState.copyFrom(mTouchState);
+ isSplit = mTempTouchState.split;
+ wrongDevice = mTempTouchState.down
+ && (mTempTouchState.deviceId != entry->deviceId
+ || mTempTouchState.source != entry->source);
+ if (wrongDevice) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+ LOGD("Dropping event because a pointer for a different device is already down.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+ }
+
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN
+ || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
+ /* Case 1: New splittable pointer going down. */
+
+ int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+ int32_t x = int32_t(entry->firstSample.pointerCoords[pointerIndex].x);
+ int32_t y = int32_t(entry->firstSample.pointerCoords[pointerIndex].y);
+ const InputWindow* newTouchedWindow = NULL;
+ const InputWindow* topErrorWindow = NULL;
+
+ // Traverse windows from front to back to find touched window and outside targets.
+ size_t numWindows = mWindows.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ const InputWindow* window = & mWindows.editItemAt(i);
+ int32_t flags = window->layoutParamsFlags;
+
+ if (flags & InputWindow::FLAG_SYSTEM_ERROR) {
+ if (! topErrorWindow) {
+ topErrorWindow = window;
+ }
+ }
+
+ if (window->visible) {
+ if (! (flags & InputWindow::FLAG_NOT_TOUCHABLE)) {
+ bool isTouchModal = (flags & (InputWindow::FLAG_NOT_FOCUSABLE
+ | InputWindow::FLAG_NOT_TOUCH_MODAL)) == 0;
+ if (isTouchModal || window->touchableRegionContainsPoint(x, y)) {
+ if (! screenWasOff || flags & InputWindow::FLAG_TOUCHABLE_WHEN_WAKING) {
+ newTouchedWindow = window;
+ }
+ break; // found touched window, exit window loop
+ }
+ }
+
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN
+ && (flags & InputWindow::FLAG_WATCH_OUTSIDE_TOUCH)) {
+ int32_t outsideTargetFlags = InputTarget::FLAG_OUTSIDE;
+ if (isWindowObscuredAtPointLocked(window, x, y)) {
+ outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ }
+
+ mTempTouchState.addOrUpdateWindow(window, outsideTargetFlags, BitSet32(0));
+ }
+ }
+ }
+
+ // If there is an error window but it is not taking focus (typically because
+ // it is invisible) then wait for it. Any other focused window may in
+ // fact be in ANR state.
+ if (topErrorWindow && newTouchedWindow != topErrorWindow) {
+#if DEBUG_FOCUS
+ LOGD("Waiting because system error window is pending.");
+#endif
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ NULL, NULL, nextWakeupTime);
+ injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+ goto Unresponsive;
+ }
+
+ // Figure out whether splitting will be allowed for this window.
+ if (newTouchedWindow && newTouchedWindow->supportsSplitTouch()) {
+ // New window supports splitting.
+ isSplit = true;
+ } else if (isSplit) {
+ // New window does not support splitting but we have already split events.
+ // Assign the pointer to the first foreground window we find.
+ // (May be NULL which is why we put this code block before the next check.)
+ newTouchedWindow = mTempTouchState.getFirstForegroundWindow();
+ }
+
+ // If we did not find a touched window then fail.
+ if (! newTouchedWindow) {
+ if (mFocusedApplication) {
+#if DEBUG_FOCUS
+ LOGD("Waiting because there is no touched window but there is a "
+ "focused application that may eventually add a new window: %s.",
+ getApplicationWindowLabelLocked(mFocusedApplication, NULL).string());
+#endif
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplication, NULL, nextWakeupTime);
+ goto Unresponsive;
+ }
+
+ LOGI("Dropping event because there is no touched window or focused application.");
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+
+ // Set target flags.
+ int32_t targetFlags = InputTarget::FLAG_FOREGROUND;
+ if (isSplit) {
+ targetFlags |= InputTarget::FLAG_SPLIT;
+ }
+ if (isWindowObscuredAtPointLocked(newTouchedWindow, x, y)) {
+ targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ }
+
+ // Update the temporary touch state.
+ BitSet32 pointerIds;
+ if (isSplit) {
+ uint32_t pointerId = entry->pointerIds[pointerIndex];
+ pointerIds.markBit(pointerId);
+ }
+ mTempTouchState.addOrUpdateWindow(newTouchedWindow, targetFlags, pointerIds);
+ } else {
+ /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
+
+ // If the pointer is not currently down, then ignore the event.
+ if (! mTempTouchState.down) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+ LOGD("Dropping event because the pointer is not down or we previously "
+ "dropped the pointer down event.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+ }
+
+ // Check permission to inject into all touched foreground windows and ensure there
+ // is at least one touched foreground window.
+ {
+ bool haveForegroundWindow = false;
+ for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
+ if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ haveForegroundWindow = true;
+ if (! checkInjectionPermission(touchedWindow.window, entry->injectionState)) {
+ injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+ injectionPermission = INJECTION_PERMISSION_DENIED;
+ goto Failed;
+ }
+ }
+ }
+ if (! haveForegroundWindow) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+ LOGD("Dropping event because there is no touched foreground window to receive it.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+
+ // Permission granted to injection into all touched foreground windows.
+ injectionPermission = INJECTION_PERMISSION_GRANTED;
+ }
+
+ // Ensure all touched foreground windows are ready for new input.
+ for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
+ if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ // If the touched window is paused then keep waiting.
+ if (touchedWindow.window->paused) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+ LOGD("Waiting because touched window is paused.");
+#endif
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ NULL, touchedWindow.window, nextWakeupTime);
+ goto Unresponsive;
+ }
+
+ // If the touched window is still working on previous events then keep waiting.
+ if (! isWindowFinishedWithPreviousInputLocked(touchedWindow.window)) {
+#if DEBUG_FOCUS
+ LOGD("Waiting because touched window still processing previous input.");
+#endif
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ NULL, touchedWindow.window, nextWakeupTime);
+ goto Unresponsive;
+ }
+ }
+ }
+
+ // If this is the first pointer going down and the touched window has a wallpaper
+ // then also add the touched wallpaper windows so they are locked in for the duration
+ // of the touch gesture.
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+ const InputWindow* foregroundWindow = mTempTouchState.getFirstForegroundWindow();
+ if (foregroundWindow->hasWallpaper) {
+ for (size_t i = 0; i < mWindows.size(); i++) {
+ const InputWindow* window = & mWindows[i];
+ if (window->layoutParamsType == InputWindow::TYPE_WALLPAPER) {
+ mTempTouchState.addOrUpdateWindow(window,
+ InputTarget::FLAG_WINDOW_IS_OBSCURED, BitSet32(0));
+ }
+ }
+ }
+ }
+
+ // Success! Output targets.
+ injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+
+ for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTempTouchState.windows.itemAt(i);
+ addWindowTargetLocked(touchedWindow.window, touchedWindow.targetFlags,
+ touchedWindow.pointerIds);
+ }
+
+ // Drop the outside touch window since we will not care about them in the next iteration.
+ mTempTouchState.removeOutsideTouchWindows();
+
+Failed:
+ // Check injection permission once and for all.
+ if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) {
+ if (checkInjectionPermission(NULL, entry->injectionState)) {
+ injectionPermission = INJECTION_PERMISSION_GRANTED;
+ } else {
+ injectionPermission = INJECTION_PERMISSION_DENIED;
+ }
+ }
+
+ // Update final pieces of touch state if the injector had permission.
+ if (injectionPermission == INJECTION_PERMISSION_GRANTED) {
+ if (!wrongDevice) {
+ if (maskedAction == AMOTION_EVENT_ACTION_UP
+ || maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
+ // All pointers up or canceled.
+ mTempTouchState.reset();
+ } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+ // First pointer went down.
+ if (mTouchState.down) {
+#if DEBUG_FOCUS
+ LOGD("Pointer down received while already down.");
+#endif
+ }
+ } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
+ // One pointer went up.
+ if (isSplit) {
+ int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+ uint32_t pointerId = entry->pointerIds[pointerIndex];
+
+ for (size_t i = 0; i < mTempTouchState.windows.size(); ) {
+ TouchedWindow& touchedWindow = mTempTouchState.windows.editItemAt(i);
+ if (touchedWindow.targetFlags & InputTarget::FLAG_SPLIT) {
+ touchedWindow.pointerIds.clearBit(pointerId);
+ if (touchedWindow.pointerIds.isEmpty()) {
+ mTempTouchState.windows.removeAt(i);
+ continue;
+ }
+ }
+ i += 1;
+ }
+ }
+ }
+
+ // Save changes to touch state.
+ mTouchState.copyFrom(mTempTouchState);
+ }
+ } else {
+#if DEBUG_FOCUS
+ LOGD("Not updating touch focus because injection was denied.");
+#endif
+ }
+
+Unresponsive:
+ // Reset temporary touch state to ensure we release unnecessary references to input channels.
+ mTempTouchState.reset();
+
+ nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
+ updateDispatchStatisticsLocked(currentTime, entry,
+ injectionResult, timeSpentWaitingForApplication);
+#if DEBUG_FOCUS
+ LOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d, "
+ "timeSpentWaitingForApplication=%0.1fms",
+ injectionResult, injectionPermission, timeSpentWaitingForApplication / 1000000.0);
+#endif
+ return injectionResult;
+}
+
+void InputDispatcher::addWindowTargetLocked(const InputWindow* window, int32_t targetFlags,
+ BitSet32 pointerIds) {
+ mCurrentInputTargets.push();
+
+ InputTarget& target = mCurrentInputTargets.editTop();
+ target.inputChannel = window->inputChannel;
+ target.flags = targetFlags;
+ target.xOffset = - window->frameLeft;
+ target.yOffset = - window->frameTop;
+ target.pointerIds = pointerIds;
+}
+
+void InputDispatcher::addMonitoringTargetsLocked() {
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ mCurrentInputTargets.push();
+
+ InputTarget& target = mCurrentInputTargets.editTop();
+ target.inputChannel = mMonitoringChannels[i];
+ target.flags = 0;
+ target.xOffset = 0;
+ target.yOffset = 0;
+ }
+}
+
+bool InputDispatcher::checkInjectionPermission(const InputWindow* window,
+ const InjectionState* injectionState) {
+ if (injectionState
+ && (window == NULL || window->ownerUid != injectionState->injectorUid)
+ && !hasInjectionPermission(injectionState->injectorPid, injectionState->injectorUid)) {
+ if (window) {
+ LOGW("Permission denied: injecting event from pid %d uid %d to window "
+ "with input channel %s owned by uid %d",
+ injectionState->injectorPid, injectionState->injectorUid,
+ window->inputChannel->getName().string(),
+ window->ownerUid);
+ } else {
+ LOGW("Permission denied: injecting event from pid %d uid %d",
+ injectionState->injectorPid, injectionState->injectorUid);
+ }
+ return false;
+ }
+ return true;
+}
+
+bool InputDispatcher::isWindowObscuredAtPointLocked(
+ const InputWindow* window, int32_t x, int32_t y) const {
+ size_t numWindows = mWindows.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ const InputWindow* other = & mWindows.itemAt(i);
+ if (other == window) {
+ break;
+ }
+ if (other->visible && ! other->isTrustedOverlay() && other->frameContainsPoint(x, y)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool InputDispatcher::isWindowFinishedWithPreviousInputLocked(const InputWindow* window) {
+ ssize_t connectionIndex = getConnectionIndexLocked(window->inputChannel);
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+ return connection->outboundQueue.isEmpty();
+ } else {
+ return true;
+ }
+}
+
+String8 InputDispatcher::getApplicationWindowLabelLocked(const InputApplication* application,
+ const InputWindow* window) {
+ if (application) {
+ if (window) {
+ String8 label(application->name);
+ label.append(" - ");
+ label.append(window->name);
+ return label;
+ } else {
+ return application->name;
+ }
+ } else if (window) {
+ return window->name;
+ } else {
+ return String8("<unknown application or window>");
+ }
+}
+
+void InputDispatcher::pokeUserActivityLocked(const EventEntry* eventEntry) {
+ int32_t eventType = POWER_MANAGER_BUTTON_EVENT;
+ switch (eventEntry->type) {
+ case EventEntry::TYPE_MOTION: {
+ const MotionEntry* motionEntry = static_cast<const MotionEntry*>(eventEntry);
+ if (motionEntry->action == AMOTION_EVENT_ACTION_CANCEL) {
+ return;
+ }
+
+ if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) {
+ eventType = POWER_MANAGER_TOUCH_EVENT;
+ }
+ break;
+ }
+ case EventEntry::TYPE_KEY: {
+ const KeyEntry* keyEntry = static_cast<const KeyEntry*>(eventEntry);
+ if (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED) {
+ return;
+ }
+ break;
+ }
+ }
+
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doPokeUserActivityLockedInterruptible);
+ commandEntry->eventTime = eventEntry->eventTime;
+ commandEntry->userActivityEventType = eventType;
+}
+
+void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
+ bool resumeWithAppendedMotionSample) {
+#if DEBUG_DISPATCH_CYCLE
+ LOGD("channel '%s' ~ prepareDispatchCycle - flags=%d, "
+ "xOffset=%f, yOffset=%f, "
+ "pointerIds=0x%x, "
+ "resumeWithAppendedMotionSample=%s",
+ connection->getInputChannelName(), inputTarget->flags,
+ inputTarget->xOffset, inputTarget->yOffset,
+ inputTarget->pointerIds.value,
+ toString(resumeWithAppendedMotionSample));
+#endif
+
+ // Make sure we are never called for streaming when splitting across multiple windows.
+ bool isSplit = inputTarget->flags & InputTarget::FLAG_SPLIT;
+ assert(! (resumeWithAppendedMotionSample && isSplit));
+
+ // Skip this event if the connection status is not normal.
+ // We don't want to enqueue additional outbound events if the connection is broken.
+ if (connection->status != Connection::STATUS_NORMAL) {
+#if DEBUG_DISPATCH_CYCLE
+ LOGD("channel '%s' ~ Dropping event because the channel status is %s",
+ connection->getInputChannelName(), connection->getStatusLabel());
+#endif
+ return;
+ }
+
+ // Split a motion event if needed.
+ if (isSplit) {
+ assert(eventEntry->type == EventEntry::TYPE_MOTION);
+
+ MotionEntry* originalMotionEntry = static_cast<MotionEntry*>(eventEntry);
+ if (inputTarget->pointerIds.count() != originalMotionEntry->pointerCount) {
+ MotionEntry* splitMotionEntry = splitMotionEvent(
+ originalMotionEntry, inputTarget->pointerIds);
+ if (!splitMotionEntry) {
+ return; // split event was dropped
+ }
+#if DEBUG_FOCUS
+ LOGD("channel '%s' ~ Split motion event.",
+ connection->getInputChannelName());
+ logOutboundMotionDetailsLocked(" ", splitMotionEntry);
+#endif
+ eventEntry = splitMotionEntry;
+ }
+ }
+
+ // Resume the dispatch cycle with a freshly appended motion sample.
+ // First we check that the last dispatch entry in the outbound queue is for the same
+ // motion event to which we appended the motion sample. If we find such a dispatch
+ // entry, and if it is currently in progress then we try to stream the new sample.
+ bool wasEmpty = connection->outboundQueue.isEmpty();
+
+ if (! wasEmpty && resumeWithAppendedMotionSample) {
+ DispatchEntry* motionEventDispatchEntry =
+ connection->findQueuedDispatchEntryForEvent(eventEntry);
+ if (motionEventDispatchEntry) {
+ // If the dispatch entry is not in progress, then we must be busy dispatching an
+ // earlier event. Not a problem, the motion event is on the outbound queue and will
+ // be dispatched later.
+ if (! motionEventDispatchEntry->inProgress) {
+#if DEBUG_BATCHING
+ LOGD("channel '%s' ~ Not streaming because the motion event has "
+ "not yet been dispatched. "
+ "(Waiting for earlier events to be consumed.)",
+ connection->getInputChannelName());
+#endif
+ return;
+ }
+
+ // If the dispatch entry is in progress but it already has a tail of pending
+ // motion samples, then it must mean that the shared memory buffer filled up.
+ // Not a problem, when this dispatch cycle is finished, we will eventually start
+ // a new dispatch cycle to process the tail and that tail includes the newly
+ // appended motion sample.
+ if (motionEventDispatchEntry->tailMotionSample) {
+#if DEBUG_BATCHING
+ LOGD("channel '%s' ~ Not streaming because no new samples can "
+ "be appended to the motion event in this dispatch cycle. "
+ "(Waiting for next dispatch cycle to start.)",
+ connection->getInputChannelName());
+#endif
+ return;
+ }
+
+ // The dispatch entry is in progress and is still potentially open for streaming.
+ // Try to stream the new motion sample. This might fail if the consumer has already
+ // consumed the motion event (or if the channel is broken).
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
+ MotionSample* appendedMotionSample = motionEntry->lastSample;
+ status_t status = connection->inputPublisher.appendMotionSample(
+ appendedMotionSample->eventTime, appendedMotionSample->pointerCoords);
+ if (status == OK) {
+#if DEBUG_BATCHING
+ LOGD("channel '%s' ~ Successfully streamed new motion sample.",
+ connection->getInputChannelName());
+#endif
+ return;
+ }
+
+#if DEBUG_BATCHING
+ if (status == NO_MEMORY) {
+ LOGD("channel '%s' ~ Could not append motion sample to currently "
+ "dispatched move event because the shared memory buffer is full. "
+ "(Waiting for next dispatch cycle to start.)",
+ connection->getInputChannelName());
+ } else if (status == status_t(FAILED_TRANSACTION)) {
+ LOGD("channel '%s' ~ Could not append motion sample to currently "
+ "dispatched move event because the event has already been consumed. "
+ "(Waiting for next dispatch cycle to start.)",
+ connection->getInputChannelName());
+ } else {
+ LOGD("channel '%s' ~ Could not append motion sample to currently "
+ "dispatched move event due to an error, status=%d. "
+ "(Waiting for next dispatch cycle to start.)",
+ connection->getInputChannelName(), status);
+ }
+#endif
+ // Failed to stream. Start a new tail of pending motion samples to dispatch
+ // in the next cycle.
+ motionEventDispatchEntry->tailMotionSample = appendedMotionSample;
+ return;
+ }
+ }
+
+ // This is a new event.
+ // Enqueue a new dispatch entry onto the outbound queue for this connection.
+ DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry, // increments ref
+ inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset);
+ if (dispatchEntry->hasForegroundTarget()) {
+ incrementPendingForegroundDispatchesLocked(eventEntry);
+ }
+
+ // Handle the case where we could not stream a new motion sample because the consumer has
+ // already consumed the motion event (otherwise the corresponding dispatch entry would
+ // still be in the outbound queue for this connection). We set the head motion sample
+ // to the list starting with the newly appended motion sample.
+ if (resumeWithAppendedMotionSample) {
+#if DEBUG_BATCHING
+ LOGD("channel '%s' ~ Preparing a new dispatch cycle for additional motion samples "
+ "that cannot be streamed because the motion event has already been consumed.",
+ connection->getInputChannelName());
+#endif
+ MotionSample* appendedMotionSample = static_cast<MotionEntry*>(eventEntry)->lastSample;
+ dispatchEntry->headMotionSample = appendedMotionSample;
+ }
+
+ // Enqueue the dispatch entry.
+ connection->outboundQueue.enqueueAtTail(dispatchEntry);
+
+ // If the outbound queue was previously empty, start the dispatch cycle going.
+ if (wasEmpty) {
+ activateConnectionLocked(connection.get());
+ startDispatchCycleLocked(currentTime, connection);
+ }
+}
+
+void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection) {
+#if DEBUG_DISPATCH_CYCLE
+ LOGD("channel '%s' ~ startDispatchCycle",
+ connection->getInputChannelName());
+#endif
+
+ assert(connection->status == Connection::STATUS_NORMAL);
+ assert(! connection->outboundQueue.isEmpty());
+
+ DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
+ assert(! dispatchEntry->inProgress);
+
+ // Mark the dispatch entry as in progress.
+ dispatchEntry->inProgress = true;
+
+ // Update the connection's input state.
+ EventEntry* eventEntry = dispatchEntry->eventEntry;
+ InputState::Consistency consistency = connection->inputState.trackEvent(eventEntry);
+
+#if FILTER_INPUT_EVENTS
+ // Filter out inconsistent sequences of input events.
+ // The input system may drop or inject events in a way that could violate implicit
+ // invariants on input state and potentially cause an application to crash
+ // or think that a key or pointer is stuck down. Technically we make no guarantees
+ // of consistency but it would be nice to improve on this where possible.
+ // XXX: This code is a proof of concept only. Not ready for prime time.
+ if (consistency == InputState::TOLERABLE) {
+#if DEBUG_DISPATCH_CYCLE
+ LOGD("channel '%s' ~ Sending an event that is inconsistent with the connection's "
+ "current input state but that is likely to be tolerated by the application.",
+ connection->getInputChannelName());
+#endif
+ } else if (consistency == InputState::BROKEN) {
+ LOGI("channel '%s' ~ Dropping an event that is inconsistent with the connection's "
+ "current input state and that is likely to cause the application to crash.",
+ connection->getInputChannelName());
+ startNextDispatchCycleLocked(currentTime, connection);
+ return;
+ }
+#endif
+
+ // Publish the event.
+ status_t status;
+ switch (eventEntry->type) {
+ case EventEntry::TYPE_KEY: {
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
+
+ // Apply target flags.
+ int32_t action = keyEntry->action;
+ int32_t flags = keyEntry->flags;
+
+ // Publish the key event.
+ status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->source,
+ action, flags, keyEntry->keyCode, keyEntry->scanCode,
+ keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
+ keyEntry->eventTime);
+
+ if (status) {
+ LOGE("channel '%s' ~ Could not publish key event, "
+ "status=%d", connection->getInputChannelName(), status);
+ abortBrokenDispatchCycleLocked(currentTime, connection);
+ return;
+ }
+ break;
+ }
+
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
+
+ // Apply target flags.
+ int32_t action = motionEntry->action;
+ int32_t flags = motionEntry->flags;
+ if (dispatchEntry->targetFlags & InputTarget::FLAG_OUTSIDE) {
+ action = AMOTION_EVENT_ACTION_OUTSIDE;
+ }
+ if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) {
+ flags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+ }
+
+ // If headMotionSample is non-NULL, then it points to the first new sample that we
+ // were unable to dispatch during the previous cycle so we resume dispatching from
+ // that point in the list of motion samples.
+ // Otherwise, we just start from the first sample of the motion event.
+ MotionSample* firstMotionSample = dispatchEntry->headMotionSample;
+ if (! firstMotionSample) {
+ firstMotionSample = & motionEntry->firstSample;
+ }
+
+ // Set the X and Y offset depending on the input source.
+ float xOffset, yOffset;
+ if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) {
+ xOffset = dispatchEntry->xOffset;
+ yOffset = dispatchEntry->yOffset;
+ } else {
+ xOffset = 0.0f;
+ yOffset = 0.0f;
+ }
+
+ // Publish the motion event and the first motion sample.
+ status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId,
+ motionEntry->source, action, flags, motionEntry->edgeFlags, motionEntry->metaState,
+ xOffset, yOffset,
+ motionEntry->xPrecision, motionEntry->yPrecision,
+ motionEntry->downTime, firstMotionSample->eventTime,
+ motionEntry->pointerCount, motionEntry->pointerIds,
+ firstMotionSample->pointerCoords);
+
+ if (status) {
+ LOGE("channel '%s' ~ Could not publish motion event, "
+ "status=%d", connection->getInputChannelName(), status);
+ abortBrokenDispatchCycleLocked(currentTime, connection);
+ return;
+ }
+
+ // Append additional motion samples.
+ MotionSample* nextMotionSample = firstMotionSample->next;
+ for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) {
+ status = connection->inputPublisher.appendMotionSample(
+ nextMotionSample->eventTime, nextMotionSample->pointerCoords);
+ if (status == NO_MEMORY) {
+#if DEBUG_DISPATCH_CYCLE
+ LOGD("channel '%s' ~ Shared memory buffer full. Some motion samples will "
+ "be sent in the next dispatch cycle.",
+ connection->getInputChannelName());
+#endif
+ break;
+ }
+ if (status != OK) {
+ LOGE("channel '%s' ~ Could not append motion sample "
+ "for a reason other than out of memory, status=%d",
+ connection->getInputChannelName(), status);
+ abortBrokenDispatchCycleLocked(currentTime, connection);
+ return;
+ }
+ }
+
+ // Remember the next motion sample that we could not dispatch, in case we ran out
+ // of space in the shared memory buffer.
+ dispatchEntry->tailMotionSample = nextMotionSample;
+ break;
+ }
+
+ default: {
+ assert(false);
+ }
+ }
+
+ // Send the dispatch signal.
+ status = connection->inputPublisher.sendDispatchSignal();
+ if (status) {
+ LOGE("channel '%s' ~ Could not send dispatch signal, status=%d",
+ connection->getInputChannelName(), status);
+ abortBrokenDispatchCycleLocked(currentTime, connection);
+ return;
+ }
+
+ // Record information about the newly started dispatch cycle.
+ connection->lastEventTime = eventEntry->eventTime;
+ connection->lastDispatchTime = currentTime;
+
+ // Notify other system components.
+ onDispatchCycleStartedLocked(currentTime, connection);
+}
+
+void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection, bool handled) {
+#if DEBUG_DISPATCH_CYCLE
+ LOGD("channel '%s' ~ finishDispatchCycle - %01.1fms since event, "
+ "%01.1fms since dispatch, handled=%s",
+ connection->getInputChannelName(),
+ connection->getEventLatencyMillis(currentTime),
+ connection->getDispatchLatencyMillis(currentTime),
+ toString(handled));
+#endif
+
+ if (connection->status == Connection::STATUS_BROKEN
+ || connection->status == Connection::STATUS_ZOMBIE) {
+ return;
+ }
+
+ // Reset the publisher since the event has been consumed.
+ // We do this now so that the publisher can release some of its internal resources
+ // while waiting for the next dispatch cycle to begin.
+ status_t status = connection->inputPublisher.reset();
+ if (status) {
+ LOGE("channel '%s' ~ Could not reset publisher, status=%d",
+ connection->getInputChannelName(), status);
+ abortBrokenDispatchCycleLocked(currentTime, connection);
+ return;
+ }
+
+ // Notify other system components and prepare to start the next dispatch cycle.
+ onDispatchCycleFinishedLocked(currentTime, connection, handled);
+}
+
+void InputDispatcher::startNextDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection) {
+ // Start the next dispatch cycle for this connection.
+ while (! connection->outboundQueue.isEmpty()) {
+ DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
+ if (dispatchEntry->inProgress) {
+ // Finish or resume current event in progress.
+ if (dispatchEntry->tailMotionSample) {
+ // We have a tail of undispatched motion samples.
+ // Reuse the same DispatchEntry and start a new cycle.
+ dispatchEntry->inProgress = false;
+ dispatchEntry->headMotionSample = dispatchEntry->tailMotionSample;
+ dispatchEntry->tailMotionSample = NULL;
+ startDispatchCycleLocked(currentTime, connection);
+ return;
+ }
+ // Finished.
+ connection->outboundQueue.dequeueAtHead();
+ if (dispatchEntry->hasForegroundTarget()) {
+ decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry);
+ }
+ mAllocator.releaseDispatchEntry(dispatchEntry);
+ } else {
+ // If the head is not in progress, then we must have already dequeued the in
+ // progress event, which means we actually aborted it.
+ // So just start the next event for this connection.
+ startDispatchCycleLocked(currentTime, connection);
+ return;
+ }
+ }
+
+ // Outbound queue is empty, deactivate the connection.
+ deactivateConnectionLocked(connection.get());
+}
+
+void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection) {
+#if DEBUG_DISPATCH_CYCLE
+ LOGD("channel '%s' ~ abortBrokenDispatchCycle",
+ connection->getInputChannelName());
+#endif
+
+ // Clear the outbound queue.
+ drainOutboundQueueLocked(connection.get());
+
+ // The connection appears to be unrecoverably broken.
+ // Ignore already broken or zombie connections.
+ if (connection->status == Connection::STATUS_NORMAL) {
+ connection->status = Connection::STATUS_BROKEN;
+
+ // Notify other system components.
+ onDispatchCycleBrokenLocked(currentTime, connection);
+ }
+}
+
+void InputDispatcher::drainOutboundQueueLocked(Connection* connection) {
+ while (! connection->outboundQueue.isEmpty()) {
+ DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead();
+ if (dispatchEntry->hasForegroundTarget()) {
+ decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry);
+ }
+ mAllocator.releaseDispatchEntry(dispatchEntry);
+ }
+
+ deactivateConnectionLocked(connection);
+}
+
+int InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) {
+ InputDispatcher* d = static_cast<InputDispatcher*>(data);
+
+ { // acquire lock
+ AutoMutex _l(d->mLock);
+
+ ssize_t connectionIndex = d->mConnectionsByReceiveFd.indexOfKey(receiveFd);
+ if (connectionIndex < 0) {
+ LOGE("Received spurious receive callback for unknown input channel. "
+ "fd=%d, events=0x%x", receiveFd, events);
+ return 0; // remove the callback
+ }
+
+ nsecs_t currentTime = now();
+
+ sp<Connection> connection = d->mConnectionsByReceiveFd.valueAt(connectionIndex);
+ if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
+ LOGE("channel '%s' ~ Consumer closed input channel or an error occurred. "
+ "events=0x%x", connection->getInputChannelName(), events);
+ d->abortBrokenDispatchCycleLocked(currentTime, connection);
+ d->runCommandsLockedInterruptible();
+ return 0; // remove the callback
+ }
+
+ if (! (events & ALOOPER_EVENT_INPUT)) {
+ LOGW("channel '%s' ~ Received spurious callback for unhandled poll event. "
+ "events=0x%x", connection->getInputChannelName(), events);
+ return 1;
+ }
+
+ bool handled = false;
+ status_t status = connection->inputPublisher.receiveFinishedSignal(&handled);
+ if (status) {
+ LOGE("channel '%s' ~ Failed to receive finished signal. status=%d",
+ connection->getInputChannelName(), status);
+ d->abortBrokenDispatchCycleLocked(currentTime, connection);
+ d->runCommandsLockedInterruptible();
+ return 0; // remove the callback
+ }
+
+ d->finishDispatchCycleLocked(currentTime, connection, handled);
+ d->runCommandsLockedInterruptible();
+ return 1;
+ } // release lock
+}
+
+void InputDispatcher::synthesizeCancelationEventsForAllConnectionsLocked(
+ InputState::CancelationOptions options, const char* reason) {
+ for (size_t i = 0; i < mConnectionsByReceiveFd.size(); i++) {
+ synthesizeCancelationEventsForConnectionLocked(
+ mConnectionsByReceiveFd.valueAt(i), options, reason);
+ }
+}
+
+void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked(
+ const sp<InputChannel>& channel, InputState::CancelationOptions options,
+ const char* reason) {
+ ssize_t index = getConnectionIndexLocked(channel);
+ if (index >= 0) {
+ synthesizeCancelationEventsForConnectionLocked(
+ mConnectionsByReceiveFd.valueAt(index), options, reason);
+ }
+}
+
+void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
+ const sp<Connection>& connection, InputState::CancelationOptions options,
+ const char* reason) {
+ nsecs_t currentTime = now();
+
+ mTempCancelationEvents.clear();
+ connection->inputState.synthesizeCancelationEvents(currentTime, & mAllocator,
+ mTempCancelationEvents, options);
+
+ if (! mTempCancelationEvents.isEmpty()
+ && connection->status != Connection::STATUS_BROKEN) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ LOGD("channel '%s' ~ Synthesized %d cancelation events to bring channel back in sync "
+ "with reality: %s, options=%d.",
+ connection->getInputChannelName(), mTempCancelationEvents.size(), reason, options);
+#endif
+ for (size_t i = 0; i < mTempCancelationEvents.size(); i++) {
+ EventEntry* cancelationEventEntry = mTempCancelationEvents.itemAt(i);
+ switch (cancelationEventEntry->type) {
+ case EventEntry::TYPE_KEY:
+ logOutboundKeyDetailsLocked("cancel - ",
+ static_cast<KeyEntry*>(cancelationEventEntry));
+ break;
+ case EventEntry::TYPE_MOTION:
+ logOutboundMotionDetailsLocked("cancel - ",
+ static_cast<MotionEntry*>(cancelationEventEntry));
+ break;
+ }
+
+ int32_t xOffset, yOffset;
+ const InputWindow* window = getWindowLocked(connection->inputChannel);
+ if (window) {
+ xOffset = -window->frameLeft;
+ yOffset = -window->frameTop;
+ } else {
+ xOffset = 0;
+ yOffset = 0;
+ }
+
+ DispatchEntry* cancelationDispatchEntry =
+ mAllocator.obtainDispatchEntry(cancelationEventEntry, // increments ref
+ 0, xOffset, yOffset);
+ connection->outboundQueue.enqueueAtTail(cancelationDispatchEntry);
+
+ mAllocator.releaseEventEntry(cancelationEventEntry);
+ }
+
+ if (!connection->outboundQueue.headSentinel.next->inProgress) {
+ startDispatchCycleLocked(currentTime, connection);
+ }
+ }
+}
+
+InputDispatcher::MotionEntry*
+InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds) {
+ assert(pointerIds.value != 0);
+
+ uint32_t splitPointerIndexMap[MAX_POINTERS];
+ int32_t splitPointerIds[MAX_POINTERS];
+ PointerCoords splitPointerCoords[MAX_POINTERS];
+
+ uint32_t originalPointerCount = originalMotionEntry->pointerCount;
+ uint32_t splitPointerCount = 0;
+
+ for (uint32_t originalPointerIndex = 0; originalPointerIndex < originalPointerCount;
+ originalPointerIndex++) {
+ int32_t pointerId = uint32_t(originalMotionEntry->pointerIds[originalPointerIndex]);
+ if (pointerIds.hasBit(pointerId)) {
+ splitPointerIndexMap[splitPointerCount] = originalPointerIndex;
+ splitPointerIds[splitPointerCount] = pointerId;
+ splitPointerCoords[splitPointerCount] =
+ originalMotionEntry->firstSample.pointerCoords[originalPointerIndex];
+ splitPointerCount += 1;
+ }
+ }
+
+ if (splitPointerCount != pointerIds.count()) {
+ // This is bad. We are missing some of the pointers that we expected to deliver.
+ // Most likely this indicates that we received an ACTION_MOVE events that has
+ // different pointer ids than we expected based on the previous ACTION_DOWN
+ // or ACTION_POINTER_DOWN events that caused us to decide to split the pointers
+ // in this way.
+ LOGW("Dropping split motion event because the pointer count is %d but "
+ "we expected there to be %d pointers. This probably means we received "
+ "a broken sequence of pointer ids from the input device.",
+ splitPointerCount, pointerIds.count());
+ return NULL;
+ }
+
+ int32_t action = originalMotionEntry->action;
+ int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
+ if (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
+ || maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
+ int32_t originalPointerIndex = getMotionEventActionPointerIndex(action);
+ int32_t pointerId = originalMotionEntry->pointerIds[originalPointerIndex];
+ if (pointerIds.hasBit(pointerId)) {
+ if (pointerIds.count() == 1) {
+ // The first/last pointer went down/up.
+ action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
+ ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
+ } else {
+ // A secondary pointer went down/up.
+ uint32_t splitPointerIndex = 0;
+ while (pointerId != splitPointerIds[splitPointerIndex]) {
+ splitPointerIndex += 1;
+ }
+ action = maskedAction | (splitPointerIndex
+ << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+ }
+ } else {
+ // An unrelated pointer changed.
+ action = AMOTION_EVENT_ACTION_MOVE;
+ }
+ }
+
+ MotionEntry* splitMotionEntry = mAllocator.obtainMotionEntry(
+ originalMotionEntry->eventTime,
+ originalMotionEntry->deviceId,
+ originalMotionEntry->source,
+ originalMotionEntry->policyFlags,
+ action,
+ originalMotionEntry->flags,
+ originalMotionEntry->metaState,
+ originalMotionEntry->edgeFlags,
+ originalMotionEntry->xPrecision,
+ originalMotionEntry->yPrecision,
+ originalMotionEntry->downTime,
+ splitPointerCount, splitPointerIds, splitPointerCoords);
+
+ for (MotionSample* originalMotionSample = originalMotionEntry->firstSample.next;
+ originalMotionSample != NULL; originalMotionSample = originalMotionSample->next) {
+ for (uint32_t splitPointerIndex = 0; splitPointerIndex < splitPointerCount;
+ splitPointerIndex++) {
+ uint32_t originalPointerIndex = splitPointerIndexMap[splitPointerIndex];
+ splitPointerCoords[splitPointerIndex] =
+ originalMotionSample->pointerCoords[originalPointerIndex];
+ }
+
+ mAllocator.appendMotionSample(splitMotionEntry, originalMotionSample->eventTime,
+ splitPointerCoords);
+ }
+
+ return splitMotionEntry;
+}
+
+void InputDispatcher::notifyConfigurationChanged(nsecs_t eventTime) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ LOGD("notifyConfigurationChanged - eventTime=%lld", eventTime);
+#endif
+
+ bool needWake;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry(eventTime);
+ needWake = enqueueInboundEventLocked(newEntry);
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
+}
+
+void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags, int32_t action, int32_t flags,
+ int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ LOGD("notifyKey - eventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, action=0x%x, "
+ "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld",
+ eventTime, deviceId, source, policyFlags, action, flags,
+ keyCode, scanCode, metaState, downTime);
+#endif
+ if (! validateKeyEvent(action)) {
+ return;
+ }
+
+ if ((policyFlags & POLICY_FLAG_VIRTUAL) || (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)) {
+ policyFlags |= POLICY_FLAG_VIRTUAL;
+ flags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
+ }
+
+ policyFlags |= POLICY_FLAG_TRUSTED;
+
+ KeyEvent event;
+ event.initialize(deviceId, source, action, flags, keyCode, scanCode,
+ metaState, 0, downTime, eventTime);
+
+ mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
+
+ if (policyFlags & POLICY_FLAG_WOKE_HERE) {
+ flags |= AKEY_EVENT_FLAG_WOKE_HERE;
+ }
+
+ bool needWake;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ int32_t repeatCount = 0;
+ KeyEntry* newEntry = mAllocator.obtainKeyEntry(eventTime,
+ deviceId, source, policyFlags, action, flags, keyCode, scanCode,
+ metaState, repeatCount, downTime);
+
+ needWake = enqueueInboundEventLocked(newEntry);
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
+}
+
+void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags, int32_t action, int32_t flags, int32_t metaState, int32_t edgeFlags,
+ uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
+ float xPrecision, float yPrecision, nsecs_t downTime) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ LOGD("notifyMotion - eventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, "
+ "action=0x%x, flags=0x%x, metaState=0x%x, edgeFlags=0x%x, "
+ "xPrecision=%f, yPrecision=%f, downTime=%lld",
+ eventTime, deviceId, source, policyFlags, action, flags, metaState, edgeFlags,
+ xPrecision, yPrecision, downTime);
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ LOGD(" Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, "
+ "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
+ "orientation=%f",
+ i, pointerIds[i], pointerCoords[i].x, pointerCoords[i].y,
+ pointerCoords[i].pressure, pointerCoords[i].size,
+ pointerCoords[i].touchMajor, pointerCoords[i].touchMinor,
+ pointerCoords[i].toolMajor, pointerCoords[i].toolMinor,
+ pointerCoords[i].orientation);
+ }
+#endif
+ if (! validateMotionEvent(action, pointerCount, pointerIds)) {
+ return;
+ }
+
+ policyFlags |= POLICY_FLAG_TRUSTED;
+ mPolicy->interceptGenericBeforeQueueing(eventTime, /*byref*/ policyFlags);
+
+ bool needWake;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ // Attempt batching and streaming of move events.
+ if (action == AMOTION_EVENT_ACTION_MOVE) {
+ // BATCHING CASE
+ //
+ // Try to append a move sample to the tail of the inbound queue for this device.
+ // Give up if we encounter a non-move motion event for this device since that
+ // means we cannot append any new samples until a new motion event has started.
+ for (EventEntry* entry = mInboundQueue.tailSentinel.prev;
+ entry != & mInboundQueue.headSentinel; entry = entry->prev) {
+ if (entry->type != EventEntry::TYPE_MOTION) {
+ // Keep looking for motion events.
+ continue;
+ }
+
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+ if (motionEntry->deviceId != deviceId) {
+ // Keep looking for this device.
+ continue;
+ }
+
+ if (motionEntry->action != AMOTION_EVENT_ACTION_MOVE
+ || motionEntry->source != source
+ || motionEntry->pointerCount != pointerCount
+ || motionEntry->isInjected()) {
+ // Last motion event in the queue for this device is not compatible for
+ // appending new samples. Stop here.
+ goto NoBatchingOrStreaming;
+ }
+
+ // The last motion event is a move and is compatible for appending.
+ // Do the batching magic.
+ mAllocator.appendMotionSample(motionEntry, eventTime, pointerCoords);
+#if DEBUG_BATCHING
+ LOGD("Appended motion sample onto batch for most recent "
+ "motion event for this device in the inbound queue.");
+#endif
+ return; // done!
+ }
+
+ // STREAMING CASE
+ //
+ // There is no pending motion event (of any kind) for this device in the inbound queue.
+ // Search the outbound queue for the current foreground targets to find a dispatched
+ // motion event that is still in progress. If found, then, appen the new sample to
+ // that event and push it out to all current targets. The logic in
+ // prepareDispatchCycleLocked takes care of the case where some targets may
+ // already have consumed the motion event by starting a new dispatch cycle if needed.
+ if (mCurrentInputTargetsValid) {
+ for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {
+ const InputTarget& inputTarget = mCurrentInputTargets[i];
+ if ((inputTarget.flags & InputTarget::FLAG_FOREGROUND) == 0) {
+ // Skip non-foreground targets. We only want to stream if there is at
+ // least one foreground target whose dispatch is still in progress.
+ continue;
+ }
+
+ ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
+ if (connectionIndex < 0) {
+ // Connection must no longer be valid.
+ continue;
+ }
+
+ sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+ if (connection->outboundQueue.isEmpty()) {
+ // This foreground target has an empty outbound queue.
+ continue;
+ }
+
+ DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
+ if (! dispatchEntry->inProgress
+ || dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION
+ || dispatchEntry->isSplit()) {
+ // No motion event is being dispatched, or it is being split across
+ // windows in which case we cannot stream.
+ continue;
+ }
+
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(
+ dispatchEntry->eventEntry);
+ if (motionEntry->action != AMOTION_EVENT_ACTION_MOVE
+ || motionEntry->deviceId != deviceId
+ || motionEntry->source != source
+ || motionEntry->pointerCount != pointerCount
+ || motionEntry->isInjected()) {
+ // The motion event is not compatible with this move.
+ continue;
+ }
+
+ // Hurray! This foreground target is currently dispatching a move event
+ // that we can stream onto. Append the motion sample and resume dispatch.
+ mAllocator.appendMotionSample(motionEntry, eventTime, pointerCoords);
+#if DEBUG_BATCHING
+ LOGD("Appended motion sample onto batch for most recently dispatched "
+ "motion event for this device in the outbound queues. "
+ "Attempting to stream the motion sample.");
+#endif
+ nsecs_t currentTime = now();
+ dispatchEventToCurrentInputTargetsLocked(currentTime, motionEntry,
+ true /*resumeWithAppendedMotionSample*/);
+
+ runCommandsLockedInterruptible();
+ return; // done!
+ }
+ }
+
+NoBatchingOrStreaming:;
+ }
+
+ // Just enqueue a new motion event.
+ MotionEntry* newEntry = mAllocator.obtainMotionEntry(eventTime,
+ deviceId, source, policyFlags, action, flags, metaState, edgeFlags,
+ xPrecision, yPrecision, downTime,
+ pointerCount, pointerIds, pointerCoords);
+
+ needWake = enqueueInboundEventLocked(newEntry);
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
+}
+
+void InputDispatcher::notifySwitch(nsecs_t when, int32_t switchCode, int32_t switchValue,
+ uint32_t policyFlags) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ LOGD("notifySwitch - switchCode=%d, switchValue=%d, policyFlags=0x%x",
+ switchCode, switchValue, policyFlags);
+#endif
+
+ policyFlags |= POLICY_FLAG_TRUSTED;
+ mPolicy->notifySwitch(when, switchCode, switchValue, policyFlags);
+}
+
+int32_t InputDispatcher::injectInputEvent(const InputEvent* event,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ LOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, "
+ "syncMode=%d, timeoutMillis=%d",
+ event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis);
+#endif
+
+ nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis);
+
+ uint32_t policyFlags = POLICY_FLAG_INJECTED;
+ if (hasInjectionPermission(injectorPid, injectorUid)) {
+ policyFlags |= POLICY_FLAG_TRUSTED;
+ }
+
+ EventEntry* injectedEntry;
+ switch (event->getType()) {
+ case AINPUT_EVENT_TYPE_KEY: {
+ const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event);
+ int32_t action = keyEvent->getAction();
+ if (! validateKeyEvent(action)) {
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
+
+ int32_t flags = keyEvent->getFlags();
+ if (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY) {
+ policyFlags |= POLICY_FLAG_VIRTUAL;
+ }
+
+ mPolicy->interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags);
+
+ if (policyFlags & POLICY_FLAG_WOKE_HERE) {
+ flags |= AKEY_EVENT_FLAG_WOKE_HERE;
+ }
+
+ mLock.lock();
+ injectedEntry = mAllocator.obtainKeyEntry(keyEvent->getEventTime(),
+ keyEvent->getDeviceId(), keyEvent->getSource(),
+ policyFlags, action, flags,
+ keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(),
+ keyEvent->getRepeatCount(), keyEvent->getDownTime());
+ break;
+ }
+
+ case AINPUT_EVENT_TYPE_MOTION: {
+ const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event);
+ int32_t action = motionEvent->getAction();
+ size_t pointerCount = motionEvent->getPointerCount();
+ const int32_t* pointerIds = motionEvent->getPointerIds();
+ if (! validateMotionEvent(action, pointerCount, pointerIds)) {
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
+
+ nsecs_t eventTime = motionEvent->getEventTime();
+ mPolicy->interceptGenericBeforeQueueing(eventTime, /*byref*/ policyFlags);
+
+ mLock.lock();
+ const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes();
+ const PointerCoords* samplePointerCoords = motionEvent->getSamplePointerCoords();
+ MotionEntry* motionEntry = mAllocator.obtainMotionEntry(*sampleEventTimes,
+ motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags,
+ action, motionEvent->getFlags(),
+ motionEvent->getMetaState(), motionEvent->getEdgeFlags(),
+ motionEvent->getXPrecision(), motionEvent->getYPrecision(),
+ motionEvent->getDownTime(), uint32_t(pointerCount),
+ pointerIds, samplePointerCoords);
+ for (size_t i = motionEvent->getHistorySize(); i > 0; i--) {
+ sampleEventTimes += 1;
+ samplePointerCoords += pointerCount;
+ mAllocator.appendMotionSample(motionEntry, *sampleEventTimes, samplePointerCoords);
+ }
+ injectedEntry = motionEntry;
+ break;
+ }
+
+ default:
+ LOGW("Cannot inject event of type %d", event->getType());
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
+
+ InjectionState* injectionState = mAllocator.obtainInjectionState(injectorPid, injectorUid);
+ if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
+ injectionState->injectionIsAsync = true;
+ }
+
+ injectionState->refCount += 1;
+ injectedEntry->injectionState = injectionState;
+
+ bool needWake = enqueueInboundEventLocked(injectedEntry);
+ mLock.unlock();
+
+ if (needWake) {
+ mLooper->wake();
+ }
+
+ int32_t injectionResult;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
+ injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+ } else {
+ for (;;) {
+ injectionResult = injectionState->injectionResult;
+ if (injectionResult != INPUT_EVENT_INJECTION_PENDING) {
+ break;
+ }
+
+ nsecs_t remainingTimeout = endTime - now();
+ if (remainingTimeout <= 0) {
+#if DEBUG_INJECTION
+ LOGD("injectInputEvent - Timed out waiting for injection result "
+ "to become available.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
+ break;
+ }
+
+ mInjectionResultAvailableCondition.waitRelative(mLock, remainingTimeout);
+ }
+
+ if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED
+ && syncMode == INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED) {
+ while (injectionState->pendingForegroundDispatches != 0) {
+#if DEBUG_INJECTION
+ LOGD("injectInputEvent - Waiting for %d pending foreground dispatches.",
+ injectionState->pendingForegroundDispatches);
+#endif
+ nsecs_t remainingTimeout = endTime - now();
+ if (remainingTimeout <= 0) {
+#if DEBUG_INJECTION
+ LOGD("injectInputEvent - Timed out waiting for pending foreground "
+ "dispatches to finish.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
+ break;
+ }
+
+ mInjectionSyncFinishedCondition.waitRelative(mLock, remainingTimeout);
+ }
+ }
+ }
+
+ mAllocator.releaseInjectionState(injectionState);
+ } // release lock
+
+#if DEBUG_INJECTION
+ LOGD("injectInputEvent - Finished with result %d. "
+ "injectorPid=%d, injectorUid=%d",
+ injectionResult, injectorPid, injectorUid);
+#endif
+
+ return injectionResult;
+}
+
+bool InputDispatcher::hasInjectionPermission(int32_t injectorPid, int32_t injectorUid) {
+ return injectorUid == 0
+ || mPolicy->checkInjectEventsPermissionNonReentrant(injectorPid, injectorUid);
+}
+
+void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t injectionResult) {
+ InjectionState* injectionState = entry->injectionState;
+ if (injectionState) {
+#if DEBUG_INJECTION
+ LOGD("Setting input event injection result to %d. "
+ "injectorPid=%d, injectorUid=%d",
+ injectionResult, injectionState->injectorPid, injectionState->injectorUid);
+#endif
+
+ if (injectionState->injectionIsAsync) {
+ // Log the outcome since the injector did not wait for the injection result.
+ switch (injectionResult) {
+ case INPUT_EVENT_INJECTION_SUCCEEDED:
+ LOGV("Asynchronous input event injection succeeded.");
+ break;
+ case INPUT_EVENT_INJECTION_FAILED:
+ LOGW("Asynchronous input event injection failed.");
+ break;
+ case INPUT_EVENT_INJECTION_PERMISSION_DENIED:
+ LOGW("Asynchronous input event injection permission denied.");
+ break;
+ case INPUT_EVENT_INJECTION_TIMED_OUT:
+ LOGW("Asynchronous input event injection timed out.");
+ break;
+ }
+ }
+
+ injectionState->injectionResult = injectionResult;
+ mInjectionResultAvailableCondition.broadcast();
+ }
+}
+
+void InputDispatcher::incrementPendingForegroundDispatchesLocked(EventEntry* entry) {
+ InjectionState* injectionState = entry->injectionState;
+ if (injectionState) {
+ injectionState->pendingForegroundDispatches += 1;
+ }
+}
+
+void InputDispatcher::decrementPendingForegroundDispatchesLocked(EventEntry* entry) {
+ InjectionState* injectionState = entry->injectionState;
+ if (injectionState) {
+ injectionState->pendingForegroundDispatches -= 1;
+
+ if (injectionState->pendingForegroundDispatches == 0) {
+ mInjectionSyncFinishedCondition.broadcast();
+ }
+ }
+}
+
+const InputWindow* InputDispatcher::getWindowLocked(const sp<InputChannel>& inputChannel) {
+ for (size_t i = 0; i < mWindows.size(); i++) {
+ const InputWindow* window = & mWindows[i];
+ if (window->inputChannel == inputChannel) {
+ return window;
+ }
+ }
+ return NULL;
+}
+
+void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) {
+#if DEBUG_FOCUS
+ LOGD("setInputWindows");
+#endif
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ // Clear old window pointers.
+ sp<InputChannel> oldFocusedWindowChannel;
+ if (mFocusedWindow) {
+ oldFocusedWindowChannel = mFocusedWindow->inputChannel;
+ mFocusedWindow = NULL;
+ }
+
+ mWindows.clear();
+
+ // Loop over new windows and rebuild the necessary window pointers for
+ // tracking focus and touch.
+ mWindows.appendVector(inputWindows);
+
+ size_t numWindows = mWindows.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ const InputWindow* window = & mWindows.itemAt(i);
+ if (window->hasFocus) {
+ mFocusedWindow = window;
+ break;
+ }
+ }
+
+ if (oldFocusedWindowChannel != NULL) {
+ if (!mFocusedWindow || oldFocusedWindowChannel != mFocusedWindow->inputChannel) {
+#if DEBUG_FOCUS
+ LOGD("Focus left window: %s",
+ oldFocusedWindowChannel->getName().string());
+#endif
+ synthesizeCancelationEventsForInputChannelLocked(oldFocusedWindowChannel,
+ InputState::CANCEL_NON_POINTER_EVENTS, "focus left window");
+ oldFocusedWindowChannel.clear();
+ }
+ }
+ if (mFocusedWindow && oldFocusedWindowChannel == NULL) {
+#if DEBUG_FOCUS
+ LOGD("Focus entered window: %s",
+ mFocusedWindow->inputChannel->getName().string());
+#endif
+ }
+
+ for (size_t i = 0; i < mTouchState.windows.size(); ) {
+ TouchedWindow& touchedWindow = mTouchState.windows.editItemAt(i);
+ const InputWindow* window = getWindowLocked(touchedWindow.channel);
+ if (window) {
+ touchedWindow.window = window;
+ i += 1;
+ } else {
+#if DEBUG_FOCUS
+ LOGD("Touched window was removed: %s", touchedWindow.channel->getName().string());
+#endif
+ synthesizeCancelationEventsForInputChannelLocked(touchedWindow.channel,
+ InputState::CANCEL_POINTER_EVENTS, "touched window was removed");
+ mTouchState.windows.removeAt(i);
+ }
+ }
+
+#if DEBUG_FOCUS
+ //logDispatchStateLocked();
+#endif
+ } // release lock
+
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mLooper->wake();
+}
+
+void InputDispatcher::setFocusedApplication(const InputApplication* inputApplication) {
+#if DEBUG_FOCUS
+ LOGD("setFocusedApplication");
+#endif
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ releaseFocusedApplicationLocked();
+
+ if (inputApplication) {
+ mFocusedApplicationStorage = *inputApplication;
+ mFocusedApplication = & mFocusedApplicationStorage;
+ }
+
+#if DEBUG_FOCUS
+ //logDispatchStateLocked();
+#endif
+ } // release lock
+
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mLooper->wake();
+}
+
+void InputDispatcher::releaseFocusedApplicationLocked() {
+ if (mFocusedApplication) {
+ mFocusedApplication = NULL;
+ mFocusedApplicationStorage.inputApplicationHandle.clear();
+ }
+}
+
+void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) {
+#if DEBUG_FOCUS
+ LOGD("setInputDispatchMode: enabled=%d, frozen=%d", enabled, frozen);
+#endif
+
+ bool changed;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mDispatchEnabled != enabled || mDispatchFrozen != frozen) {
+ if (mDispatchFrozen && !frozen) {
+ resetANRTimeoutsLocked();
+ }
+
+ if (mDispatchEnabled && !enabled) {
+ resetAndDropEverythingLocked("dispatcher is being disabled");
+ }
+
+ mDispatchEnabled = enabled;
+ mDispatchFrozen = frozen;
+ changed = true;
+ } else {
+ changed = false;
+ }
+
+#if DEBUG_FOCUS
+ //logDispatchStateLocked();
+#endif
+ } // release lock
+
+ if (changed) {
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mLooper->wake();
+ }
+}
+
+bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel,
+ const sp<InputChannel>& toChannel) {
+#if DEBUG_FOCUS
+ LOGD("transferTouchFocus: fromChannel=%s, toChannel=%s",
+ fromChannel->getName().string(), toChannel->getName().string());
+#endif
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ const InputWindow* fromWindow = getWindowLocked(fromChannel);
+ const InputWindow* toWindow = getWindowLocked(toChannel);
+ if (! fromWindow || ! toWindow) {
+#if DEBUG_FOCUS
+ LOGD("Cannot transfer focus because from or to window not found.");
+#endif
+ return false;
+ }
+ if (fromWindow == toWindow) {
+#if DEBUG_FOCUS
+ LOGD("Trivial transfer to same window.");
+#endif
+ return true;
+ }
+
+ bool found = false;
+ for (size_t i = 0; i < mTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTouchState.windows[i];
+ if (touchedWindow.window == fromWindow) {
+ int32_t oldTargetFlags = touchedWindow.targetFlags;
+ BitSet32 pointerIds = touchedWindow.pointerIds;
+
+ mTouchState.windows.removeAt(i);
+
+ int32_t newTargetFlags = oldTargetFlags
+ & (InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_SPLIT);
+ mTouchState.addOrUpdateWindow(toWindow, newTargetFlags, pointerIds);
+
+ found = true;
+ break;
+ }
+ }
+
+ if (! found) {
+#if DEBUG_FOCUS
+ LOGD("Focus transfer failed because from window did not have focus.");
+#endif
+ return false;
+ }
+
+ ssize_t fromConnectionIndex = getConnectionIndexLocked(fromChannel);
+ ssize_t toConnectionIndex = getConnectionIndexLocked(toChannel);
+ if (fromConnectionIndex >= 0 && toConnectionIndex >= 0) {
+ sp<Connection> fromConnection = mConnectionsByReceiveFd.valueAt(fromConnectionIndex);
+ sp<Connection> toConnection = mConnectionsByReceiveFd.valueAt(toConnectionIndex);
+
+ fromConnection->inputState.copyPointerStateTo(toConnection->inputState);
+ synthesizeCancelationEventsForConnectionLocked(fromConnection,
+ InputState::CANCEL_POINTER_EVENTS,
+ "transferring touch focus from this window to another window");
+ }
+
+#if DEBUG_FOCUS
+ logDispatchStateLocked();
+#endif
+ } // release lock
+
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mLooper->wake();
+ return true;
+}
+
+void InputDispatcher::resetAndDropEverythingLocked(const char* reason) {
+#if DEBUG_FOCUS
+ LOGD("Resetting and dropping all events (%s).", reason);
+#endif
+
+ synthesizeCancelationEventsForAllConnectionsLocked(InputState::CANCEL_ALL_EVENTS, reason);
+
+ resetKeyRepeatLocked();
+ releasePendingEventLocked();
+ drainInboundQueueLocked();
+ resetTargetsLocked();
+
+ mTouchState.reset();
+}
+
+void InputDispatcher::logDispatchStateLocked() {
+ String8 dump;
+ dumpDispatchStateLocked(dump);
+
+ char* text = dump.lockBuffer(dump.size());
+ char* start = text;
+ while (*start != '\0') {
+ char* end = strchr(start, '\n');
+ if (*end == '\n') {
+ *(end++) = '\0';
+ }
+ LOGD("%s", start);
+ start = end;
+ }
+}
+
+void InputDispatcher::dumpDispatchStateLocked(String8& dump) {
+ dump.appendFormat(INDENT "DispatchEnabled: %d\n", mDispatchEnabled);
+ dump.appendFormat(INDENT "DispatchFrozen: %d\n", mDispatchFrozen);
+
+ if (mFocusedApplication) {
+ dump.appendFormat(INDENT "FocusedApplication: name='%s', dispatchingTimeout=%0.3fms\n",
+ mFocusedApplication->name.string(),
+ mFocusedApplication->dispatchingTimeout / 1000000.0);
+ } else {
+ dump.append(INDENT "FocusedApplication: <null>\n");
+ }
+ dump.appendFormat(INDENT "FocusedWindow: name='%s'\n",
+ mFocusedWindow != NULL ? mFocusedWindow->name.string() : "<null>");
+
+ dump.appendFormat(INDENT "TouchDown: %s\n", toString(mTouchState.down));
+ dump.appendFormat(INDENT "TouchSplit: %s\n", toString(mTouchState.split));
+ dump.appendFormat(INDENT "TouchDeviceId: %d\n", mTouchState.deviceId);
+ dump.appendFormat(INDENT "TouchSource: 0x%08x\n", mTouchState.source);
+ if (!mTouchState.windows.isEmpty()) {
+ dump.append(INDENT "TouchedWindows:\n");
+ for (size_t i = 0; i < mTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTouchState.windows[i];
+ dump.appendFormat(INDENT2 "%d: name='%s', pointerIds=0x%0x, targetFlags=0x%x\n",
+ i, touchedWindow.window->name.string(), touchedWindow.pointerIds.value,
+ touchedWindow.targetFlags);
+ }
+ } else {
+ dump.append(INDENT "TouchedWindows: <none>\n");
+ }
+
+ if (!mWindows.isEmpty()) {
+ dump.append(INDENT "Windows:\n");
+ for (size_t i = 0; i < mWindows.size(); i++) {
+ const InputWindow& window = mWindows[i];
+ dump.appendFormat(INDENT2 "%d: name='%s', paused=%s, hasFocus=%s, hasWallpaper=%s, "
+ "visible=%s, canReceiveKeys=%s, flags=0x%08x, type=0x%08x, layer=%d, "
+ "frame=[%d,%d][%d,%d], "
+ "touchableRegion=",
+ i, window.name.string(),
+ toString(window.paused),
+ toString(window.hasFocus),
+ toString(window.hasWallpaper),
+ toString(window.visible),
+ toString(window.canReceiveKeys),
+ window.layoutParamsFlags, window.layoutParamsType,
+ window.layer,
+ window.frameLeft, window.frameTop,
+ window.frameRight, window.frameBottom);
+ dumpRegion(dump, window.touchableRegion);
+ dump.appendFormat(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
+ window.ownerPid, window.ownerUid,
+ window.dispatchingTimeout / 1000000.0);
+ }
+ } else {
+ dump.append(INDENT "Windows: <none>\n");
+ }
+
+ if (!mMonitoringChannels.isEmpty()) {
+ dump.append(INDENT "MonitoringChannels:\n");
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ const sp<InputChannel>& channel = mMonitoringChannels[i];
+ dump.appendFormat(INDENT2 "%d: '%s'\n", i, channel->getName().string());
+ }
+ } else {
+ dump.append(INDENT "MonitoringChannels: <none>\n");
+ }
+
+ dump.appendFormat(INDENT "InboundQueue: length=%u\n", mInboundQueue.count());
+
+ if (!mActiveConnections.isEmpty()) {
+ dump.append(INDENT "ActiveConnections:\n");
+ for (size_t i = 0; i < mActiveConnections.size(); i++) {
+ const Connection* connection = mActiveConnections[i];
+ dump.appendFormat(INDENT2 "%d: '%s', status=%s, outboundQueueLength=%u, "
+ "inputState.isNeutral=%s\n",
+ i, connection->getInputChannelName(), connection->getStatusLabel(),
+ connection->outboundQueue.count(),
+ toString(connection->inputState.isNeutral()));
+ }
+ } else {
+ dump.append(INDENT "ActiveConnections: <none>\n");
+ }
+
+ if (isAppSwitchPendingLocked()) {
+ dump.appendFormat(INDENT "AppSwitch: pending, due in %01.1fms\n",
+ (mAppSwitchDueTime - now()) / 1000000.0);
+ } else {
+ dump.append(INDENT "AppSwitch: not pending\n");
+ }
+}
+
+status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
+#if DEBUG_REGISTRATION
+ LOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().string(),
+ toString(monitor));
+#endif
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (getConnectionIndexLocked(inputChannel) >= 0) {
+ LOGW("Attempted to register already registered input channel '%s'",
+ inputChannel->getName().string());
+ return BAD_VALUE;
+ }
+
+ sp<Connection> connection = new Connection(inputChannel, inputWindowHandle);
+ status_t status = connection->initialize();
+ if (status) {
+ LOGE("Failed to initialize input publisher for input channel '%s', status=%d",
+ inputChannel->getName().string(), status);
+ return status;
+ }
+
+ int32_t receiveFd = inputChannel->getReceivePipeFd();
+ mConnectionsByReceiveFd.add(receiveFd, connection);
+
+ if (monitor) {
+ mMonitoringChannels.push(inputChannel);
+ }
+
+ mLooper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
+
+ runCommandsLockedInterruptible();
+ } // release lock
+ return OK;
+}
+
+status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) {
+#if DEBUG_REGISTRATION
+ LOGD("channel '%s' ~ unregisterInputChannel", inputChannel->getName().string());
+#endif
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
+ if (connectionIndex < 0) {
+ LOGW("Attempted to unregister already unregistered input channel '%s'",
+ inputChannel->getName().string());
+ return BAD_VALUE;
+ }
+
+ sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+ mConnectionsByReceiveFd.removeItemsAt(connectionIndex);
+
+ connection->status = Connection::STATUS_ZOMBIE;
+
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ if (mMonitoringChannels[i] == inputChannel) {
+ mMonitoringChannels.removeAt(i);
+ break;
+ }
+ }
+
+ mLooper->removeFd(inputChannel->getReceivePipeFd());
+
+ nsecs_t currentTime = now();
+ abortBrokenDispatchCycleLocked(currentTime, connection);
+
+ runCommandsLockedInterruptible();
+ } // release lock
+
+ // Wake the poll loop because removing the connection may have changed the current
+ // synchronization state.
+ mLooper->wake();
+ return OK;
+}
+
+ssize_t InputDispatcher::getConnectionIndexLocked(const sp<InputChannel>& inputChannel) {
+ ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(inputChannel->getReceivePipeFd());
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+ if (connection->inputChannel.get() == inputChannel.get()) {
+ return connectionIndex;
+ }
+ }
+
+ return -1;
+}
+
+void InputDispatcher::activateConnectionLocked(Connection* connection) {
+ for (size_t i = 0; i < mActiveConnections.size(); i++) {
+ if (mActiveConnections.itemAt(i) == connection) {
+ return;
+ }
+ }
+ mActiveConnections.add(connection);
+}
+
+void InputDispatcher::deactivateConnectionLocked(Connection* connection) {
+ for (size_t i = 0; i < mActiveConnections.size(); i++) {
+ if (mActiveConnections.itemAt(i) == connection) {
+ mActiveConnections.removeAt(i);
+ return;
+ }
+ }
+}
+
+void InputDispatcher::onDispatchCycleStartedLocked(
+ nsecs_t currentTime, const sp<Connection>& connection) {
+}
+
+void InputDispatcher::onDispatchCycleFinishedLocked(
+ nsecs_t currentTime, const sp<Connection>& connection, bool handled) {
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doDispatchCycleFinishedLockedInterruptible);
+ commandEntry->connection = connection;
+ commandEntry->handled = handled;
+}
+
+void InputDispatcher::onDispatchCycleBrokenLocked(
+ nsecs_t currentTime, const sp<Connection>& connection) {
+ LOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!",
+ connection->getInputChannelName());
+
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible);
+ commandEntry->connection = connection;
+}
+
+void InputDispatcher::onANRLocked(
+ nsecs_t currentTime, const InputApplication* application, const InputWindow* window,
+ nsecs_t eventTime, nsecs_t waitStartTime) {
+ LOGI("Application is not responding: %s. "
+ "%01.1fms since event, %01.1fms since wait started",
+ getApplicationWindowLabelLocked(application, window).string(),
+ (currentTime - eventTime) / 1000000.0,
+ (currentTime - waitStartTime) / 1000000.0);
+
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doNotifyANRLockedInterruptible);
+ if (application) {
+ commandEntry->inputApplicationHandle = application->inputApplicationHandle;
+ }
+ if (window) {
+ commandEntry->inputWindowHandle = window->inputWindowHandle;
+ commandEntry->inputChannel = window->inputChannel;
+ }
+}
+
+void InputDispatcher::doNotifyConfigurationChangedInterruptible(
+ CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ mPolicy->notifyConfigurationChanged(commandEntry->eventTime);
+
+ mLock.lock();
+}
+
+void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible(
+ CommandEntry* commandEntry) {
+ sp<Connection> connection = commandEntry->connection;
+
+ if (connection->status != Connection::STATUS_ZOMBIE) {
+ mLock.unlock();
+
+ mPolicy->notifyInputChannelBroken(connection->inputWindowHandle);
+
+ mLock.lock();
+ }
+}
+
+void InputDispatcher::doNotifyANRLockedInterruptible(
+ CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ nsecs_t newTimeout = mPolicy->notifyANR(
+ commandEntry->inputApplicationHandle, commandEntry->inputWindowHandle);
+
+ mLock.lock();
+
+ resumeAfterTargetsNotReadyTimeoutLocked(newTimeout, commandEntry->inputChannel);
+}
+
+void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
+ CommandEntry* commandEntry) {
+ KeyEntry* entry = commandEntry->keyEntry;
+
+ KeyEvent event;
+ initializeKeyEvent(&event, entry);
+
+ mLock.unlock();
+
+ bool consumed = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,
+ &event, entry->policyFlags);
+
+ mLock.lock();
+
+ entry->interceptKeyResult = consumed
+ ? KeyEntry::INTERCEPT_KEY_RESULT_SKIP
+ : KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+ mAllocator.releaseKeyEntry(entry);
+}
+
+void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(
+ CommandEntry* commandEntry) {
+ sp<Connection> connection = commandEntry->connection;
+ bool handled = commandEntry->handled;
+
+ if (!connection->outboundQueue.isEmpty()) {
+ DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
+ if (dispatchEntry->inProgress
+ && dispatchEntry->hasForegroundTarget()
+ && dispatchEntry->eventEntry->type == EventEntry::TYPE_KEY) {
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry);
+ if (!(keyEntry->flags & AKEY_EVENT_FLAG_FALLBACK)) {
+ if (handled) {
+ // If the application handled a non-fallback key, then immediately
+ // cancel all fallback keys previously dispatched to the application.
+ // This behavior will prevent chording with fallback keys (so they cannot
+ // be used as modifiers) but it will ensure that fallback keys do not
+ // get stuck. This takes care of the case where the application does not handle
+ // the original DOWN so we generate a fallback DOWN but it does handle
+ // the original UP in which case we would not generate the fallback UP.
+ synthesizeCancelationEventsForConnectionLocked(connection,
+ InputState::CANCEL_FALLBACK_EVENTS,
+ "application handled a non-fallback event, canceling all fallback events");
+ } else {
+ // If the application did not handle a non-fallback key, then ask
+ // the policy what to do with it. We might generate a fallback key
+ // event here.
+ KeyEvent event;
+ initializeKeyEvent(&event, keyEntry);
+
+ mLock.unlock();
+
+ bool fallback = mPolicy->dispatchUnhandledKey(connection->inputWindowHandle,
+ &event, keyEntry->policyFlags, &event);
+
+ mLock.lock();
+
+ if (connection->status != Connection::STATUS_NORMAL) {
+ return;
+ }
+
+ assert(connection->outboundQueue.headSentinel.next == dispatchEntry);
+
+ if (fallback) {
+ // Restart the dispatch cycle using the fallback key.
+ keyEntry->eventTime = event.getEventTime();
+ keyEntry->deviceId = event.getDeviceId();
+ keyEntry->source = event.getSource();
+ keyEntry->flags = event.getFlags() | AKEY_EVENT_FLAG_FALLBACK;
+ keyEntry->keyCode = event.getKeyCode();
+ keyEntry->scanCode = event.getScanCode();
+ keyEntry->metaState = event.getMetaState();
+ keyEntry->repeatCount = event.getRepeatCount();
+ keyEntry->downTime = event.getDownTime();
+ keyEntry->syntheticRepeat = false;
+
+ dispatchEntry->inProgress = false;
+ startDispatchCycleLocked(now(), connection);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ startNextDispatchCycleLocked(now(), connection);
+}
+
+void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ mPolicy->pokeUserActivity(commandEntry->eventTime, commandEntry->userActivityEventType);
+
+ mLock.lock();
+}
+
+void InputDispatcher::initializeKeyEvent(KeyEvent* event, const KeyEntry* entry) {
+ event->initialize(entry->deviceId, entry->source, entry->action, entry->flags,
+ entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount,
+ entry->downTime, entry->eventTime);
+}
+
+void InputDispatcher::updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
+ int32_t injectionResult, nsecs_t timeSpentWaitingForApplication) {
+ // TODO Write some statistics about how long we spend waiting.
+}
+
+void InputDispatcher::dump(String8& dump) {
+ dump.append("Input Dispatcher State:\n");
+ dumpDispatchStateLocked(dump);
+}
+
+
+// --- InputDispatcher::Queue ---
+
+template <typename T>
+uint32_t InputDispatcher::Queue<T>::count() const {
+ uint32_t result = 0;
+ for (const T* entry = headSentinel.next; entry != & tailSentinel; entry = entry->next) {
+ result += 1;
+ }
+ return result;
+}
+
+
+// --- InputDispatcher::Allocator ---
+
+InputDispatcher::Allocator::Allocator() {
+}
+
+InputDispatcher::InjectionState*
+InputDispatcher::Allocator::obtainInjectionState(int32_t injectorPid, int32_t injectorUid) {
+ InjectionState* injectionState = mInjectionStatePool.alloc();
+ injectionState->refCount = 1;
+ injectionState->injectorPid = injectorPid;
+ injectionState->injectorUid = injectorUid;
+ injectionState->injectionIsAsync = false;
+ injectionState->injectionResult = INPUT_EVENT_INJECTION_PENDING;
+ injectionState->pendingForegroundDispatches = 0;
+ return injectionState;
+}
+
+void InputDispatcher::Allocator::initializeEventEntry(EventEntry* entry, int32_t type,
+ nsecs_t eventTime, uint32_t policyFlags) {
+ entry->type = type;
+ entry->refCount = 1;
+ entry->dispatchInProgress = false;
+ entry->eventTime = eventTime;
+ entry->policyFlags = policyFlags;
+ entry->injectionState = NULL;
+}
+
+void InputDispatcher::Allocator::releaseEventEntryInjectionState(EventEntry* entry) {
+ if (entry->injectionState) {
+ releaseInjectionState(entry->injectionState);
+ entry->injectionState = NULL;
+ }
+}
+
+InputDispatcher::ConfigurationChangedEntry*
+InputDispatcher::Allocator::obtainConfigurationChangedEntry(nsecs_t eventTime) {
+ ConfigurationChangedEntry* entry = mConfigurationChangeEntryPool.alloc();
+ initializeEventEntry(entry, EventEntry::TYPE_CONFIGURATION_CHANGED, eventTime, 0);
+ return entry;
+}
+
+InputDispatcher::KeyEntry* InputDispatcher::Allocator::obtainKeyEntry(nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action,
+ int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
+ int32_t repeatCount, nsecs_t downTime) {
+ KeyEntry* entry = mKeyEntryPool.alloc();
+ initializeEventEntry(entry, EventEntry::TYPE_KEY, eventTime, policyFlags);
+
+ entry->deviceId = deviceId;
+ entry->source = source;
+ entry->action = action;
+ entry->flags = flags;
+ entry->keyCode = keyCode;
+ entry->scanCode = scanCode;
+ entry->metaState = metaState;
+ entry->repeatCount = repeatCount;
+ entry->downTime = downTime;
+ entry->syntheticRepeat = false;
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+ return entry;
+}
+
+InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry(nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action, int32_t flags,
+ int32_t metaState, int32_t edgeFlags, float xPrecision, float yPrecision,
+ nsecs_t downTime, uint32_t pointerCount,
+ const int32_t* pointerIds, const PointerCoords* pointerCoords) {
+ MotionEntry* entry = mMotionEntryPool.alloc();
+ initializeEventEntry(entry, EventEntry::TYPE_MOTION, eventTime, policyFlags);
+
+ entry->eventTime = eventTime;
+ entry->deviceId = deviceId;
+ entry->source = source;
+ entry->action = action;
+ entry->flags = flags;
+ entry->metaState = metaState;
+ entry->edgeFlags = edgeFlags;
+ entry->xPrecision = xPrecision;
+ entry->yPrecision = yPrecision;
+ entry->downTime = downTime;
+ entry->pointerCount = pointerCount;
+ entry->firstSample.eventTime = eventTime;
+ entry->firstSample.next = NULL;
+ entry->lastSample = & entry->firstSample;
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ entry->pointerIds[i] = pointerIds[i];
+ entry->firstSample.pointerCoords[i] = pointerCoords[i];
+ }
+ return entry;
+}
+
+InputDispatcher::DispatchEntry* InputDispatcher::Allocator::obtainDispatchEntry(
+ EventEntry* eventEntry,
+ int32_t targetFlags, float xOffset, float yOffset) {
+ DispatchEntry* entry = mDispatchEntryPool.alloc();
+ entry->eventEntry = eventEntry;
+ eventEntry->refCount += 1;
+ entry->targetFlags = targetFlags;
+ entry->xOffset = xOffset;
+ entry->yOffset = yOffset;
+ entry->inProgress = false;
+ entry->headMotionSample = NULL;
+ entry->tailMotionSample = NULL;
+ return entry;
+}
+
+InputDispatcher::CommandEntry* InputDispatcher::Allocator::obtainCommandEntry(Command command) {
+ CommandEntry* entry = mCommandEntryPool.alloc();
+ entry->command = command;
+ return entry;
+}
+
+void InputDispatcher::Allocator::releaseInjectionState(InjectionState* injectionState) {
+ injectionState->refCount -= 1;
+ if (injectionState->refCount == 0) {
+ mInjectionStatePool.free(injectionState);
+ } else {
+ assert(injectionState->refCount > 0);
+ }
+}
+
+void InputDispatcher::Allocator::releaseEventEntry(EventEntry* entry) {
+ switch (entry->type) {
+ case EventEntry::TYPE_CONFIGURATION_CHANGED:
+ releaseConfigurationChangedEntry(static_cast<ConfigurationChangedEntry*>(entry));
+ break;
+ case EventEntry::TYPE_KEY:
+ releaseKeyEntry(static_cast<KeyEntry*>(entry));
+ break;
+ case EventEntry::TYPE_MOTION:
+ releaseMotionEntry(static_cast<MotionEntry*>(entry));
+ break;
+ default:
+ assert(false);
+ break;
+ }
+}
+
+void InputDispatcher::Allocator::releaseConfigurationChangedEntry(
+ ConfigurationChangedEntry* entry) {
+ entry->refCount -= 1;
+ if (entry->refCount == 0) {
+ releaseEventEntryInjectionState(entry);
+ mConfigurationChangeEntryPool.free(entry);
+ } else {
+ assert(entry->refCount > 0);
+ }
+}
+
+void InputDispatcher::Allocator::releaseKeyEntry(KeyEntry* entry) {
+ entry->refCount -= 1;
+ if (entry->refCount == 0) {
+ releaseEventEntryInjectionState(entry);
+ mKeyEntryPool.free(entry);
+ } else {
+ assert(entry->refCount > 0);
+ }
+}
+
+void InputDispatcher::Allocator::releaseMotionEntry(MotionEntry* entry) {
+ entry->refCount -= 1;
+ if (entry->refCount == 0) {
+ releaseEventEntryInjectionState(entry);
+ for (MotionSample* sample = entry->firstSample.next; sample != NULL; ) {
+ MotionSample* next = sample->next;
+ mMotionSamplePool.free(sample);
+ sample = next;
+ }
+ mMotionEntryPool.free(entry);
+ } else {
+ assert(entry->refCount > 0);
+ }
+}
+
+void InputDispatcher::Allocator::releaseDispatchEntry(DispatchEntry* entry) {
+ releaseEventEntry(entry->eventEntry);
+ mDispatchEntryPool.free(entry);
+}
+
+void InputDispatcher::Allocator::releaseCommandEntry(CommandEntry* entry) {
+ mCommandEntryPool.free(entry);
+}
+
+void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry,
+ nsecs_t eventTime, const PointerCoords* pointerCoords) {
+ MotionSample* sample = mMotionSamplePool.alloc();
+ sample->eventTime = eventTime;
+ uint32_t pointerCount = motionEntry->pointerCount;
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ sample->pointerCoords[i] = pointerCoords[i];
+ }
+
+ sample->next = NULL;
+ motionEntry->lastSample->next = sample;
+ motionEntry->lastSample = sample;
+}
+
+void InputDispatcher::Allocator::recycleKeyEntry(KeyEntry* keyEntry) {
+ releaseEventEntryInjectionState(keyEntry);
+
+ keyEntry->dispatchInProgress = false;
+ keyEntry->syntheticRepeat = false;
+ keyEntry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+}
+
+
+// --- InputDispatcher::MotionEntry ---
+
+uint32_t InputDispatcher::MotionEntry::countSamples() const {
+ uint32_t count = 1;
+ for (MotionSample* sample = firstSample.next; sample != NULL; sample = sample->next) {
+ count += 1;
+ }
+ return count;
+}
+
+
+// --- InputDispatcher::InputState ---
+
+InputDispatcher::InputState::InputState() {
+}
+
+InputDispatcher::InputState::~InputState() {
+}
+
+bool InputDispatcher::InputState::isNeutral() const {
+ return mKeyMementos.isEmpty() && mMotionMementos.isEmpty();
+}
+
+InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackEvent(
+ const EventEntry* entry) {
+ switch (entry->type) {
+ case EventEntry::TYPE_KEY:
+ return trackKey(static_cast<const KeyEntry*>(entry));
+
+ case EventEntry::TYPE_MOTION:
+ return trackMotion(static_cast<const MotionEntry*>(entry));
+
+ default:
+ return CONSISTENT;
+ }
+}
+
+InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackKey(
+ const KeyEntry* entry) {
+ int32_t action = entry->action;
+ for (size_t i = 0; i < mKeyMementos.size(); i++) {
+ KeyMemento& memento = mKeyMementos.editItemAt(i);
+ if (memento.deviceId == entry->deviceId
+ && memento.source == entry->source
+ && memento.keyCode == entry->keyCode
+ && memento.scanCode == entry->scanCode) {
+ switch (action) {
+ case AKEY_EVENT_ACTION_UP:
+ mKeyMementos.removeAt(i);
+ return CONSISTENT;
+
+ case AKEY_EVENT_ACTION_DOWN:
+ return TOLERABLE;
+
+ default:
+ return BROKEN;
+ }
+ }
+ }
+
+ switch (action) {
+ case AKEY_EVENT_ACTION_DOWN: {
+ mKeyMementos.push();
+ KeyMemento& memento = mKeyMementos.editTop();
+ memento.deviceId = entry->deviceId;
+ memento.source = entry->source;
+ memento.keyCode = entry->keyCode;
+ memento.scanCode = entry->scanCode;
+ memento.flags = entry->flags;
+ memento.downTime = entry->downTime;
+ return CONSISTENT;
+ }
+
+ default:
+ return BROKEN;
+ }
+}
+
+InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackMotion(
+ const MotionEntry* entry) {
+ int32_t action = entry->action & AMOTION_EVENT_ACTION_MASK;
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ MotionMemento& memento = mMotionMementos.editItemAt(i);
+ if (memento.deviceId == entry->deviceId
+ && memento.source == entry->source) {
+ switch (action) {
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_CANCEL:
+ mMotionMementos.removeAt(i);
+ return CONSISTENT;
+
+ case AMOTION_EVENT_ACTION_DOWN:
+ return TOLERABLE;
+
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ if (entry->pointerCount == memento.pointerCount + 1) {
+ memento.setPointers(entry);
+ return CONSISTENT;
+ }
+ return BROKEN;
+
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ if (entry->pointerCount == memento.pointerCount - 1) {
+ memento.setPointers(entry);
+ return CONSISTENT;
+ }
+ return BROKEN;
+
+ case AMOTION_EVENT_ACTION_MOVE:
+ if (entry->pointerCount == memento.pointerCount) {
+ return CONSISTENT;
+ }
+ return BROKEN;
+
+ default:
+ return BROKEN;
+ }
+ }
+ }
+
+ switch (action) {
+ case AMOTION_EVENT_ACTION_DOWN: {
+ mMotionMementos.push();
+ MotionMemento& memento = mMotionMementos.editTop();
+ memento.deviceId = entry->deviceId;
+ memento.source = entry->source;
+ memento.xPrecision = entry->xPrecision;
+ memento.yPrecision = entry->yPrecision;
+ memento.downTime = entry->downTime;
+ memento.setPointers(entry);
+ return CONSISTENT;
+ }
+
+ default:
+ return BROKEN;
+ }
+}
+
+void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) {
+ pointerCount = entry->pointerCount;
+ for (uint32_t i = 0; i < entry->pointerCount; i++) {
+ pointerIds[i] = entry->pointerIds[i];
+ pointerCoords[i] = entry->lastSample->pointerCoords[i];
+ }
+}
+
+void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTime,
+ Allocator* allocator, Vector<EventEntry*>& outEvents,
+ CancelationOptions options) {
+ for (size_t i = 0; i < mKeyMementos.size(); ) {
+ const KeyMemento& memento = mKeyMementos.itemAt(i);
+ if (shouldCancelKey(memento, options)) {
+ outEvents.push(allocator->obtainKeyEntry(currentTime,
+ memento.deviceId, memento.source, 0,
+ AKEY_EVENT_ACTION_UP, memento.flags | AKEY_EVENT_FLAG_CANCELED,
+ memento.keyCode, memento.scanCode, 0, 0, memento.downTime));
+ mKeyMementos.removeAt(i);
+ } else {
+ i += 1;
+ }
+ }
+
+ for (size_t i = 0; i < mMotionMementos.size(); ) {
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
+ if (shouldCancelMotion(memento, options)) {
+ outEvents.push(allocator->obtainMotionEntry(currentTime,
+ memento.deviceId, memento.source, 0,
+ AMOTION_EVENT_ACTION_CANCEL, 0, 0, 0,
+ memento.xPrecision, memento.yPrecision, memento.downTime,
+ memento.pointerCount, memento.pointerIds, memento.pointerCoords));
+ mMotionMementos.removeAt(i);
+ } else {
+ i += 1;
+ }
+ }
+}
+
+void InputDispatcher::InputState::clear() {
+ mKeyMementos.clear();
+ mMotionMementos.clear();
+}
+
+void InputDispatcher::InputState::copyPointerStateTo(InputState& other) const {
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
+ if (memento.source & AINPUT_SOURCE_CLASS_POINTER) {
+ for (size_t j = 0; j < other.mMotionMementos.size(); ) {
+ const MotionMemento& otherMemento = other.mMotionMementos.itemAt(j);
+ if (memento.deviceId == otherMemento.deviceId
+ && memento.source == otherMemento.source) {
+ other.mMotionMementos.removeAt(j);
+ } else {
+ j += 1;
+ }
+ }
+ other.mMotionMementos.push(memento);
+ }
+ }
+}
+
+bool InputDispatcher::InputState::shouldCancelKey(const KeyMemento& memento,
+ CancelationOptions options) {
+ switch (options) {
+ case CANCEL_ALL_EVENTS:
+ case CANCEL_NON_POINTER_EVENTS:
+ return true;
+ case CANCEL_FALLBACK_EVENTS:
+ return memento.flags & AKEY_EVENT_FLAG_FALLBACK;
+ default:
+ return false;
+ }
+}
+
+bool InputDispatcher::InputState::shouldCancelMotion(const MotionMemento& memento,
+ CancelationOptions options) {
+ switch (options) {
+ case CANCEL_ALL_EVENTS:
+ return true;
+ case CANCEL_POINTER_EVENTS:
+ return memento.source & AINPUT_SOURCE_CLASS_POINTER;
+ case CANCEL_NON_POINTER_EVENTS:
+ return !(memento.source & AINPUT_SOURCE_CLASS_POINTER);
+ default:
+ return false;
+ }
+}
+
+
+// --- InputDispatcher::Connection ---
+
+InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle) :
+ status(STATUS_NORMAL), inputChannel(inputChannel), inputWindowHandle(inputWindowHandle),
+ inputPublisher(inputChannel),
+ lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX) {
+}
+
+InputDispatcher::Connection::~Connection() {
+}
+
+status_t InputDispatcher::Connection::initialize() {
+ return inputPublisher.initialize();
+}
+
+const char* InputDispatcher::Connection::getStatusLabel() const {
+ switch (status) {
+ case STATUS_NORMAL:
+ return "NORMAL";
+
+ case STATUS_BROKEN:
+ return "BROKEN";
+
+ case STATUS_ZOMBIE:
+ return "ZOMBIE";
+
+ default:
+ return "UNKNOWN";
+ }
+}
+
+InputDispatcher::DispatchEntry* InputDispatcher::Connection::findQueuedDispatchEntryForEvent(
+ const EventEntry* eventEntry) const {
+ for (DispatchEntry* dispatchEntry = outboundQueue.tailSentinel.prev;
+ dispatchEntry != & outboundQueue.headSentinel; dispatchEntry = dispatchEntry->prev) {
+ if (dispatchEntry->eventEntry == eventEntry) {
+ return dispatchEntry;
+ }
+ }
+ return NULL;
+}
+
+
+// --- InputDispatcher::CommandEntry ---
+
+InputDispatcher::CommandEntry::CommandEntry() :
+ keyEntry(NULL) {
+}
+
+InputDispatcher::CommandEntry::~CommandEntry() {
+}
+
+
+// --- InputDispatcher::TouchState ---
+
+InputDispatcher::TouchState::TouchState() :
+ down(false), split(false), deviceId(-1), source(0) {
+}
+
+InputDispatcher::TouchState::~TouchState() {
+}
+
+void InputDispatcher::TouchState::reset() {
+ down = false;
+ split = false;
+ deviceId = -1;
+ source = 0;
+ windows.clear();
+}
+
+void InputDispatcher::TouchState::copyFrom(const TouchState& other) {
+ down = other.down;
+ split = other.split;
+ deviceId = other.deviceId;
+ source = other.source;
+ windows.clear();
+ windows.appendVector(other.windows);
+}
+
+void InputDispatcher::TouchState::addOrUpdateWindow(const InputWindow* window,
+ int32_t targetFlags, BitSet32 pointerIds) {
+ if (targetFlags & InputTarget::FLAG_SPLIT) {
+ split = true;
+ }
+
+ for (size_t i = 0; i < windows.size(); i++) {
+ TouchedWindow& touchedWindow = windows.editItemAt(i);
+ if (touchedWindow.window == window) {
+ touchedWindow.targetFlags |= targetFlags;
+ touchedWindow.pointerIds.value |= pointerIds.value;
+ return;
+ }
+ }
+
+ windows.push();
+
+ TouchedWindow& touchedWindow = windows.editTop();
+ touchedWindow.window = window;
+ touchedWindow.targetFlags = targetFlags;
+ touchedWindow.pointerIds = pointerIds;
+ touchedWindow.channel = window->inputChannel;
+}
+
+void InputDispatcher::TouchState::removeOutsideTouchWindows() {
+ for (size_t i = 0 ; i < windows.size(); ) {
+ if (windows[i].targetFlags & InputTarget::FLAG_OUTSIDE) {
+ windows.removeAt(i);
+ } else {
+ i += 1;
+ }
+ }
+}
+
+const InputWindow* InputDispatcher::TouchState::getFirstForegroundWindow() {
+ for (size_t i = 0; i < windows.size(); i++) {
+ if (windows[i].targetFlags & InputTarget::FLAG_FOREGROUND) {
+ return windows[i].window;
+ }
+ }
+ return NULL;
+}
+
+
+// --- InputDispatcherThread ---
+
+InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) :
+ Thread(/*canCallJava*/ true), mDispatcher(dispatcher) {
+}
+
+InputDispatcherThread::~InputDispatcherThread() {
+}
+
+bool InputDispatcherThread::threadLoop() {
+ mDispatcher->dispatchOnce();
+ return true;
+}
+
+} // namespace android
diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h
new file mode 100644
index 0000000..006c6b8
--- /dev/null
+++ b/services/input/InputDispatcher.h
@@ -0,0 +1,1001 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_DISPATCHER_H
+#define _UI_INPUT_DISPATCHER_H
+
+#include <ui/Input.h>
+#include <ui/InputTransport.h>
+#include <utils/KeyedVector.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/Looper.h>
+#include <utils/Pool.h>
+#include <utils/BitSet.h>
+
+#include <stddef.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "InputWindow.h"
+#include "InputApplication.h"
+
+
+namespace android {
+
+/*
+ * Constants used to report the outcome of input event injection.
+ */
+enum {
+ /* (INTERNAL USE ONLY) Specifies that injection is pending and its outcome is unknown. */
+ INPUT_EVENT_INJECTION_PENDING = -1,
+
+ /* Injection succeeded. */
+ INPUT_EVENT_INJECTION_SUCCEEDED = 0,
+
+ /* Injection failed because the injector did not have permission to inject
+ * into the application with input focus. */
+ INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1,
+
+ /* Injection failed because there were no available input targets. */
+ INPUT_EVENT_INJECTION_FAILED = 2,
+
+ /* Injection failed due to a timeout. */
+ INPUT_EVENT_INJECTION_TIMED_OUT = 3
+};
+
+/*
+ * Constants used to determine the input event injection synchronization mode.
+ */
+enum {
+ /* Injection is asynchronous and is assumed always to be successful. */
+ INPUT_EVENT_INJECTION_SYNC_NONE = 0,
+
+ /* Waits for previous events to be dispatched so that the input dispatcher can determine
+ * whether input event injection willbe permitted based on the current input focus.
+ * Does not wait for the input event to finish processing. */
+ INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT = 1,
+
+ /* Waits for the input event to be completely processed. */
+ INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED = 2,
+};
+
+
+/*
+ * An input target specifies how an input event is to be dispatched to a particular window
+ * including the window's input channel, control flags, a timeout, and an X / Y offset to
+ * be added to input event coordinates to compensate for the absolute position of the
+ * window area.
+ */
+struct InputTarget {
+ enum {
+ /* This flag indicates that the event is being delivered to a foreground application. */
+ FLAG_FOREGROUND = 0x01,
+
+ /* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside
+ * of the area of this target and so should instead be delivered as an
+ * AMOTION_EVENT_ACTION_OUTSIDE to this target. */
+ FLAG_OUTSIDE = 0x02,
+
+ /* This flag indicates that the target of a MotionEvent is partly or wholly
+ * obscured by another visible window above it. The motion event should be
+ * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */
+ FLAG_WINDOW_IS_OBSCURED = 0x04,
+
+ /* This flag indicates that a motion event is being split across multiple windows. */
+ FLAG_SPLIT = 0x08,
+ };
+
+ // The input channel to be targeted.
+ sp<InputChannel> inputChannel;
+
+ // Flags for the input target.
+ int32_t flags;
+
+ // The x and y offset to add to a MotionEvent as it is delivered.
+ // (ignored for KeyEvents)
+ float xOffset, yOffset;
+
+ // The subset of pointer ids to include in motion events dispatched to this input target
+ // if FLAG_SPLIT is set.
+ BitSet32 pointerIds;
+};
+
+
+/*
+ * Input dispatcher policy interface.
+ *
+ * The input reader policy is used by the input reader to interact with the Window Manager
+ * and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI. This interface is also mocked in the unit tests.
+ */
+class InputDispatcherPolicyInterface : public virtual RefBase {
+protected:
+ InputDispatcherPolicyInterface() { }
+ virtual ~InputDispatcherPolicyInterface() { }
+
+public:
+ /* Notifies the system that a configuration change has occurred. */
+ virtual void notifyConfigurationChanged(nsecs_t when) = 0;
+
+ /* Notifies the system that an application is not responding.
+ * Returns a new timeout to continue waiting, or 0 to abort dispatch. */
+ virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+ const sp<InputWindowHandle>& inputWindowHandle) = 0;
+
+ /* Notifies the system that an input channel is unrecoverably broken. */
+ virtual void notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle) = 0;
+
+ /* Gets the key repeat initial timeout or -1 if automatic key repeating is disabled. */
+ virtual nsecs_t getKeyRepeatTimeout() = 0;
+
+ /* Gets the key repeat inter-key delay. */
+ virtual nsecs_t getKeyRepeatDelay() = 0;
+
+ /* Gets the maximum suggested event delivery rate per second.
+ * This value is used to throttle motion event movement actions on a per-device
+ * basis. It is not intended to be a hard limit.
+ */
+ virtual int32_t getMaxEventsPerSecond() = 0;
+
+ /* Intercepts a key event immediately before queueing it.
+ * The policy can use this method as an opportunity to perform power management functions
+ * and early event preprocessing such as updating policy flags.
+ *
+ * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
+ * should be dispatched to applications.
+ */
+ virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) = 0;
+
+ /* Intercepts a generic touch, trackball or other event before queueing it.
+ * The policy can use this method as an opportunity to perform power management functions
+ * and early event preprocessing such as updating policy flags.
+ *
+ * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
+ * should be dispatched to applications.
+ */
+ virtual void interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags) = 0;
+
+ /* Allows the policy a chance to intercept a key before dispatching. */
+ virtual bool interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle,
+ const KeyEvent* keyEvent, uint32_t policyFlags) = 0;
+
+ /* Allows the policy a chance to perform default processing for an unhandled key.
+ * Returns an alternate keycode to redispatch as a fallback, or 0 to give up. */
+ virtual bool dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle,
+ const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) = 0;
+
+ /* Notifies the policy about switch events.
+ */
+ virtual void notifySwitch(nsecs_t when,
+ int32_t switchCode, int32_t switchValue, uint32_t policyFlags) = 0;
+
+ /* Poke user activity for an event dispatched to a window. */
+ virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) = 0;
+
+ /* Checks whether a given application pid/uid has permission to inject input events
+ * into other applications.
+ *
+ * This method is special in that its implementation promises to be non-reentrant and
+ * is safe to call while holding other locks. (Most other methods make no such guarantees!)
+ */
+ virtual bool checkInjectEventsPermissionNonReentrant(
+ int32_t injectorPid, int32_t injectorUid) = 0;
+};
+
+
+/* Notifies the system about input events generated by the input reader.
+ * The dispatcher is expected to be mostly asynchronous. */
+class InputDispatcherInterface : public virtual RefBase {
+protected:
+ InputDispatcherInterface() { }
+ virtual ~InputDispatcherInterface() { }
+
+public:
+ /* Dumps the state of the input dispatcher.
+ *
+ * This method may be called on any thread (usually by the input manager). */
+ virtual void dump(String8& dump) = 0;
+
+ /* Runs a single iteration of the dispatch loop.
+ * Nominally processes one queued event, a timeout, or a response from an input consumer.
+ *
+ * This method should only be called on the input dispatcher thread.
+ */
+ virtual void dispatchOnce() = 0;
+
+ /* Notifies the dispatcher about new events.
+ *
+ * These methods should only be called on the input reader thread.
+ */
+ virtual void notifyConfigurationChanged(nsecs_t eventTime) = 0;
+ virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
+ int32_t scanCode, int32_t metaState, nsecs_t downTime) = 0;
+ virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags, int32_t action, int32_t flags,
+ int32_t metaState, int32_t edgeFlags,
+ uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
+ float xPrecision, float yPrecision, nsecs_t downTime) = 0;
+ virtual void notifySwitch(nsecs_t when,
+ int32_t switchCode, int32_t switchValue, uint32_t policyFlags) = 0;
+
+ /* Injects an input event and optionally waits for sync.
+ * The synchronization mode determines whether the method blocks while waiting for
+ * input injection to proceed.
+ * Returns one of the INPUT_EVENT_INJECTION_XXX constants.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual int32_t injectInputEvent(const InputEvent* event,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) = 0;
+
+ /* Sets the list of input windows.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual void setInputWindows(const Vector<InputWindow>& inputWindows) = 0;
+
+ /* Sets the focused application.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual void setFocusedApplication(const InputApplication* inputApplication) = 0;
+
+ /* Sets the input dispatching mode.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual void setInputDispatchMode(bool enabled, bool frozen) = 0;
+
+ /* Transfers touch focus from the window associated with one channel to the
+ * window associated with the other channel.
+ *
+ * Returns true on success. False if the window did not actually have touch focus.
+ */
+ virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+ const sp<InputChannel>& toChannel) = 0;
+
+ /* Registers or unregister input channels that may be used as targets for input events.
+ * If monitor is true, the channel will receive a copy of all input events.
+ *
+ * These methods may be called on any thread (usually by the input manager).
+ */
+ virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor) = 0;
+ virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0;
+};
+
+/* Dispatches events to input targets. Some functions of the input dispatcher, such as
+ * identifying input targets, are controlled by a separate policy object.
+ *
+ * IMPORTANT INVARIANT:
+ * Because the policy can potentially block or cause re-entrance into the input dispatcher,
+ * the input dispatcher never calls into the policy while holding its internal locks.
+ * The implementation is also carefully designed to recover from scenarios such as an
+ * input channel becoming unregistered while identifying input targets or processing timeouts.
+ *
+ * Methods marked 'Locked' must be called with the lock acquired.
+ *
+ * Methods marked 'LockedInterruptible' must be called with the lock acquired but
+ * may during the course of their execution release the lock, call into the policy, and
+ * then reacquire the lock. The caller is responsible for recovering gracefully.
+ *
+ * A 'LockedInterruptible' method may called a 'Locked' method, but NOT vice-versa.
+ */
+class InputDispatcher : public InputDispatcherInterface {
+protected:
+ virtual ~InputDispatcher();
+
+public:
+ explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy);
+
+ virtual void dump(String8& dump);
+
+ virtual void dispatchOnce();
+
+ virtual void notifyConfigurationChanged(nsecs_t eventTime);
+ virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
+ int32_t scanCode, int32_t metaState, nsecs_t downTime);
+ virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags, int32_t action, int32_t flags,
+ int32_t metaState, int32_t edgeFlags,
+ uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
+ float xPrecision, float yPrecision, nsecs_t downTime);
+ virtual void notifySwitch(nsecs_t when,
+ int32_t switchCode, int32_t switchValue, uint32_t policyFlags) ;
+
+ virtual int32_t injectInputEvent(const InputEvent* event,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis);
+
+ virtual void setInputWindows(const Vector<InputWindow>& inputWindows);
+ virtual void setFocusedApplication(const InputApplication* inputApplication);
+ virtual void setInputDispatchMode(bool enabled, bool frozen);
+
+ virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+ const sp<InputChannel>& toChannel);
+
+ virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor);
+ virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
+
+private:
+ template <typename T>
+ struct Link {
+ T* next;
+ T* prev;
+ };
+
+ struct InjectionState {
+ mutable int32_t refCount;
+
+ int32_t injectorPid;
+ int32_t injectorUid;
+ int32_t injectionResult; // initially INPUT_EVENT_INJECTION_PENDING
+ bool injectionIsAsync; // set to true if injection is not waiting for the result
+ int32_t pendingForegroundDispatches; // the number of foreground dispatches in progress
+ };
+
+ struct EventEntry : Link<EventEntry> {
+ enum {
+ TYPE_SENTINEL,
+ TYPE_CONFIGURATION_CHANGED,
+ TYPE_KEY,
+ TYPE_MOTION
+ };
+
+ mutable int32_t refCount;
+ int32_t type;
+ nsecs_t eventTime;
+ uint32_t policyFlags;
+ InjectionState* injectionState;
+
+ bool dispatchInProgress; // initially false, set to true while dispatching
+
+ inline bool isInjected() { return injectionState != NULL; }
+ };
+
+ struct ConfigurationChangedEntry : EventEntry {
+ };
+
+ struct KeyEntry : EventEntry {
+ int32_t deviceId;
+ uint32_t source;
+ int32_t action;
+ int32_t flags;
+ int32_t keyCode;
+ int32_t scanCode;
+ int32_t metaState;
+ int32_t repeatCount;
+ nsecs_t downTime;
+
+ bool syntheticRepeat; // set to true for synthetic key repeats
+
+ enum InterceptKeyResult {
+ INTERCEPT_KEY_RESULT_UNKNOWN,
+ INTERCEPT_KEY_RESULT_SKIP,
+ INTERCEPT_KEY_RESULT_CONTINUE,
+ };
+ InterceptKeyResult interceptKeyResult; // set based on the interception result
+ };
+
+ struct MotionSample {
+ MotionSample* next;
+
+ nsecs_t eventTime;
+ PointerCoords pointerCoords[MAX_POINTERS];
+ };
+
+ struct MotionEntry : EventEntry {
+ int32_t deviceId;
+ uint32_t source;
+ int32_t action;
+ int32_t flags;
+ int32_t metaState;
+ int32_t edgeFlags;
+ float xPrecision;
+ float yPrecision;
+ nsecs_t downTime;
+ uint32_t pointerCount;
+ int32_t pointerIds[MAX_POINTERS];
+
+ // Linked list of motion samples associated with this motion event.
+ MotionSample firstSample;
+ MotionSample* lastSample;
+
+ uint32_t countSamples() const;
+ };
+
+ // Tracks the progress of dispatching a particular event to a particular connection.
+ struct DispatchEntry : Link<DispatchEntry> {
+ EventEntry* eventEntry; // the event to dispatch
+ int32_t targetFlags;
+ float xOffset;
+ float yOffset;
+
+ // True if dispatch has started.
+ bool inProgress;
+
+ // For motion events:
+ // Pointer to the first motion sample to dispatch in this cycle.
+ // Usually NULL to indicate that the list of motion samples begins at
+ // MotionEntry::firstSample. Otherwise, some samples were dispatched in a previous
+ // cycle and this pointer indicates the location of the first remainining sample
+ // to dispatch during the current cycle.
+ MotionSample* headMotionSample;
+ // Pointer to a motion sample to dispatch in the next cycle if the dispatcher was
+ // unable to send all motion samples during this cycle. On the next cycle,
+ // headMotionSample will be initialized to tailMotionSample and tailMotionSample
+ // will be set to NULL.
+ MotionSample* tailMotionSample;
+
+ inline bool hasForegroundTarget() const {
+ return targetFlags & InputTarget::FLAG_FOREGROUND;
+ }
+
+ inline bool isSplit() const {
+ return targetFlags & InputTarget::FLAG_SPLIT;
+ }
+ };
+
+ // A command entry captures state and behavior for an action to be performed in the
+ // dispatch loop after the initial processing has taken place. It is essentially
+ // a kind of continuation used to postpone sensitive policy interactions to a point
+ // in the dispatch loop where it is safe to release the lock (generally after finishing
+ // the critical parts of the dispatch cycle).
+ //
+ // The special thing about commands is that they can voluntarily release and reacquire
+ // the dispatcher lock at will. Initially when the command starts running, the
+ // dispatcher lock is held. However, if the command needs to call into the policy to
+ // do some work, it can release the lock, do the work, then reacquire the lock again
+ // before returning.
+ //
+ // This mechanism is a bit clunky but it helps to preserve the invariant that the dispatch
+ // never calls into the policy while holding its lock.
+ //
+ // Commands are implicitly 'LockedInterruptible'.
+ struct CommandEntry;
+ typedef void (InputDispatcher::*Command)(CommandEntry* commandEntry);
+
+ class Connection;
+ struct CommandEntry : Link<CommandEntry> {
+ CommandEntry();
+ ~CommandEntry();
+
+ Command command;
+
+ // parameters for the command (usage varies by command)
+ sp<Connection> connection;
+ nsecs_t eventTime;
+ KeyEntry* keyEntry;
+ sp<InputChannel> inputChannel;
+ sp<InputApplicationHandle> inputApplicationHandle;
+ sp<InputWindowHandle> inputWindowHandle;
+ int32_t userActivityEventType;
+ bool handled;
+ };
+
+ // Generic queue implementation.
+ template <typename T>
+ struct Queue {
+ T headSentinel;
+ T tailSentinel;
+
+ inline Queue() {
+ headSentinel.prev = NULL;
+ headSentinel.next = & tailSentinel;
+ tailSentinel.prev = & headSentinel;
+ tailSentinel.next = NULL;
+ }
+
+ inline bool isEmpty() const {
+ return headSentinel.next == & tailSentinel;
+ }
+
+ inline void enqueueAtTail(T* entry) {
+ T* last = tailSentinel.prev;
+ last->next = entry;
+ entry->prev = last;
+ entry->next = & tailSentinel;
+ tailSentinel.prev = entry;
+ }
+
+ inline void enqueueAtHead(T* entry) {
+ T* first = headSentinel.next;
+ headSentinel.next = entry;
+ entry->prev = & headSentinel;
+ entry->next = first;
+ first->prev = entry;
+ }
+
+ inline void dequeue(T* entry) {
+ entry->prev->next = entry->next;
+ entry->next->prev = entry->prev;
+ }
+
+ inline T* dequeueAtHead() {
+ T* first = headSentinel.next;
+ dequeue(first);
+ return first;
+ }
+
+ uint32_t count() const;
+ };
+
+ /* Allocates queue entries and performs reference counting as needed. */
+ class Allocator {
+ public:
+ Allocator();
+
+ InjectionState* obtainInjectionState(int32_t injectorPid, int32_t injectorUid);
+ ConfigurationChangedEntry* obtainConfigurationChangedEntry(nsecs_t eventTime);
+ KeyEntry* obtainKeyEntry(nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action,
+ int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
+ int32_t repeatCount, nsecs_t downTime);
+ MotionEntry* obtainMotionEntry(nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action,
+ int32_t flags, int32_t metaState, int32_t edgeFlags,
+ float xPrecision, float yPrecision,
+ nsecs_t downTime, uint32_t pointerCount,
+ const int32_t* pointerIds, const PointerCoords* pointerCoords);
+ DispatchEntry* obtainDispatchEntry(EventEntry* eventEntry,
+ int32_t targetFlags, float xOffset, float yOffset);
+ CommandEntry* obtainCommandEntry(Command command);
+
+ void releaseInjectionState(InjectionState* injectionState);
+ void releaseEventEntry(EventEntry* entry);
+ void releaseConfigurationChangedEntry(ConfigurationChangedEntry* entry);
+ void releaseKeyEntry(KeyEntry* entry);
+ void releaseMotionEntry(MotionEntry* entry);
+ void releaseDispatchEntry(DispatchEntry* entry);
+ void releaseCommandEntry(CommandEntry* entry);
+
+ void recycleKeyEntry(KeyEntry* entry);
+
+ void appendMotionSample(MotionEntry* motionEntry,
+ nsecs_t eventTime, const PointerCoords* pointerCoords);
+
+ private:
+ Pool<InjectionState> mInjectionStatePool;
+ Pool<ConfigurationChangedEntry> mConfigurationChangeEntryPool;
+ Pool<KeyEntry> mKeyEntryPool;
+ Pool<MotionEntry> mMotionEntryPool;
+ Pool<MotionSample> mMotionSamplePool;
+ Pool<DispatchEntry> mDispatchEntryPool;
+ Pool<CommandEntry> mCommandEntryPool;
+
+ void initializeEventEntry(EventEntry* entry, int32_t type, nsecs_t eventTime,
+ uint32_t policyFlags);
+ void releaseEventEntryInjectionState(EventEntry* entry);
+ };
+
+ /* Tracks dispatched key and motion event state so that cancelation events can be
+ * synthesized when events are dropped. */
+ class InputState {
+ public:
+ // Specifies whether a given event will violate input state consistency.
+ enum Consistency {
+ // The event is consistent with the current input state.
+ CONSISTENT,
+ // The event is inconsistent with the current input state but applications
+ // will tolerate it. eg. Down followed by another down.
+ TOLERABLE,
+ // The event is inconsistent with the current input state and will probably
+ // cause applications to crash. eg. Up without prior down, move with
+ // unexpected number of pointers.
+ BROKEN
+ };
+
+ // Specifies the sources to cancel.
+ enum CancelationOptions {
+ CANCEL_ALL_EVENTS = 0,
+ CANCEL_POINTER_EVENTS = 1,
+ CANCEL_NON_POINTER_EVENTS = 2,
+ CANCEL_FALLBACK_EVENTS = 3,
+ };
+
+ InputState();
+ ~InputState();
+
+ // Returns true if there is no state to be canceled.
+ bool isNeutral() const;
+
+ // Records tracking information for an event that has just been published.
+ // Returns whether the event is consistent with the current input state.
+ Consistency trackEvent(const EventEntry* entry);
+
+ // Records tracking information for a key event that has just been published.
+ // Returns whether the event is consistent with the current input state.
+ Consistency trackKey(const KeyEntry* entry);
+
+ // Records tracking information for a motion event that has just been published.
+ // Returns whether the event is consistent with the current input state.
+ Consistency trackMotion(const MotionEntry* entry);
+
+ // Synthesizes cancelation events for the current state and resets the tracked state.
+ void synthesizeCancelationEvents(nsecs_t currentTime, Allocator* allocator,
+ Vector<EventEntry*>& outEvents, CancelationOptions options);
+
+ // Clears the current state.
+ void clear();
+
+ // Copies pointer-related parts of the input state to another instance.
+ void copyPointerStateTo(InputState& other) const;
+
+ private:
+ struct KeyMemento {
+ int32_t deviceId;
+ uint32_t source;
+ int32_t keyCode;
+ int32_t scanCode;
+ int32_t flags;
+ nsecs_t downTime;
+ };
+
+ struct MotionMemento {
+ int32_t deviceId;
+ uint32_t source;
+ float xPrecision;
+ float yPrecision;
+ nsecs_t downTime;
+ uint32_t pointerCount;
+ int32_t pointerIds[MAX_POINTERS];
+ PointerCoords pointerCoords[MAX_POINTERS];
+
+ void setPointers(const MotionEntry* entry);
+ };
+
+ Vector<KeyMemento> mKeyMementos;
+ Vector<MotionMemento> mMotionMementos;
+
+ static bool shouldCancelKey(const KeyMemento& memento,
+ CancelationOptions options);
+ static bool shouldCancelMotion(const MotionMemento& memento,
+ CancelationOptions options);
+ };
+
+ /* Manages the dispatch state associated with a single input channel. */
+ class Connection : public RefBase {
+ protected:
+ virtual ~Connection();
+
+ public:
+ enum Status {
+ // Everything is peachy.
+ STATUS_NORMAL,
+ // An unrecoverable communication error has occurred.
+ STATUS_BROKEN,
+ // The input channel has been unregistered.
+ STATUS_ZOMBIE
+ };
+
+ Status status;
+ sp<InputChannel> inputChannel; // never null
+ sp<InputWindowHandle> inputWindowHandle; // may be null
+ InputPublisher inputPublisher;
+ InputState inputState;
+ Queue<DispatchEntry> outboundQueue;
+
+ nsecs_t lastEventTime; // the time when the event was originally captured
+ nsecs_t lastDispatchTime; // the time when the last event was dispatched
+
+ explicit Connection(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle);
+
+ inline const char* getInputChannelName() const { return inputChannel->getName().string(); }
+
+ const char* getStatusLabel() const;
+
+ // Finds a DispatchEntry in the outbound queue associated with the specified event.
+ // Returns NULL if not found.
+ DispatchEntry* findQueuedDispatchEntryForEvent(const EventEntry* eventEntry) const;
+
+ // Gets the time since the current event was originally obtained from the input driver.
+ inline double getEventLatencyMillis(nsecs_t currentTime) const {
+ return (currentTime - lastEventTime) / 1000000.0;
+ }
+
+ // Gets the time since the current event entered the outbound dispatch queue.
+ inline double getDispatchLatencyMillis(nsecs_t currentTime) const {
+ return (currentTime - lastDispatchTime) / 1000000.0;
+ }
+
+ status_t initialize();
+ };
+
+ enum DropReason {
+ DROP_REASON_NOT_DROPPED = 0,
+ DROP_REASON_POLICY = 1,
+ DROP_REASON_APP_SWITCH = 2,
+ DROP_REASON_DISABLED = 3,
+ DROP_REASON_BLOCKED = 4,
+ DROP_REASON_STALE = 5,
+ };
+
+ sp<InputDispatcherPolicyInterface> mPolicy;
+
+ Mutex mLock;
+
+ Allocator mAllocator;
+ sp<Looper> mLooper;
+
+ EventEntry* mPendingEvent;
+ Queue<EventEntry> mInboundQueue;
+ Queue<CommandEntry> mCommandQueue;
+
+ Vector<EventEntry*> mTempCancelationEvents;
+
+ void dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout, nsecs_t keyRepeatDelay,
+ nsecs_t* nextWakeupTime);
+
+ // Enqueues an inbound event. Returns true if mLooper->wake() should be called.
+ bool enqueueInboundEventLocked(EventEntry* entry);
+
+ // Cleans up input state when dropping an inbound event.
+ void dropInboundEventLocked(EventEntry* entry, DropReason dropReason);
+
+ // App switch latency optimization.
+ bool mAppSwitchSawKeyDown;
+ nsecs_t mAppSwitchDueTime;
+
+ static bool isAppSwitchKeyCode(int32_t keyCode);
+ bool isAppSwitchKeyEventLocked(KeyEntry* keyEntry);
+ bool isAppSwitchPendingLocked();
+ void resetPendingAppSwitchLocked(bool handled);
+
+ // Stale event latency optimization.
+ static bool isStaleEventLocked(nsecs_t currentTime, EventEntry* entry);
+
+ // Blocked event latency optimization. Drops old events when the user intends
+ // to transfer focus to a new application.
+ EventEntry* mNextUnblockedEvent;
+
+ const InputWindow* findTouchedWindowAtLocked(int32_t x, int32_t y);
+
+ // All registered connections mapped by receive pipe file descriptor.
+ KeyedVector<int, sp<Connection> > mConnectionsByReceiveFd;
+
+ ssize_t getConnectionIndexLocked(const sp<InputChannel>& inputChannel);
+
+ // Active connections are connections that have a non-empty outbound queue.
+ // We don't use a ref-counted pointer here because we explicitly abort connections
+ // during unregistration which causes the connection's outbound queue to be cleared
+ // and the connection itself to be deactivated.
+ Vector<Connection*> mActiveConnections;
+
+ // Input channels that will receive a copy of all input events.
+ Vector<sp<InputChannel> > mMonitoringChannels;
+
+ // Event injection and synchronization.
+ Condition mInjectionResultAvailableCondition;
+ bool hasInjectionPermission(int32_t injectorPid, int32_t injectorUid);
+ void setInjectionResultLocked(EventEntry* entry, int32_t injectionResult);
+
+ Condition mInjectionSyncFinishedCondition;
+ void incrementPendingForegroundDispatchesLocked(EventEntry* entry);
+ void decrementPendingForegroundDispatchesLocked(EventEntry* entry);
+
+ // Throttling state.
+ struct ThrottleState {
+ nsecs_t minTimeBetweenEvents;
+
+ nsecs_t lastEventTime;
+ int32_t lastDeviceId;
+ uint32_t lastSource;
+
+ uint32_t originalSampleCount; // only collected during debugging
+ } mThrottleState;
+
+ // Key repeat tracking.
+ struct KeyRepeatState {
+ KeyEntry* lastKeyEntry; // or null if no repeat
+ nsecs_t nextRepeatTime;
+ } mKeyRepeatState;
+
+ void resetKeyRepeatLocked();
+ KeyEntry* synthesizeKeyRepeatLocked(nsecs_t currentTime, nsecs_t keyRepeatTimeout);
+
+ // Deferred command processing.
+ bool runCommandsLockedInterruptible();
+ CommandEntry* postCommandLocked(Command command);
+
+ // Inbound event processing.
+ void drainInboundQueueLocked();
+ void releasePendingEventLocked();
+ void releaseInboundEventLocked(EventEntry* entry);
+
+ // Dispatch state.
+ bool mDispatchEnabled;
+ bool mDispatchFrozen;
+
+ Vector<InputWindow> mWindows;
+
+ const InputWindow* getWindowLocked(const sp<InputChannel>& inputChannel);
+
+ // Focus tracking for keys, trackball, etc.
+ const InputWindow* mFocusedWindow;
+
+ // Focus tracking for touch.
+ struct TouchedWindow {
+ const InputWindow* window;
+ int32_t targetFlags;
+ BitSet32 pointerIds; // zero unless target flag FLAG_SPLIT is set
+ sp<InputChannel> channel;
+ };
+ struct TouchState {
+ bool down;
+ bool split;
+ int32_t deviceId; // id of the device that is currently down, others are rejected
+ uint32_t source; // source of the device that is current down, others are rejected
+ Vector<TouchedWindow> windows;
+
+ TouchState();
+ ~TouchState();
+ void reset();
+ void copyFrom(const TouchState& other);
+ void addOrUpdateWindow(const InputWindow* window, int32_t targetFlags, BitSet32 pointerIds);
+ void removeOutsideTouchWindows();
+ const InputWindow* getFirstForegroundWindow();
+ };
+
+ TouchState mTouchState;
+ TouchState mTempTouchState;
+
+ // Focused application.
+ InputApplication* mFocusedApplication;
+ InputApplication mFocusedApplicationStorage; // preallocated storage for mFocusedApplication
+ void releaseFocusedApplicationLocked();
+
+ // Dispatch inbound events.
+ bool dispatchConfigurationChangedLocked(
+ nsecs_t currentTime, ConfigurationChangedEntry* entry);
+ bool dispatchKeyLocked(
+ nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout,
+ DropReason* dropReason, nsecs_t* nextWakeupTime);
+ bool dispatchMotionLocked(
+ nsecs_t currentTime, MotionEntry* entry,
+ DropReason* dropReason, nsecs_t* nextWakeupTime);
+ void dispatchEventToCurrentInputTargetsLocked(
+ nsecs_t currentTime, EventEntry* entry, bool resumeWithAppendedMotionSample);
+
+ void logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry);
+ void logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry);
+
+ // The input targets that were most recently identified for dispatch.
+ bool mCurrentInputTargetsValid; // false while targets are being recomputed
+ Vector<InputTarget> mCurrentInputTargets;
+
+ enum InputTargetWaitCause {
+ INPUT_TARGET_WAIT_CAUSE_NONE,
+ INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY,
+ INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY,
+ };
+
+ InputTargetWaitCause mInputTargetWaitCause;
+ nsecs_t mInputTargetWaitStartTime;
+ nsecs_t mInputTargetWaitTimeoutTime;
+ bool mInputTargetWaitTimeoutExpired;
+ sp<InputApplicationHandle> mInputTargetWaitApplication;
+
+ // Finding targets for input events.
+ void resetTargetsLocked();
+ void commitTargetsLocked();
+ int32_t handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry* entry,
+ const InputApplication* application, const InputWindow* window,
+ nsecs_t* nextWakeupTime);
+ void resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout,
+ const sp<InputChannel>& inputChannel);
+ nsecs_t getTimeSpentWaitingForApplicationLocked(nsecs_t currentTime);
+ void resetANRTimeoutsLocked();
+
+ int32_t findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry* entry,
+ nsecs_t* nextWakeupTime);
+ int32_t findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry* entry,
+ nsecs_t* nextWakeupTime);
+
+ void addWindowTargetLocked(const InputWindow* window, int32_t targetFlags,
+ BitSet32 pointerIds);
+ void addMonitoringTargetsLocked();
+ void pokeUserActivityLocked(const EventEntry* eventEntry);
+ bool checkInjectionPermission(const InputWindow* window, const InjectionState* injectionState);
+ bool isWindowObscuredAtPointLocked(const InputWindow* window, int32_t x, int32_t y) const;
+ bool isWindowFinishedWithPreviousInputLocked(const InputWindow* window);
+ String8 getApplicationWindowLabelLocked(const InputApplication* application,
+ const InputWindow* window);
+
+ // Manage the dispatch cycle for a single connection.
+ // These methods are deliberately not Interruptible because doing all of the work
+ // with the mutex held makes it easier to ensure that connection invariants are maintained.
+ // If needed, the methods post commands to run later once the critical bits are done.
+ void prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+ EventEntry* eventEntry, const InputTarget* inputTarget,
+ bool resumeWithAppendedMotionSample);
+ void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
+ void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+ bool handled);
+ void startNextDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
+ void abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
+ void drainOutboundQueueLocked(Connection* connection);
+ static int handleReceiveCallback(int receiveFd, int events, void* data);
+
+ void synthesizeCancelationEventsForAllConnectionsLocked(
+ InputState::CancelationOptions options, const char* reason);
+ void synthesizeCancelationEventsForInputChannelLocked(const sp<InputChannel>& channel,
+ InputState::CancelationOptions options, const char* reason);
+ void synthesizeCancelationEventsForConnectionLocked(const sp<Connection>& connection,
+ InputState::CancelationOptions options, const char* reason);
+
+ // Splitting motion events across windows.
+ MotionEntry* splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds);
+
+ // Reset and drop everything the dispatcher is doing.
+ void resetAndDropEverythingLocked(const char* reason);
+
+ // Dump state.
+ void dumpDispatchStateLocked(String8& dump);
+ void logDispatchStateLocked();
+
+ // Add or remove a connection to the mActiveConnections vector.
+ void activateConnectionLocked(Connection* connection);
+ void deactivateConnectionLocked(Connection* connection);
+
+ // Interesting events that we might like to log or tell the framework about.
+ void onDispatchCycleStartedLocked(
+ nsecs_t currentTime, const sp<Connection>& connection);
+ void onDispatchCycleFinishedLocked(
+ nsecs_t currentTime, const sp<Connection>& connection, bool handled);
+ void onDispatchCycleBrokenLocked(
+ nsecs_t currentTime, const sp<Connection>& connection);
+ void onANRLocked(
+ nsecs_t currentTime, const InputApplication* application, const InputWindow* window,
+ nsecs_t eventTime, nsecs_t waitStartTime);
+
+ // Outbound policy interactions.
+ void doNotifyConfigurationChangedInterruptible(CommandEntry* commandEntry);
+ void doNotifyInputChannelBrokenLockedInterruptible(CommandEntry* commandEntry);
+ void doNotifyANRLockedInterruptible(CommandEntry* commandEntry);
+ void doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry);
+ void doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry);
+ void doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry);
+ void initializeKeyEvent(KeyEvent* event, const KeyEntry* entry);
+
+ // Statistics gathering.
+ void updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
+ int32_t injectionResult, nsecs_t timeSpentWaitingForApplication);
+};
+
+/* Enqueues and dispatches input events, endlessly. */
+class InputDispatcherThread : public Thread {
+public:
+ explicit InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher);
+ ~InputDispatcherThread();
+
+private:
+ virtual bool threadLoop();
+
+ sp<InputDispatcherInterface> mDispatcher;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_DISPATCHER_H
diff --git a/services/input/InputManager.cpp b/services/input/InputManager.cpp
new file mode 100644
index 0000000..5dfa5d5
--- /dev/null
+++ b/services/input/InputManager.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputManager"
+
+//#define LOG_NDEBUG 0
+
+#include "InputManager.h"
+
+#include <cutils/log.h>
+
+namespace android {
+
+InputManager::InputManager(
+ const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& readerPolicy,
+ const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
+ mDispatcher = new InputDispatcher(dispatcherPolicy);
+ mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
+ initialize();
+}
+
+InputManager::InputManager(
+ const sp<InputReaderInterface>& reader,
+ const sp<InputDispatcherInterface>& dispatcher) :
+ mReader(reader),
+ mDispatcher(dispatcher) {
+ initialize();
+}
+
+InputManager::~InputManager() {
+ stop();
+}
+
+void InputManager::initialize() {
+ mReaderThread = new InputReaderThread(mReader);
+ mDispatcherThread = new InputDispatcherThread(mDispatcher);
+}
+
+status_t InputManager::start() {
+ status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
+ if (result) {
+ LOGE("Could not start InputDispatcher thread due to error %d.", result);
+ return result;
+ }
+
+ result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
+ if (result) {
+ LOGE("Could not start InputReader thread due to error %d.", result);
+
+ mDispatcherThread->requestExit();
+ return result;
+ }
+
+ return OK;
+}
+
+status_t InputManager::stop() {
+ status_t result = mReaderThread->requestExitAndWait();
+ if (result) {
+ LOGW("Could not stop InputReader thread due to error %d.", result);
+ }
+
+ result = mDispatcherThread->requestExitAndWait();
+ if (result) {
+ LOGW("Could not stop InputDispatcher thread due to error %d.", result);
+ }
+
+ return OK;
+}
+
+sp<InputReaderInterface> InputManager::getReader() {
+ return mReader;
+}
+
+sp<InputDispatcherInterface> InputManager::getDispatcher() {
+ return mDispatcher;
+}
+
+} // namespace android
diff --git a/services/input/InputManager.h b/services/input/InputManager.h
new file mode 100644
index 0000000..df4d299
--- /dev/null
+++ b/services/input/InputManager.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_MANAGER_H
+#define _UI_INPUT_MANAGER_H
+
+/**
+ * Native input manager.
+ */
+
+#include "EventHub.h"
+#include "InputReader.h"
+#include "InputDispatcher.h"
+
+#include <ui/Input.h>
+#include <ui/InputTransport.h>
+#include <utils/Errors.h>
+#include <utils/Vector.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+namespace android {
+
+/*
+ * The input manager is the core of the system event processing.
+ *
+ * The input manager uses two threads.
+ *
+ * 1. The InputReaderThread (called "InputReader") reads and preprocesses raw input events,
+ * applies policy, and posts messages to a queue managed by the DispatcherThread.
+ * 2. The InputDispatcherThread (called "InputDispatcher") thread waits for new events on the
+ * queue and asynchronously dispatches them to applications.
+ *
+ * By design, the InputReaderThread class and InputDispatcherThread class do not share any
+ * internal state. Moreover, all communication is done one way from the InputReaderThread
+ * into the InputDispatcherThread and never the reverse. Both classes may interact with the
+ * InputDispatchPolicy, however.
+ *
+ * The InputManager class never makes any calls into Java itself. Instead, the
+ * InputDispatchPolicy is responsible for performing all external interactions with the
+ * system, including calling DVM services.
+ */
+class InputManagerInterface : public virtual RefBase {
+protected:
+ InputManagerInterface() { }
+ virtual ~InputManagerInterface() { }
+
+public:
+ /* Starts the input manager threads. */
+ virtual status_t start() = 0;
+
+ /* Stops the input manager threads and waits for them to exit. */
+ virtual status_t stop() = 0;
+
+ /* Gets the input reader. */
+ virtual sp<InputReaderInterface> getReader() = 0;
+
+ /* Gets the input dispatcher. */
+ virtual sp<InputDispatcherInterface> getDispatcher() = 0;
+};
+
+class InputManager : public InputManagerInterface {
+protected:
+ virtual ~InputManager();
+
+public:
+ InputManager(
+ const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& readerPolicy,
+ const sp<InputDispatcherPolicyInterface>& dispatcherPolicy);
+
+ // (used for testing purposes)
+ InputManager(
+ const sp<InputReaderInterface>& reader,
+ const sp<InputDispatcherInterface>& dispatcher);
+
+ virtual status_t start();
+ virtual status_t stop();
+
+ virtual sp<InputReaderInterface> getReader();
+ virtual sp<InputDispatcherInterface> getDispatcher();
+
+private:
+ sp<InputReaderInterface> mReader;
+ sp<InputReaderThread> mReaderThread;
+
+ sp<InputDispatcherInterface> mDispatcher;
+ sp<InputDispatcherThread> mDispatcherThread;
+
+ void initialize();
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_MANAGER_H
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
new file mode 100644
index 0000000..46d374d
--- /dev/null
+++ b/services/input/InputReader.cpp
@@ -0,0 +1,3708 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputReader"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages for each raw event received from the EventHub.
+#define DEBUG_RAW_EVENTS 0
+
+// Log debug messages about touch screen filtering hacks.
+#define DEBUG_HACKS 0
+
+// Log debug messages about virtual key processing.
+#define DEBUG_VIRTUAL_KEYS 0
+
+// Log debug messages about pointers.
+#define DEBUG_POINTERS 0
+
+// Log debug messages about pointer assignment calculations.
+#define DEBUG_POINTER_ASSIGNMENT 0
+
+#include "InputReader.h"
+
+#include <cutils/log.h>
+#include <ui/Keyboard.h>
+#include <ui/VirtualKeyMap.h>
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <math.h>
+
+#define INDENT " "
+#define INDENT2 " "
+#define INDENT3 " "
+#define INDENT4 " "
+
+namespace android {
+
+// --- Static Functions ---
+
+template<typename T>
+inline static T abs(const T& value) {
+ return value < 0 ? - value : value;
+}
+
+template<typename T>
+inline static T min(const T& a, const T& b) {
+ return a < b ? a : b;
+}
+
+template<typename T>
+inline static void swap(T& a, T& b) {
+ T temp = a;
+ a = b;
+ b = temp;
+}
+
+inline static float avg(float x, float y) {
+ return (x + y) / 2;
+}
+
+inline static float pythag(float x, float y) {
+ return sqrtf(x * x + y * y);
+}
+
+inline static int32_t signExtendNybble(int32_t value) {
+ return value >= 8 ? value - 16 : value;
+}
+
+static inline const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
+static const int32_t keyCodeRotationMap[][4] = {
+ // key codes enumerated counter-clockwise with the original (unrotated) key first
+ // no rotation, 90 degree rotation, 180 degree rotation, 270 degree rotation
+ { AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT },
+ { AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN },
+ { AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT },
+ { AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP },
+};
+static const int keyCodeRotationMapSize =
+ sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]);
+
+int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) {
+ if (orientation != DISPLAY_ORIENTATION_0) {
+ for (int i = 0; i < keyCodeRotationMapSize; i++) {
+ if (keyCode == keyCodeRotationMap[i][0]) {
+ return keyCodeRotationMap[i][orientation];
+ }
+ }
+ }
+ return keyCode;
+}
+
+static inline bool sourcesMatchMask(uint32_t sources, uint32_t sourceMask) {
+ return (sources & sourceMask & ~ AINPUT_SOURCE_CLASS_MASK) != 0;
+}
+
+
+// --- InputReader ---
+
+InputReader::InputReader(const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& policy,
+ const sp<InputDispatcherInterface>& dispatcher) :
+ mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher),
+ mGlobalMetaState(0), mDisableVirtualKeysTimeout(-1) {
+ configureExcludedDevices();
+ updateGlobalMetaState();
+ updateInputConfiguration();
+}
+
+InputReader::~InputReader() {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ delete mDevices.valueAt(i);
+ }
+}
+
+void InputReader::loopOnce() {
+ RawEvent rawEvent;
+ mEventHub->getEvent(& rawEvent);
+
+#if DEBUG_RAW_EVENTS
+ LOGD("Input event: device=%d type=0x%x scancode=%d keycode=%d value=%d",
+ rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode,
+ rawEvent.value);
+#endif
+
+ process(& rawEvent);
+}
+
+void InputReader::process(const RawEvent* rawEvent) {
+ switch (rawEvent->type) {
+ case EventHubInterface::DEVICE_ADDED:
+ addDevice(rawEvent->deviceId);
+ break;
+
+ case EventHubInterface::DEVICE_REMOVED:
+ removeDevice(rawEvent->deviceId);
+ break;
+
+ case EventHubInterface::FINISHED_DEVICE_SCAN:
+ handleConfigurationChanged(rawEvent->when);
+ break;
+
+ default:
+ consumeEvent(rawEvent);
+ break;
+ }
+}
+
+void InputReader::addDevice(int32_t deviceId) {
+ String8 name = mEventHub->getDeviceName(deviceId);
+ uint32_t classes = mEventHub->getDeviceClasses(deviceId);
+
+ InputDevice* device = createDevice(deviceId, name, classes);
+ device->configure();
+
+ if (device->isIgnored()) {
+ LOGI("Device added: id=%d, name='%s' (ignored non-input device)", deviceId, name.string());
+ } else {
+ LOGI("Device added: id=%d, name='%s', sources=0x%08x", deviceId, name.string(),
+ device->getSources());
+ }
+
+ bool added = false;
+ { // acquire device registry writer lock
+ RWLock::AutoWLock _wl(mDeviceRegistryLock);
+
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex < 0) {
+ mDevices.add(deviceId, device);
+ added = true;
+ }
+ } // release device registry writer lock
+
+ if (! added) {
+ LOGW("Ignoring spurious device added event for deviceId %d.", deviceId);
+ delete device;
+ return;
+ }
+}
+
+void InputReader::removeDevice(int32_t deviceId) {
+ bool removed = false;
+ InputDevice* device = NULL;
+ { // acquire device registry writer lock
+ RWLock::AutoWLock _wl(mDeviceRegistryLock);
+
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ device = mDevices.valueAt(deviceIndex);
+ mDevices.removeItemsAt(deviceIndex, 1);
+ removed = true;
+ }
+ } // release device registry writer lock
+
+ if (! removed) {
+ LOGW("Ignoring spurious device removed event for deviceId %d.", deviceId);
+ return;
+ }
+
+ if (device->isIgnored()) {
+ LOGI("Device removed: id=%d, name='%s' (ignored non-input device)",
+ device->getId(), device->getName().string());
+ } else {
+ LOGI("Device removed: id=%d, name='%s', sources=0x%08x",
+ device->getId(), device->getName().string(), device->getSources());
+ }
+
+ device->reset();
+
+ delete device;
+}
+
+InputDevice* InputReader::createDevice(int32_t deviceId, const String8& name, uint32_t classes) {
+ InputDevice* device = new InputDevice(this, deviceId, name);
+
+ // Switch-like devices.
+ if (classes & INPUT_DEVICE_CLASS_SWITCH) {
+ device->addMapper(new SwitchInputMapper(device));
+ }
+
+ // Keyboard-like devices.
+ uint32_t keyboardSources = 0;
+ int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
+ if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {
+ keyboardSources |= AINPUT_SOURCE_KEYBOARD;
+ }
+ if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) {
+ keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;
+ }
+ if (classes & INPUT_DEVICE_CLASS_DPAD) {
+ keyboardSources |= AINPUT_SOURCE_DPAD;
+ }
+
+ if (keyboardSources != 0) {
+ device->addMapper(new KeyboardInputMapper(device, keyboardSources, keyboardType));
+ }
+
+ // Cursor-like devices.
+ if (classes & INPUT_DEVICE_CLASS_CURSOR) {
+ device->addMapper(new CursorInputMapper(device));
+ }
+
+ // Touchscreens and touchpad devices.
+ if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) {
+ device->addMapper(new MultiTouchInputMapper(device));
+ } else if (classes & INPUT_DEVICE_CLASS_TOUCH) {
+ device->addMapper(new SingleTouchInputMapper(device));
+ }
+
+ return device;
+}
+
+void InputReader::consumeEvent(const RawEvent* rawEvent) {
+ int32_t deviceId = rawEvent->deviceId;
+
+ { // acquire device registry reader lock
+ RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex < 0) {
+ LOGW("Discarding event for unknown deviceId %d.", deviceId);
+ return;
+ }
+
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ if (device->isIgnored()) {
+ //LOGD("Discarding event for ignored deviceId %d.", deviceId);
+ return;
+ }
+
+ device->process(rawEvent);
+ } // release device registry reader lock
+}
+
+void InputReader::handleConfigurationChanged(nsecs_t when) {
+ // Reset global meta state because it depends on the list of all configured devices.
+ updateGlobalMetaState();
+
+ // Update input configuration.
+ updateInputConfiguration();
+
+ // Enqueue configuration changed.
+ mDispatcher->notifyConfigurationChanged(when);
+}
+
+void InputReader::configureExcludedDevices() {
+ Vector<String8> excludedDeviceNames;
+ mPolicy->getExcludedDeviceNames(excludedDeviceNames);
+
+ for (size_t i = 0; i < excludedDeviceNames.size(); i++) {
+ mEventHub->addExcludedDevice(excludedDeviceNames[i]);
+ }
+}
+
+void InputReader::updateGlobalMetaState() {
+ { // acquire state lock
+ AutoMutex _l(mStateLock);
+
+ mGlobalMetaState = 0;
+
+ { // acquire device registry reader lock
+ RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ mGlobalMetaState |= device->getMetaState();
+ }
+ } // release device registry reader lock
+ } // release state lock
+}
+
+int32_t InputReader::getGlobalMetaState() {
+ { // acquire state lock
+ AutoMutex _l(mStateLock);
+
+ return mGlobalMetaState;
+ } // release state lock
+}
+
+void InputReader::updateInputConfiguration() {
+ { // acquire state lock
+ AutoMutex _l(mStateLock);
+
+ int32_t touchScreenConfig = InputConfiguration::TOUCHSCREEN_NOTOUCH;
+ int32_t keyboardConfig = InputConfiguration::KEYBOARD_NOKEYS;
+ int32_t navigationConfig = InputConfiguration::NAVIGATION_NONAV;
+ { // acquire device registry reader lock
+ RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+ InputDeviceInfo deviceInfo;
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ device->getDeviceInfo(& deviceInfo);
+ uint32_t sources = deviceInfo.getSources();
+
+ if ((sources & AINPUT_SOURCE_TOUCHSCREEN) == AINPUT_SOURCE_TOUCHSCREEN) {
+ touchScreenConfig = InputConfiguration::TOUCHSCREEN_FINGER;
+ }
+ if ((sources & AINPUT_SOURCE_TRACKBALL) == AINPUT_SOURCE_TRACKBALL) {
+ navigationConfig = InputConfiguration::NAVIGATION_TRACKBALL;
+ } else if ((sources & AINPUT_SOURCE_DPAD) == AINPUT_SOURCE_DPAD) {
+ navigationConfig = InputConfiguration::NAVIGATION_DPAD;
+ }
+ if (deviceInfo.getKeyboardType() == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
+ keyboardConfig = InputConfiguration::KEYBOARD_QWERTY;
+ }
+ }
+ } // release device registry reader lock
+
+ mInputConfiguration.touchScreen = touchScreenConfig;
+ mInputConfiguration.keyboard = keyboardConfig;
+ mInputConfiguration.navigation = navigationConfig;
+ } // release state lock
+}
+
+void InputReader::disableVirtualKeysUntil(nsecs_t time) {
+ mDisableVirtualKeysTimeout = time;
+}
+
+bool InputReader::shouldDropVirtualKey(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode) {
+ if (now < mDisableVirtualKeysTimeout) {
+ LOGI("Dropping virtual key from device %s because virtual keys are "
+ "temporarily disabled for the next %0.3fms. keyCode=%d, scanCode=%d",
+ device->getName().string(),
+ (mDisableVirtualKeysTimeout - now) * 0.000001,
+ keyCode, scanCode);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void InputReader::getInputConfiguration(InputConfiguration* outConfiguration) {
+ { // acquire state lock
+ AutoMutex _l(mStateLock);
+
+ *outConfiguration = mInputConfiguration;
+ } // release state lock
+}
+
+status_t InputReader::getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) {
+ { // acquire device registry reader lock
+ RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex < 0) {
+ return NAME_NOT_FOUND;
+ }
+
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ if (device->isIgnored()) {
+ return NAME_NOT_FOUND;
+ }
+
+ device->getDeviceInfo(outDeviceInfo);
+ return OK;
+ } // release device registy reader lock
+}
+
+void InputReader::getInputDeviceIds(Vector<int32_t>& outDeviceIds) {
+ outDeviceIds.clear();
+
+ { // acquire device registry reader lock
+ RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+ size_t numDevices = mDevices.size();
+ for (size_t i = 0; i < numDevices; i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ if (! device->isIgnored()) {
+ outDeviceIds.add(device->getId());
+ }
+ }
+ } // release device registy reader lock
+}
+
+int32_t InputReader::getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t keyCode) {
+ return getState(deviceId, sourceMask, keyCode, & InputDevice::getKeyCodeState);
+}
+
+int32_t InputReader::getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t scanCode) {
+ return getState(deviceId, sourceMask, scanCode, & InputDevice::getScanCodeState);
+}
+
+int32_t InputReader::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t switchCode) {
+ return getState(deviceId, sourceMask, switchCode, & InputDevice::getSwitchState);
+}
+
+int32_t InputReader::getState(int32_t deviceId, uint32_t sourceMask, int32_t code,
+ GetStateFunc getStateFunc) {
+ { // acquire device registry reader lock
+ RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+ int32_t result = AKEY_STATE_UNKNOWN;
+ if (deviceId >= 0) {
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+ result = (device->*getStateFunc)(sourceMask, code);
+ }
+ }
+ } else {
+ size_t numDevices = mDevices.size();
+ for (size_t i = 0; i < numDevices; i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+ result = (device->*getStateFunc)(sourceMask, code);
+ if (result >= AKEY_STATE_DOWN) {
+ return result;
+ }
+ }
+ }
+ }
+ return result;
+ } // release device registy reader lock
+}
+
+bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask,
+ size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) {
+ memset(outFlags, 0, numCodes);
+ return markSupportedKeyCodes(deviceId, sourceMask, numCodes, keyCodes, outFlags);
+}
+
+bool InputReader::markSupportedKeyCodes(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ { // acquire device registry reader lock
+ RWLock::AutoRLock _rl(mDeviceRegistryLock);
+ bool result = false;
+ if (deviceId >= 0) {
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+ result = device->markSupportedKeyCodes(sourceMask,
+ numCodes, keyCodes, outFlags);
+ }
+ }
+ } else {
+ size_t numDevices = mDevices.size();
+ for (size_t i = 0; i < numDevices; i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+ result |= device->markSupportedKeyCodes(sourceMask,
+ numCodes, keyCodes, outFlags);
+ }
+ }
+ }
+ return result;
+ } // release device registy reader lock
+}
+
+void InputReader::dump(String8& dump) {
+ mEventHub->dump(dump);
+ dump.append("\n");
+
+ dump.append("Input Reader State:\n");
+
+ { // acquire device registry reader lock
+ RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ mDevices.valueAt(i)->dump(dump);
+ }
+ } // release device registy reader lock
+}
+
+
+// --- InputReaderThread ---
+
+InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :
+ Thread(/*canCallJava*/ true), mReader(reader) {
+}
+
+InputReaderThread::~InputReaderThread() {
+}
+
+bool InputReaderThread::threadLoop() {
+ mReader->loopOnce();
+ return true;
+}
+
+
+// --- InputDevice ---
+
+InputDevice::InputDevice(InputReaderContext* context, int32_t id, const String8& name) :
+ mContext(context), mId(id), mName(name), mSources(0) {
+}
+
+InputDevice::~InputDevice() {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ delete mMappers[i];
+ }
+ mMappers.clear();
+}
+
+static void dumpMotionRange(String8& dump, const InputDeviceInfo& deviceInfo,
+ int32_t rangeType, const char* name) {
+ const InputDeviceInfo::MotionRange* range = deviceInfo.getMotionRange(rangeType);
+ if (range) {
+ dump.appendFormat(INDENT3 "%s: min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f\n",
+ name, range->min, range->max, range->flat, range->fuzz);
+ }
+}
+
+void InputDevice::dump(String8& dump) {
+ InputDeviceInfo deviceInfo;
+ getDeviceInfo(& deviceInfo);
+
+ dump.appendFormat(INDENT "Device %d: %s\n", deviceInfo.getId(),
+ deviceInfo.getName().string());
+ dump.appendFormat(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources());
+ dump.appendFormat(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType());
+ if (!deviceInfo.getMotionRanges().isEmpty()) {
+ dump.append(INDENT2 "Motion Ranges:\n");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_X, "X");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_Y, "Y");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_PRESSURE, "Pressure");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_SIZE, "Size");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOUCH_MAJOR, "TouchMajor");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOUCH_MINOR, "TouchMinor");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOOL_MAJOR, "ToolMajor");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_TOOL_MINOR, "ToolMinor");
+ dumpMotionRange(dump, deviceInfo, AINPUT_MOTION_RANGE_ORIENTATION, "Orientation");
+ }
+
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->dump(dump);
+ }
+}
+
+void InputDevice::addMapper(InputMapper* mapper) {
+ mMappers.add(mapper);
+}
+
+void InputDevice::configure() {
+ if (! isIgnored()) {
+ mContext->getEventHub()->getConfiguration(mId, &mConfiguration);
+ }
+
+ mSources = 0;
+
+ for (size_t i = 0; i < mMappers.size(); i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->configure();
+
+ uint32_t sources = mapper->getSources();
+ if (sources) {
+ mSources |= sources;
+ } else {
+ // The input mapper does not provide any sources. Remove it from the list.
+ mMappers.removeAt(i);
+ delete mapper;
+ i -= 1;
+ }
+ }
+}
+
+void InputDevice::reset() {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->reset();
+ }
+}
+
+void InputDevice::process(const RawEvent* rawEvent) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->process(rawEvent);
+ }
+}
+
+void InputDevice::getDeviceInfo(InputDeviceInfo* outDeviceInfo) {
+ outDeviceInfo->initialize(mId, mName);
+
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->populateDeviceInfo(outDeviceInfo);
+ }
+}
+
+int32_t InputDevice::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ return getState(sourceMask, keyCode, & InputMapper::getKeyCodeState);
+}
+
+int32_t InputDevice::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ return getState(sourceMask, scanCode, & InputMapper::getScanCodeState);
+}
+
+int32_t InputDevice::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+ return getState(sourceMask, switchCode, & InputMapper::getSwitchState);
+}
+
+int32_t InputDevice::getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc) {
+ int32_t result = AKEY_STATE_UNKNOWN;
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ if (sourcesMatchMask(mapper->getSources(), sourceMask)) {
+ result = (mapper->*getStateFunc)(sourceMask, code);
+ if (result >= AKEY_STATE_DOWN) {
+ return result;
+ }
+ }
+ }
+ return result;
+}
+
+bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ bool result = false;
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ if (sourcesMatchMask(mapper->getSources(), sourceMask)) {
+ result |= mapper->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags);
+ }
+ }
+ return result;
+}
+
+int32_t InputDevice::getMetaState() {
+ int32_t result = 0;
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ result |= mapper->getMetaState();
+ }
+ return result;
+}
+
+
+// --- InputMapper ---
+
+InputMapper::InputMapper(InputDevice* device) :
+ mDevice(device), mContext(device->getContext()) {
+}
+
+InputMapper::~InputMapper() {
+}
+
+void InputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ info->addSource(getSources());
+}
+
+void InputMapper::dump(String8& dump) {
+}
+
+void InputMapper::configure() {
+}
+
+void InputMapper::reset() {
+}
+
+int32_t InputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t InputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t InputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+ return AKEY_STATE_UNKNOWN;
+}
+
+bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ return false;
+}
+
+int32_t InputMapper::getMetaState() {
+ return 0;
+}
+
+
+// --- SwitchInputMapper ---
+
+SwitchInputMapper::SwitchInputMapper(InputDevice* device) :
+ InputMapper(device) {
+}
+
+SwitchInputMapper::~SwitchInputMapper() {
+}
+
+uint32_t SwitchInputMapper::getSources() {
+ return AINPUT_SOURCE_SWITCH;
+}
+
+void SwitchInputMapper::process(const RawEvent* rawEvent) {
+ switch (rawEvent->type) {
+ case EV_SW:
+ processSwitch(rawEvent->when, rawEvent->scanCode, rawEvent->value);
+ break;
+ }
+}
+
+void SwitchInputMapper::processSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue) {
+ getDispatcher()->notifySwitch(when, switchCode, switchValue, 0);
+}
+
+int32_t SwitchInputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+ return getEventHub()->getSwitchState(getDeviceId(), switchCode);
+}
+
+
+// --- KeyboardInputMapper ---
+
+KeyboardInputMapper::KeyboardInputMapper(InputDevice* device,
+ uint32_t sources, int32_t keyboardType) :
+ InputMapper(device), mSources(sources),
+ mKeyboardType(keyboardType) {
+ initializeLocked();
+}
+
+KeyboardInputMapper::~KeyboardInputMapper() {
+}
+
+void KeyboardInputMapper::initializeLocked() {
+ mLocked.metaState = AMETA_NONE;
+ mLocked.downTime = 0;
+}
+
+uint32_t KeyboardInputMapper::getSources() {
+ return mSources;
+}
+
+void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ info->setKeyboardType(mKeyboardType);
+}
+
+void KeyboardInputMapper::dump(String8& dump) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+ dump.append(INDENT2 "Keyboard Input Mapper:\n");
+ dumpParameters(dump);
+ dump.appendFormat(INDENT3 "KeyboardType: %d\n", mKeyboardType);
+ dump.appendFormat(INDENT3 "KeyDowns: %d keys currently down\n", mLocked.keyDowns.size());
+ dump.appendFormat(INDENT3 "MetaState: 0x%0x\n", mLocked.metaState);
+ dump.appendFormat(INDENT3 "DownTime: %lld\n", mLocked.downTime);
+ } // release lock
+}
+
+
+void KeyboardInputMapper::configure() {
+ InputMapper::configure();
+
+ // Configure basic parameters.
+ configureParameters();
+
+ // Reset LEDs.
+ {
+ AutoMutex _l(mLock);
+ resetLedStateLocked();
+ }
+}
+
+void KeyboardInputMapper::configureParameters() {
+ mParameters.orientationAware = false;
+ getDevice()->getConfiguration().tryGetProperty(String8("keyboard.orientationAware"),
+ mParameters.orientationAware);
+
+ mParameters.associatedDisplayId = mParameters.orientationAware ? 0 : -1;
+}
+
+void KeyboardInputMapper::dumpParameters(String8& dump) {
+ dump.append(INDENT3 "Parameters:\n");
+ dump.appendFormat(INDENT4 "AssociatedDisplayId: %d\n",
+ mParameters.associatedDisplayId);
+ dump.appendFormat(INDENT4 "OrientationAware: %s\n",
+ toString(mParameters.orientationAware));
+}
+
+void KeyboardInputMapper::reset() {
+ for (;;) {
+ int32_t keyCode, scanCode;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ // Synthesize key up event on reset if keys are currently down.
+ if (mLocked.keyDowns.isEmpty()) {
+ initializeLocked();
+ resetLedStateLocked();
+ break; // done
+ }
+
+ const KeyDown& keyDown = mLocked.keyDowns.top();
+ keyCode = keyDown.keyCode;
+ scanCode = keyDown.scanCode;
+ } // release lock
+
+ nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
+ processKey(when, false, keyCode, scanCode, 0);
+ }
+
+ InputMapper::reset();
+ getContext()->updateGlobalMetaState();
+}
+
+void KeyboardInputMapper::process(const RawEvent* rawEvent) {
+ switch (rawEvent->type) {
+ case EV_KEY: {
+ int32_t scanCode = rawEvent->scanCode;
+ if (isKeyboardOrGamepadKey(scanCode)) {
+ processKey(rawEvent->when, rawEvent->value != 0, rawEvent->keyCode, scanCode,
+ rawEvent->flags);
+ }
+ break;
+ }
+ }
+}
+
+bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) {
+ return scanCode < BTN_MOUSE
+ || scanCode >= KEY_OK
+ || (scanCode >= BTN_GAMEPAD && scanCode < BTN_DIGI);
+}
+
+void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
+ int32_t scanCode, uint32_t policyFlags) {
+ int32_t newMetaState;
+ nsecs_t downTime;
+ bool metaStateChanged = false;
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (down) {
+ // Rotate key codes according to orientation if needed.
+ // Note: getDisplayInfo is non-reentrant so we can continue holding the lock.
+ if (mParameters.orientationAware && mParameters.associatedDisplayId >= 0) {
+ int32_t orientation;
+ if (!getPolicy()->getDisplayInfo(mParameters.associatedDisplayId,
+ NULL, NULL, & orientation)) {
+ orientation = DISPLAY_ORIENTATION_0;
+ }
+
+ keyCode = rotateKeyCode(keyCode, orientation);
+ }
+
+ // Add key down.
+ ssize_t keyDownIndex = findKeyDownLocked(scanCode);
+ if (keyDownIndex >= 0) {
+ // key repeat, be sure to use same keycode as before in case of rotation
+ keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode;
+ } else {
+ // key down
+ if ((policyFlags & POLICY_FLAG_VIRTUAL)
+ && mContext->shouldDropVirtualKey(when,
+ getDevice(), keyCode, scanCode)) {
+ return;
+ }
+
+ mLocked.keyDowns.push();
+ KeyDown& keyDown = mLocked.keyDowns.editTop();
+ keyDown.keyCode = keyCode;
+ keyDown.scanCode = scanCode;
+ }
+
+ mLocked.downTime = when;
+ } else {
+ // Remove key down.
+ ssize_t keyDownIndex = findKeyDownLocked(scanCode);
+ if (keyDownIndex >= 0) {
+ // key up, be sure to use same keycode as before in case of rotation
+ keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode;
+ mLocked.keyDowns.removeAt(size_t(keyDownIndex));
+ } else {
+ // key was not actually down
+ LOGI("Dropping key up from device %s because the key was not down. "
+ "keyCode=%d, scanCode=%d",
+ getDeviceName().string(), keyCode, scanCode);
+ return;
+ }
+ }
+
+ int32_t oldMetaState = mLocked.metaState;
+ newMetaState = updateMetaState(keyCode, down, oldMetaState);
+ if (oldMetaState != newMetaState) {
+ mLocked.metaState = newMetaState;
+ metaStateChanged = true;
+ updateLedStateLocked(false);
+ }
+
+ downTime = mLocked.downTime;
+ } // release lock
+
+ if (metaStateChanged) {
+ getContext()->updateGlobalMetaState();
+ }
+
+ if (policyFlags & POLICY_FLAG_FUNCTION) {
+ newMetaState |= AMETA_FUNCTION_ON;
+ }
+ getDispatcher()->notifyKey(when, getDeviceId(), mSources, policyFlags,
+ down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
+ AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
+}
+
+ssize_t KeyboardInputMapper::findKeyDownLocked(int32_t scanCode) {
+ size_t n = mLocked.keyDowns.size();
+ for (size_t i = 0; i < n; i++) {
+ if (mLocked.keyDowns[i].scanCode == scanCode) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+int32_t KeyboardInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ return getEventHub()->getKeyCodeState(getDeviceId(), keyCode);
+}
+
+int32_t KeyboardInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ return getEventHub()->getScanCodeState(getDeviceId(), scanCode);
+}
+
+bool KeyboardInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ return getEventHub()->markSupportedKeyCodes(getDeviceId(), numCodes, keyCodes, outFlags);
+}
+
+int32_t KeyboardInputMapper::getMetaState() {
+ { // acquire lock
+ AutoMutex _l(mLock);
+ return mLocked.metaState;
+ } // release lock
+}
+
+void KeyboardInputMapper::resetLedStateLocked() {
+ initializeLedStateLocked(mLocked.capsLockLedState, LED_CAPSL);
+ initializeLedStateLocked(mLocked.numLockLedState, LED_NUML);
+ initializeLedStateLocked(mLocked.scrollLockLedState, LED_SCROLLL);
+
+ updateLedStateLocked(true);
+}
+
+void KeyboardInputMapper::initializeLedStateLocked(LockedState::LedState& ledState, int32_t led) {
+ ledState.avail = getEventHub()->hasLed(getDeviceId(), led);
+ ledState.on = false;
+}
+
+void KeyboardInputMapper::updateLedStateLocked(bool reset) {
+ updateLedStateForModifierLocked(mLocked.capsLockLedState, LED_CAPSL,
+ AMETA_CAPS_LOCK_ON, reset);
+ updateLedStateForModifierLocked(mLocked.numLockLedState, LED_NUML,
+ AMETA_NUM_LOCK_ON, reset);
+ updateLedStateForModifierLocked(mLocked.scrollLockLedState, LED_SCROLLL,
+ AMETA_SCROLL_LOCK_ON, reset);
+}
+
+void KeyboardInputMapper::updateLedStateForModifierLocked(LockedState::LedState& ledState,
+ int32_t led, int32_t modifier, bool reset) {
+ if (ledState.avail) {
+ bool desiredState = (mLocked.metaState & modifier) != 0;
+ if (reset || ledState.on != desiredState) {
+ getEventHub()->setLedState(getDeviceId(), led, desiredState);
+ ledState.on = desiredState;
+ }
+ }
+}
+
+
+// --- CursorInputMapper ---
+
+CursorInputMapper::CursorInputMapper(InputDevice* device) :
+ InputMapper(device) {
+ initializeLocked();
+}
+
+CursorInputMapper::~CursorInputMapper() {
+}
+
+uint32_t CursorInputMapper::getSources() {
+ return mSources;
+}
+
+void CursorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ if (mParameters.mode == Parameters::MODE_POINTER) {
+ float minX, minY, maxX, maxY;
+ if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) {
+ info->addMotionRange(AINPUT_MOTION_RANGE_X, minX, maxX, 0.0f, 0.0f);
+ info->addMotionRange(AINPUT_MOTION_RANGE_Y, minY, maxY, 0.0f, 0.0f);
+ }
+ } else {
+ info->addMotionRange(AINPUT_MOTION_RANGE_X, -1.0f, 1.0f, 0.0f, mXScale);
+ info->addMotionRange(AINPUT_MOTION_RANGE_Y, -1.0f, 1.0f, 0.0f, mYScale);
+ }
+ info->addMotionRange(AINPUT_MOTION_RANGE_PRESSURE, 0.0f, 1.0f, 0.0f, 0.0f);
+}
+
+void CursorInputMapper::dump(String8& dump) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+ dump.append(INDENT2 "Cursor Input Mapper:\n");
+ dumpParameters(dump);
+ dump.appendFormat(INDENT3 "XPrecision: %0.3f\n", mXPrecision);
+ dump.appendFormat(INDENT3 "YPrecision: %0.3f\n", mYPrecision);
+ dump.appendFormat(INDENT3 "Down: %s\n", toString(mLocked.down));
+ dump.appendFormat(INDENT3 "DownTime: %lld\n", mLocked.downTime);
+ } // release lock
+}
+
+void CursorInputMapper::configure() {
+ InputMapper::configure();
+
+ // Configure basic parameters.
+ configureParameters();
+
+ // Configure device mode.
+ switch (mParameters.mode) {
+ case Parameters::MODE_POINTER:
+ mSources = 0; // AINPUT_SOURCE_MOUSE; disable mouse support
+ mXPrecision = 1.0f;
+ mYPrecision = 1.0f;
+ mXScale = 1.0f;
+ mYScale = 1.0f;
+ mPointerController = getPolicy()->obtainPointerController(getDeviceId());
+ break;
+ case Parameters::MODE_NAVIGATION:
+ mSources = AINPUT_SOURCE_TRACKBALL;
+ mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+ mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+ mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+ mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+ break;
+ }
+}
+
+void CursorInputMapper::configureParameters() {
+ mParameters.mode = Parameters::MODE_POINTER;
+ String8 cursorModeString;
+ if (getDevice()->getConfiguration().tryGetProperty(String8("cursor.mode"), cursorModeString)) {
+ if (cursorModeString == "navigation") {
+ mParameters.mode = Parameters::MODE_NAVIGATION;
+ } else if (cursorModeString != "pointer" && cursorModeString != "default") {
+ LOGW("Invalid value for cursor.mode: '%s'", cursorModeString.string());
+ }
+ }
+
+ mParameters.orientationAware = false;
+ getDevice()->getConfiguration().tryGetProperty(String8("cursor.orientationAware"),
+ mParameters.orientationAware);
+
+ mParameters.associatedDisplayId = mParameters.mode == Parameters::MODE_POINTER
+ || mParameters.orientationAware ? 0 : -1;
+}
+
+void CursorInputMapper::dumpParameters(String8& dump) {
+ dump.append(INDENT3 "Parameters:\n");
+ dump.appendFormat(INDENT4 "AssociatedDisplayId: %d\n",
+ mParameters.associatedDisplayId);
+
+ switch (mParameters.mode) {
+ case Parameters::MODE_POINTER:
+ dump.append(INDENT4 "Mode: pointer\n");
+ break;
+ case Parameters::MODE_NAVIGATION:
+ dump.append(INDENT4 "Mode: navigation\n");
+ break;
+ default:
+ assert(false);
+ }
+
+ dump.appendFormat(INDENT4 "OrientationAware: %s\n",
+ toString(mParameters.orientationAware));
+}
+
+void CursorInputMapper::initializeLocked() {
+ mAccumulator.clear();
+
+ mLocked.down = false;
+ mLocked.downTime = 0;
+}
+
+void CursorInputMapper::reset() {
+ for (;;) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (! mLocked.down) {
+ initializeLocked();
+ break; // done
+ }
+ } // release lock
+
+ // Synthesize button up event on reset.
+ nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
+ mAccumulator.fields = Accumulator::FIELD_BTN_MOUSE;
+ mAccumulator.btnMouse = false;
+ sync(when);
+ }
+
+ InputMapper::reset();
+}
+
+void CursorInputMapper::process(const RawEvent* rawEvent) {
+ switch (rawEvent->type) {
+ case EV_KEY:
+ switch (rawEvent->scanCode) {
+ case BTN_MOUSE:
+ mAccumulator.fields |= Accumulator::FIELD_BTN_MOUSE;
+ mAccumulator.btnMouse = rawEvent->value != 0;
+ // Sync now since BTN_MOUSE is not necessarily followed by SYN_REPORT and
+ // we need to ensure that we report the up/down promptly.
+ sync(rawEvent->when);
+ break;
+ }
+ break;
+
+ case EV_REL:
+ switch (rawEvent->scanCode) {
+ case REL_X:
+ mAccumulator.fields |= Accumulator::FIELD_REL_X;
+ mAccumulator.relX = rawEvent->value;
+ break;
+ case REL_Y:
+ mAccumulator.fields |= Accumulator::FIELD_REL_Y;
+ mAccumulator.relY = rawEvent->value;
+ break;
+ }
+ break;
+
+ case EV_SYN:
+ switch (rawEvent->scanCode) {
+ case SYN_REPORT:
+ sync(rawEvent->when);
+ break;
+ }
+ break;
+ }
+}
+
+void CursorInputMapper::sync(nsecs_t when) {
+ uint32_t fields = mAccumulator.fields;
+ if (fields == 0) {
+ return; // no new state changes, so nothing to do
+ }
+
+ int motionEventAction;
+ PointerCoords pointerCoords;
+ nsecs_t downTime;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ bool downChanged = fields & Accumulator::FIELD_BTN_MOUSE;
+
+ if (downChanged) {
+ if (mAccumulator.btnMouse) {
+ if (!mLocked.down) {
+ mLocked.down = true;
+ mLocked.downTime = when;
+ } else {
+ downChanged = false;
+ }
+ } else {
+ if (mLocked.down) {
+ mLocked.down = false;
+ } else {
+ downChanged = false;
+ }
+ }
+ }
+
+ downTime = mLocked.downTime;
+ float deltaX = fields & Accumulator::FIELD_REL_X ? mAccumulator.relX * mXScale : 0.0f;
+ float deltaY = fields & Accumulator::FIELD_REL_Y ? mAccumulator.relY * mYScale : 0.0f;
+
+ if (downChanged) {
+ motionEventAction = mLocked.down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
+ } else {
+ motionEventAction = AMOTION_EVENT_ACTION_MOVE;
+ }
+
+ if (mParameters.orientationAware && mParameters.associatedDisplayId >= 0
+ && (deltaX != 0.0f || deltaY != 0.0f)) {
+ // Rotate motion based on display orientation if needed.
+ // Note: getDisplayInfo is non-reentrant so we can continue holding the lock.
+ int32_t orientation;
+ if (! getPolicy()->getDisplayInfo(mParameters.associatedDisplayId,
+ NULL, NULL, & orientation)) {
+ orientation = DISPLAY_ORIENTATION_0;
+ }
+
+ float temp;
+ switch (orientation) {
+ case DISPLAY_ORIENTATION_90:
+ temp = deltaX;
+ deltaX = deltaY;
+ deltaY = -temp;
+ break;
+
+ case DISPLAY_ORIENTATION_180:
+ deltaX = -deltaX;
+ deltaY = -deltaY;
+ break;
+
+ case DISPLAY_ORIENTATION_270:
+ temp = deltaX;
+ deltaX = -deltaY;
+ deltaY = temp;
+ break;
+ }
+ }
+
+ if (mPointerController != NULL) {
+ mPointerController->move(deltaX, deltaY);
+ if (downChanged) {
+ mPointerController->setButtonState(mLocked.down ? POINTER_BUTTON_1 : 0);
+ }
+ mPointerController->getPosition(&pointerCoords.x, &pointerCoords.y);
+ } else {
+ pointerCoords.x = deltaX;
+ pointerCoords.y = deltaY;
+ }
+
+ pointerCoords.pressure = mLocked.down ? 1.0f : 0.0f;
+ pointerCoords.size = 0;
+ pointerCoords.touchMajor = 0;
+ pointerCoords.touchMinor = 0;
+ pointerCoords.toolMajor = 0;
+ pointerCoords.toolMinor = 0;
+ pointerCoords.orientation = 0;
+ } // release lock
+
+ int32_t metaState = mContext->getGlobalMetaState();
+ int32_t pointerId = 0;
+ getDispatcher()->notifyMotion(when, getDeviceId(), mSources, 0,
+ motionEventAction, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ 1, &pointerId, &pointerCoords, mXPrecision, mYPrecision, downTime);
+
+ mAccumulator.clear();
+}
+
+int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ if (scanCode >= BTN_MOUSE && scanCode < BTN_JOYSTICK) {
+ return getEventHub()->getScanCodeState(getDeviceId(), scanCode);
+ } else {
+ return AKEY_STATE_UNKNOWN;
+ }
+}
+
+
+// --- TouchInputMapper ---
+
+TouchInputMapper::TouchInputMapper(InputDevice* device) :
+ InputMapper(device) {
+ mLocked.surfaceOrientation = -1;
+ mLocked.surfaceWidth = -1;
+ mLocked.surfaceHeight = -1;
+
+ initializeLocked();
+}
+
+TouchInputMapper::~TouchInputMapper() {
+}
+
+uint32_t TouchInputMapper::getSources() {
+ return mSources;
+}
+
+void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ // Ensure surface information is up to date so that orientation changes are
+ // noticed immediately.
+ configureSurfaceLocked();
+
+ info->addMotionRange(AINPUT_MOTION_RANGE_X, mLocked.orientedRanges.x);
+ info->addMotionRange(AINPUT_MOTION_RANGE_Y, mLocked.orientedRanges.y);
+
+ if (mLocked.orientedRanges.havePressure) {
+ info->addMotionRange(AINPUT_MOTION_RANGE_PRESSURE,
+ mLocked.orientedRanges.pressure);
+ }
+
+ if (mLocked.orientedRanges.haveSize) {
+ info->addMotionRange(AINPUT_MOTION_RANGE_SIZE,
+ mLocked.orientedRanges.size);
+ }
+
+ if (mLocked.orientedRanges.haveTouchSize) {
+ info->addMotionRange(AINPUT_MOTION_RANGE_TOUCH_MAJOR,
+ mLocked.orientedRanges.touchMajor);
+ info->addMotionRange(AINPUT_MOTION_RANGE_TOUCH_MINOR,
+ mLocked.orientedRanges.touchMinor);
+ }
+
+ if (mLocked.orientedRanges.haveToolSize) {
+ info->addMotionRange(AINPUT_MOTION_RANGE_TOOL_MAJOR,
+ mLocked.orientedRanges.toolMajor);
+ info->addMotionRange(AINPUT_MOTION_RANGE_TOOL_MINOR,
+ mLocked.orientedRanges.toolMinor);
+ }
+
+ if (mLocked.orientedRanges.haveOrientation) {
+ info->addMotionRange(AINPUT_MOTION_RANGE_ORIENTATION,
+ mLocked.orientedRanges.orientation);
+ }
+ } // release lock
+}
+
+void TouchInputMapper::dump(String8& dump) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+ dump.append(INDENT2 "Touch Input Mapper:\n");
+ dumpParameters(dump);
+ dumpVirtualKeysLocked(dump);
+ dumpRawAxes(dump);
+ dumpCalibration(dump);
+ dumpSurfaceLocked(dump);
+ dump.appendFormat(INDENT3 "Translation and Scaling Factors:\n");
+ dump.appendFormat(INDENT4 "XOrigin: %d\n", mLocked.xOrigin);
+ dump.appendFormat(INDENT4 "YOrigin: %d\n", mLocked.yOrigin);
+ dump.appendFormat(INDENT4 "XScale: %0.3f\n", mLocked.xScale);
+ dump.appendFormat(INDENT4 "YScale: %0.3f\n", mLocked.yScale);
+ dump.appendFormat(INDENT4 "XPrecision: %0.3f\n", mLocked.xPrecision);
+ dump.appendFormat(INDENT4 "YPrecision: %0.3f\n", mLocked.yPrecision);
+ dump.appendFormat(INDENT4 "GeometricScale: %0.3f\n", mLocked.geometricScale);
+ dump.appendFormat(INDENT4 "ToolSizeLinearScale: %0.3f\n", mLocked.toolSizeLinearScale);
+ dump.appendFormat(INDENT4 "ToolSizeLinearBias: %0.3f\n", mLocked.toolSizeLinearBias);
+ dump.appendFormat(INDENT4 "ToolSizeAreaScale: %0.3f\n", mLocked.toolSizeAreaScale);
+ dump.appendFormat(INDENT4 "ToolSizeAreaBias: %0.3f\n", mLocked.toolSizeAreaBias);
+ dump.appendFormat(INDENT4 "PressureScale: %0.3f\n", mLocked.pressureScale);
+ dump.appendFormat(INDENT4 "SizeScale: %0.3f\n", mLocked.sizeScale);
+ dump.appendFormat(INDENT4 "OrientationSCale: %0.3f\n", mLocked.orientationScale);
+ } // release lock
+}
+
+void TouchInputMapper::initializeLocked() {
+ mCurrentTouch.clear();
+ mLastTouch.clear();
+ mDownTime = 0;
+
+ for (uint32_t i = 0; i < MAX_POINTERS; i++) {
+ mAveragingTouchFilter.historyStart[i] = 0;
+ mAveragingTouchFilter.historyEnd[i] = 0;
+ }
+
+ mJumpyTouchFilter.jumpyPointsDropped = 0;
+
+ mLocked.currentVirtualKey.down = false;
+
+ mLocked.orientedRanges.havePressure = false;
+ mLocked.orientedRanges.haveSize = false;
+ mLocked.orientedRanges.haveTouchSize = false;
+ mLocked.orientedRanges.haveToolSize = false;
+ mLocked.orientedRanges.haveOrientation = false;
+}
+
+void TouchInputMapper::configure() {
+ InputMapper::configure();
+
+ // Configure basic parameters.
+ configureParameters();
+
+ // Configure sources.
+ switch (mParameters.deviceType) {
+ case Parameters::DEVICE_TYPE_TOUCH_SCREEN:
+ mSources = AINPUT_SOURCE_TOUCHSCREEN;
+ break;
+ case Parameters::DEVICE_TYPE_TOUCH_PAD:
+ mSources = AINPUT_SOURCE_TOUCHPAD;
+ break;
+ default:
+ assert(false);
+ }
+
+ // Configure absolute axis information.
+ configureRawAxes();
+
+ // Prepare input device calibration.
+ parseCalibration();
+ resolveCalibration();
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ // Configure surface dimensions and orientation.
+ configureSurfaceLocked();
+ } // release lock
+}
+
+void TouchInputMapper::configureParameters() {
+ mParameters.useBadTouchFilter = getPolicy()->filterTouchEvents();
+ mParameters.useAveragingTouchFilter = getPolicy()->filterTouchEvents();
+ mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents();
+ mParameters.virtualKeyQuietTime = getPolicy()->getVirtualKeyQuietTime();
+
+ String8 deviceTypeString;
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
+ if (getDevice()->getConfiguration().tryGetProperty(String8("touch.deviceType"),
+ deviceTypeString)) {
+ if (deviceTypeString == "touchScreen") {
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+ } else if (deviceTypeString != "touchPad") {
+ LOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.string());
+ }
+ }
+ bool isTouchScreen = mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+
+ mParameters.orientationAware = isTouchScreen;
+ getDevice()->getConfiguration().tryGetProperty(String8("touch.orientationAware"),
+ mParameters.orientationAware);
+
+ mParameters.associatedDisplayId = mParameters.orientationAware || isTouchScreen ? 0 : -1;
+}
+
+void TouchInputMapper::dumpParameters(String8& dump) {
+ dump.append(INDENT3 "Parameters:\n");
+
+ switch (mParameters.deviceType) {
+ case Parameters::DEVICE_TYPE_TOUCH_SCREEN:
+ dump.append(INDENT4 "DeviceType: touchScreen\n");
+ break;
+ case Parameters::DEVICE_TYPE_TOUCH_PAD:
+ dump.append(INDENT4 "DeviceType: touchPad\n");
+ break;
+ default:
+ assert(false);
+ }
+
+ dump.appendFormat(INDENT4 "AssociatedDisplayId: %d\n",
+ mParameters.associatedDisplayId);
+ dump.appendFormat(INDENT4 "OrientationAware: %s\n",
+ toString(mParameters.orientationAware));
+
+ dump.appendFormat(INDENT4 "UseBadTouchFilter: %s\n",
+ toString(mParameters.useBadTouchFilter));
+ dump.appendFormat(INDENT4 "UseAveragingTouchFilter: %s\n",
+ toString(mParameters.useAveragingTouchFilter));
+ dump.appendFormat(INDENT4 "UseJumpyTouchFilter: %s\n",
+ toString(mParameters.useJumpyTouchFilter));
+}
+
+void TouchInputMapper::configureRawAxes() {
+ mRawAxes.x.clear();
+ mRawAxes.y.clear();
+ mRawAxes.pressure.clear();
+ mRawAxes.touchMajor.clear();
+ mRawAxes.touchMinor.clear();
+ mRawAxes.toolMajor.clear();
+ mRawAxes.toolMinor.clear();
+ mRawAxes.orientation.clear();
+}
+
+static void dumpAxisInfo(String8& dump, RawAbsoluteAxisInfo axis, const char* name) {
+ if (axis.valid) {
+ dump.appendFormat(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d\n",
+ name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz);
+ } else {
+ dump.appendFormat(INDENT4 "%s: unknown range\n", name);
+ }
+}
+
+void TouchInputMapper::dumpRawAxes(String8& dump) {
+ dump.append(INDENT3 "Raw Axes:\n");
+ dumpAxisInfo(dump, mRawAxes.x, "X");
+ dumpAxisInfo(dump, mRawAxes.y, "Y");
+ dumpAxisInfo(dump, mRawAxes.pressure, "Pressure");
+ dumpAxisInfo(dump, mRawAxes.touchMajor, "TouchMajor");
+ dumpAxisInfo(dump, mRawAxes.touchMinor, "TouchMinor");
+ dumpAxisInfo(dump, mRawAxes.toolMajor, "ToolMajor");
+ dumpAxisInfo(dump, mRawAxes.toolMinor, "ToolMinor");
+ dumpAxisInfo(dump, mRawAxes.orientation, "Orientation");
+}
+
+bool TouchInputMapper::configureSurfaceLocked() {
+ // Update orientation and dimensions if needed.
+ int32_t orientation = DISPLAY_ORIENTATION_0;
+ int32_t width = mRawAxes.x.getRange();
+ int32_t height = mRawAxes.y.getRange();
+
+ if (mParameters.associatedDisplayId >= 0) {
+ bool wantSize = mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+ bool wantOrientation = mParameters.orientationAware;
+
+ // Note: getDisplayInfo is non-reentrant so we can continue holding the lock.
+ if (! getPolicy()->getDisplayInfo(mParameters.associatedDisplayId,
+ wantSize ? &width : NULL, wantSize ? &height : NULL,
+ wantOrientation ? &orientation : NULL)) {
+ return false;
+ }
+ }
+
+ bool orientationChanged = mLocked.surfaceOrientation != orientation;
+ if (orientationChanged) {
+ mLocked.surfaceOrientation = orientation;
+ }
+
+ bool sizeChanged = mLocked.surfaceWidth != width || mLocked.surfaceHeight != height;
+ if (sizeChanged) {
+ LOGI("Device reconfigured: id=%d, name='%s', display size is now %dx%d",
+ getDeviceId(), getDeviceName().string(), width, height);
+
+ mLocked.surfaceWidth = width;
+ mLocked.surfaceHeight = height;
+
+ // Configure X and Y factors.
+ if (mRawAxes.x.valid && mRawAxes.y.valid) {
+ mLocked.xOrigin = mCalibration.haveXOrigin
+ ? mCalibration.xOrigin
+ : mRawAxes.x.minValue;
+ mLocked.yOrigin = mCalibration.haveYOrigin
+ ? mCalibration.yOrigin
+ : mRawAxes.y.minValue;
+ mLocked.xScale = mCalibration.haveXScale
+ ? mCalibration.xScale
+ : float(width) / mRawAxes.x.getRange();
+ mLocked.yScale = mCalibration.haveYScale
+ ? mCalibration.yScale
+ : float(height) / mRawAxes.y.getRange();
+ mLocked.xPrecision = 1.0f / mLocked.xScale;
+ mLocked.yPrecision = 1.0f / mLocked.yScale;
+
+ configureVirtualKeysLocked();
+ } else {
+ LOGW(INDENT "Touch device did not report support for X or Y axis!");
+ mLocked.xOrigin = 0;
+ mLocked.yOrigin = 0;
+ mLocked.xScale = 1.0f;
+ mLocked.yScale = 1.0f;
+ mLocked.xPrecision = 1.0f;
+ mLocked.yPrecision = 1.0f;
+ }
+
+ // Scale factor for terms that are not oriented in a particular axis.
+ // If the pixels are square then xScale == yScale otherwise we fake it
+ // by choosing an average.
+ mLocked.geometricScale = avg(mLocked.xScale, mLocked.yScale);
+
+ // Size of diagonal axis.
+ float diagonalSize = pythag(width, height);
+
+ // TouchMajor and TouchMinor factors.
+ if (mCalibration.touchSizeCalibration != Calibration::TOUCH_SIZE_CALIBRATION_NONE) {
+ mLocked.orientedRanges.haveTouchSize = true;
+ mLocked.orientedRanges.touchMajor.min = 0;
+ mLocked.orientedRanges.touchMajor.max = diagonalSize;
+ mLocked.orientedRanges.touchMajor.flat = 0;
+ mLocked.orientedRanges.touchMajor.fuzz = 0;
+ mLocked.orientedRanges.touchMinor = mLocked.orientedRanges.touchMajor;
+ }
+
+ // ToolMajor and ToolMinor factors.
+ mLocked.toolSizeLinearScale = 0;
+ mLocked.toolSizeLinearBias = 0;
+ mLocked.toolSizeAreaScale = 0;
+ mLocked.toolSizeAreaBias = 0;
+ if (mCalibration.toolSizeCalibration != Calibration::TOOL_SIZE_CALIBRATION_NONE) {
+ if (mCalibration.toolSizeCalibration == Calibration::TOOL_SIZE_CALIBRATION_LINEAR) {
+ if (mCalibration.haveToolSizeLinearScale) {
+ mLocked.toolSizeLinearScale = mCalibration.toolSizeLinearScale;
+ } else if (mRawAxes.toolMajor.valid && mRawAxes.toolMajor.maxValue != 0) {
+ mLocked.toolSizeLinearScale = float(min(width, height))
+ / mRawAxes.toolMajor.maxValue;
+ }
+
+ if (mCalibration.haveToolSizeLinearBias) {
+ mLocked.toolSizeLinearBias = mCalibration.toolSizeLinearBias;
+ }
+ } else if (mCalibration.toolSizeCalibration ==
+ Calibration::TOOL_SIZE_CALIBRATION_AREA) {
+ if (mCalibration.haveToolSizeLinearScale) {
+ mLocked.toolSizeLinearScale = mCalibration.toolSizeLinearScale;
+ } else {
+ mLocked.toolSizeLinearScale = min(width, height);
+ }
+
+ if (mCalibration.haveToolSizeLinearBias) {
+ mLocked.toolSizeLinearBias = mCalibration.toolSizeLinearBias;
+ }
+
+ if (mCalibration.haveToolSizeAreaScale) {
+ mLocked.toolSizeAreaScale = mCalibration.toolSizeAreaScale;
+ } else if (mRawAxes.toolMajor.valid && mRawAxes.toolMajor.maxValue != 0) {
+ mLocked.toolSizeAreaScale = 1.0f / mRawAxes.toolMajor.maxValue;
+ }
+
+ if (mCalibration.haveToolSizeAreaBias) {
+ mLocked.toolSizeAreaBias = mCalibration.toolSizeAreaBias;
+ }
+ }
+
+ mLocked.orientedRanges.haveToolSize = true;
+ mLocked.orientedRanges.toolMajor.min = 0;
+ mLocked.orientedRanges.toolMajor.max = diagonalSize;
+ mLocked.orientedRanges.toolMajor.flat = 0;
+ mLocked.orientedRanges.toolMajor.fuzz = 0;
+ mLocked.orientedRanges.toolMinor = mLocked.orientedRanges.toolMajor;
+ }
+
+ // Pressure factors.
+ mLocked.pressureScale = 0;
+ if (mCalibration.pressureCalibration != Calibration::PRESSURE_CALIBRATION_NONE) {
+ RawAbsoluteAxisInfo rawPressureAxis;
+ switch (mCalibration.pressureSource) {
+ case Calibration::PRESSURE_SOURCE_PRESSURE:
+ rawPressureAxis = mRawAxes.pressure;
+ break;
+ case Calibration::PRESSURE_SOURCE_TOUCH:
+ rawPressureAxis = mRawAxes.touchMajor;
+ break;
+ default:
+ rawPressureAxis.clear();
+ }
+
+ if (mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_PHYSICAL
+ || mCalibration.pressureCalibration
+ == Calibration::PRESSURE_CALIBRATION_AMPLITUDE) {
+ if (mCalibration.havePressureScale) {
+ mLocked.pressureScale = mCalibration.pressureScale;
+ } else if (rawPressureAxis.valid && rawPressureAxis.maxValue != 0) {
+ mLocked.pressureScale = 1.0f / rawPressureAxis.maxValue;
+ }
+ }
+
+ mLocked.orientedRanges.havePressure = true;
+ mLocked.orientedRanges.pressure.min = 0;
+ mLocked.orientedRanges.pressure.max = 1.0;
+ mLocked.orientedRanges.pressure.flat = 0;
+ mLocked.orientedRanges.pressure.fuzz = 0;
+ }
+
+ // Size factors.
+ mLocked.sizeScale = 0;
+ if (mCalibration.sizeCalibration != Calibration::SIZE_CALIBRATION_NONE) {
+ if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_NORMALIZED) {
+ if (mRawAxes.toolMajor.valid && mRawAxes.toolMajor.maxValue != 0) {
+ mLocked.sizeScale = 1.0f / mRawAxes.toolMajor.maxValue;
+ }
+ }
+
+ mLocked.orientedRanges.haveSize = true;
+ mLocked.orientedRanges.size.min = 0;
+ mLocked.orientedRanges.size.max = 1.0;
+ mLocked.orientedRanges.size.flat = 0;
+ mLocked.orientedRanges.size.fuzz = 0;
+ }
+
+ // Orientation
+ mLocked.orientationScale = 0;
+ if (mCalibration.orientationCalibration != Calibration::ORIENTATION_CALIBRATION_NONE) {
+ if (mCalibration.orientationCalibration
+ == Calibration::ORIENTATION_CALIBRATION_INTERPOLATED) {
+ if (mRawAxes.orientation.valid && mRawAxes.orientation.maxValue != 0) {
+ mLocked.orientationScale = float(M_PI_2) / mRawAxes.orientation.maxValue;
+ }
+ }
+
+ mLocked.orientedRanges.orientation.min = - M_PI_2;
+ mLocked.orientedRanges.orientation.max = M_PI_2;
+ mLocked.orientedRanges.orientation.flat = 0;
+ mLocked.orientedRanges.orientation.fuzz = 0;
+ }
+ }
+
+ if (orientationChanged || sizeChanged) {
+ // Compute oriented surface dimensions, precision, and scales.
+ float orientedXScale, orientedYScale;
+ switch (mLocked.surfaceOrientation) {
+ case DISPLAY_ORIENTATION_90:
+ case DISPLAY_ORIENTATION_270:
+ mLocked.orientedSurfaceWidth = mLocked.surfaceHeight;
+ mLocked.orientedSurfaceHeight = mLocked.surfaceWidth;
+ mLocked.orientedXPrecision = mLocked.yPrecision;
+ mLocked.orientedYPrecision = mLocked.xPrecision;
+ orientedXScale = mLocked.yScale;
+ orientedYScale = mLocked.xScale;
+ break;
+ default:
+ mLocked.orientedSurfaceWidth = mLocked.surfaceWidth;
+ mLocked.orientedSurfaceHeight = mLocked.surfaceHeight;
+ mLocked.orientedXPrecision = mLocked.xPrecision;
+ mLocked.orientedYPrecision = mLocked.yPrecision;
+ orientedXScale = mLocked.xScale;
+ orientedYScale = mLocked.yScale;
+ break;
+ }
+
+ // Configure position ranges.
+ mLocked.orientedRanges.x.min = 0;
+ mLocked.orientedRanges.x.max = mLocked.orientedSurfaceWidth;
+ mLocked.orientedRanges.x.flat = 0;
+ mLocked.orientedRanges.x.fuzz = orientedXScale;
+
+ mLocked.orientedRanges.y.min = 0;
+ mLocked.orientedRanges.y.max = mLocked.orientedSurfaceHeight;
+ mLocked.orientedRanges.y.flat = 0;
+ mLocked.orientedRanges.y.fuzz = orientedYScale;
+ }
+
+ return true;
+}
+
+void TouchInputMapper::dumpSurfaceLocked(String8& dump) {
+ dump.appendFormat(INDENT3 "SurfaceWidth: %dpx\n", mLocked.surfaceWidth);
+ dump.appendFormat(INDENT3 "SurfaceHeight: %dpx\n", mLocked.surfaceHeight);
+ dump.appendFormat(INDENT3 "SurfaceOrientation: %d\n", mLocked.surfaceOrientation);
+}
+
+void TouchInputMapper::configureVirtualKeysLocked() {
+ assert(mRawAxes.x.valid && mRawAxes.y.valid);
+
+ Vector<VirtualKeyDefinition> virtualKeyDefinitions;
+ getEventHub()->getVirtualKeyDefinitions(getDeviceId(), virtualKeyDefinitions);
+
+ mLocked.virtualKeys.clear();
+
+ if (virtualKeyDefinitions.size() == 0) {
+ return;
+ }
+
+ mLocked.virtualKeys.setCapacity(virtualKeyDefinitions.size());
+
+ int32_t touchScreenLeft = mRawAxes.x.minValue;
+ int32_t touchScreenTop = mRawAxes.y.minValue;
+ int32_t touchScreenWidth = mRawAxes.x.getRange();
+ int32_t touchScreenHeight = mRawAxes.y.getRange();
+
+ for (size_t i = 0; i < virtualKeyDefinitions.size(); i++) {
+ const VirtualKeyDefinition& virtualKeyDefinition =
+ virtualKeyDefinitions[i];
+
+ mLocked.virtualKeys.add();
+ VirtualKey& virtualKey = mLocked.virtualKeys.editTop();
+
+ virtualKey.scanCode = virtualKeyDefinition.scanCode;
+ int32_t keyCode;
+ uint32_t flags;
+ if (getEventHub()->scancodeToKeycode(getDeviceId(), virtualKey.scanCode,
+ & keyCode, & flags)) {
+ LOGW(INDENT "VirtualKey %d: could not obtain key code, ignoring",
+ virtualKey.scanCode);
+ mLocked.virtualKeys.pop(); // drop the key
+ continue;
+ }
+
+ virtualKey.keyCode = keyCode;
+ virtualKey.flags = flags;
+
+ // convert the key definition's display coordinates into touch coordinates for a hit box
+ int32_t halfWidth = virtualKeyDefinition.width / 2;
+ int32_t halfHeight = virtualKeyDefinition.height / 2;
+
+ virtualKey.hitLeft = (virtualKeyDefinition.centerX - halfWidth)
+ * touchScreenWidth / mLocked.surfaceWidth + touchScreenLeft;
+ virtualKey.hitRight= (virtualKeyDefinition.centerX + halfWidth)
+ * touchScreenWidth / mLocked.surfaceWidth + touchScreenLeft;
+ virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight)
+ * touchScreenHeight / mLocked.surfaceHeight + touchScreenTop;
+ virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight)
+ * touchScreenHeight / mLocked.surfaceHeight + touchScreenTop;
+
+ }
+}
+
+void TouchInputMapper::dumpVirtualKeysLocked(String8& dump) {
+ if (!mLocked.virtualKeys.isEmpty()) {
+ dump.append(INDENT3 "Virtual Keys:\n");
+
+ for (size_t i = 0; i < mLocked.virtualKeys.size(); i++) {
+ const VirtualKey& virtualKey = mLocked.virtualKeys.itemAt(i);
+ dump.appendFormat(INDENT4 "%d: scanCode=%d, keyCode=%d, "
+ "hitLeft=%d, hitRight=%d, hitTop=%d, hitBottom=%d\n",
+ i, virtualKey.scanCode, virtualKey.keyCode,
+ virtualKey.hitLeft, virtualKey.hitRight,
+ virtualKey.hitTop, virtualKey.hitBottom);
+ }
+ }
+}
+
+void TouchInputMapper::parseCalibration() {
+ const PropertyMap& in = getDevice()->getConfiguration();
+ Calibration& out = mCalibration;
+
+ // Position
+ out.haveXOrigin = in.tryGetProperty(String8("touch.position.xOrigin"), out.xOrigin);
+ out.haveYOrigin = in.tryGetProperty(String8("touch.position.yOrigin"), out.yOrigin);
+ out.haveXScale = in.tryGetProperty(String8("touch.position.xScale"), out.xScale);
+ out.haveYScale = in.tryGetProperty(String8("touch.position.yScale"), out.yScale);
+
+ // Touch Size
+ out.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_DEFAULT;
+ String8 touchSizeCalibrationString;
+ if (in.tryGetProperty(String8("touch.touchSize.calibration"), touchSizeCalibrationString)) {
+ if (touchSizeCalibrationString == "none") {
+ out.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_NONE;
+ } else if (touchSizeCalibrationString == "geometric") {
+ out.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_GEOMETRIC;
+ } else if (touchSizeCalibrationString == "pressure") {
+ out.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE;
+ } else if (touchSizeCalibrationString != "default") {
+ LOGW("Invalid value for touch.touchSize.calibration: '%s'",
+ touchSizeCalibrationString.string());
+ }
+ }
+
+ // Tool Size
+ out.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_DEFAULT;
+ String8 toolSizeCalibrationString;
+ if (in.tryGetProperty(String8("touch.toolSize.calibration"), toolSizeCalibrationString)) {
+ if (toolSizeCalibrationString == "none") {
+ out.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_NONE;
+ } else if (toolSizeCalibrationString == "geometric") {
+ out.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_GEOMETRIC;
+ } else if (toolSizeCalibrationString == "linear") {
+ out.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_LINEAR;
+ } else if (toolSizeCalibrationString == "area") {
+ out.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_AREA;
+ } else if (toolSizeCalibrationString != "default") {
+ LOGW("Invalid value for touch.toolSize.calibration: '%s'",
+ toolSizeCalibrationString.string());
+ }
+ }
+
+ out.haveToolSizeLinearScale = in.tryGetProperty(String8("touch.toolSize.linearScale"),
+ out.toolSizeLinearScale);
+ out.haveToolSizeLinearBias = in.tryGetProperty(String8("touch.toolSize.linearBias"),
+ out.toolSizeLinearBias);
+ out.haveToolSizeAreaScale = in.tryGetProperty(String8("touch.toolSize.areaScale"),
+ out.toolSizeAreaScale);
+ out.haveToolSizeAreaBias = in.tryGetProperty(String8("touch.toolSize.areaBias"),
+ out.toolSizeAreaBias);
+ out.haveToolSizeIsSummed = in.tryGetProperty(String8("touch.toolSize.isSummed"),
+ out.toolSizeIsSummed);
+
+ // Pressure
+ out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_DEFAULT;
+ String8 pressureCalibrationString;
+ if (in.tryGetProperty(String8("touch.pressure.calibration"), pressureCalibrationString)) {
+ if (pressureCalibrationString == "none") {
+ out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE;
+ } else if (pressureCalibrationString == "physical") {
+ out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_PHYSICAL;
+ } else if (pressureCalibrationString == "amplitude") {
+ out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_AMPLITUDE;
+ } else if (pressureCalibrationString != "default") {
+ LOGW("Invalid value for touch.pressure.calibration: '%s'",
+ pressureCalibrationString.string());
+ }
+ }
+
+ out.pressureSource = Calibration::PRESSURE_SOURCE_DEFAULT;
+ String8 pressureSourceString;
+ if (in.tryGetProperty(String8("touch.pressure.source"), pressureSourceString)) {
+ if (pressureSourceString == "pressure") {
+ out.pressureSource = Calibration::PRESSURE_SOURCE_PRESSURE;
+ } else if (pressureSourceString == "touch") {
+ out.pressureSource = Calibration::PRESSURE_SOURCE_TOUCH;
+ } else if (pressureSourceString != "default") {
+ LOGW("Invalid value for touch.pressure.source: '%s'",
+ pressureSourceString.string());
+ }
+ }
+
+ out.havePressureScale = in.tryGetProperty(String8("touch.pressure.scale"),
+ out.pressureScale);
+
+ // Size
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_DEFAULT;
+ String8 sizeCalibrationString;
+ if (in.tryGetProperty(String8("touch.size.calibration"), sizeCalibrationString)) {
+ if (sizeCalibrationString == "none") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE;
+ } else if (sizeCalibrationString == "normalized") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_NORMALIZED;
+ } else if (sizeCalibrationString != "default") {
+ LOGW("Invalid value for touch.size.calibration: '%s'",
+ sizeCalibrationString.string());
+ }
+ }
+
+ // Orientation
+ out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_DEFAULT;
+ String8 orientationCalibrationString;
+ if (in.tryGetProperty(String8("touch.orientation.calibration"), orientationCalibrationString)) {
+ if (orientationCalibrationString == "none") {
+ out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE;
+ } else if (orientationCalibrationString == "interpolated") {
+ out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED;
+ } else if (orientationCalibrationString == "vector") {
+ out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_VECTOR;
+ } else if (orientationCalibrationString != "default") {
+ LOGW("Invalid value for touch.orientation.calibration: '%s'",
+ orientationCalibrationString.string());
+ }
+ }
+}
+
+void TouchInputMapper::resolveCalibration() {
+ // Pressure
+ switch (mCalibration.pressureSource) {
+ case Calibration::PRESSURE_SOURCE_DEFAULT:
+ if (mRawAxes.pressure.valid) {
+ mCalibration.pressureSource = Calibration::PRESSURE_SOURCE_PRESSURE;
+ } else if (mRawAxes.touchMajor.valid) {
+ mCalibration.pressureSource = Calibration::PRESSURE_SOURCE_TOUCH;
+ }
+ break;
+
+ case Calibration::PRESSURE_SOURCE_PRESSURE:
+ if (! mRawAxes.pressure.valid) {
+ LOGW("Calibration property touch.pressure.source is 'pressure' but "
+ "the pressure axis is not available.");
+ }
+ break;
+
+ case Calibration::PRESSURE_SOURCE_TOUCH:
+ if (! mRawAxes.touchMajor.valid) {
+ LOGW("Calibration property touch.pressure.source is 'touch' but "
+ "the touchMajor axis is not available.");
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ switch (mCalibration.pressureCalibration) {
+ case Calibration::PRESSURE_CALIBRATION_DEFAULT:
+ if (mCalibration.pressureSource != Calibration::PRESSURE_SOURCE_DEFAULT) {
+ mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_AMPLITUDE;
+ } else {
+ mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // Tool Size
+ switch (mCalibration.toolSizeCalibration) {
+ case Calibration::TOOL_SIZE_CALIBRATION_DEFAULT:
+ if (mRawAxes.toolMajor.valid) {
+ mCalibration.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_LINEAR;
+ } else {
+ mCalibration.toolSizeCalibration = Calibration::TOOL_SIZE_CALIBRATION_NONE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // Touch Size
+ switch (mCalibration.touchSizeCalibration) {
+ case Calibration::TOUCH_SIZE_CALIBRATION_DEFAULT:
+ if (mCalibration.pressureCalibration != Calibration::PRESSURE_CALIBRATION_NONE
+ && mCalibration.toolSizeCalibration != Calibration::TOOL_SIZE_CALIBRATION_NONE) {
+ mCalibration.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE;
+ } else {
+ mCalibration.touchSizeCalibration = Calibration::TOUCH_SIZE_CALIBRATION_NONE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // Size
+ switch (mCalibration.sizeCalibration) {
+ case Calibration::SIZE_CALIBRATION_DEFAULT:
+ if (mRawAxes.toolMajor.valid) {
+ mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_NORMALIZED;
+ } else {
+ mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ // Orientation
+ switch (mCalibration.orientationCalibration) {
+ case Calibration::ORIENTATION_CALIBRATION_DEFAULT:
+ if (mRawAxes.orientation.valid) {
+ mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED;
+ } else {
+ mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+void TouchInputMapper::dumpCalibration(String8& dump) {
+ dump.append(INDENT3 "Calibration:\n");
+
+ // Position
+ if (mCalibration.haveXOrigin) {
+ dump.appendFormat(INDENT4 "touch.position.xOrigin: %d\n", mCalibration.xOrigin);
+ }
+ if (mCalibration.haveYOrigin) {
+ dump.appendFormat(INDENT4 "touch.position.yOrigin: %d\n", mCalibration.yOrigin);
+ }
+ if (mCalibration.haveXScale) {
+ dump.appendFormat(INDENT4 "touch.position.xScale: %0.3f\n", mCalibration.xScale);
+ }
+ if (mCalibration.haveYScale) {
+ dump.appendFormat(INDENT4 "touch.position.yScale: %0.3f\n", mCalibration.yScale);
+ }
+
+ // Touch Size
+ switch (mCalibration.touchSizeCalibration) {
+ case Calibration::TOUCH_SIZE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.touchSize.calibration: none\n");
+ break;
+ case Calibration::TOUCH_SIZE_CALIBRATION_GEOMETRIC:
+ dump.append(INDENT4 "touch.touchSize.calibration: geometric\n");
+ break;
+ case Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE:
+ dump.append(INDENT4 "touch.touchSize.calibration: pressure\n");
+ break;
+ default:
+ assert(false);
+ }
+
+ // Tool Size
+ switch (mCalibration.toolSizeCalibration) {
+ case Calibration::TOOL_SIZE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.toolSize.calibration: none\n");
+ break;
+ case Calibration::TOOL_SIZE_CALIBRATION_GEOMETRIC:
+ dump.append(INDENT4 "touch.toolSize.calibration: geometric\n");
+ break;
+ case Calibration::TOOL_SIZE_CALIBRATION_LINEAR:
+ dump.append(INDENT4 "touch.toolSize.calibration: linear\n");
+ break;
+ case Calibration::TOOL_SIZE_CALIBRATION_AREA:
+ dump.append(INDENT4 "touch.toolSize.calibration: area\n");
+ break;
+ default:
+ assert(false);
+ }
+
+ if (mCalibration.haveToolSizeLinearScale) {
+ dump.appendFormat(INDENT4 "touch.toolSize.linearScale: %0.3f\n",
+ mCalibration.toolSizeLinearScale);
+ }
+
+ if (mCalibration.haveToolSizeLinearBias) {
+ dump.appendFormat(INDENT4 "touch.toolSize.linearBias: %0.3f\n",
+ mCalibration.toolSizeLinearBias);
+ }
+
+ if (mCalibration.haveToolSizeAreaScale) {
+ dump.appendFormat(INDENT4 "touch.toolSize.areaScale: %0.3f\n",
+ mCalibration.toolSizeAreaScale);
+ }
+
+ if (mCalibration.haveToolSizeAreaBias) {
+ dump.appendFormat(INDENT4 "touch.toolSize.areaBias: %0.3f\n",
+ mCalibration.toolSizeAreaBias);
+ }
+
+ if (mCalibration.haveToolSizeIsSummed) {
+ dump.appendFormat(INDENT4 "touch.toolSize.isSummed: %s\n",
+ toString(mCalibration.toolSizeIsSummed));
+ }
+
+ // Pressure
+ switch (mCalibration.pressureCalibration) {
+ case Calibration::PRESSURE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.pressure.calibration: none\n");
+ break;
+ case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
+ dump.append(INDENT4 "touch.pressure.calibration: physical\n");
+ break;
+ case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
+ dump.append(INDENT4 "touch.pressure.calibration: amplitude\n");
+ break;
+ default:
+ assert(false);
+ }
+
+ switch (mCalibration.pressureSource) {
+ case Calibration::PRESSURE_SOURCE_PRESSURE:
+ dump.append(INDENT4 "touch.pressure.source: pressure\n");
+ break;
+ case Calibration::PRESSURE_SOURCE_TOUCH:
+ dump.append(INDENT4 "touch.pressure.source: touch\n");
+ break;
+ case Calibration::PRESSURE_SOURCE_DEFAULT:
+ break;
+ default:
+ assert(false);
+ }
+
+ if (mCalibration.havePressureScale) {
+ dump.appendFormat(INDENT4 "touch.pressure.scale: %0.3f\n",
+ mCalibration.pressureScale);
+ }
+
+ // Size
+ switch (mCalibration.sizeCalibration) {
+ case Calibration::SIZE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.size.calibration: none\n");
+ break;
+ case Calibration::SIZE_CALIBRATION_NORMALIZED:
+ dump.append(INDENT4 "touch.size.calibration: normalized\n");
+ break;
+ default:
+ assert(false);
+ }
+
+ // Orientation
+ switch (mCalibration.orientationCalibration) {
+ case Calibration::ORIENTATION_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.orientation.calibration: none\n");
+ break;
+ case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
+ dump.append(INDENT4 "touch.orientation.calibration: interpolated\n");
+ break;
+ case Calibration::ORIENTATION_CALIBRATION_VECTOR:
+ dump.append(INDENT4 "touch.orientation.calibration: vector\n");
+ break;
+ default:
+ assert(false);
+ }
+}
+
+void TouchInputMapper::reset() {
+ // Synthesize touch up event if touch is currently down.
+ // This will also take care of finishing virtual key processing if needed.
+ if (mLastTouch.pointerCount != 0) {
+ nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
+ mCurrentTouch.clear();
+ syncTouch(when, true);
+ }
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+ initializeLocked();
+ } // release lock
+
+ InputMapper::reset();
+}
+
+void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) {
+ uint32_t policyFlags = 0;
+
+ // Preprocess pointer data.
+
+ if (mParameters.useBadTouchFilter) {
+ if (applyBadTouchFilter()) {
+ havePointerIds = false;
+ }
+ }
+
+ if (mParameters.useJumpyTouchFilter) {
+ if (applyJumpyTouchFilter()) {
+ havePointerIds = false;
+ }
+ }
+
+ if (! havePointerIds) {
+ calculatePointerIds();
+ }
+
+ TouchData temp;
+ TouchData* savedTouch;
+ if (mParameters.useAveragingTouchFilter) {
+ temp.copyFrom(mCurrentTouch);
+ savedTouch = & temp;
+
+ applyAveragingTouchFilter();
+ } else {
+ savedTouch = & mCurrentTouch;
+ }
+
+ // Process touches and virtual keys.
+
+ TouchResult touchResult = consumeOffScreenTouches(when, policyFlags);
+ if (touchResult == DISPATCH_TOUCH) {
+ detectGestures(when);
+ dispatchTouches(when, policyFlags);
+ }
+
+ // Copy current touch to last touch in preparation for the next cycle.
+
+ if (touchResult == DROP_STROKE) {
+ mLastTouch.clear();
+ } else {
+ mLastTouch.copyFrom(*savedTouch);
+ }
+}
+
+TouchInputMapper::TouchResult TouchInputMapper::consumeOffScreenTouches(
+ nsecs_t when, uint32_t policyFlags) {
+ int32_t keyEventAction, keyEventFlags;
+ int32_t keyCode, scanCode, downTime;
+ TouchResult touchResult;
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ // Update surface size and orientation, including virtual key positions.
+ if (! configureSurfaceLocked()) {
+ return DROP_STROKE;
+ }
+
+ // Check for virtual key press.
+ if (mLocked.currentVirtualKey.down) {
+ if (mCurrentTouch.pointerCount == 0) {
+ // Pointer went up while virtual key was down.
+ mLocked.currentVirtualKey.down = false;
+#if DEBUG_VIRTUAL_KEYS
+ LOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d",
+ mLocked.currentVirtualKey.keyCode, mLocked.currentVirtualKey.scanCode);
+#endif
+ keyEventAction = AKEY_EVENT_ACTION_UP;
+ keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
+ touchResult = SKIP_TOUCH;
+ goto DispatchVirtualKey;
+ }
+
+ if (mCurrentTouch.pointerCount == 1) {
+ int32_t x = mCurrentTouch.pointers[0].x;
+ int32_t y = mCurrentTouch.pointers[0].y;
+ const VirtualKey* virtualKey = findVirtualKeyHitLocked(x, y);
+ if (virtualKey && virtualKey->keyCode == mLocked.currentVirtualKey.keyCode) {
+ // Pointer is still within the space of the virtual key.
+ return SKIP_TOUCH;
+ }
+ }
+
+ // Pointer left virtual key area or another pointer also went down.
+ // Send key cancellation and drop the stroke so subsequent motions will be
+ // considered fresh downs. This is useful when the user swipes away from the
+ // virtual key area into the main display surface.
+ mLocked.currentVirtualKey.down = false;
+#if DEBUG_VIRTUAL_KEYS
+ LOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d",
+ mLocked.currentVirtualKey.keyCode, mLocked.currentVirtualKey.scanCode);
+#endif
+ keyEventAction = AKEY_EVENT_ACTION_UP;
+ keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY
+ | AKEY_EVENT_FLAG_CANCELED;
+
+ // Check whether the pointer moved inside the display area where we should
+ // start a new stroke.
+ int32_t x = mCurrentTouch.pointers[0].x;
+ int32_t y = mCurrentTouch.pointers[0].y;
+ if (isPointInsideSurfaceLocked(x, y)) {
+ mLastTouch.clear();
+ touchResult = DISPATCH_TOUCH;
+ } else {
+ touchResult = DROP_STROKE;
+ }
+ } else {
+ if (mCurrentTouch.pointerCount >= 1 && mLastTouch.pointerCount == 0) {
+ // Pointer just went down. Handle off-screen touches, if needed.
+ int32_t x = mCurrentTouch.pointers[0].x;
+ int32_t y = mCurrentTouch.pointers[0].y;
+ if (! isPointInsideSurfaceLocked(x, y)) {
+ // If exactly one pointer went down, check for virtual key hit.
+ // Otherwise we will drop the entire stroke.
+ if (mCurrentTouch.pointerCount == 1) {
+ const VirtualKey* virtualKey = findVirtualKeyHitLocked(x, y);
+ if (virtualKey) {
+ if (mContext->shouldDropVirtualKey(when, getDevice(),
+ virtualKey->keyCode, virtualKey->scanCode)) {
+ return DROP_STROKE;
+ }
+
+ mLocked.currentVirtualKey.down = true;
+ mLocked.currentVirtualKey.downTime = when;
+ mLocked.currentVirtualKey.keyCode = virtualKey->keyCode;
+ mLocked.currentVirtualKey.scanCode = virtualKey->scanCode;
+#if DEBUG_VIRTUAL_KEYS
+ LOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d",
+ mLocked.currentVirtualKey.keyCode,
+ mLocked.currentVirtualKey.scanCode);
+#endif
+ keyEventAction = AKEY_EVENT_ACTION_DOWN;
+ keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM
+ | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
+ touchResult = SKIP_TOUCH;
+ goto DispatchVirtualKey;
+ }
+ }
+ return DROP_STROKE;
+ }
+ }
+ return DISPATCH_TOUCH;
+ }
+
+ DispatchVirtualKey:
+ // Collect remaining state needed to dispatch virtual key.
+ keyCode = mLocked.currentVirtualKey.keyCode;
+ scanCode = mLocked.currentVirtualKey.scanCode;
+ downTime = mLocked.currentVirtualKey.downTime;
+ } // release lock
+
+ // Dispatch virtual key.
+ int32_t metaState = mContext->getGlobalMetaState();
+ policyFlags |= POLICY_FLAG_VIRTUAL;
+ getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags,
+ keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime);
+ return touchResult;
+}
+
+void TouchInputMapper::detectGestures(nsecs_t when) {
+ // Disable all virtual key touches that happen within a short time interval of the
+ // most recent touch. The idea is to filter out stray virtual key presses when
+ // interacting with the touch screen.
+ //
+ // Problems we're trying to solve:
+ //
+ // 1. While scrolling a list or dragging the window shade, the user swipes down into a
+ // virtual key area that is implemented by a separate touch panel and accidentally
+ // triggers a virtual key.
+ //
+ // 2. While typing in the on screen keyboard, the user taps slightly outside the screen
+ // area and accidentally triggers a virtual key. This often happens when virtual keys
+ // are layed out below the screen near to where the on screen keyboard's space bar
+ // is displayed.
+ if (mParameters.virtualKeyQuietTime > 0 && mCurrentTouch.pointerCount != 0) {
+ mContext->disableVirtualKeysUntil(when + mParameters.virtualKeyQuietTime);
+ }
+}
+
+void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
+ uint32_t currentPointerCount = mCurrentTouch.pointerCount;
+ uint32_t lastPointerCount = mLastTouch.pointerCount;
+ if (currentPointerCount == 0 && lastPointerCount == 0) {
+ return; // nothing to do!
+ }
+
+ BitSet32 currentIdBits = mCurrentTouch.idBits;
+ BitSet32 lastIdBits = mLastTouch.idBits;
+
+ if (currentIdBits == lastIdBits) {
+ // No pointer id changes so this is a move event.
+ // The dispatcher takes care of batching moves so we don't have to deal with that here.
+ int32_t motionEventAction = AMOTION_EVENT_ACTION_MOVE;
+ dispatchTouch(when, policyFlags, & mCurrentTouch,
+ currentIdBits, -1, currentPointerCount, motionEventAction);
+ } else {
+ // There may be pointers going up and pointers going down and pointers moving
+ // all at the same time.
+ BitSet32 upIdBits(lastIdBits.value & ~ currentIdBits.value);
+ BitSet32 downIdBits(currentIdBits.value & ~ lastIdBits.value);
+ BitSet32 activeIdBits(lastIdBits.value);
+ uint32_t pointerCount = lastPointerCount;
+
+ // Produce an intermediate representation of the touch data that consists of the
+ // old location of pointers that have just gone up and the new location of pointers that
+ // have just moved but omits the location of pointers that have just gone down.
+ TouchData interimTouch;
+ interimTouch.copyFrom(mLastTouch);
+
+ BitSet32 moveIdBits(lastIdBits.value & currentIdBits.value);
+ bool moveNeeded = false;
+ while (!moveIdBits.isEmpty()) {
+ uint32_t moveId = moveIdBits.firstMarkedBit();
+ moveIdBits.clearBit(moveId);
+
+ int32_t oldIndex = mLastTouch.idToIndex[moveId];
+ int32_t newIndex = mCurrentTouch.idToIndex[moveId];
+ if (mLastTouch.pointers[oldIndex] != mCurrentTouch.pointers[newIndex]) {
+ interimTouch.pointers[oldIndex] = mCurrentTouch.pointers[newIndex];
+ moveNeeded = true;
+ }
+ }
+
+ // Dispatch pointer up events using the interim pointer locations.
+ while (!upIdBits.isEmpty()) {
+ uint32_t upId = upIdBits.firstMarkedBit();
+ upIdBits.clearBit(upId);
+ BitSet32 oldActiveIdBits = activeIdBits;
+ activeIdBits.clearBit(upId);
+
+ int32_t motionEventAction;
+ if (activeIdBits.isEmpty()) {
+ motionEventAction = AMOTION_EVENT_ACTION_UP;
+ } else {
+ motionEventAction = AMOTION_EVENT_ACTION_POINTER_UP;
+ }
+
+ dispatchTouch(when, policyFlags, &interimTouch,
+ oldActiveIdBits, upId, pointerCount, motionEventAction);
+ pointerCount -= 1;
+ }
+
+ // Dispatch move events if any of the remaining pointers moved from their old locations.
+ // Although applications receive new locations as part of individual pointer up
+ // events, they do not generally handle them except when presented in a move event.
+ if (moveNeeded) {
+ dispatchTouch(when, policyFlags, &mCurrentTouch,
+ activeIdBits, -1, pointerCount, AMOTION_EVENT_ACTION_MOVE);
+ }
+
+ // Dispatch pointer down events using the new pointer locations.
+ while (!downIdBits.isEmpty()) {
+ uint32_t downId = downIdBits.firstMarkedBit();
+ downIdBits.clearBit(downId);
+ BitSet32 oldActiveIdBits = activeIdBits;
+ activeIdBits.markBit(downId);
+
+ int32_t motionEventAction;
+ if (oldActiveIdBits.isEmpty()) {
+ motionEventAction = AMOTION_EVENT_ACTION_DOWN;
+ mDownTime = when;
+ } else {
+ motionEventAction = AMOTION_EVENT_ACTION_POINTER_DOWN;
+ }
+
+ pointerCount += 1;
+ dispatchTouch(when, policyFlags, &mCurrentTouch,
+ activeIdBits, downId, pointerCount, motionEventAction);
+ }
+ }
+}
+
+void TouchInputMapper::dispatchTouch(nsecs_t when, uint32_t policyFlags,
+ TouchData* touch, BitSet32 idBits, uint32_t changedId, uint32_t pointerCount,
+ int32_t motionEventAction) {
+ int32_t pointerIds[MAX_POINTERS];
+ PointerCoords pointerCoords[MAX_POINTERS];
+ int32_t motionEventEdgeFlags = 0;
+ float xPrecision, yPrecision;
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ // Walk through the the active pointers and map touch screen coordinates (TouchData) into
+ // display coordinates (PointerCoords) and adjust for display orientation.
+ for (uint32_t outIndex = 0; ! idBits.isEmpty(); outIndex++) {
+ uint32_t id = idBits.firstMarkedBit();
+ idBits.clearBit(id);
+ uint32_t inIndex = touch->idToIndex[id];
+
+ const PointerData& in = touch->pointers[inIndex];
+
+ // X and Y
+ float x = float(in.x - mLocked.xOrigin) * mLocked.xScale;
+ float y = float(in.y - mLocked.yOrigin) * mLocked.yScale;
+
+ // ToolMajor and ToolMinor
+ float toolMajor, toolMinor;
+ switch (mCalibration.toolSizeCalibration) {
+ case Calibration::TOOL_SIZE_CALIBRATION_GEOMETRIC:
+ toolMajor = in.toolMajor * mLocked.geometricScale;
+ if (mRawAxes.toolMinor.valid) {
+ toolMinor = in.toolMinor * mLocked.geometricScale;
+ } else {
+ toolMinor = toolMajor;
+ }
+ break;
+ case Calibration::TOOL_SIZE_CALIBRATION_LINEAR:
+ toolMajor = in.toolMajor != 0
+ ? in.toolMajor * mLocked.toolSizeLinearScale + mLocked.toolSizeLinearBias
+ : 0;
+ if (mRawAxes.toolMinor.valid) {
+ toolMinor = in.toolMinor != 0
+ ? in.toolMinor * mLocked.toolSizeLinearScale
+ + mLocked.toolSizeLinearBias
+ : 0;
+ } else {
+ toolMinor = toolMajor;
+ }
+ break;
+ case Calibration::TOOL_SIZE_CALIBRATION_AREA:
+ if (in.toolMajor != 0) {
+ float diameter = sqrtf(in.toolMajor
+ * mLocked.toolSizeAreaScale + mLocked.toolSizeAreaBias);
+ toolMajor = diameter * mLocked.toolSizeLinearScale + mLocked.toolSizeLinearBias;
+ } else {
+ toolMajor = 0;
+ }
+ toolMinor = toolMajor;
+ break;
+ default:
+ toolMajor = 0;
+ toolMinor = 0;
+ break;
+ }
+
+ if (mCalibration.haveToolSizeIsSummed && mCalibration.toolSizeIsSummed) {
+ toolMajor /= pointerCount;
+ toolMinor /= pointerCount;
+ }
+
+ // Pressure
+ float rawPressure;
+ switch (mCalibration.pressureSource) {
+ case Calibration::PRESSURE_SOURCE_PRESSURE:
+ rawPressure = in.pressure;
+ break;
+ case Calibration::PRESSURE_SOURCE_TOUCH:
+ rawPressure = in.touchMajor;
+ break;
+ default:
+ rawPressure = 0;
+ }
+
+ float pressure;
+ switch (mCalibration.pressureCalibration) {
+ case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
+ case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
+ pressure = rawPressure * mLocked.pressureScale;
+ break;
+ default:
+ pressure = 1;
+ break;
+ }
+
+ // TouchMajor and TouchMinor
+ float touchMajor, touchMinor;
+ switch (mCalibration.touchSizeCalibration) {
+ case Calibration::TOUCH_SIZE_CALIBRATION_GEOMETRIC:
+ touchMajor = in.touchMajor * mLocked.geometricScale;
+ if (mRawAxes.touchMinor.valid) {
+ touchMinor = in.touchMinor * mLocked.geometricScale;
+ } else {
+ touchMinor = touchMajor;
+ }
+ break;
+ case Calibration::TOUCH_SIZE_CALIBRATION_PRESSURE:
+ touchMajor = toolMajor * pressure;
+ touchMinor = toolMinor * pressure;
+ break;
+ default:
+ touchMajor = 0;
+ touchMinor = 0;
+ break;
+ }
+
+ if (touchMajor > toolMajor) {
+ touchMajor = toolMajor;
+ }
+ if (touchMinor > toolMinor) {
+ touchMinor = toolMinor;
+ }
+
+ // Size
+ float size;
+ switch (mCalibration.sizeCalibration) {
+ case Calibration::SIZE_CALIBRATION_NORMALIZED: {
+ float rawSize = mRawAxes.toolMinor.valid
+ ? avg(in.toolMajor, in.toolMinor)
+ : in.toolMajor;
+ size = rawSize * mLocked.sizeScale;
+ break;
+ }
+ default:
+ size = 0;
+ break;
+ }
+
+ // Orientation
+ float orientation;
+ switch (mCalibration.orientationCalibration) {
+ case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
+ orientation = in.orientation * mLocked.orientationScale;
+ break;
+ case Calibration::ORIENTATION_CALIBRATION_VECTOR: {
+ int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4);
+ int32_t c2 = signExtendNybble(in.orientation & 0x0f);
+ if (c1 != 0 || c2 != 0) {
+ orientation = atan2f(c1, c2) * 0.5f;
+ float minorAxisScale = (16.0f - pythag(c1, c2)) / 16.0f;
+ toolMinor *= minorAxisScale;
+ touchMinor *= minorAxisScale;
+ } else {
+ orientation = 0;
+ }
+ break;
+ }
+ default:
+ orientation = 0;
+ }
+
+ // Adjust coords for orientation.
+ switch (mLocked.surfaceOrientation) {
+ case DISPLAY_ORIENTATION_90: {
+ float xTemp = x;
+ x = y;
+ y = mLocked.surfaceWidth - xTemp;
+ orientation -= M_PI_2;
+ if (orientation < - M_PI_2) {
+ orientation += M_PI;
+ }
+ break;
+ }
+ case DISPLAY_ORIENTATION_180: {
+ x = mLocked.surfaceWidth - x;
+ y = mLocked.surfaceHeight - y;
+ break;
+ }
+ case DISPLAY_ORIENTATION_270: {
+ float xTemp = x;
+ x = mLocked.surfaceHeight - y;
+ y = xTemp;
+ orientation += M_PI_2;
+ if (orientation > M_PI_2) {
+ orientation -= M_PI;
+ }
+ break;
+ }
+ }
+
+ // Write output coords.
+ PointerCoords& out = pointerCoords[outIndex];
+ out.x = x;
+ out.y = y;
+ out.pressure = pressure;
+ out.size = size;
+ out.touchMajor = touchMajor;
+ out.touchMinor = touchMinor;
+ out.toolMajor = toolMajor;
+ out.toolMinor = toolMinor;
+ out.orientation = orientation;
+
+ pointerIds[outIndex] = int32_t(id);
+
+ if (id == changedId) {
+ motionEventAction |= outIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+ }
+ }
+
+ // Check edge flags by looking only at the first pointer since the flags are
+ // global to the event.
+ if (motionEventAction == AMOTION_EVENT_ACTION_DOWN) {
+ if (pointerCoords[0].x <= 0) {
+ motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_LEFT;
+ } else if (pointerCoords[0].x >= mLocked.orientedSurfaceWidth) {
+ motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_RIGHT;
+ }
+ if (pointerCoords[0].y <= 0) {
+ motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_TOP;
+ } else if (pointerCoords[0].y >= mLocked.orientedSurfaceHeight) {
+ motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_BOTTOM;
+ }
+ }
+
+ xPrecision = mLocked.orientedXPrecision;
+ yPrecision = mLocked.orientedYPrecision;
+ } // release lock
+
+ getDispatcher()->notifyMotion(when, getDeviceId(), mSources, policyFlags,
+ motionEventAction, 0, getContext()->getGlobalMetaState(), motionEventEdgeFlags,
+ pointerCount, pointerIds, pointerCoords,
+ xPrecision, yPrecision, mDownTime);
+}
+
+bool TouchInputMapper::isPointInsideSurfaceLocked(int32_t x, int32_t y) {
+ if (mRawAxes.x.valid && mRawAxes.y.valid) {
+ return x >= mRawAxes.x.minValue && x <= mRawAxes.x.maxValue
+ && y >= mRawAxes.y.minValue && y <= mRawAxes.y.maxValue;
+ }
+ return true;
+}
+
+const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHitLocked(
+ int32_t x, int32_t y) {
+ size_t numVirtualKeys = mLocked.virtualKeys.size();
+ for (size_t i = 0; i < numVirtualKeys; i++) {
+ const VirtualKey& virtualKey = mLocked.virtualKeys[i];
+
+#if DEBUG_VIRTUAL_KEYS
+ LOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, "
+ "left=%d, top=%d, right=%d, bottom=%d",
+ x, y,
+ virtualKey.keyCode, virtualKey.scanCode,
+ virtualKey.hitLeft, virtualKey.hitTop,
+ virtualKey.hitRight, virtualKey.hitBottom);
+#endif
+
+ if (virtualKey.isHit(x, y)) {
+ return & virtualKey;
+ }
+ }
+
+ return NULL;
+}
+
+void TouchInputMapper::calculatePointerIds() {
+ uint32_t currentPointerCount = mCurrentTouch.pointerCount;
+ uint32_t lastPointerCount = mLastTouch.pointerCount;
+
+ if (currentPointerCount == 0) {
+ // No pointers to assign.
+ mCurrentTouch.idBits.clear();
+ } else if (lastPointerCount == 0) {
+ // All pointers are new.
+ mCurrentTouch.idBits.clear();
+ for (uint32_t i = 0; i < currentPointerCount; i++) {
+ mCurrentTouch.pointers[i].id = i;
+ mCurrentTouch.idToIndex[i] = i;
+ mCurrentTouch.idBits.markBit(i);
+ }
+ } else if (currentPointerCount == 1 && lastPointerCount == 1) {
+ // Only one pointer and no change in count so it must have the same id as before.
+ uint32_t id = mLastTouch.pointers[0].id;
+ mCurrentTouch.pointers[0].id = id;
+ mCurrentTouch.idToIndex[id] = 0;
+ mCurrentTouch.idBits.value = BitSet32::valueForBit(id);
+ } else {
+ // General case.
+ // We build a heap of squared euclidean distances between current and last pointers
+ // associated with the current and last pointer indices. Then, we find the best
+ // match (by distance) for each current pointer.
+ PointerDistanceHeapElement heap[MAX_POINTERS * MAX_POINTERS];
+
+ uint32_t heapSize = 0;
+ for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount;
+ currentPointerIndex++) {
+ for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount;
+ lastPointerIndex++) {
+ int64_t deltaX = mCurrentTouch.pointers[currentPointerIndex].x
+ - mLastTouch.pointers[lastPointerIndex].x;
+ int64_t deltaY = mCurrentTouch.pointers[currentPointerIndex].y
+ - mLastTouch.pointers[lastPointerIndex].y;
+
+ uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY);
+
+ // Insert new element into the heap (sift up).
+ heap[heapSize].currentPointerIndex = currentPointerIndex;
+ heap[heapSize].lastPointerIndex = lastPointerIndex;
+ heap[heapSize].distance = distance;
+ heapSize += 1;
+ }
+ }
+
+ // Heapify
+ for (uint32_t startIndex = heapSize / 2; startIndex != 0; ) {
+ startIndex -= 1;
+ for (uint32_t parentIndex = startIndex; ;) {
+ uint32_t childIndex = parentIndex * 2 + 1;
+ if (childIndex >= heapSize) {
+ break;
+ }
+
+ if (childIndex + 1 < heapSize
+ && heap[childIndex + 1].distance < heap[childIndex].distance) {
+ childIndex += 1;
+ }
+
+ if (heap[parentIndex].distance <= heap[childIndex].distance) {
+ break;
+ }
+
+ swap(heap[parentIndex], heap[childIndex]);
+ parentIndex = childIndex;
+ }
+ }
+
+#if DEBUG_POINTER_ASSIGNMENT
+ LOGD("calculatePointerIds - initial distance min-heap: size=%d", heapSize);
+ for (size_t i = 0; i < heapSize; i++) {
+ LOGD(" heap[%d]: cur=%d, last=%d, distance=%lld",
+ i, heap[i].currentPointerIndex, heap[i].lastPointerIndex,
+ heap[i].distance);
+ }
+#endif
+
+ // Pull matches out by increasing order of distance.
+ // To avoid reassigning pointers that have already been matched, the loop keeps track
+ // of which last and current pointers have been matched using the matchedXXXBits variables.
+ // It also tracks the used pointer id bits.
+ BitSet32 matchedLastBits(0);
+ BitSet32 matchedCurrentBits(0);
+ BitSet32 usedIdBits(0);
+ bool first = true;
+ for (uint32_t i = min(currentPointerCount, lastPointerCount); i > 0; i--) {
+ for (;;) {
+ if (first) {
+ // The first time through the loop, we just consume the root element of
+ // the heap (the one with smallest distance).
+ first = false;
+ } else {
+ // Previous iterations consumed the root element of the heap.
+ // Pop root element off of the heap (sift down).
+ heapSize -= 1;
+ assert(heapSize > 0);
+
+ // Sift down.
+ heap[0] = heap[heapSize];
+ for (uint32_t parentIndex = 0; ;) {
+ uint32_t childIndex = parentIndex * 2 + 1;
+ if (childIndex >= heapSize) {
+ break;
+ }
+
+ if (childIndex + 1 < heapSize
+ && heap[childIndex + 1].distance < heap[childIndex].distance) {
+ childIndex += 1;
+ }
+
+ if (heap[parentIndex].distance <= heap[childIndex].distance) {
+ break;
+ }
+
+ swap(heap[parentIndex], heap[childIndex]);
+ parentIndex = childIndex;
+ }
+
+#if DEBUG_POINTER_ASSIGNMENT
+ LOGD("calculatePointerIds - reduced distance min-heap: size=%d", heapSize);
+ for (size_t i = 0; i < heapSize; i++) {
+ LOGD(" heap[%d]: cur=%d, last=%d, distance=%lld",
+ i, heap[i].currentPointerIndex, heap[i].lastPointerIndex,
+ heap[i].distance);
+ }
+#endif
+ }
+
+ uint32_t currentPointerIndex = heap[0].currentPointerIndex;
+ if (matchedCurrentBits.hasBit(currentPointerIndex)) continue; // already matched
+
+ uint32_t lastPointerIndex = heap[0].lastPointerIndex;
+ if (matchedLastBits.hasBit(lastPointerIndex)) continue; // already matched
+
+ matchedCurrentBits.markBit(currentPointerIndex);
+ matchedLastBits.markBit(lastPointerIndex);
+
+ uint32_t id = mLastTouch.pointers[lastPointerIndex].id;
+ mCurrentTouch.pointers[currentPointerIndex].id = id;
+ mCurrentTouch.idToIndex[id] = currentPointerIndex;
+ usedIdBits.markBit(id);
+
+#if DEBUG_POINTER_ASSIGNMENT
+ LOGD("calculatePointerIds - matched: cur=%d, last=%d, id=%d, distance=%lld",
+ lastPointerIndex, currentPointerIndex, id, heap[0].distance);
+#endif
+ break;
+ }
+ }
+
+ // Assign fresh ids to new pointers.
+ if (currentPointerCount > lastPointerCount) {
+ for (uint32_t i = currentPointerCount - lastPointerCount; ;) {
+ uint32_t currentPointerIndex = matchedCurrentBits.firstUnmarkedBit();
+ uint32_t id = usedIdBits.firstUnmarkedBit();
+
+ mCurrentTouch.pointers[currentPointerIndex].id = id;
+ mCurrentTouch.idToIndex[id] = currentPointerIndex;
+ usedIdBits.markBit(id);
+
+#if DEBUG_POINTER_ASSIGNMENT
+ LOGD("calculatePointerIds - assigned: cur=%d, id=%d",
+ currentPointerIndex, id);
+#endif
+
+ if (--i == 0) break; // done
+ matchedCurrentBits.markBit(currentPointerIndex);
+ }
+ }
+
+ // Fix id bits.
+ mCurrentTouch.idBits = usedIdBits;
+ }
+}
+
+/* Special hack for devices that have bad screen data: if one of the
+ * points has moved more than a screen height from the last position,
+ * then drop it. */
+bool TouchInputMapper::applyBadTouchFilter() {
+ // This hack requires valid axis parameters.
+ if (! mRawAxes.y.valid) {
+ return false;
+ }
+
+ uint32_t pointerCount = mCurrentTouch.pointerCount;
+
+ // Nothing to do if there are no points.
+ if (pointerCount == 0) {
+ return false;
+ }
+
+ // Don't do anything if a finger is going down or up. We run
+ // here before assigning pointer IDs, so there isn't a good
+ // way to do per-finger matching.
+ if (pointerCount != mLastTouch.pointerCount) {
+ return false;
+ }
+
+ // We consider a single movement across more than a 7/16 of
+ // the long size of the screen to be bad. This was a magic value
+ // determined by looking at the maximum distance it is feasible
+ // to actually move in one sample.
+ int32_t maxDeltaY = mRawAxes.y.getRange() * 7 / 16;
+
+ // XXX The original code in InputDevice.java included commented out
+ // code for testing the X axis. Note that when we drop a point
+ // we don't actually restore the old X either. Strange.
+ // The old code also tries to track when bad points were previously
+ // detected but it turns out that due to the placement of a "break"
+ // at the end of the loop, we never set mDroppedBadPoint to true
+ // so it is effectively dead code.
+ // Need to figure out if the old code is busted or just overcomplicated
+ // but working as intended.
+
+ // Look through all new points and see if any are farther than
+ // acceptable from all previous points.
+ for (uint32_t i = pointerCount; i-- > 0; ) {
+ int32_t y = mCurrentTouch.pointers[i].y;
+ int32_t closestY = INT_MAX;
+ int32_t closestDeltaY = 0;
+
+#if DEBUG_HACKS
+ LOGD("BadTouchFilter: Looking at next point #%d: y=%d", i, y);
+#endif
+
+ for (uint32_t j = pointerCount; j-- > 0; ) {
+ int32_t lastY = mLastTouch.pointers[j].y;
+ int32_t deltaY = abs(y - lastY);
+
+#if DEBUG_HACKS
+ LOGD("BadTouchFilter: Comparing with last point #%d: y=%d deltaY=%d",
+ j, lastY, deltaY);
+#endif
+
+ if (deltaY < maxDeltaY) {
+ goto SkipSufficientlyClosePoint;
+ }
+ if (deltaY < closestDeltaY) {
+ closestDeltaY = deltaY;
+ closestY = lastY;
+ }
+ }
+
+ // Must not have found a close enough match.
+#if DEBUG_HACKS
+ LOGD("BadTouchFilter: Dropping bad point #%d: newY=%d oldY=%d deltaY=%d maxDeltaY=%d",
+ i, y, closestY, closestDeltaY, maxDeltaY);
+#endif
+
+ mCurrentTouch.pointers[i].y = closestY;
+ return true; // XXX original code only corrects one point
+
+ SkipSufficientlyClosePoint: ;
+ }
+
+ // No change.
+ return false;
+}
+
+/* Special hack for devices that have bad screen data: drop points where
+ * the coordinate value for one axis has jumped to the other pointer's location.
+ */
+bool TouchInputMapper::applyJumpyTouchFilter() {
+ // This hack requires valid axis parameters.
+ if (! mRawAxes.y.valid) {
+ return false;
+ }
+
+ uint32_t pointerCount = mCurrentTouch.pointerCount;
+ if (mLastTouch.pointerCount != pointerCount) {
+#if DEBUG_HACKS
+ LOGD("JumpyTouchFilter: Different pointer count %d -> %d",
+ mLastTouch.pointerCount, pointerCount);
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ LOGD(" Pointer %d (%d, %d)", i,
+ mCurrentTouch.pointers[i].x, mCurrentTouch.pointers[i].y);
+ }
+#endif
+
+ if (mJumpyTouchFilter.jumpyPointsDropped < JUMPY_TRANSITION_DROPS) {
+ if (mLastTouch.pointerCount == 1 && pointerCount == 2) {
+ // Just drop the first few events going from 1 to 2 pointers.
+ // They're bad often enough that they're not worth considering.
+ mCurrentTouch.pointerCount = 1;
+ mJumpyTouchFilter.jumpyPointsDropped += 1;
+
+#if DEBUG_HACKS
+ LOGD("JumpyTouchFilter: Pointer 2 dropped");
+#endif
+ return true;
+ } else if (mLastTouch.pointerCount == 2 && pointerCount == 1) {
+ // The event when we go from 2 -> 1 tends to be messed up too
+ mCurrentTouch.pointerCount = 2;
+ mCurrentTouch.pointers[0] = mLastTouch.pointers[0];
+ mCurrentTouch.pointers[1] = mLastTouch.pointers[1];
+ mJumpyTouchFilter.jumpyPointsDropped += 1;
+
+#if DEBUG_HACKS
+ for (int32_t i = 0; i < 2; i++) {
+ LOGD("JumpyTouchFilter: Pointer %d replaced (%d, %d)", i,
+ mCurrentTouch.pointers[i].x, mCurrentTouch.pointers[i].y);
+ }
+#endif
+ return true;
+ }
+ }
+ // Reset jumpy points dropped on other transitions or if limit exceeded.
+ mJumpyTouchFilter.jumpyPointsDropped = 0;
+
+#if DEBUG_HACKS
+ LOGD("JumpyTouchFilter: Transition - drop limit reset");
+#endif
+ return false;
+ }
+
+ // We have the same number of pointers as last time.
+ // A 'jumpy' point is one where the coordinate value for one axis
+ // has jumped to the other pointer's location. No need to do anything
+ // else if we only have one pointer.
+ if (pointerCount < 2) {
+ return false;
+ }
+
+ if (mJumpyTouchFilter.jumpyPointsDropped < JUMPY_DROP_LIMIT) {
+ int jumpyEpsilon = mRawAxes.y.getRange() / JUMPY_EPSILON_DIVISOR;
+
+ // We only replace the single worst jumpy point as characterized by pointer distance
+ // in a single axis.
+ int32_t badPointerIndex = -1;
+ int32_t badPointerReplacementIndex = -1;
+ int32_t badPointerDistance = INT_MIN; // distance to be corrected
+
+ for (uint32_t i = pointerCount; i-- > 0; ) {
+ int32_t x = mCurrentTouch.pointers[i].x;
+ int32_t y = mCurrentTouch.pointers[i].y;
+
+#if DEBUG_HACKS
+ LOGD("JumpyTouchFilter: Point %d (%d, %d)", i, x, y);
+#endif
+
+ // Check if a touch point is too close to another's coordinates
+ bool dropX = false, dropY = false;
+ for (uint32_t j = 0; j < pointerCount; j++) {
+ if (i == j) {
+ continue;
+ }
+
+ if (abs(x - mCurrentTouch.pointers[j].x) <= jumpyEpsilon) {
+ dropX = true;
+ break;
+ }
+
+ if (abs(y - mCurrentTouch.pointers[j].y) <= jumpyEpsilon) {
+ dropY = true;
+ break;
+ }
+ }
+ if (! dropX && ! dropY) {
+ continue; // not jumpy
+ }
+
+ // Find a replacement candidate by comparing with older points on the
+ // complementary (non-jumpy) axis.
+ int32_t distance = INT_MIN; // distance to be corrected
+ int32_t replacementIndex = -1;
+
+ if (dropX) {
+ // X looks too close. Find an older replacement point with a close Y.
+ int32_t smallestDeltaY = INT_MAX;
+ for (uint32_t j = 0; j < pointerCount; j++) {
+ int32_t deltaY = abs(y - mLastTouch.pointers[j].y);
+ if (deltaY < smallestDeltaY) {
+ smallestDeltaY = deltaY;
+ replacementIndex = j;
+ }
+ }
+ distance = abs(x - mLastTouch.pointers[replacementIndex].x);
+ } else {
+ // Y looks too close. Find an older replacement point with a close X.
+ int32_t smallestDeltaX = INT_MAX;
+ for (uint32_t j = 0; j < pointerCount; j++) {
+ int32_t deltaX = abs(x - mLastTouch.pointers[j].x);
+ if (deltaX < smallestDeltaX) {
+ smallestDeltaX = deltaX;
+ replacementIndex = j;
+ }
+ }
+ distance = abs(y - mLastTouch.pointers[replacementIndex].y);
+ }
+
+ // If replacing this pointer would correct a worse error than the previous ones
+ // considered, then use this replacement instead.
+ if (distance > badPointerDistance) {
+ badPointerIndex = i;
+ badPointerReplacementIndex = replacementIndex;
+ badPointerDistance = distance;
+ }
+ }
+
+ // Correct the jumpy pointer if one was found.
+ if (badPointerIndex >= 0) {
+#if DEBUG_HACKS
+ LOGD("JumpyTouchFilter: Replacing bad pointer %d with (%d, %d)",
+ badPointerIndex,
+ mLastTouch.pointers[badPointerReplacementIndex].x,
+ mLastTouch.pointers[badPointerReplacementIndex].y);
+#endif
+
+ mCurrentTouch.pointers[badPointerIndex].x =
+ mLastTouch.pointers[badPointerReplacementIndex].x;
+ mCurrentTouch.pointers[badPointerIndex].y =
+ mLastTouch.pointers[badPointerReplacementIndex].y;
+ mJumpyTouchFilter.jumpyPointsDropped += 1;
+ return true;
+ }
+ }
+
+ mJumpyTouchFilter.jumpyPointsDropped = 0;
+ return false;
+}
+
+/* Special hack for devices that have bad screen data: aggregate and
+ * compute averages of the coordinate data, to reduce the amount of
+ * jitter seen by applications. */
+void TouchInputMapper::applyAveragingTouchFilter() {
+ for (uint32_t currentIndex = 0; currentIndex < mCurrentTouch.pointerCount; currentIndex++) {
+ uint32_t id = mCurrentTouch.pointers[currentIndex].id;
+ int32_t x = mCurrentTouch.pointers[currentIndex].x;
+ int32_t y = mCurrentTouch.pointers[currentIndex].y;
+ int32_t pressure;
+ switch (mCalibration.pressureSource) {
+ case Calibration::PRESSURE_SOURCE_PRESSURE:
+ pressure = mCurrentTouch.pointers[currentIndex].pressure;
+ break;
+ case Calibration::PRESSURE_SOURCE_TOUCH:
+ pressure = mCurrentTouch.pointers[currentIndex].touchMajor;
+ break;
+ default:
+ pressure = 1;
+ break;
+ }
+
+ if (mLastTouch.idBits.hasBit(id)) {
+ // Pointer was down before and is still down now.
+ // Compute average over history trace.
+ uint32_t start = mAveragingTouchFilter.historyStart[id];
+ uint32_t end = mAveragingTouchFilter.historyEnd[id];
+
+ int64_t deltaX = x - mAveragingTouchFilter.historyData[end].pointers[id].x;
+ int64_t deltaY = y - mAveragingTouchFilter.historyData[end].pointers[id].y;
+ uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY);
+
+#if DEBUG_HACKS
+ LOGD("AveragingTouchFilter: Pointer id %d - Distance from last sample: %lld",
+ id, distance);
+#endif
+
+ if (distance < AVERAGING_DISTANCE_LIMIT) {
+ // Increment end index in preparation for recording new historical data.
+ end += 1;
+ if (end > AVERAGING_HISTORY_SIZE) {
+ end = 0;
+ }
+
+ // If the end index has looped back to the start index then we have filled
+ // the historical trace up to the desired size so we drop the historical
+ // data at the start of the trace.
+ if (end == start) {
+ start += 1;
+ if (start > AVERAGING_HISTORY_SIZE) {
+ start = 0;
+ }
+ }
+
+ // Add the raw data to the historical trace.
+ mAveragingTouchFilter.historyStart[id] = start;
+ mAveragingTouchFilter.historyEnd[id] = end;
+ mAveragingTouchFilter.historyData[end].pointers[id].x = x;
+ mAveragingTouchFilter.historyData[end].pointers[id].y = y;
+ mAveragingTouchFilter.historyData[end].pointers[id].pressure = pressure;
+
+ // Average over all historical positions in the trace by total pressure.
+ int32_t averagedX = 0;
+ int32_t averagedY = 0;
+ int32_t totalPressure = 0;
+ for (;;) {
+ int32_t historicalX = mAveragingTouchFilter.historyData[start].pointers[id].x;
+ int32_t historicalY = mAveragingTouchFilter.historyData[start].pointers[id].y;
+ int32_t historicalPressure = mAveragingTouchFilter.historyData[start]
+ .pointers[id].pressure;
+
+ averagedX += historicalX * historicalPressure;
+ averagedY += historicalY * historicalPressure;
+ totalPressure += historicalPressure;
+
+ if (start == end) {
+ break;
+ }
+
+ start += 1;
+ if (start > AVERAGING_HISTORY_SIZE) {
+ start = 0;
+ }
+ }
+
+ if (totalPressure != 0) {
+ averagedX /= totalPressure;
+ averagedY /= totalPressure;
+
+#if DEBUG_HACKS
+ LOGD("AveragingTouchFilter: Pointer id %d - "
+ "totalPressure=%d, averagedX=%d, averagedY=%d", id, totalPressure,
+ averagedX, averagedY);
+#endif
+
+ mCurrentTouch.pointers[currentIndex].x = averagedX;
+ mCurrentTouch.pointers[currentIndex].y = averagedY;
+ }
+ } else {
+#if DEBUG_HACKS
+ LOGD("AveragingTouchFilter: Pointer id %d - Exceeded max distance", id);
+#endif
+ }
+ } else {
+#if DEBUG_HACKS
+ LOGD("AveragingTouchFilter: Pointer id %d - Pointer went up", id);
+#endif
+ }
+
+ // Reset pointer history.
+ mAveragingTouchFilter.historyStart[id] = 0;
+ mAveragingTouchFilter.historyEnd[id] = 0;
+ mAveragingTouchFilter.historyData[0].pointers[id].x = x;
+ mAveragingTouchFilter.historyData[0].pointers[id].y = y;
+ mAveragingTouchFilter.historyData[0].pointers[id].pressure = pressure;
+ }
+}
+
+int32_t TouchInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mLocked.currentVirtualKey.down && mLocked.currentVirtualKey.keyCode == keyCode) {
+ return AKEY_STATE_VIRTUAL;
+ }
+
+ size_t numVirtualKeys = mLocked.virtualKeys.size();
+ for (size_t i = 0; i < numVirtualKeys; i++) {
+ const VirtualKey& virtualKey = mLocked.virtualKeys[i];
+ if (virtualKey.keyCode == keyCode) {
+ return AKEY_STATE_UP;
+ }
+ }
+ } // release lock
+
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t TouchInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mLocked.currentVirtualKey.down && mLocked.currentVirtualKey.scanCode == scanCode) {
+ return AKEY_STATE_VIRTUAL;
+ }
+
+ size_t numVirtualKeys = mLocked.virtualKeys.size();
+ for (size_t i = 0; i < numVirtualKeys; i++) {
+ const VirtualKey& virtualKey = mLocked.virtualKeys[i];
+ if (virtualKey.scanCode == scanCode) {
+ return AKEY_STATE_UP;
+ }
+ }
+ } // release lock
+
+ return AKEY_STATE_UNKNOWN;
+}
+
+bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ size_t numVirtualKeys = mLocked.virtualKeys.size();
+ for (size_t i = 0; i < numVirtualKeys; i++) {
+ const VirtualKey& virtualKey = mLocked.virtualKeys[i];
+
+ for (size_t i = 0; i < numCodes; i++) {
+ if (virtualKey.keyCode == keyCodes[i]) {
+ outFlags[i] = 1;
+ }
+ }
+ }
+ } // release lock
+
+ return true;
+}
+
+
+// --- SingleTouchInputMapper ---
+
+SingleTouchInputMapper::SingleTouchInputMapper(InputDevice* device) :
+ TouchInputMapper(device) {
+ initialize();
+}
+
+SingleTouchInputMapper::~SingleTouchInputMapper() {
+}
+
+void SingleTouchInputMapper::initialize() {
+ mAccumulator.clear();
+
+ mDown = false;
+ mX = 0;
+ mY = 0;
+ mPressure = 0; // default to 0 for devices that don't report pressure
+ mToolWidth = 0; // default to 0 for devices that don't report tool width
+}
+
+void SingleTouchInputMapper::reset() {
+ TouchInputMapper::reset();
+
+ initialize();
+ }
+
+void SingleTouchInputMapper::process(const RawEvent* rawEvent) {
+ switch (rawEvent->type) {
+ case EV_KEY:
+ switch (rawEvent->scanCode) {
+ case BTN_TOUCH:
+ mAccumulator.fields |= Accumulator::FIELD_BTN_TOUCH;
+ mAccumulator.btnTouch = rawEvent->value != 0;
+ // Don't sync immediately. Wait until the next SYN_REPORT since we might
+ // not have received valid position information yet. This logic assumes that
+ // BTN_TOUCH is always followed by SYN_REPORT as part of a complete packet.
+ break;
+ }
+ break;
+
+ case EV_ABS:
+ switch (rawEvent->scanCode) {
+ case ABS_X:
+ mAccumulator.fields |= Accumulator::FIELD_ABS_X;
+ mAccumulator.absX = rawEvent->value;
+ break;
+ case ABS_Y:
+ mAccumulator.fields |= Accumulator::FIELD_ABS_Y;
+ mAccumulator.absY = rawEvent->value;
+ break;
+ case ABS_PRESSURE:
+ mAccumulator.fields |= Accumulator::FIELD_ABS_PRESSURE;
+ mAccumulator.absPressure = rawEvent->value;
+ break;
+ case ABS_TOOL_WIDTH:
+ mAccumulator.fields |= Accumulator::FIELD_ABS_TOOL_WIDTH;
+ mAccumulator.absToolWidth = rawEvent->value;
+ break;
+ }
+ break;
+
+ case EV_SYN:
+ switch (rawEvent->scanCode) {
+ case SYN_REPORT:
+ sync(rawEvent->when);
+ break;
+ }
+ break;
+ }
+}
+
+void SingleTouchInputMapper::sync(nsecs_t when) {
+ uint32_t fields = mAccumulator.fields;
+ if (fields == 0) {
+ return; // no new state changes, so nothing to do
+ }
+
+ if (fields & Accumulator::FIELD_BTN_TOUCH) {
+ mDown = mAccumulator.btnTouch;
+ }
+
+ if (fields & Accumulator::FIELD_ABS_X) {
+ mX = mAccumulator.absX;
+ }
+
+ if (fields & Accumulator::FIELD_ABS_Y) {
+ mY = mAccumulator.absY;
+ }
+
+ if (fields & Accumulator::FIELD_ABS_PRESSURE) {
+ mPressure = mAccumulator.absPressure;
+ }
+
+ if (fields & Accumulator::FIELD_ABS_TOOL_WIDTH) {
+ mToolWidth = mAccumulator.absToolWidth;
+ }
+
+ mCurrentTouch.clear();
+
+ if (mDown) {
+ mCurrentTouch.pointerCount = 1;
+ mCurrentTouch.pointers[0].id = 0;
+ mCurrentTouch.pointers[0].x = mX;
+ mCurrentTouch.pointers[0].y = mY;
+ mCurrentTouch.pointers[0].pressure = mPressure;
+ mCurrentTouch.pointers[0].touchMajor = 0;
+ mCurrentTouch.pointers[0].touchMinor = 0;
+ mCurrentTouch.pointers[0].toolMajor = mToolWidth;
+ mCurrentTouch.pointers[0].toolMinor = mToolWidth;
+ mCurrentTouch.pointers[0].orientation = 0;
+ mCurrentTouch.idToIndex[0] = 0;
+ mCurrentTouch.idBits.markBit(0);
+ }
+
+ syncTouch(when, true);
+
+ mAccumulator.clear();
+}
+
+void SingleTouchInputMapper::configureRawAxes() {
+ TouchInputMapper::configureRawAxes();
+
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_X, & mRawAxes.x);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_Y, & mRawAxes.y);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_PRESSURE, & mRawAxes.pressure);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_TOOL_WIDTH, & mRawAxes.toolMajor);
+}
+
+
+// --- MultiTouchInputMapper ---
+
+MultiTouchInputMapper::MultiTouchInputMapper(InputDevice* device) :
+ TouchInputMapper(device) {
+ initialize();
+}
+
+MultiTouchInputMapper::~MultiTouchInputMapper() {
+}
+
+void MultiTouchInputMapper::initialize() {
+ mAccumulator.clear();
+}
+
+void MultiTouchInputMapper::reset() {
+ TouchInputMapper::reset();
+
+ initialize();
+}
+
+void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
+ switch (rawEvent->type) {
+ case EV_ABS: {
+ uint32_t pointerIndex = mAccumulator.pointerCount;
+ Accumulator::Pointer* pointer = & mAccumulator.pointers[pointerIndex];
+
+ switch (rawEvent->scanCode) {
+ case ABS_MT_POSITION_X:
+ pointer->fields |= Accumulator::FIELD_ABS_MT_POSITION_X;
+ pointer->absMTPositionX = rawEvent->value;
+ break;
+ case ABS_MT_POSITION_Y:
+ pointer->fields |= Accumulator::FIELD_ABS_MT_POSITION_Y;
+ pointer->absMTPositionY = rawEvent->value;
+ break;
+ case ABS_MT_TOUCH_MAJOR:
+ pointer->fields |= Accumulator::FIELD_ABS_MT_TOUCH_MAJOR;
+ pointer->absMTTouchMajor = rawEvent->value;
+ break;
+ case ABS_MT_TOUCH_MINOR:
+ pointer->fields |= Accumulator::FIELD_ABS_MT_TOUCH_MINOR;
+ pointer->absMTTouchMinor = rawEvent->value;
+ break;
+ case ABS_MT_WIDTH_MAJOR:
+ pointer->fields |= Accumulator::FIELD_ABS_MT_WIDTH_MAJOR;
+ pointer->absMTWidthMajor = rawEvent->value;
+ break;
+ case ABS_MT_WIDTH_MINOR:
+ pointer->fields |= Accumulator::FIELD_ABS_MT_WIDTH_MINOR;
+ pointer->absMTWidthMinor = rawEvent->value;
+ break;
+ case ABS_MT_ORIENTATION:
+ pointer->fields |= Accumulator::FIELD_ABS_MT_ORIENTATION;
+ pointer->absMTOrientation = rawEvent->value;
+ break;
+ case ABS_MT_TRACKING_ID:
+ pointer->fields |= Accumulator::FIELD_ABS_MT_TRACKING_ID;
+ pointer->absMTTrackingId = rawEvent->value;
+ break;
+ case ABS_MT_PRESSURE:
+ pointer->fields |= Accumulator::FIELD_ABS_MT_PRESSURE;
+ pointer->absMTPressure = rawEvent->value;
+ break;
+ }
+ break;
+ }
+
+ case EV_SYN:
+ switch (rawEvent->scanCode) {
+ case SYN_MT_REPORT: {
+ // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
+ uint32_t pointerIndex = mAccumulator.pointerCount;
+
+ if (mAccumulator.pointers[pointerIndex].fields) {
+ if (pointerIndex == MAX_POINTERS) {
+ LOGW("MultiTouch device driver returned more than maximum of %d pointers.",
+ MAX_POINTERS);
+ } else {
+ pointerIndex += 1;
+ mAccumulator.pointerCount = pointerIndex;
+ }
+ }
+
+ mAccumulator.pointers[pointerIndex].clear();
+ break;
+ }
+
+ case SYN_REPORT:
+ sync(rawEvent->when);
+ break;
+ }
+ break;
+ }
+}
+
+void MultiTouchInputMapper::sync(nsecs_t when) {
+ static const uint32_t REQUIRED_FIELDS =
+ Accumulator::FIELD_ABS_MT_POSITION_X | Accumulator::FIELD_ABS_MT_POSITION_Y;
+
+ uint32_t inCount = mAccumulator.pointerCount;
+ uint32_t outCount = 0;
+ bool havePointerIds = true;
+
+ mCurrentTouch.clear();
+
+ for (uint32_t inIndex = 0; inIndex < inCount; inIndex++) {
+ const Accumulator::Pointer& inPointer = mAccumulator.pointers[inIndex];
+ uint32_t fields = inPointer.fields;
+
+ if ((fields & REQUIRED_FIELDS) != REQUIRED_FIELDS) {
+ // Some drivers send empty MT sync packets without X / Y to indicate a pointer up.
+ // Drop this finger.
+ continue;
+ }
+
+ PointerData& outPointer = mCurrentTouch.pointers[outCount];
+ outPointer.x = inPointer.absMTPositionX;
+ outPointer.y = inPointer.absMTPositionY;
+
+ if (fields & Accumulator::FIELD_ABS_MT_PRESSURE) {
+ if (inPointer.absMTPressure <= 0) {
+ // Some devices send sync packets with X / Y but with a 0 pressure to indicate
+ // a pointer going up. Drop this finger.
+ continue;
+ }
+ outPointer.pressure = inPointer.absMTPressure;
+ } else {
+ // Default pressure to 0 if absent.
+ outPointer.pressure = 0;
+ }
+
+ if (fields & Accumulator::FIELD_ABS_MT_TOUCH_MAJOR) {
+ if (inPointer.absMTTouchMajor <= 0) {
+ // Some devices send sync packets with X / Y but with a 0 touch major to indicate
+ // a pointer going up. Drop this finger.
+ continue;
+ }
+ outPointer.touchMajor = inPointer.absMTTouchMajor;
+ } else {
+ // Default touch area to 0 if absent.
+ outPointer.touchMajor = 0;
+ }
+
+ if (fields & Accumulator::FIELD_ABS_MT_TOUCH_MINOR) {
+ outPointer.touchMinor = inPointer.absMTTouchMinor;
+ } else {
+ // Assume touch area is circular.
+ outPointer.touchMinor = outPointer.touchMajor;
+ }
+
+ if (fields & Accumulator::FIELD_ABS_MT_WIDTH_MAJOR) {
+ outPointer.toolMajor = inPointer.absMTWidthMajor;
+ } else {
+ // Default tool area to 0 if absent.
+ outPointer.toolMajor = 0;
+ }
+
+ if (fields & Accumulator::FIELD_ABS_MT_WIDTH_MINOR) {
+ outPointer.toolMinor = inPointer.absMTWidthMinor;
+ } else {
+ // Assume tool area is circular.
+ outPointer.toolMinor = outPointer.toolMajor;
+ }
+
+ if (fields & Accumulator::FIELD_ABS_MT_ORIENTATION) {
+ outPointer.orientation = inPointer.absMTOrientation;
+ } else {
+ // Default orientation to vertical if absent.
+ outPointer.orientation = 0;
+ }
+
+ // Assign pointer id using tracking id if available.
+ if (havePointerIds) {
+ if (fields & Accumulator::FIELD_ABS_MT_TRACKING_ID) {
+ uint32_t id = uint32_t(inPointer.absMTTrackingId);
+
+ if (id > MAX_POINTER_ID) {
+#if DEBUG_POINTERS
+ LOGD("Pointers: Ignoring driver provided pointer id %d because "
+ "it is larger than max supported id %d",
+ id, MAX_POINTER_ID);
+#endif
+ havePointerIds = false;
+ }
+ else {
+ outPointer.id = id;
+ mCurrentTouch.idToIndex[id] = outCount;
+ mCurrentTouch.idBits.markBit(id);
+ }
+ } else {
+ havePointerIds = false;
+ }
+ }
+
+ outCount += 1;
+ }
+
+ mCurrentTouch.pointerCount = outCount;
+
+ syncTouch(when, havePointerIds);
+
+ mAccumulator.clear();
+}
+
+void MultiTouchInputMapper::configureRawAxes() {
+ TouchInputMapper::configureRawAxes();
+
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_X, & mRawAxes.x);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_Y, & mRawAxes.y);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MAJOR, & mRawAxes.touchMajor);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MINOR, & mRawAxes.touchMinor);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MAJOR, & mRawAxes.toolMajor);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MINOR, & mRawAxes.toolMinor);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_ORIENTATION, & mRawAxes.orientation);
+ getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_PRESSURE, & mRawAxes.pressure);
+}
+
+
+} // namespace android
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
new file mode 100644
index 0000000..7619682
--- /dev/null
+++ b/services/input/InputReader.h
@@ -0,0 +1,973 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_READER_H
+#define _UI_INPUT_READER_H
+
+#include "EventHub.h"
+#include "InputDispatcher.h"
+#include "PointerController.h"
+
+#include <ui/Input.h>
+#include <ui/DisplayInfo.h>
+#include <utils/KeyedVector.h>
+#include <utils/threads.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/BitSet.h>
+
+#include <stddef.h>
+#include <unistd.h>
+
+namespace android {
+
+class InputDevice;
+class InputMapper;
+
+
+/*
+ * Input reader policy interface.
+ *
+ * The input reader policy is used by the input reader to interact with the Window Manager
+ * and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI. This interface is also mocked in the unit tests.
+ */
+class InputReaderPolicyInterface : public virtual RefBase {
+protected:
+ InputReaderPolicyInterface() { }
+ virtual ~InputReaderPolicyInterface() { }
+
+public:
+ /* Display orientations. */
+ enum {
+ ROTATION_0 = 0,
+ ROTATION_90 = 1,
+ ROTATION_180 = 2,
+ ROTATION_270 = 3
+ };
+
+ /* Gets information about the display with the specified id.
+ * Returns true if the display info is available, false otherwise.
+ */
+ virtual bool getDisplayInfo(int32_t displayId,
+ int32_t* width, int32_t* height, int32_t* orientation) = 0;
+
+ /* Determines whether to turn on some hacks we have to improve the touch interaction with a
+ * certain device whose screen currently is not all that good.
+ */
+ virtual bool filterTouchEvents() = 0;
+
+ /* Determines whether to turn on some hacks to improve touch interaction with another device
+ * where touch coordinate data can get corrupted.
+ */
+ virtual bool filterJumpyTouchEvents() = 0;
+
+ /* Gets the amount of time to disable virtual keys after the screen is touched
+ * in order to filter out accidental virtual key presses due to swiping gestures
+ * or taps near the edge of the display. May be 0 to disable the feature.
+ */
+ virtual nsecs_t getVirtualKeyQuietTime() = 0;
+
+ /* Gets the excluded device names for the platform. */
+ virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) = 0;
+
+ /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
+ virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) = 0;
+};
+
+
+/* Processes raw input events and sends cooked event data to an input dispatcher. */
+class InputReaderInterface : public virtual RefBase {
+protected:
+ InputReaderInterface() { }
+ virtual ~InputReaderInterface() { }
+
+public:
+ /* Dumps the state of the input reader.
+ *
+ * This method may be called on any thread (usually by the input manager). */
+ virtual void dump(String8& dump) = 0;
+
+ /* Runs a single iteration of the processing loop.
+ * Nominally reads and processes one incoming message from the EventHub.
+ *
+ * This method should be called on the input reader thread.
+ */
+ virtual void loopOnce() = 0;
+
+ /* Gets the current input device configuration.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual void getInputConfiguration(InputConfiguration* outConfiguration) = 0;
+
+ /* Gets information about the specified input device.
+ * Returns OK if the device information was obtained or NAME_NOT_FOUND if there
+ * was no such device.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) = 0;
+
+ /* Gets the list of all registered device ids. */
+ virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds) = 0;
+
+ /* Query current input state. */
+ virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t scanCode) = 0;
+ virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t keyCode) = 0;
+ virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
+ int32_t sw) = 0;
+
+ /* Determine whether physical keys exist for the given framework-domain key codes. */
+ virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
+ size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) = 0;
+};
+
+
+/* Internal interface used by individual input devices to access global input device state
+ * and parameters maintained by the input reader.
+ */
+class InputReaderContext {
+public:
+ InputReaderContext() { }
+ virtual ~InputReaderContext() { }
+
+ virtual void updateGlobalMetaState() = 0;
+ virtual int32_t getGlobalMetaState() = 0;
+
+ virtual void disableVirtualKeysUntil(nsecs_t time) = 0;
+ virtual bool shouldDropVirtualKey(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode) = 0;
+
+ virtual InputReaderPolicyInterface* getPolicy() = 0;
+ virtual InputDispatcherInterface* getDispatcher() = 0;
+ virtual EventHubInterface* getEventHub() = 0;
+};
+
+
+/* The input reader reads raw event data from the event hub and processes it into input events
+ * that it sends to the input dispatcher. Some functions of the input reader, such as early
+ * event filtering in low power states, are controlled by a separate policy object.
+ *
+ * IMPORTANT INVARIANT:
+ * Because the policy and dispatcher can potentially block or cause re-entrance into
+ * the input reader, the input reader never calls into other components while holding
+ * an exclusive internal lock whenever re-entrance can happen.
+ */
+class InputReader : public InputReaderInterface, protected InputReaderContext {
+public:
+ InputReader(const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& policy,
+ const sp<InputDispatcherInterface>& dispatcher);
+ virtual ~InputReader();
+
+ virtual void dump(String8& dump);
+
+ virtual void loopOnce();
+
+ virtual void getInputConfiguration(InputConfiguration* outConfiguration);
+
+ virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo);
+ virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds);
+
+ virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t scanCode);
+ virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t keyCode);
+ virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
+ int32_t sw);
+
+ virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
+ size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags);
+
+protected:
+ // These methods are protected virtual so they can be overridden and instrumented
+ // by test cases.
+ virtual InputDevice* createDevice(int32_t deviceId, const String8& name, uint32_t classes);
+
+private:
+ sp<EventHubInterface> mEventHub;
+ sp<InputReaderPolicyInterface> mPolicy;
+ sp<InputDispatcherInterface> mDispatcher;
+
+ virtual InputReaderPolicyInterface* getPolicy() { return mPolicy.get(); }
+ virtual InputDispatcherInterface* getDispatcher() { return mDispatcher.get(); }
+ virtual EventHubInterface* getEventHub() { return mEventHub.get(); }
+
+ // This reader/writer lock guards the list of input devices.
+ // The writer lock must be held whenever the list of input devices is modified
+ // and then promptly released.
+ // The reader lock must be held whenever the list of input devices is traversed or an
+ // input device in the list is accessed.
+ // This lock only protects the registry and prevents inadvertent deletion of device objects
+ // that are in use. Individual devices are responsible for guarding their own internal state
+ // as needed for concurrent operation.
+ RWLock mDeviceRegistryLock;
+ KeyedVector<int32_t, InputDevice*> mDevices;
+
+ // low-level input event decoding and device management
+ void process(const RawEvent* rawEvent);
+
+ void addDevice(int32_t deviceId);
+ void removeDevice(int32_t deviceId);
+ void configureExcludedDevices();
+
+ void consumeEvent(const RawEvent* rawEvent);
+
+ void handleConfigurationChanged(nsecs_t when);
+
+ // state management for all devices
+ Mutex mStateLock;
+
+ int32_t mGlobalMetaState;
+ virtual void updateGlobalMetaState();
+ virtual int32_t getGlobalMetaState();
+
+ InputConfiguration mInputConfiguration;
+ void updateInputConfiguration();
+
+ nsecs_t mDisableVirtualKeysTimeout;
+ virtual void disableVirtualKeysUntil(nsecs_t time);
+ virtual bool shouldDropVirtualKey(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode);
+
+ // state queries
+ typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code);
+ int32_t getState(int32_t deviceId, uint32_t sourceMask, int32_t code,
+ GetStateFunc getStateFunc);
+ bool markSupportedKeyCodes(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+};
+
+
+/* Reads raw events from the event hub and processes them, endlessly. */
+class InputReaderThread : public Thread {
+public:
+ InputReaderThread(const sp<InputReaderInterface>& reader);
+ virtual ~InputReaderThread();
+
+private:
+ sp<InputReaderInterface> mReader;
+
+ virtual bool threadLoop();
+};
+
+
+/* Represents the state of a single input device. */
+class InputDevice {
+public:
+ InputDevice(InputReaderContext* context, int32_t id, const String8& name);
+ ~InputDevice();
+
+ inline InputReaderContext* getContext() { return mContext; }
+ inline int32_t getId() { return mId; }
+ inline const String8& getName() { return mName; }
+ inline uint32_t getSources() { return mSources; }
+
+ inline bool isIgnored() { return mMappers.isEmpty(); }
+
+ void dump(String8& dump);
+ void addMapper(InputMapper* mapper);
+ void configure();
+ void reset();
+ void process(const RawEvent* rawEvent);
+
+ void getDeviceInfo(InputDeviceInfo* outDeviceInfo);
+ int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+ int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+ int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+ bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+
+ int32_t getMetaState();
+
+ inline const PropertyMap& getConfiguration() {
+ return mConfiguration;
+ }
+
+private:
+ InputReaderContext* mContext;
+ int32_t mId;
+
+ Vector<InputMapper*> mMappers;
+
+ String8 mName;
+ uint32_t mSources;
+
+ typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code);
+ int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc);
+
+ PropertyMap mConfiguration;
+};
+
+
+/* An input mapper transforms raw input events into cooked event data.
+ * A single input device can have multiple associated input mappers in order to interpret
+ * different classes of events.
+ */
+class InputMapper {
+public:
+ InputMapper(InputDevice* device);
+ virtual ~InputMapper();
+
+ inline InputDevice* getDevice() { return mDevice; }
+ inline int32_t getDeviceId() { return mDevice->getId(); }
+ inline const String8 getDeviceName() { return mDevice->getName(); }
+ inline InputReaderContext* getContext() { return mContext; }
+ inline InputReaderPolicyInterface* getPolicy() { return mContext->getPolicy(); }
+ inline InputDispatcherInterface* getDispatcher() { return mContext->getDispatcher(); }
+ inline EventHubInterface* getEventHub() { return mContext->getEventHub(); }
+
+ virtual uint32_t getSources() = 0;
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure();
+ virtual void reset();
+ virtual void process(const RawEvent* rawEvent) = 0;
+
+ virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+ virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+ virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+
+ virtual int32_t getMetaState();
+
+protected:
+ InputDevice* mDevice;
+ InputReaderContext* mContext;
+};
+
+
+class SwitchInputMapper : public InputMapper {
+public:
+ SwitchInputMapper(InputDevice* device);
+ virtual ~SwitchInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+
+private:
+ void processSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue);
+};
+
+
+class KeyboardInputMapper : public InputMapper {
+public:
+ KeyboardInputMapper(InputDevice* device, uint32_t sources, int32_t keyboardType);
+ virtual ~KeyboardInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure();
+ virtual void reset();
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+ virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+
+ virtual int32_t getMetaState();
+
+private:
+ Mutex mLock;
+
+ struct KeyDown {
+ int32_t keyCode;
+ int32_t scanCode;
+ };
+
+ uint32_t mSources;
+ int32_t mKeyboardType;
+
+ // Immutable configuration parameters.
+ struct Parameters {
+ int32_t associatedDisplayId;
+ bool orientationAware;
+ } mParameters;
+
+ struct LockedState {
+ Vector<KeyDown> keyDowns; // keys that are down
+ int32_t metaState;
+ nsecs_t downTime; // time of most recent key down
+
+ struct LedState {
+ bool avail; // led is available
+ bool on; // we think the led is currently on
+ };
+ LedState capsLockLedState;
+ LedState numLockLedState;
+ LedState scrollLockLedState;
+ } mLocked;
+
+ void initializeLocked();
+
+ void configureParameters();
+ void dumpParameters(String8& dump);
+
+ bool isKeyboardOrGamepadKey(int32_t scanCode);
+
+ void processKey(nsecs_t when, bool down, int32_t keyCode, int32_t scanCode,
+ uint32_t policyFlags);
+
+ ssize_t findKeyDownLocked(int32_t scanCode);
+
+ void resetLedStateLocked();
+ void initializeLedStateLocked(LockedState::LedState& ledState, int32_t led);
+ void updateLedStateLocked(bool reset);
+ void updateLedStateForModifierLocked(LockedState::LedState& ledState, int32_t led,
+ int32_t modifier, bool reset);
+};
+
+
+class CursorInputMapper : public InputMapper {
+public:
+ CursorInputMapper(InputDevice* device);
+ virtual ~CursorInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure();
+ virtual void reset();
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+
+private:
+ // Amount that trackball needs to move in order to generate a key event.
+ static const int32_t TRACKBALL_MOVEMENT_THRESHOLD = 6;
+
+ Mutex mLock;
+
+ // Immutable configuration parameters.
+ struct Parameters {
+ enum Mode {
+ MODE_POINTER,
+ MODE_NAVIGATION,
+ };
+
+ Mode mode;
+ int32_t associatedDisplayId;
+ bool orientationAware;
+ } mParameters;
+
+ struct Accumulator {
+ enum {
+ FIELD_BTN_MOUSE = 1,
+ FIELD_REL_X = 2,
+ FIELD_REL_Y = 4
+ };
+
+ uint32_t fields;
+
+ bool btnMouse;
+ int32_t relX;
+ int32_t relY;
+
+ inline void clear() {
+ fields = 0;
+ }
+ } mAccumulator;
+
+ int32_t mSources;
+ float mXScale;
+ float mYScale;
+ float mXPrecision;
+ float mYPrecision;
+ sp<PointerControllerInterface> mPointerController;
+
+ struct LockedState {
+ bool down;
+ nsecs_t downTime;
+ } mLocked;
+
+ void initializeLocked();
+
+ void configureParameters();
+ void dumpParameters(String8& dump);
+
+ void sync(nsecs_t when);
+};
+
+
+class TouchInputMapper : public InputMapper {
+public:
+ TouchInputMapper(InputDevice* device);
+ virtual ~TouchInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure();
+ virtual void reset();
+
+ virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+ virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+
+protected:
+ Mutex mLock;
+
+ struct VirtualKey {
+ int32_t keyCode;
+ int32_t scanCode;
+ uint32_t flags;
+
+ // computed hit box, specified in touch screen coords based on known display size
+ int32_t hitLeft;
+ int32_t hitTop;
+ int32_t hitRight;
+ int32_t hitBottom;
+
+ inline bool isHit(int32_t x, int32_t y) const {
+ return x >= hitLeft && x <= hitRight && y >= hitTop && y <= hitBottom;
+ }
+ };
+
+ // Raw data for a single pointer.
+ struct PointerData {
+ uint32_t id;
+ int32_t x;
+ int32_t y;
+ int32_t pressure;
+ int32_t touchMajor;
+ int32_t touchMinor;
+ int32_t toolMajor;
+ int32_t toolMinor;
+ int32_t orientation;
+
+ inline bool operator== (const PointerData& other) const {
+ return id == other.id
+ && x == other.x
+ && y == other.y
+ && pressure == other.pressure
+ && touchMajor == other.touchMajor
+ && touchMinor == other.touchMinor
+ && toolMajor == other.toolMajor
+ && toolMinor == other.toolMinor
+ && orientation == other.orientation;
+ }
+ inline bool operator!= (const PointerData& other) const {
+ return !(*this == other);
+ }
+ };
+
+ // Raw data for a collection of pointers including a pointer id mapping table.
+ struct TouchData {
+ uint32_t pointerCount;
+ PointerData pointers[MAX_POINTERS];
+ BitSet32 idBits;
+ uint32_t idToIndex[MAX_POINTER_ID + 1];
+
+ void copyFrom(const TouchData& other) {
+ pointerCount = other.pointerCount;
+ idBits = other.idBits;
+
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ pointers[i] = other.pointers[i];
+
+ int id = pointers[i].id;
+ idToIndex[id] = other.idToIndex[id];
+ }
+ }
+
+ inline void clear() {
+ pointerCount = 0;
+ idBits.clear();
+ }
+ };
+
+ // Input sources supported by the device.
+ int32_t mSources;
+
+ // Immutable configuration parameters.
+ struct Parameters {
+ enum DeviceType {
+ DEVICE_TYPE_TOUCH_SCREEN,
+ DEVICE_TYPE_TOUCH_PAD,
+ };
+
+ DeviceType deviceType;
+ int32_t associatedDisplayId;
+ bool orientationAware;
+
+ bool useBadTouchFilter;
+ bool useJumpyTouchFilter;
+ bool useAveragingTouchFilter;
+ nsecs_t virtualKeyQuietTime;
+ } mParameters;
+
+ // Immutable calibration parameters in parsed form.
+ struct Calibration {
+ // Position
+ bool haveXOrigin;
+ int32_t xOrigin;
+ bool haveYOrigin;
+ int32_t yOrigin;
+ bool haveXScale;
+ float xScale;
+ bool haveYScale;
+ float yScale;
+
+ // Touch Size
+ enum TouchSizeCalibration {
+ TOUCH_SIZE_CALIBRATION_DEFAULT,
+ TOUCH_SIZE_CALIBRATION_NONE,
+ TOUCH_SIZE_CALIBRATION_GEOMETRIC,
+ TOUCH_SIZE_CALIBRATION_PRESSURE,
+ };
+
+ TouchSizeCalibration touchSizeCalibration;
+
+ // Tool Size
+ enum ToolSizeCalibration {
+ TOOL_SIZE_CALIBRATION_DEFAULT,
+ TOOL_SIZE_CALIBRATION_NONE,
+ TOOL_SIZE_CALIBRATION_GEOMETRIC,
+ TOOL_SIZE_CALIBRATION_LINEAR,
+ TOOL_SIZE_CALIBRATION_AREA,
+ };
+
+ ToolSizeCalibration toolSizeCalibration;
+ bool haveToolSizeLinearScale;
+ float toolSizeLinearScale;
+ bool haveToolSizeLinearBias;
+ float toolSizeLinearBias;
+ bool haveToolSizeAreaScale;
+ float toolSizeAreaScale;
+ bool haveToolSizeAreaBias;
+ float toolSizeAreaBias;
+ bool haveToolSizeIsSummed;
+ bool toolSizeIsSummed;
+
+ // Pressure
+ enum PressureCalibration {
+ PRESSURE_CALIBRATION_DEFAULT,
+ PRESSURE_CALIBRATION_NONE,
+ PRESSURE_CALIBRATION_PHYSICAL,
+ PRESSURE_CALIBRATION_AMPLITUDE,
+ };
+ enum PressureSource {
+ PRESSURE_SOURCE_DEFAULT,
+ PRESSURE_SOURCE_PRESSURE,
+ PRESSURE_SOURCE_TOUCH,
+ };
+
+ PressureCalibration pressureCalibration;
+ PressureSource pressureSource;
+ bool havePressureScale;
+ float pressureScale;
+
+ // Size
+ enum SizeCalibration {
+ SIZE_CALIBRATION_DEFAULT,
+ SIZE_CALIBRATION_NONE,
+ SIZE_CALIBRATION_NORMALIZED,
+ };
+
+ SizeCalibration sizeCalibration;
+
+ // Orientation
+ enum OrientationCalibration {
+ ORIENTATION_CALIBRATION_DEFAULT,
+ ORIENTATION_CALIBRATION_NONE,
+ ORIENTATION_CALIBRATION_INTERPOLATED,
+ ORIENTATION_CALIBRATION_VECTOR,
+ };
+
+ OrientationCalibration orientationCalibration;
+ } mCalibration;
+
+ // Raw axis information from the driver.
+ struct RawAxes {
+ RawAbsoluteAxisInfo x;
+ RawAbsoluteAxisInfo y;
+ RawAbsoluteAxisInfo pressure;
+ RawAbsoluteAxisInfo touchMajor;
+ RawAbsoluteAxisInfo touchMinor;
+ RawAbsoluteAxisInfo toolMajor;
+ RawAbsoluteAxisInfo toolMinor;
+ RawAbsoluteAxisInfo orientation;
+ } mRawAxes;
+
+ // Current and previous touch sample data.
+ TouchData mCurrentTouch;
+ TouchData mLastTouch;
+
+ // The time the primary pointer last went down.
+ nsecs_t mDownTime;
+
+ struct LockedState {
+ Vector<VirtualKey> virtualKeys;
+
+ // The surface orientation and width and height set by configureSurfaceLocked().
+ int32_t surfaceOrientation;
+ int32_t surfaceWidth, surfaceHeight;
+
+ // Translation and scaling factors, orientation-independent.
+ int32_t xOrigin;
+ float xScale;
+ float xPrecision;
+
+ int32_t yOrigin;
+ float yScale;
+ float yPrecision;
+
+ float geometricScale;
+
+ float toolSizeLinearScale;
+ float toolSizeLinearBias;
+ float toolSizeAreaScale;
+ float toolSizeAreaBias;
+
+ float pressureScale;
+
+ float sizeScale;
+
+ float orientationScale;
+
+ // Oriented motion ranges for input device info.
+ struct OrientedRanges {
+ InputDeviceInfo::MotionRange x;
+ InputDeviceInfo::MotionRange y;
+
+ bool havePressure;
+ InputDeviceInfo::MotionRange pressure;
+
+ bool haveSize;
+ InputDeviceInfo::MotionRange size;
+
+ bool haveTouchSize;
+ InputDeviceInfo::MotionRange touchMajor;
+ InputDeviceInfo::MotionRange touchMinor;
+
+ bool haveToolSize;
+ InputDeviceInfo::MotionRange toolMajor;
+ InputDeviceInfo::MotionRange toolMinor;
+
+ bool haveOrientation;
+ InputDeviceInfo::MotionRange orientation;
+ } orientedRanges;
+
+ // Oriented dimensions and precision.
+ float orientedSurfaceWidth, orientedSurfaceHeight;
+ float orientedXPrecision, orientedYPrecision;
+
+ struct CurrentVirtualKeyState {
+ bool down;
+ nsecs_t downTime;
+ int32_t keyCode;
+ int32_t scanCode;
+ } currentVirtualKey;
+ } mLocked;
+
+ virtual void configureParameters();
+ virtual void dumpParameters(String8& dump);
+ virtual void configureRawAxes();
+ virtual void dumpRawAxes(String8& dump);
+ virtual bool configureSurfaceLocked();
+ virtual void dumpSurfaceLocked(String8& dump);
+ virtual void configureVirtualKeysLocked();
+ virtual void dumpVirtualKeysLocked(String8& dump);
+ virtual void parseCalibration();
+ virtual void resolveCalibration();
+ virtual void dumpCalibration(String8& dump);
+
+ enum TouchResult {
+ // Dispatch the touch normally.
+ DISPATCH_TOUCH,
+ // Do not dispatch the touch, but keep tracking the current stroke.
+ SKIP_TOUCH,
+ // Do not dispatch the touch, and drop all information associated with the current stoke
+ // so the next movement will appear as a new down.
+ DROP_STROKE
+ };
+
+ void syncTouch(nsecs_t when, bool havePointerIds);
+
+private:
+ /* Maximum number of historical samples to average. */
+ static const uint32_t AVERAGING_HISTORY_SIZE = 5;
+
+ /* Slop distance for jumpy pointer detection.
+ * The vertical range of the screen divided by this is our epsilon value. */
+ static const uint32_t JUMPY_EPSILON_DIVISOR = 212;
+
+ /* Number of jumpy points to drop for touchscreens that need it. */
+ static const uint32_t JUMPY_TRANSITION_DROPS = 3;
+ static const uint32_t JUMPY_DROP_LIMIT = 3;
+
+ /* Maximum squared distance for averaging.
+ * If moving farther than this, turn of averaging to avoid lag in response. */
+ static const uint64_t AVERAGING_DISTANCE_LIMIT = 75 * 75;
+
+ struct AveragingTouchFilterState {
+ // Individual history tracks are stored by pointer id
+ uint32_t historyStart[MAX_POINTERS];
+ uint32_t historyEnd[MAX_POINTERS];
+ struct {
+ struct {
+ int32_t x;
+ int32_t y;
+ int32_t pressure;
+ } pointers[MAX_POINTERS];
+ } historyData[AVERAGING_HISTORY_SIZE];
+ } mAveragingTouchFilter;
+
+ struct JumpyTouchFilterState {
+ uint32_t jumpyPointsDropped;
+ } mJumpyTouchFilter;
+
+ struct PointerDistanceHeapElement {
+ uint32_t currentPointerIndex : 8;
+ uint32_t lastPointerIndex : 8;
+ uint64_t distance : 48; // squared distance
+ };
+
+ void initializeLocked();
+
+ TouchResult consumeOffScreenTouches(nsecs_t when, uint32_t policyFlags);
+ void dispatchTouches(nsecs_t when, uint32_t policyFlags);
+ void dispatchTouch(nsecs_t when, uint32_t policyFlags, TouchData* touch,
+ BitSet32 idBits, uint32_t changedId, uint32_t pointerCount,
+ int32_t motionEventAction);
+ void detectGestures(nsecs_t when);
+
+ bool isPointInsideSurfaceLocked(int32_t x, int32_t y);
+ const VirtualKey* findVirtualKeyHitLocked(int32_t x, int32_t y);
+
+ bool applyBadTouchFilter();
+ bool applyJumpyTouchFilter();
+ void applyAveragingTouchFilter();
+ void calculatePointerIds();
+};
+
+
+class SingleTouchInputMapper : public TouchInputMapper {
+public:
+ SingleTouchInputMapper(InputDevice* device);
+ virtual ~SingleTouchInputMapper();
+
+ virtual void reset();
+ virtual void process(const RawEvent* rawEvent);
+
+protected:
+ virtual void configureRawAxes();
+
+private:
+ struct Accumulator {
+ enum {
+ FIELD_BTN_TOUCH = 1,
+ FIELD_ABS_X = 2,
+ FIELD_ABS_Y = 4,
+ FIELD_ABS_PRESSURE = 8,
+ FIELD_ABS_TOOL_WIDTH = 16
+ };
+
+ uint32_t fields;
+
+ bool btnTouch;
+ int32_t absX;
+ int32_t absY;
+ int32_t absPressure;
+ int32_t absToolWidth;
+
+ inline void clear() {
+ fields = 0;
+ }
+ } mAccumulator;
+
+ bool mDown;
+ int32_t mX;
+ int32_t mY;
+ int32_t mPressure;
+ int32_t mToolWidth;
+
+ void initialize();
+
+ void sync(nsecs_t when);
+};
+
+
+class MultiTouchInputMapper : public TouchInputMapper {
+public:
+ MultiTouchInputMapper(InputDevice* device);
+ virtual ~MultiTouchInputMapper();
+
+ virtual void reset();
+ virtual void process(const RawEvent* rawEvent);
+
+protected:
+ virtual void configureRawAxes();
+
+private:
+ struct Accumulator {
+ enum {
+ FIELD_ABS_MT_POSITION_X = 1,
+ FIELD_ABS_MT_POSITION_Y = 2,
+ FIELD_ABS_MT_TOUCH_MAJOR = 4,
+ FIELD_ABS_MT_TOUCH_MINOR = 8,
+ FIELD_ABS_MT_WIDTH_MAJOR = 16,
+ FIELD_ABS_MT_WIDTH_MINOR = 32,
+ FIELD_ABS_MT_ORIENTATION = 64,
+ FIELD_ABS_MT_TRACKING_ID = 128,
+ FIELD_ABS_MT_PRESSURE = 256,
+ };
+
+ uint32_t pointerCount;
+ struct Pointer {
+ uint32_t fields;
+
+ int32_t absMTPositionX;
+ int32_t absMTPositionY;
+ int32_t absMTTouchMajor;
+ int32_t absMTTouchMinor;
+ int32_t absMTWidthMajor;
+ int32_t absMTWidthMinor;
+ int32_t absMTOrientation;
+ int32_t absMTTrackingId;
+ int32_t absMTPressure;
+
+ inline void clear() {
+ fields = 0;
+ }
+ } pointers[MAX_POINTERS + 1]; // + 1 to remove the need for extra range checks
+
+ inline void clear() {
+ pointerCount = 0;
+ pointers[0].clear();
+ }
+ } mAccumulator;
+
+ void initialize();
+
+ void sync(nsecs_t when);
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_READER_H
diff --git a/services/input/InputWindow.cpp b/services/input/InputWindow.cpp
new file mode 100644
index 0000000..b552f6d
--- /dev/null
+++ b/services/input/InputWindow.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputWindow"
+
+#include "InputWindow.h"
+
+#include <cutils/log.h>
+
+namespace android {
+
+// --- InputWindow ---
+
+bool InputWindow::touchableRegionContainsPoint(int32_t x, int32_t y) const {
+ return touchableRegion.contains(x, y);
+}
+
+bool InputWindow::frameContainsPoint(int32_t x, int32_t y) const {
+ return x >= frameLeft && x <= frameRight
+ && y >= frameTop && y <= frameBottom;
+}
+
+bool InputWindow::isTrustedOverlay() const {
+ return layoutParamsType == TYPE_INPUT_METHOD
+ || layoutParamsType == TYPE_INPUT_METHOD_DIALOG
+ || layoutParamsType == TYPE_SECURE_SYSTEM_OVERLAY;
+}
+
+bool InputWindow::supportsSplitTouch() const {
+ return layoutParamsFlags & InputWindow::FLAG_SPLIT_TOUCH;
+}
+
+} // namespace android
diff --git a/services/input/InputWindow.h b/services/input/InputWindow.h
new file mode 100644
index 0000000..9c43067
--- /dev/null
+++ b/services/input/InputWindow.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_WINDOW_H
+#define _UI_INPUT_WINDOW_H
+
+#include <ui/Input.h>
+#include <ui/InputTransport.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+#include <utils/String8.h>
+
+#include <SkRegion.h>
+
+#include "InputApplication.h"
+
+namespace android {
+
+/*
+ * A handle to a window that can receive input.
+ * Used by the native input dispatcher to indirectly refer to the window manager objects
+ * that describe a window.
+ */
+class InputWindowHandle : public RefBase {
+protected:
+ InputWindowHandle(const sp<InputApplicationHandle>& inputApplicationHandle) :
+ mInputApplicationHandle(inputApplicationHandle) { }
+ virtual ~InputWindowHandle() { }
+
+public:
+ inline sp<InputApplicationHandle> getInputApplicationHandle() {
+ return mInputApplicationHandle;
+ }
+
+private:
+ sp<InputApplicationHandle> mInputApplicationHandle;
+};
+
+
+/*
+ * An input window describes the bounds of a window that can receive input.
+ */
+struct InputWindow {
+ // Window flags from WindowManager.LayoutParams
+ enum {
+ FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001,
+ FLAG_DIM_BEHIND = 0x00000002,
+ FLAG_BLUR_BEHIND = 0x00000004,
+ FLAG_NOT_FOCUSABLE = 0x00000008,
+ FLAG_NOT_TOUCHABLE = 0x00000010,
+ FLAG_NOT_TOUCH_MODAL = 0x00000020,
+ FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040,
+ FLAG_KEEP_SCREEN_ON = 0x00000080,
+ FLAG_LAYOUT_IN_SCREEN = 0x00000100,
+ FLAG_LAYOUT_NO_LIMITS = 0x00000200,
+ FLAG_FULLSCREEN = 0x00000400,
+ FLAG_FORCE_NOT_FULLSCREEN = 0x00000800,
+ FLAG_DITHER = 0x00001000,
+ FLAG_SECURE = 0x00002000,
+ FLAG_SCALED = 0x00004000,
+ FLAG_IGNORE_CHEEK_PRESSES = 0x00008000,
+ FLAG_LAYOUT_INSET_DECOR = 0x00010000,
+ FLAG_ALT_FOCUSABLE_IM = 0x00020000,
+ FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000,
+ FLAG_SHOW_WHEN_LOCKED = 0x00080000,
+ FLAG_SHOW_WALLPAPER = 0x00100000,
+ FLAG_TURN_SCREEN_ON = 0x00200000,
+ FLAG_DISMISS_KEYGUARD = 0x00400000,
+ FLAG_SPLIT_TOUCH = 0x00800000,
+ FLAG_KEEP_SURFACE_WHILE_ANIMATING = 0x10000000,
+ FLAG_COMPATIBLE_WINDOW = 0x20000000,
+ FLAG_SYSTEM_ERROR = 0x40000000,
+ };
+
+ // Window types from WindowManager.LayoutParams
+ enum {
+ FIRST_APPLICATION_WINDOW = 1,
+ TYPE_BASE_APPLICATION = 1,
+ TYPE_APPLICATION = 2,
+ TYPE_APPLICATION_STARTING = 3,
+ LAST_APPLICATION_WINDOW = 99,
+ FIRST_SUB_WINDOW = 1000,
+ TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW,
+ TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1,
+ TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2,
+ TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3,
+ TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4,
+ LAST_SUB_WINDOW = 1999,
+ FIRST_SYSTEM_WINDOW = 2000,
+ TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW,
+ TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1,
+ TYPE_PHONE = FIRST_SYSTEM_WINDOW+2,
+ TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3,
+ TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4,
+ TYPE_TOAST = FIRST_SYSTEM_WINDOW+5,
+ TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6,
+ TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7,
+ TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8,
+ TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9,
+ TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10,
+ TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11,
+ TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12,
+ TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13,
+ TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+14,
+ TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15,
+ TYPE_DRAG = FIRST_SYSTEM_WINDOW+16,
+ TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+17,
+ LAST_SYSTEM_WINDOW = 2999,
+ };
+
+ sp<InputWindowHandle> inputWindowHandle;
+ sp<InputChannel> inputChannel;
+ String8 name;
+ int32_t layoutParamsFlags;
+ int32_t layoutParamsType;
+ nsecs_t dispatchingTimeout;
+ int32_t frameLeft;
+ int32_t frameTop;
+ int32_t frameRight;
+ int32_t frameBottom;
+ SkRegion touchableRegion;
+ bool visible;
+ bool canReceiveKeys;
+ bool hasFocus;
+ bool hasWallpaper;
+ bool paused;
+ int32_t layer;
+ int32_t ownerPid;
+ int32_t ownerUid;
+
+ bool touchableRegionContainsPoint(int32_t x, int32_t y) const;
+ bool frameContainsPoint(int32_t x, int32_t y) const;
+
+ /* Returns true if the window is of a trusted type that is allowed to silently
+ * overlay other windows for the purpose of implementing the secure views feature.
+ * Trusted overlays, such as IME windows, can partly obscure other windows without causing
+ * motion events to be delivered to them with AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED.
+ */
+ bool isTrustedOverlay() const;
+
+ bool supportsSplitTouch() const;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_WINDOW_H
diff --git a/services/input/PointerController.cpp b/services/input/PointerController.cpp
new file mode 100644
index 0000000..92af51e
--- /dev/null
+++ b/services/input/PointerController.cpp
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "PointerController"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages about pointer updates
+#define DEBUG_POINTER_UPDATES 0
+
+#include "PointerController.h"
+
+#include <cutils/log.h>
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkPaint.h>
+#include <SkXfermode.h>
+
+namespace android {
+
+// --- PointerController ---
+
+PointerController::PointerController(int32_t pointerLayer) :
+ mPointerLayer(pointerLayer) {
+ AutoMutex _l(mLock);
+
+ mLocked.displayWidth = -1;
+ mLocked.displayHeight = -1;
+ mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
+
+ mLocked.pointerX = 0;
+ mLocked.pointerY = 0;
+ mLocked.buttonState = 0;
+
+ mLocked.iconBitmap = NULL;
+ mLocked.iconHotSpotX = 0;
+ mLocked.iconHotSpotY = 0;
+
+ mLocked.wantVisible = false;
+ mLocked.visible = false;
+ mLocked.drawn = false;
+}
+
+PointerController::~PointerController() {
+ if (mSurfaceControl != NULL) {
+ mSurfaceControl->clear();
+ mSurfaceControl.clear();
+ }
+
+ if (mSurfaceComposerClient != NULL) {
+ mSurfaceComposerClient->dispose();
+ mSurfaceComposerClient.clear();
+ }
+
+ delete mLocked.iconBitmap;
+}
+
+bool PointerController::getBounds(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const {
+ AutoMutex _l(mLock);
+
+ return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY);
+}
+
+bool PointerController::getBoundsLocked(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const {
+ if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) {
+ return false;
+ }
+
+ *outMinX = 0;
+ *outMinY = 0;
+ switch (mLocked.displayOrientation) {
+ case DISPLAY_ORIENTATION_90:
+ case DISPLAY_ORIENTATION_270:
+ *outMaxX = mLocked.displayHeight;
+ *outMaxY = mLocked.displayWidth;
+ break;
+ default:
+ *outMaxX = mLocked.displayWidth;
+ *outMaxY = mLocked.displayHeight;
+ break;
+ }
+ return true;
+}
+
+void PointerController::move(float deltaX, float deltaY) {
+#if DEBUG_POINTER_UPDATES
+ LOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
+#endif
+ if (deltaX == 0.0f && deltaY == 0.0f) {
+ return;
+ }
+
+ AutoMutex _l(mLock);
+
+ setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
+}
+
+void PointerController::setButtonState(uint32_t buttonState) {
+#if DEBUG_POINTER_UPDATES
+ LOGD("Set button state 0x%08x", buttonState);
+#endif
+ AutoMutex _l(mLock);
+
+ if (mLocked.buttonState != buttonState) {
+ mLocked.buttonState = buttonState;
+ mLocked.wantVisible = true;
+ updateLocked();
+ }
+}
+
+uint32_t PointerController::getButtonState() const {
+ AutoMutex _l(mLock);
+
+ return mLocked.buttonState;
+}
+
+void PointerController::setPosition(float x, float y) {
+#if DEBUG_POINTER_UPDATES
+ LOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y);
+#endif
+ AutoMutex _l(mLock);
+
+ setPositionLocked(x, y);
+}
+
+void PointerController::setPositionLocked(float x, float y) {
+ float minX, minY, maxX, maxY;
+ if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
+ if (x <= minX) {
+ mLocked.pointerX = minX;
+ } else if (x >= maxX) {
+ mLocked.pointerX = maxX;
+ } else {
+ mLocked.pointerX = x;
+ }
+ if (y <= minY) {
+ mLocked.pointerY = minY;
+ } else if (y >= maxY) {
+ mLocked.pointerY = maxY;
+ } else {
+ mLocked.pointerY = y;
+ }
+ mLocked.wantVisible = true;
+ updateLocked();
+ }
+}
+
+void PointerController::getPosition(float* outX, float* outY) const {
+ AutoMutex _l(mLock);
+
+ *outX = mLocked.pointerX;
+ *outY = mLocked.pointerY;
+}
+
+void PointerController::updateLocked() {
+ bool wantVisibleAndHavePointerIcon = mLocked.wantVisible && mLocked.iconBitmap;
+
+ if (wantVisibleAndHavePointerIcon) {
+ // Want the pointer to be visible.
+ // Ensure the surface is created and drawn.
+ if (!createSurfaceIfNeededLocked() || !drawPointerIfNeededLocked()) {
+ return;
+ }
+ } else {
+ // Don't want the pointer to be visible.
+ // If it is not visible then we are done.
+ if (mSurfaceControl == NULL || !mLocked.visible) {
+ return;
+ }
+ }
+
+ status_t status = mSurfaceComposerClient->openTransaction();
+ if (status) {
+ LOGE("Error opening surface transaction to update pointer surface.");
+ return;
+ }
+
+ if (wantVisibleAndHavePointerIcon) {
+ status = mSurfaceControl->setPosition(
+ mLocked.pointerX - mLocked.iconHotSpotX,
+ mLocked.pointerY - mLocked.iconHotSpotY);
+ if (status) {
+ LOGE("Error %d moving pointer surface.", status);
+ goto CloseTransaction;
+ }
+
+ if (!mLocked.visible) {
+ status = mSurfaceControl->setLayer(mPointerLayer);
+ if (status) {
+ LOGE("Error %d setting pointer surface layer.", status);
+ goto CloseTransaction;
+ }
+
+ status = mSurfaceControl->show(mPointerLayer);
+ if (status) {
+ LOGE("Error %d showing pointer surface.", status);
+ goto CloseTransaction;
+ }
+
+ mLocked.visible = true;
+ }
+ } else {
+ if (mLocked.visible) {
+ status = mSurfaceControl->hide();
+ if (status) {
+ LOGE("Error %d hiding pointer surface.", status);
+ goto CloseTransaction;
+ }
+
+ mLocked.visible = false;
+ }
+ }
+
+CloseTransaction:
+ status = mSurfaceComposerClient->closeTransaction();
+ if (status) {
+ LOGE("Error closing surface transaction to update pointer surface.");
+ }
+}
+
+void PointerController::setDisplaySize(int32_t width, int32_t height) {
+ AutoMutex _l(mLock);
+
+ if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
+ mLocked.displayWidth = width;
+ mLocked.displayHeight = height;
+
+ float minX, minY, maxX, maxY;
+ if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
+ mLocked.pointerX = (minX + maxX) * 0.5f;
+ mLocked.pointerY = (minY + maxY) * 0.5f;
+ } else {
+ mLocked.pointerX = 0;
+ mLocked.pointerY = 0;
+ }
+
+ updateLocked();
+ }
+}
+
+void PointerController::setDisplayOrientation(int32_t orientation) {
+ AutoMutex _l(mLock);
+
+ if (mLocked.displayOrientation != orientation) {
+ float absoluteX, absoluteY;
+
+ // Map from oriented display coordinates to absolute display coordinates.
+ switch (mLocked.displayOrientation) {
+ case DISPLAY_ORIENTATION_90:
+ absoluteX = mLocked.displayWidth - mLocked.pointerY;
+ absoluteY = mLocked.pointerX;
+ break;
+ case DISPLAY_ORIENTATION_180:
+ absoluteX = mLocked.displayWidth - mLocked.pointerX;
+ absoluteY = mLocked.displayHeight - mLocked.pointerY;
+ break;
+ case DISPLAY_ORIENTATION_270:
+ absoluteX = mLocked.pointerY;
+ absoluteY = mLocked.displayHeight - mLocked.pointerX;
+ break;
+ default:
+ absoluteX = mLocked.pointerX;
+ absoluteY = mLocked.pointerY;
+ break;
+ }
+
+ // Map from absolute display coordinates to oriented display coordinates.
+ switch (orientation) {
+ case DISPLAY_ORIENTATION_90:
+ mLocked.pointerX = absoluteY;
+ mLocked.pointerY = mLocked.displayWidth - absoluteX;
+ break;
+ case DISPLAY_ORIENTATION_180:
+ mLocked.pointerX = mLocked.displayWidth - absoluteX;
+ mLocked.pointerY = mLocked.displayHeight - absoluteY;
+ break;
+ case DISPLAY_ORIENTATION_270:
+ mLocked.pointerX = mLocked.displayHeight - absoluteY;
+ mLocked.pointerY = absoluteX;
+ break;
+ default:
+ mLocked.pointerX = absoluteX;
+ mLocked.pointerY = absoluteY;
+ break;
+ }
+
+ mLocked.displayOrientation = orientation;
+
+ updateLocked();
+ }
+}
+
+void PointerController::setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY) {
+ AutoMutex _l(mLock);
+
+ if (mLocked.iconBitmap) {
+ delete mLocked.iconBitmap;
+ mLocked.iconBitmap = NULL;
+ }
+
+ if (bitmap) {
+ mLocked.iconBitmap = new SkBitmap();
+ bitmap->copyTo(mLocked.iconBitmap, SkBitmap::kARGB_8888_Config);
+ }
+
+ mLocked.iconHotSpotX = hotSpotX;
+ mLocked.iconHotSpotY = hotSpotY;
+ mLocked.drawn = false;
+}
+
+bool PointerController::createSurfaceIfNeededLocked() {
+ if (!mLocked.iconBitmap) {
+ // If we don't have a pointer icon, then no point allocating a surface now.
+ return false;
+ }
+
+ if (mSurfaceComposerClient == NULL) {
+ mSurfaceComposerClient = new SurfaceComposerClient();
+ }
+
+ if (mSurfaceControl == NULL) {
+ mSurfaceControl = mSurfaceComposerClient->createSurface(getpid(),
+ String8("Pointer Icon"), 0,
+ mLocked.iconBitmap->width(), mLocked.iconBitmap->height(),
+ PIXEL_FORMAT_RGBA_8888);
+ if (mSurfaceControl == NULL) {
+ LOGE("Error creating pointer surface.");
+ return false;
+ }
+ }
+ return true;
+}
+
+bool PointerController::drawPointerIfNeededLocked() {
+ if (!mLocked.drawn) {
+ if (!mLocked.iconBitmap) {
+ return false;
+ }
+
+ if (!resizeSurfaceLocked(mLocked.iconBitmap->width(), mLocked.iconBitmap->height())) {
+ return false;
+ }
+
+ sp<Surface> surface = mSurfaceControl->getSurface();
+
+ Surface::SurfaceInfo surfaceInfo;
+ status_t status = surface->lock(&surfaceInfo);
+ if (status) {
+ LOGE("Error %d locking pointer surface before drawing.", status);
+ return false;
+ }
+
+ SkBitmap surfaceBitmap;
+ ssize_t bpr = surfaceInfo.s * bytesPerPixel(surfaceInfo.format);
+ surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config, surfaceInfo.w, surfaceInfo.h, bpr);
+ surfaceBitmap.setPixels(surfaceInfo.bits);
+
+ SkCanvas surfaceCanvas;
+ surfaceCanvas.setBitmapDevice(surfaceBitmap);
+
+ SkPaint paint;
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ surfaceCanvas.drawBitmap(*mLocked.iconBitmap, 0, 0, &paint);
+
+ status = surface->unlockAndPost();
+ if (status) {
+ LOGE("Error %d unlocking pointer surface after drawing.", status);
+ return false;
+ }
+ }
+
+ mLocked.drawn = true;
+ return true;
+}
+
+bool PointerController::resizeSurfaceLocked(int32_t width, int32_t height) {
+ status_t status = mSurfaceComposerClient->openTransaction();
+ if (status) {
+ LOGE("Error opening surface transaction to resize pointer surface.");
+ return false;
+ }
+
+ status = mSurfaceControl->setSize(width, height);
+ if (status) {
+ LOGE("Error %d setting pointer surface size.", status);
+ return false;
+ }
+
+ status = mSurfaceComposerClient->closeTransaction();
+ if (status) {
+ LOGE("Error closing surface transaction to resize pointer surface.");
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace android
diff --git a/services/input/PointerController.h b/services/input/PointerController.h
new file mode 100644
index 0000000..a2a9955
--- /dev/null
+++ b/services/input/PointerController.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_POINTER_CONTROLLER_H
+#define _UI_POINTER_CONTROLLER_H
+
+#include <ui/DisplayInfo.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+#include <surfaceflinger/Surface.h>
+#include <surfaceflinger/SurfaceComposerClient.h>
+#include <surfaceflinger/ISurfaceComposer.h>
+
+#include <SkBitmap.h>
+
+namespace android {
+
+enum {
+ POINTER_BUTTON_1 = 1 << 0,
+};
+
+/**
+ * Interface for tracking a single (mouse) pointer.
+ *
+ * The pointer controller is responsible for providing synchronization and for tracking
+ * display orientation changes if needed.
+ */
+class PointerControllerInterface : public virtual RefBase {
+protected:
+ PointerControllerInterface() { }
+ virtual ~PointerControllerInterface() { }
+
+public:
+ /* Gets the bounds of the region that the pointer can traverse.
+ * Returns true if the bounds are available. */
+ virtual bool getBounds(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const = 0;
+
+ /* Move the pointer. */
+ virtual void move(float deltaX, float deltaY) = 0;
+
+ /* Sets a mask that indicates which buttons are pressed. */
+ virtual void setButtonState(uint32_t buttonState) = 0;
+
+ /* Gets a mask that indicates which buttons are pressed. */
+ virtual uint32_t getButtonState() const = 0;
+
+ /* Sets the absolute location of the pointer. */
+ virtual void setPosition(float x, float y) = 0;
+
+ /* Gets the absolute location of the pointer. */
+ virtual void getPosition(float* outX, float* outY) const = 0;
+};
+
+
+/*
+ * Tracks pointer movements and draws the pointer sprite to a surface.
+ *
+ * Handles pointer acceleration and animation.
+ */
+class PointerController : public PointerControllerInterface {
+protected:
+ virtual ~PointerController();
+
+public:
+ PointerController(int32_t pointerLayer);
+
+ virtual bool getBounds(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const;
+ virtual void move(float deltaX, float deltaY);
+ virtual void setButtonState(uint32_t buttonState);
+ virtual uint32_t getButtonState() const;
+ virtual void setPosition(float x, float y);
+ virtual void getPosition(float* outX, float* outY) const;
+
+ void setDisplaySize(int32_t width, int32_t height);
+ void setDisplayOrientation(int32_t orientation);
+ void setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY);
+
+private:
+ mutable Mutex mLock;
+
+ int32_t mPointerLayer;
+ sp<SurfaceComposerClient> mSurfaceComposerClient;
+ sp<SurfaceControl> mSurfaceControl;
+
+ struct Locked {
+ int32_t displayWidth;
+ int32_t displayHeight;
+ int32_t displayOrientation;
+
+ float pointerX;
+ float pointerY;
+ uint32_t buttonState;
+
+ SkBitmap* iconBitmap;
+ float iconHotSpotX;
+ float iconHotSpotY;
+
+ bool wantVisible;
+ bool visible;
+ bool drawn;
+ } mLocked;
+
+ bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+ void setPositionLocked(float x, float y);
+ void updateLocked();
+ bool createSurfaceIfNeededLocked();
+ bool drawPointerIfNeededLocked();
+ bool resizeSurfaceLocked(int32_t width, int32_t height);
+};
+
+} // namespace android
+
+#endif // _UI_POINTER_CONTROLLER_H
diff --git a/services/input/tests/Android.mk b/services/input/tests/Android.mk
new file mode 100644
index 0000000..799eb76
--- /dev/null
+++ b/services/input/tests/Android.mk
@@ -0,0 +1,50 @@
+# Build the unit tests.
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+ifneq ($(TARGET_SIMULATOR),true)
+
+# Build the unit tests.
+test_src_files := \
+ InputReader_test.cpp \
+ InputDispatcher_test.cpp
+
+shared_libraries := \
+ libcutils \
+ libutils \
+ libhardware \
+ libhardware_legacy \
+ libui \
+ libsurfaceflinger_client \
+ libskia \
+ libstlport \
+ libinput
+
+static_libraries := \
+ libgtest \
+ libgtest_main
+
+c_includes := \
+ bionic \
+ bionic/libstdc++/include \
+ external/gtest/include \
+ external/stlport/stlport \
+ external/skia/include/core
+
+module_tags := eng tests
+
+$(foreach file,$(test_src_files), \
+ $(eval include $(CLEAR_VARS)) \
+ $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \
+ $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \
+ $(eval LOCAL_C_INCLUDES := $(c_includes)) \
+ $(eval LOCAL_SRC_FILES := $(file)) \
+ $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
+ $(eval LOCAL_MODULE_TAGS := $(module_tags)) \
+ $(eval include $(BUILD_EXECUTABLE)) \
+)
+
+# Build the manual test programs.
+include $(call all-subdir-makefiles)
+
+endif \ No newline at end of file
diff --git a/services/input/tests/InputDispatcher_test.cpp b/services/input/tests/InputDispatcher_test.cpp
new file mode 100644
index 0000000..5ba1867
--- /dev/null
+++ b/services/input/tests/InputDispatcher_test.cpp
@@ -0,0 +1,230 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+
+#include "../InputDispatcher.h"
+
+#include <gtest/gtest.h>
+#include <linux/input.h>
+
+namespace android {
+
+// An arbitrary time value.
+static const nsecs_t ARBITRARY_TIME = 1234;
+
+// An arbitrary device id.
+static const int32_t DEVICE_ID = 1;
+
+// An arbitrary injector pid / uid pair that has permission to inject events.
+static const int32_t INJECTOR_PID = 999;
+static const int32_t INJECTOR_UID = 1001;
+
+
+// --- FakeInputDispatcherPolicy ---
+
+class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
+protected:
+ virtual ~FakeInputDispatcherPolicy() {
+ }
+
+public:
+ FakeInputDispatcherPolicy() {
+ }
+
+private:
+ virtual void notifyConfigurationChanged(nsecs_t when) {
+ }
+
+ virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+ const sp<InputWindowHandle>& inputWindowHandle) {
+ return 0;
+ }
+
+ virtual void notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle) {
+ }
+
+ virtual nsecs_t getKeyRepeatTimeout() {
+ return 500 * 1000000LL;
+ }
+
+ virtual nsecs_t getKeyRepeatDelay() {
+ return 50 * 1000000LL;
+ }
+
+ virtual int32_t getMaxEventsPerSecond() {
+ return 60;
+ }
+
+ virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) {
+ }
+
+ virtual void interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags) {
+ }
+
+ virtual bool interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle,
+ const KeyEvent* keyEvent, uint32_t policyFlags) {
+ return false;
+ }
+
+ virtual bool dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle,
+ const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) {
+ return false;
+ }
+
+ virtual void notifySwitch(nsecs_t when,
+ int32_t switchCode, int32_t switchValue, uint32_t policyFlags) {
+ }
+
+ virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) {
+ }
+
+ virtual bool checkInjectEventsPermissionNonReentrant(
+ int32_t injectorPid, int32_t injectorUid) {
+ return false;
+ }
+};
+
+
+// --- InputDispatcherTest ---
+
+class InputDispatcherTest : public testing::Test {
+protected:
+ sp<FakeInputDispatcherPolicy> mFakePolicy;
+ sp<InputDispatcher> mDispatcher;
+
+ virtual void SetUp() {
+ mFakePolicy = new FakeInputDispatcherPolicy();
+ mDispatcher = new InputDispatcher(mFakePolicy);
+ }
+
+ virtual void TearDown() {
+ mFakePolicy.clear();
+ mDispatcher.clear();
+ }
+};
+
+
+TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) {
+ KeyEvent event;
+
+ // Rejects undefined key actions.
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+ /*action*/ -1, 0,
+ AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+ << "Should reject key events with undefined action.";
+
+ // Rejects ACTION_MULTIPLE since it is not supported despite being defined in the API.
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+ AKEY_EVENT_ACTION_MULTIPLE, 0,
+ AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+ << "Should reject key events with ACTION_MULTIPLE.";
+}
+
+TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) {
+ MotionEvent event;
+ int32_t pointerIds[MAX_POINTERS + 1];
+ PointerCoords pointerCoords[MAX_POINTERS + 1];
+ for (int i = 0; i <= MAX_POINTERS; i++) {
+ pointerIds[i] = i;
+ }
+
+ // Rejects undefined motion actions.
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ /*action*/ -1, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 1, pointerIds, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+ << "Should reject motion events with undefined action.";
+
+ // Rejects pointer down with invalid index.
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ 0, 0, AMETA_NONE, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 1, pointerIds, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+ << "Should reject motion events with pointer down index too large.";
+
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_POINTER_DOWN | (-1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ 0, 0, AMETA_NONE, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 1, pointerIds, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+ << "Should reject motion events with pointer down index too small.";
+
+ // Rejects pointer up with invalid index.
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ 0, 0, AMETA_NONE, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 1, pointerIds, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+ << "Should reject motion events with pointer up index too large.";
+
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_POINTER_UP | (-1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ 0, 0, AMETA_NONE, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 1, pointerIds, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+ << "Should reject motion events with pointer up index too small.";
+
+ // Rejects motion events with invalid number of pointers.
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 0, pointerIds, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+ << "Should reject motion events with 0 pointers.";
+
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ MAX_POINTERS + 1, pointerIds, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+ << "Should reject motion events with more than MAX_POINTERS pointers.";
+
+ // Rejects motion events with invalid pointer ids.
+ pointerIds[0] = -1;
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 1, pointerIds, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+ << "Should reject motion events with pointer ids less than 0.";
+
+ pointerIds[0] = MAX_POINTER_ID + 1;
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 1, pointerIds, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+ << "Should reject motion events with pointer ids greater than MAX_POINTER_ID.";
+
+ // Rejects motion events with duplicate pointer ids.
+ pointerIds[0] = 1;
+ pointerIds[1] = 1;
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 2, pointerIds, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+ << "Should reject motion events with duplicate pointer ids.";
+}
+
+} // namespace android
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
new file mode 100644
index 0000000..25030d8
--- /dev/null
+++ b/services/input/tests/InputReader_test.cpp
@@ -0,0 +1,3696 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+
+#include "../InputReader.h"
+
+#include <utils/List.h>
+#include <gtest/gtest.h>
+#include <math.h>
+
+namespace android {
+
+// An arbitrary time value.
+static const nsecs_t ARBITRARY_TIME = 1234;
+
+// Arbitrary display properties.
+static const int32_t DISPLAY_ID = 0;
+static const int32_t DISPLAY_WIDTH = 480;
+static const int32_t DISPLAY_HEIGHT = 800;
+
+// Error tolerance for floating point assertions.
+static const float EPSILON = 0.001f;
+
+template<typename T>
+static inline T min(T a, T b) {
+ return a < b ? a : b;
+}
+
+static inline float avg(float x, float y) {
+ return (x + y) / 2;
+}
+
+
+// --- FakePointerController ---
+
+class FakePointerController : public PointerControllerInterface {
+ bool mHaveBounds;
+ float mMinX, mMinY, mMaxX, mMaxY;
+
+protected:
+ virtual ~FakePointerController() { }
+
+public:
+ FakePointerController() :
+ mHaveBounds(false), mMinX(0), mMinY(0), mMaxX(0), mMaxY(0) {
+ }
+
+ void setBounds(float minX, float minY, float maxX, float maxY) {
+ mHaveBounds = true;
+ mMinX = minX;
+ mMinY = minY;
+ mMaxX = maxX;
+ mMaxY = maxY;
+ }
+
+private:
+ virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const {
+ *outMinX = mMinX;
+ *outMinY = mMinY;
+ *outMaxX = mMaxX;
+ *outMaxY = mMaxY;
+ return mHaveBounds;
+ }
+
+ virtual void move(float deltaX, float deltaY) {
+ }
+
+ virtual void setButtonState(uint32_t buttonState) {
+ }
+
+ virtual uint32_t getButtonState() const {
+ return 0;
+ }
+
+ virtual void setPosition(float x, float y) {
+ }
+
+ virtual void getPosition(float* outX, float* outY) const {
+ *outX = 0;
+ *outY = 0;
+ }
+};
+
+
+// --- FakeInputReaderPolicy ---
+
+class FakeInputReaderPolicy : public InputReaderPolicyInterface {
+ struct DisplayInfo {
+ int32_t width;
+ int32_t height;
+ int32_t orientation;
+ };
+
+ KeyedVector<int32_t, DisplayInfo> mDisplayInfos;
+ bool mFilterTouchEvents;
+ bool mFilterJumpyTouchEvents;
+ Vector<String8> mExcludedDeviceNames;
+ KeyedVector<int32_t, sp<FakePointerController> > mPointerControllers;
+
+protected:
+ virtual ~FakeInputReaderPolicy() { }
+
+public:
+ FakeInputReaderPolicy() :
+ mFilterTouchEvents(false), mFilterJumpyTouchEvents(false) {
+ }
+
+ void removeDisplayInfo(int32_t displayId) {
+ mDisplayInfos.removeItem(displayId);
+ }
+
+ void setDisplayInfo(int32_t displayId, int32_t width, int32_t height, int32_t orientation) {
+ removeDisplayInfo(displayId);
+
+ DisplayInfo info;
+ info.width = width;
+ info.height = height;
+ info.orientation = orientation;
+ mDisplayInfos.add(displayId, info);
+ }
+
+ void setFilterTouchEvents(bool enabled) {
+ mFilterTouchEvents = enabled;
+ }
+
+ void setFilterJumpyTouchEvents(bool enabled) {
+ mFilterJumpyTouchEvents = enabled;
+ }
+
+ virtual nsecs_t getVirtualKeyQuietTime() {
+ return 0;
+ }
+
+ void addExcludedDeviceName(const String8& deviceName) {
+ mExcludedDeviceNames.push(deviceName);
+ }
+
+ void setPointerController(int32_t deviceId, const sp<FakePointerController>& controller) {
+ mPointerControllers.add(deviceId, controller);
+ }
+
+private:
+ virtual bool getDisplayInfo(int32_t displayId,
+ int32_t* width, int32_t* height, int32_t* orientation) {
+ ssize_t index = mDisplayInfos.indexOfKey(displayId);
+ if (index >= 0) {
+ const DisplayInfo& info = mDisplayInfos.valueAt(index);
+ if (width) {
+ *width = info.width;
+ }
+ if (height) {
+ *height = info.height;
+ }
+ if (orientation) {
+ *orientation = info.orientation;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ virtual bool filterTouchEvents() {
+ return mFilterTouchEvents;
+ }
+
+ virtual bool filterJumpyTouchEvents() {
+ return mFilterJumpyTouchEvents;
+ }
+
+ virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) {
+ outExcludedDeviceNames.appendVector(mExcludedDeviceNames);
+ }
+
+ virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) {
+ return mPointerControllers.valueFor(deviceId);
+ }
+};
+
+
+// --- FakeInputDispatcher ---
+
+class FakeInputDispatcher : public InputDispatcherInterface {
+public:
+ struct NotifyConfigurationChangedArgs {
+ nsecs_t eventTime;
+ };
+
+ struct NotifyKeyArgs {
+ nsecs_t eventTime;
+ int32_t deviceId;
+ uint32_t source;
+ uint32_t policyFlags;
+ int32_t action;
+ int32_t flags;
+ int32_t keyCode;
+ int32_t scanCode;
+ int32_t metaState;
+ nsecs_t downTime;
+ };
+
+ struct NotifyMotionArgs {
+ nsecs_t eventTime;
+ int32_t deviceId;
+ uint32_t source;
+ uint32_t policyFlags;
+ int32_t action;
+ int32_t flags;
+ int32_t metaState;
+ int32_t edgeFlags;
+ uint32_t pointerCount;
+ Vector<int32_t> pointerIds;
+ Vector<PointerCoords> pointerCoords;
+ float xPrecision;
+ float yPrecision;
+ nsecs_t downTime;
+ };
+
+ struct NotifySwitchArgs {
+ nsecs_t when;
+ int32_t switchCode;
+ int32_t switchValue;
+ uint32_t policyFlags;
+ };
+
+private:
+ List<NotifyConfigurationChangedArgs> mNotifyConfigurationChangedArgs;
+ List<NotifyKeyArgs> mNotifyKeyArgs;
+ List<NotifyMotionArgs> mNotifyMotionArgs;
+ List<NotifySwitchArgs> mNotifySwitchArgs;
+
+protected:
+ virtual ~FakeInputDispatcher() { }
+
+public:
+ FakeInputDispatcher() {
+ }
+
+ void assertNotifyConfigurationChangedWasCalled(NotifyConfigurationChangedArgs* outArgs = NULL) {
+ ASSERT_FALSE(mNotifyConfigurationChangedArgs.empty())
+ << "Expected notifyConfigurationChanged() to have been called.";
+ if (outArgs) {
+ *outArgs = *mNotifyConfigurationChangedArgs.begin();
+ }
+ mNotifyConfigurationChangedArgs.erase(mNotifyConfigurationChangedArgs.begin());
+ }
+
+ void assertNotifyKeyWasCalled(NotifyKeyArgs* outArgs = NULL) {
+ ASSERT_FALSE(mNotifyKeyArgs.empty())
+ << "Expected notifyKey() to have been called.";
+ if (outArgs) {
+ *outArgs = *mNotifyKeyArgs.begin();
+ }
+ mNotifyKeyArgs.erase(mNotifyKeyArgs.begin());
+ }
+
+ void assertNotifyKeyWasNotCalled() {
+ ASSERT_TRUE(mNotifyKeyArgs.empty())
+ << "Expected notifyKey() to not have been called.";
+ }
+
+ void assertNotifyMotionWasCalled(NotifyMotionArgs* outArgs = NULL) {
+ ASSERT_FALSE(mNotifyMotionArgs.empty())
+ << "Expected notifyMotion() to have been called.";
+ if (outArgs) {
+ *outArgs = *mNotifyMotionArgs.begin();
+ }
+ mNotifyMotionArgs.erase(mNotifyMotionArgs.begin());
+ }
+
+ void assertNotifyMotionWasNotCalled() {
+ ASSERT_TRUE(mNotifyMotionArgs.empty())
+ << "Expected notifyMotion() to not have been called.";
+ }
+
+ void assertNotifySwitchWasCalled(NotifySwitchArgs* outArgs = NULL) {
+ ASSERT_FALSE(mNotifySwitchArgs.empty())
+ << "Expected notifySwitch() to have been called.";
+ if (outArgs) {
+ *outArgs = *mNotifySwitchArgs.begin();
+ }
+ mNotifySwitchArgs.erase(mNotifySwitchArgs.begin());
+ }
+
+private:
+ virtual void notifyConfigurationChanged(nsecs_t eventTime) {
+ NotifyConfigurationChangedArgs args;
+ args.eventTime = eventTime;
+ mNotifyConfigurationChangedArgs.push_back(args);
+ }
+
+ virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
+ int32_t scanCode, int32_t metaState, nsecs_t downTime) {
+ NotifyKeyArgs args;
+ args.eventTime = eventTime;
+ args.deviceId = deviceId;
+ args.source = source;
+ args.policyFlags = policyFlags;
+ args.action = action;
+ args.flags = flags;
+ args.keyCode = keyCode;
+ args.scanCode = scanCode;
+ args.metaState = metaState;
+ args.downTime = downTime;
+ mNotifyKeyArgs.push_back(args);
+ }
+
+ virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags, int32_t action, int32_t flags,
+ int32_t metaState, int32_t edgeFlags,
+ uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
+ float xPrecision, float yPrecision, nsecs_t downTime) {
+ NotifyMotionArgs args;
+ args.eventTime = eventTime;
+ args.deviceId = deviceId;
+ args.source = source;
+ args.policyFlags = policyFlags;
+ args.action = action;
+ args.flags = flags;
+ args.metaState = metaState;
+ args.edgeFlags = edgeFlags;
+ args.pointerCount = pointerCount;
+ args.pointerIds.clear();
+ args.pointerIds.appendArray(pointerIds, pointerCount);
+ args.pointerCoords.clear();
+ args.pointerCoords.appendArray(pointerCoords, pointerCount);
+ args.xPrecision = xPrecision;
+ args.yPrecision = yPrecision;
+ args.downTime = downTime;
+ mNotifyMotionArgs.push_back(args);
+ }
+
+ virtual void notifySwitch(nsecs_t when,
+ int32_t switchCode, int32_t switchValue, uint32_t policyFlags) {
+ NotifySwitchArgs args;
+ args.when = when;
+ args.switchCode = switchCode;
+ args.switchValue = switchValue;
+ args.policyFlags = policyFlags;
+ mNotifySwitchArgs.push_back(args);
+ }
+
+ virtual void dump(String8& dump) {
+ ADD_FAILURE() << "Should never be called by input reader.";
+ }
+
+ virtual void dispatchOnce() {
+ ADD_FAILURE() << "Should never be called by input reader.";
+ }
+
+ virtual int32_t injectInputEvent(const InputEvent* event,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) {
+ ADD_FAILURE() << "Should never be called by input reader.";
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
+
+ virtual void setInputWindows(const Vector<InputWindow>& inputWindows) {
+ ADD_FAILURE() << "Should never be called by input reader.";
+ }
+
+ virtual void setFocusedApplication(const InputApplication* inputApplication) {
+ ADD_FAILURE() << "Should never be called by input reader.";
+ }
+
+ virtual void setInputDispatchMode(bool enabled, bool frozen) {
+ ADD_FAILURE() << "Should never be called by input reader.";
+ }
+
+ virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+ const sp<InputChannel>& toChannel) {
+ ADD_FAILURE() << "Should never be called by input reader.";
+ return 0;
+ }
+
+ virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
+ ADD_FAILURE() << "Should never be called by input reader.";
+ return 0;
+ }
+
+ virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) {
+ ADD_FAILURE() << "Should never be called by input reader.";
+ return 0;
+ }
+};
+
+
+// --- FakeEventHub ---
+
+class FakeEventHub : public EventHubInterface {
+ struct KeyInfo {
+ int32_t keyCode;
+ uint32_t flags;
+ };
+
+ struct Device {
+ String8 name;
+ uint32_t classes;
+ PropertyMap configuration;
+ KeyedVector<int, RawAbsoluteAxisInfo> axes;
+ KeyedVector<int32_t, int32_t> keyCodeStates;
+ KeyedVector<int32_t, int32_t> scanCodeStates;
+ KeyedVector<int32_t, int32_t> switchStates;
+ KeyedVector<int32_t, KeyInfo> keys;
+ KeyedVector<int32_t, bool> leds;
+ Vector<VirtualKeyDefinition> virtualKeys;
+
+ Device(const String8& name, uint32_t classes) :
+ name(name), classes(classes) {
+ }
+ };
+
+ KeyedVector<int32_t, Device*> mDevices;
+ Vector<String8> mExcludedDevices;
+ List<RawEvent> mEvents;
+
+protected:
+ virtual ~FakeEventHub() {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ delete mDevices.valueAt(i);
+ }
+ }
+
+public:
+ FakeEventHub() { }
+
+ void addDevice(int32_t deviceId, const String8& name, uint32_t classes) {
+ Device* device = new Device(name, classes);
+ mDevices.add(deviceId, device);
+
+ enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0, 0, 0);
+ }
+
+ void removeDevice(int32_t deviceId) {
+ delete mDevices.valueFor(deviceId);
+ mDevices.removeItem(deviceId);
+
+ enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0, 0, 0);
+ }
+
+ void finishDeviceScan() {
+ enqueueEvent(ARBITRARY_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0, 0, 0);
+ }
+
+ void addConfigurationProperty(int32_t deviceId, const String8& key, const String8& value) {
+ Device* device = getDevice(deviceId);
+ device->configuration.addProperty(key, value);
+ }
+
+ void addConfigurationMap(int32_t deviceId, const PropertyMap* configuration) {
+ Device* device = getDevice(deviceId);
+ device->configuration.addAll(configuration);
+ }
+
+ void addAxis(int32_t deviceId, int axis,
+ int32_t minValue, int32_t maxValue, int flat, int fuzz) {
+ Device* device = getDevice(deviceId);
+
+ RawAbsoluteAxisInfo info;
+ info.valid = true;
+ info.minValue = minValue;
+ info.maxValue = maxValue;
+ info.flat = flat;
+ info.fuzz = fuzz;
+ device->axes.add(axis, info);
+ }
+
+ void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state) {
+ Device* device = getDevice(deviceId);
+ device->keyCodeStates.replaceValueFor(keyCode, state);
+ }
+
+ void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) {
+ Device* device = getDevice(deviceId);
+ device->scanCodeStates.replaceValueFor(scanCode, state);
+ }
+
+ void setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state) {
+ Device* device = getDevice(deviceId);
+ device->switchStates.replaceValueFor(switchCode, state);
+ }
+
+ void addKey(int32_t deviceId, int32_t scanCode, int32_t keyCode, uint32_t flags) {
+ Device* device = getDevice(deviceId);
+ KeyInfo info;
+ info.keyCode = keyCode;
+ info.flags = flags;
+ device->keys.add(scanCode, info);
+ }
+
+ void addLed(int32_t deviceId, int32_t led, bool initialState) {
+ Device* device = getDevice(deviceId);
+ device->leds.add(led, initialState);
+ }
+
+ bool getLedState(int32_t deviceId, int32_t led) {
+ Device* device = getDevice(deviceId);
+ return device->leds.valueFor(led);
+ }
+
+ Vector<String8>& getExcludedDevices() {
+ return mExcludedDevices;
+ }
+
+ void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition) {
+ Device* device = getDevice(deviceId);
+ device->virtualKeys.push(definition);
+ }
+
+ void enqueueEvent(nsecs_t when, int32_t deviceId, int32_t type,
+ int32_t scanCode, int32_t keyCode, int32_t value, uint32_t flags) {
+ RawEvent event;
+ event.when = when;
+ event.deviceId = deviceId;
+ event.type = type;
+ event.scanCode = scanCode;
+ event.keyCode = keyCode;
+ event.value = value;
+ event.flags = flags;
+ mEvents.push_back(event);
+ }
+
+ void assertQueueIsEmpty() {
+ ASSERT_EQ(size_t(0), mEvents.size())
+ << "Expected the event queue to be empty (fully consumed).";
+ }
+
+private:
+ Device* getDevice(int32_t deviceId) const {
+ ssize_t index = mDevices.indexOfKey(deviceId);
+ return index >= 0 ? mDevices.valueAt(index) : NULL;
+ }
+
+ virtual uint32_t getDeviceClasses(int32_t deviceId) const {
+ Device* device = getDevice(deviceId);
+ return device ? device->classes : 0;
+ }
+
+ virtual String8 getDeviceName(int32_t deviceId) const {
+ Device* device = getDevice(deviceId);
+ return device ? device->name : String8("unknown");
+ }
+
+ virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ *outConfiguration = device->configuration;
+ }
+ }
+
+ virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+ RawAbsoluteAxisInfo* outAxisInfo) const {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ ssize_t index = device->axes.indexOfKey(axis);
+ if (index >= 0) {
+ *outAxisInfo = device->axes.valueAt(index);
+ return OK;
+ }
+ }
+ return -1;
+ }
+
+ virtual status_t scancodeToKeycode(int32_t deviceId, int scancode,
+ int32_t* outKeycode, uint32_t* outFlags) const {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ ssize_t index = device->keys.indexOfKey(scancode);
+ if (index >= 0) {
+ if (outKeycode) {
+ *outKeycode = device->keys.valueAt(index).keyCode;
+ }
+ if (outFlags) {
+ *outFlags = device->keys.valueAt(index).flags;
+ }
+ return OK;
+ }
+ }
+ return NAME_NOT_FOUND;
+ }
+
+ virtual void addExcludedDevice(const char* deviceName) {
+ mExcludedDevices.add(String8(deviceName));
+ }
+
+ virtual bool getEvent(RawEvent* outEvent) {
+ if (mEvents.empty()) {
+ return false;
+ }
+
+ *outEvent = *mEvents.begin();
+ mEvents.erase(mEvents.begin());
+ return true;
+ }
+
+ virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ ssize_t index = device->scanCodeStates.indexOfKey(scanCode);
+ if (index >= 0) {
+ return device->scanCodeStates.valueAt(index);
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+ }
+
+ virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ ssize_t index = device->keyCodeStates.indexOfKey(keyCode);
+ if (index >= 0) {
+ return device->keyCodeStates.valueAt(index);
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+ }
+
+ virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ ssize_t index = device->switchStates.indexOfKey(sw);
+ if (index >= 0) {
+ return device->switchStates.valueAt(index);
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+ }
+
+ virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
+ uint8_t* outFlags) const {
+ bool result = false;
+ Device* device = getDevice(deviceId);
+ if (device) {
+ for (size_t i = 0; i < numCodes; i++) {
+ for (size_t j = 0; j < device->keys.size(); j++) {
+ if (keyCodes[i] == device->keys.valueAt(j).keyCode) {
+ outFlags[i] = 1;
+ result = true;
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ virtual bool hasLed(int32_t deviceId, int32_t led) const {
+ Device* device = getDevice(deviceId);
+ return device && device->leds.indexOfKey(led) >= 0;
+ }
+
+ virtual void setLedState(int32_t deviceId, int32_t led, bool on) {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ ssize_t index = device->leds.indexOfKey(led);
+ if (index >= 0) {
+ device->leds.replaceValueAt(led, on);
+ } else {
+ ADD_FAILURE()
+ << "Attempted to set the state of an LED that the EventHub declared "
+ "was not present. led=" << led;
+ }
+ }
+ }
+
+ virtual void getVirtualKeyDefinitions(int32_t deviceId,
+ Vector<VirtualKeyDefinition>& outVirtualKeys) const {
+ outVirtualKeys.clear();
+
+ Device* device = getDevice(deviceId);
+ if (device) {
+ outVirtualKeys.appendVector(device->virtualKeys);
+ }
+ }
+
+ virtual void dump(String8& dump) {
+ }
+};
+
+
+// --- FakeInputReaderContext ---
+
+class FakeInputReaderContext : public InputReaderContext {
+ sp<EventHubInterface> mEventHub;
+ sp<InputReaderPolicyInterface> mPolicy;
+ sp<InputDispatcherInterface> mDispatcher;
+ int32_t mGlobalMetaState;
+ bool mUpdateGlobalMetaStateWasCalled;
+
+public:
+ FakeInputReaderContext(const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& policy,
+ const sp<InputDispatcherInterface>& dispatcher) :
+ mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher),
+ mGlobalMetaState(0) {
+ }
+
+ virtual ~FakeInputReaderContext() { }
+
+ void assertUpdateGlobalMetaStateWasCalled() {
+ ASSERT_TRUE(mUpdateGlobalMetaStateWasCalled)
+ << "Expected updateGlobalMetaState() to have been called.";
+ mUpdateGlobalMetaStateWasCalled = false;
+ }
+
+ void setGlobalMetaState(int32_t state) {
+ mGlobalMetaState = state;
+ }
+
+private:
+ virtual void updateGlobalMetaState() {
+ mUpdateGlobalMetaStateWasCalled = true;
+ }
+
+ virtual int32_t getGlobalMetaState() {
+ return mGlobalMetaState;
+ }
+
+ virtual EventHubInterface* getEventHub() {
+ return mEventHub.get();
+ }
+
+ virtual InputReaderPolicyInterface* getPolicy() {
+ return mPolicy.get();
+ }
+
+ virtual InputDispatcherInterface* getDispatcher() {
+ return mDispatcher.get();
+ }
+
+ virtual void disableVirtualKeysUntil(nsecs_t time) {
+ }
+
+ virtual bool shouldDropVirtualKey(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode) {
+ return false;
+ }
+};
+
+
+// --- FakeInputMapper ---
+
+class FakeInputMapper : public InputMapper {
+ uint32_t mSources;
+ int32_t mKeyboardType;
+ int32_t mMetaState;
+ KeyedVector<int32_t, int32_t> mKeyCodeStates;
+ KeyedVector<int32_t, int32_t> mScanCodeStates;
+ KeyedVector<int32_t, int32_t> mSwitchStates;
+ Vector<int32_t> mSupportedKeyCodes;
+ RawEvent mLastEvent;
+
+ bool mConfigureWasCalled;
+ bool mResetWasCalled;
+ bool mProcessWasCalled;
+
+public:
+ FakeInputMapper(InputDevice* device, uint32_t sources) :
+ InputMapper(device),
+ mSources(sources), mKeyboardType(AINPUT_KEYBOARD_TYPE_NONE),
+ mMetaState(0),
+ mConfigureWasCalled(false), mResetWasCalled(false), mProcessWasCalled(false) {
+ }
+
+ virtual ~FakeInputMapper() { }
+
+ void setKeyboardType(int32_t keyboardType) {
+ mKeyboardType = keyboardType;
+ }
+
+ void setMetaState(int32_t metaState) {
+ mMetaState = metaState;
+ }
+
+ void assertConfigureWasCalled() {
+ ASSERT_TRUE(mConfigureWasCalled)
+ << "Expected configure() to have been called.";
+ mConfigureWasCalled = false;
+ }
+
+ void assertResetWasCalled() {
+ ASSERT_TRUE(mResetWasCalled)
+ << "Expected reset() to have been called.";
+ mResetWasCalled = false;
+ }
+
+ void assertProcessWasCalled(RawEvent* outLastEvent = NULL) {
+ ASSERT_TRUE(mProcessWasCalled)
+ << "Expected process() to have been called.";
+ if (outLastEvent) {
+ *outLastEvent = mLastEvent;
+ }
+ mProcessWasCalled = false;
+ }
+
+ void setKeyCodeState(int32_t keyCode, int32_t state) {
+ mKeyCodeStates.replaceValueFor(keyCode, state);
+ }
+
+ void setScanCodeState(int32_t scanCode, int32_t state) {
+ mScanCodeStates.replaceValueFor(scanCode, state);
+ }
+
+ void setSwitchState(int32_t switchCode, int32_t state) {
+ mSwitchStates.replaceValueFor(switchCode, state);
+ }
+
+ void addSupportedKeyCode(int32_t keyCode) {
+ mSupportedKeyCodes.add(keyCode);
+ }
+
+private:
+ virtual uint32_t getSources() {
+ return mSources;
+ }
+
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) {
+ InputMapper::populateDeviceInfo(deviceInfo);
+
+ if (mKeyboardType != AINPUT_KEYBOARD_TYPE_NONE) {
+ deviceInfo->setKeyboardType(mKeyboardType);
+ }
+ }
+
+ virtual void configure() {
+ mConfigureWasCalled = true;
+ }
+
+ virtual void reset() {
+ mResetWasCalled = true;
+ }
+
+ virtual void process(const RawEvent* rawEvent) {
+ mLastEvent = *rawEvent;
+ mProcessWasCalled = true;
+ }
+
+ virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ ssize_t index = mKeyCodeStates.indexOfKey(keyCode);
+ return index >= 0 ? mKeyCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN;
+ }
+
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ ssize_t index = mScanCodeStates.indexOfKey(scanCode);
+ return index >= 0 ? mScanCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN;
+ }
+
+ virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+ ssize_t index = mSwitchStates.indexOfKey(switchCode);
+ return index >= 0 ? mSwitchStates.valueAt(index) : AKEY_STATE_UNKNOWN;
+ }
+
+ virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ bool result = false;
+ for (size_t i = 0; i < numCodes; i++) {
+ for (size_t j = 0; j < mSupportedKeyCodes.size(); j++) {
+ if (keyCodes[i] == mSupportedKeyCodes[j]) {
+ outFlags[i] = 1;
+ result = true;
+ }
+ }
+ }
+ return result;
+ }
+
+ virtual int32_t getMetaState() {
+ return mMetaState;
+ }
+};
+
+
+// --- InstrumentedInputReader ---
+
+class InstrumentedInputReader : public InputReader {
+ InputDevice* mNextDevice;
+
+public:
+ InstrumentedInputReader(const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& policy,
+ const sp<InputDispatcherInterface>& dispatcher) :
+ InputReader(eventHub, policy, dispatcher) {
+ }
+
+ virtual ~InstrumentedInputReader() {
+ if (mNextDevice) {
+ delete mNextDevice;
+ }
+ }
+
+ void setNextDevice(InputDevice* device) {
+ mNextDevice = device;
+ }
+
+protected:
+ virtual InputDevice* createDevice(int32_t deviceId, const String8& name, uint32_t classes) {
+ if (mNextDevice) {
+ InputDevice* device = mNextDevice;
+ mNextDevice = NULL;
+ return device;
+ }
+ return InputReader::createDevice(deviceId, name, classes);
+ }
+
+ friend class InputReaderTest;
+};
+
+
+// --- InputReaderTest ---
+
+class InputReaderTest : public testing::Test {
+protected:
+ sp<FakeInputDispatcher> mFakeDispatcher;
+ sp<FakeInputReaderPolicy> mFakePolicy;
+ sp<FakeEventHub> mFakeEventHub;
+ sp<InstrumentedInputReader> mReader;
+
+ virtual void SetUp() {
+ mFakeEventHub = new FakeEventHub();
+ mFakePolicy = new FakeInputReaderPolicy();
+ mFakeDispatcher = new FakeInputDispatcher();
+
+ mReader = new InstrumentedInputReader(mFakeEventHub, mFakePolicy, mFakeDispatcher);
+ }
+
+ virtual void TearDown() {
+ mReader.clear();
+
+ mFakeDispatcher.clear();
+ mFakePolicy.clear();
+ mFakeEventHub.clear();
+ }
+
+ void addDevice(int32_t deviceId, const String8& name, uint32_t classes,
+ const PropertyMap* configuration) {
+ mFakeEventHub->addDevice(deviceId, name, classes);
+ if (configuration) {
+ mFakeEventHub->addConfigurationMap(deviceId, configuration);
+ }
+ mFakeEventHub->finishDeviceScan();
+ mReader->loopOnce();
+ mReader->loopOnce();
+ mFakeEventHub->assertQueueIsEmpty();
+ }
+
+ FakeInputMapper* addDeviceWithFakeInputMapper(int32_t deviceId,
+ const String8& name, uint32_t classes, uint32_t sources,
+ const PropertyMap* configuration) {
+ InputDevice* device = new InputDevice(mReader.get(), deviceId, name);
+ FakeInputMapper* mapper = new FakeInputMapper(device, sources);
+ device->addMapper(mapper);
+ mReader->setNextDevice(device);
+ addDevice(deviceId, name, classes, configuration);
+ return mapper;
+ }
+};
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenNoDevices_ReturnsDefaults) {
+ InputConfiguration config;
+ mReader->getInputConfiguration(&config);
+
+ ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard);
+ ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation);
+ ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenAlphabeticKeyboardPresent_ReturnsQwertyKeyboard) {
+ ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("keyboard"),
+ INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_ALPHAKEY, NULL));
+
+ InputConfiguration config;
+ mReader->getInputConfiguration(&config);
+
+ ASSERT_EQ(InputConfiguration::KEYBOARD_QWERTY, config.keyboard);
+ ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation);
+ ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenTouchScreenPresent_ReturnsFingerTouchScreen) {
+ PropertyMap configuration;
+ configuration.addProperty(String8("touch.deviceType"), String8("touchScreen"));
+ ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("touchscreen"),
+ INPUT_DEVICE_CLASS_TOUCH, &configuration));
+
+ InputConfiguration config;
+ mReader->getInputConfiguration(&config);
+
+ ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard);
+ ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation);
+ ASSERT_EQ(InputConfiguration::TOUCHSCREEN_FINGER, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenTouchPadPresent_ReturnsFingerNoTouch) {
+ ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("touchpad"),
+ INPUT_DEVICE_CLASS_TOUCH, NULL));
+
+ InputConfiguration config;
+ mReader->getInputConfiguration(&config);
+
+ ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard);
+ ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation);
+ ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenMousePresent_ReturnsNoNavigation) {
+ sp<FakePointerController> controller = new FakePointerController();
+ mFakePolicy->setPointerController(0, controller);
+
+ PropertyMap configuration;
+ configuration.addProperty(String8("cursor.mode"), String8("pointer"));
+ ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("mouse"),
+ INPUT_DEVICE_CLASS_CURSOR, &configuration));
+
+ InputConfiguration config;
+ mReader->getInputConfiguration(&config);
+
+ ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard);
+ ASSERT_EQ(InputConfiguration::NAVIGATION_NONAV, config.navigation);
+ ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenTrackballPresent_ReturnsTrackballNavigation) {
+ PropertyMap configuration;
+ configuration.addProperty(String8("cursor.mode"), String8("navigation"));
+ ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("trackball"),
+ INPUT_DEVICE_CLASS_CURSOR, &configuration));
+
+ InputConfiguration config;
+ mReader->getInputConfiguration(&config);
+
+ ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard);
+ ASSERT_EQ(InputConfiguration::NAVIGATION_TRACKBALL, config.navigation);
+ ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputConfiguration_WhenDPadPresent_ReturnsDPadNavigation) {
+ ASSERT_NO_FATAL_FAILURE(addDevice(0, String8("dpad"),
+ INPUT_DEVICE_CLASS_DPAD, NULL));
+
+ InputConfiguration config;
+ mReader->getInputConfiguration(&config);
+
+ ASSERT_EQ(InputConfiguration::KEYBOARD_NOKEYS, config.keyboard);
+ ASSERT_EQ(InputConfiguration::NAVIGATION_DPAD, config.navigation);
+ ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
+}
+
+TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsValid) {
+ ASSERT_NO_FATAL_FAILURE(addDevice(1, String8("keyboard"),
+ INPUT_DEVICE_CLASS_KEYBOARD, NULL));
+
+ InputDeviceInfo info;
+ status_t result = mReader->getInputDeviceInfo(1, &info);
+
+ ASSERT_EQ(OK, result);
+ ASSERT_EQ(1, info.getId());
+ ASSERT_STREQ("keyboard", info.getName().string());
+ ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, info.getKeyboardType());
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, info.getSources());
+ ASSERT_EQ(size_t(0), info.getMotionRanges().size());
+}
+
+TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsInvalid) {
+ InputDeviceInfo info;
+ status_t result = mReader->getInputDeviceInfo(-1, &info);
+
+ ASSERT_EQ(NAME_NOT_FOUND, result);
+}
+
+TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsIgnored) {
+ addDevice(1, String8("ignored"), 0, NULL); // no classes so device will be ignored
+
+ InputDeviceInfo info;
+ status_t result = mReader->getInputDeviceInfo(1, &info);
+
+ ASSERT_EQ(NAME_NOT_FOUND, result);
+}
+
+TEST_F(InputReaderTest, GetInputDeviceIds) {
+ sp<FakePointerController> controller = new FakePointerController();
+ mFakePolicy->setPointerController(2, controller);
+
+ ASSERT_NO_FATAL_FAILURE(addDevice(1, String8("keyboard"),
+ INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_ALPHAKEY, NULL));
+ ASSERT_NO_FATAL_FAILURE(addDevice(2, String8("mouse"),
+ INPUT_DEVICE_CLASS_CURSOR, NULL));
+
+ Vector<int32_t> ids;
+ mReader->getInputDeviceIds(ids);
+
+ ASSERT_EQ(size_t(2), ids.size());
+ ASSERT_EQ(1, ids[0]);
+ ASSERT_EQ(2, ids[1]);
+}
+
+TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToMappers) {
+ FakeInputMapper* mapper = NULL;
+ ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
+ INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
+ mapper->setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN);
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(0,
+ AINPUT_SOURCE_ANY, AKEYCODE_A))
+ << "Should return unknown when the device id is >= 0 but unknown.";
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(1,
+ AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+ << "Should return unknown when the device id is valid but the sources are not supported by the device.";
+
+ ASSERT_EQ(AKEY_STATE_DOWN, mReader->getKeyCodeState(1,
+ AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+ << "Should return value provided by mapper when device id is valid and the device supports some of the sources.";
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(-1,
+ AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+ << "Should return unknown when the device id is < 0 but the sources are not supported by any device.";
+
+ ASSERT_EQ(AKEY_STATE_DOWN, mReader->getKeyCodeState(-1,
+ AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+ << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
+}
+
+TEST_F(InputReaderTest, GetScanCodeState_ForwardsRequestsToMappers) {
+ FakeInputMapper* mapper = NULL;
+ ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
+ INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
+ mapper->setScanCodeState(KEY_A, AKEY_STATE_DOWN);
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(0,
+ AINPUT_SOURCE_ANY, KEY_A))
+ << "Should return unknown when the device id is >= 0 but unknown.";
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(1,
+ AINPUT_SOURCE_TRACKBALL, KEY_A))
+ << "Should return unknown when the device id is valid but the sources are not supported by the device.";
+
+ ASSERT_EQ(AKEY_STATE_DOWN, mReader->getScanCodeState(1,
+ AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, KEY_A))
+ << "Should return value provided by mapper when device id is valid and the device supports some of the sources.";
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(-1,
+ AINPUT_SOURCE_TRACKBALL, KEY_A))
+ << "Should return unknown when the device id is < 0 but the sources are not supported by any device.";
+
+ ASSERT_EQ(AKEY_STATE_DOWN, mReader->getScanCodeState(-1,
+ AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, KEY_A))
+ << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
+}
+
+TEST_F(InputReaderTest, GetSwitchState_ForwardsRequestsToMappers) {
+ FakeInputMapper* mapper = NULL;
+ ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
+ INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
+ mapper->setSwitchState(SW_LID, AKEY_STATE_DOWN);
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(0,
+ AINPUT_SOURCE_ANY, SW_LID))
+ << "Should return unknown when the device id is >= 0 but unknown.";
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(1,
+ AINPUT_SOURCE_TRACKBALL, SW_LID))
+ << "Should return unknown when the device id is valid but the sources are not supported by the device.";
+
+ ASSERT_EQ(AKEY_STATE_DOWN, mReader->getSwitchState(1,
+ AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, SW_LID))
+ << "Should return value provided by mapper when device id is valid and the device supports some of the sources.";
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(-1,
+ AINPUT_SOURCE_TRACKBALL, SW_LID))
+ << "Should return unknown when the device id is < 0 but the sources are not supported by any device.";
+
+ ASSERT_EQ(AKEY_STATE_DOWN, mReader->getSwitchState(-1,
+ AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, SW_LID))
+ << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
+}
+
+TEST_F(InputReaderTest, MarkSupportedKeyCodes_ForwardsRequestsToMappers) {
+ FakeInputMapper* mapper = NULL;
+ ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
+ INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
+ mapper->addSupportedKeyCode(AKEYCODE_A);
+ mapper->addSupportedKeyCode(AKEYCODE_B);
+
+ const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 };
+ uint8_t flags[4] = { 0, 0, 0, 1 };
+
+ ASSERT_FALSE(mReader->hasKeys(0, AINPUT_SOURCE_ANY, 4, keyCodes, flags))
+ << "Should return false when device id is >= 0 but unknown.";
+ ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]);
+
+ flags[3] = 1;
+ ASSERT_FALSE(mReader->hasKeys(1, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+ << "Should return false when device id is valid but the sources are not supported by the device.";
+ ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]);
+
+ flags[3] = 1;
+ ASSERT_TRUE(mReader->hasKeys(1, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+ << "Should return value provided by mapper when device id is valid and the device supports some of the sources.";
+ ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]);
+
+ flags[3] = 1;
+ ASSERT_FALSE(mReader->hasKeys(-1, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+ << "Should return false when the device id is < 0 but the sources are not supported by any device.";
+ ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]);
+
+ flags[3] = 1;
+ ASSERT_TRUE(mReader->hasKeys(-1, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+ << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
+ ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]);
+}
+
+TEST_F(InputReaderTest, LoopOnce_WhenDeviceScanFinished_SendsConfigurationChanged) {
+ addDevice(1, String8("ignored"), INPUT_DEVICE_CLASS_KEYBOARD, NULL);
+
+ FakeInputDispatcher::NotifyConfigurationChangedArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyConfigurationChangedWasCalled(&args));
+ ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+}
+
+TEST_F(InputReaderTest, LoopOnce_ForwardsRawEventsToMappers) {
+ FakeInputMapper* mapper = NULL;
+ ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
+ INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
+
+ mFakeEventHub->enqueueEvent(0, 1, EV_KEY, KEY_A, AKEYCODE_A, 1, POLICY_FLAG_WAKE);
+ mReader->loopOnce();
+ ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty());
+
+ RawEvent event;
+ ASSERT_NO_FATAL_FAILURE(mapper->assertProcessWasCalled(&event));
+ ASSERT_EQ(0, event.when);
+ ASSERT_EQ(1, event.deviceId);
+ ASSERT_EQ(EV_KEY, event.type);
+ ASSERT_EQ(KEY_A, event.scanCode);
+ ASSERT_EQ(AKEYCODE_A, event.keyCode);
+ ASSERT_EQ(1, event.value);
+ ASSERT_EQ(POLICY_FLAG_WAKE, event.flags);
+}
+
+
+// --- InputDeviceTest ---
+
+class InputDeviceTest : public testing::Test {
+protected:
+ static const char* DEVICE_NAME;
+ static const int32_t DEVICE_ID;
+
+ sp<FakeEventHub> mFakeEventHub;
+ sp<FakeInputReaderPolicy> mFakePolicy;
+ sp<FakeInputDispatcher> mFakeDispatcher;
+ FakeInputReaderContext* mFakeContext;
+
+ InputDevice* mDevice;
+
+ virtual void SetUp() {
+ mFakeEventHub = new FakeEventHub();
+ mFakePolicy = new FakeInputReaderPolicy();
+ mFakeDispatcher = new FakeInputDispatcher();
+ mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeDispatcher);
+
+ mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0);
+ mDevice = new InputDevice(mFakeContext, DEVICE_ID, String8(DEVICE_NAME));
+ }
+
+ virtual void TearDown() {
+ delete mDevice;
+
+ delete mFakeContext;
+ mFakeDispatcher.clear();
+ mFakePolicy.clear();
+ mFakeEventHub.clear();
+ }
+};
+
+const char* InputDeviceTest::DEVICE_NAME = "device";
+const int32_t InputDeviceTest::DEVICE_ID = 1;
+
+TEST_F(InputDeviceTest, ImmutableProperties) {
+ ASSERT_EQ(DEVICE_ID, mDevice->getId());
+ ASSERT_STREQ(DEVICE_NAME, mDevice->getName());
+}
+
+TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) {
+ // Configuration.
+ mDevice->configure();
+
+ // Metadata.
+ ASSERT_TRUE(mDevice->isIgnored());
+ ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mDevice->getSources());
+
+ InputDeviceInfo info;
+ mDevice->getDeviceInfo(&info);
+ ASSERT_EQ(DEVICE_ID, info.getId());
+ ASSERT_STREQ(DEVICE_NAME, info.getName().string());
+ ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NONE, info.getKeyboardType());
+ ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, info.getSources());
+
+ // State queries.
+ ASSERT_EQ(0, mDevice->getMetaState());
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_KEYBOARD, 0))
+ << "Ignored device should return unknown key code state.";
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getScanCodeState(AINPUT_SOURCE_KEYBOARD, 0))
+ << "Ignored device should return unknown scan code state.";
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 0))
+ << "Ignored device should return unknown switch state.";
+
+ const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B };
+ uint8_t flags[2] = { 0, 1 };
+ ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, 2, keyCodes, flags))
+ << "Ignored device should never mark any key codes.";
+ ASSERT_EQ(0, flags[0]) << "Flag for unsupported key should be unchanged.";
+ ASSERT_EQ(1, flags[1]) << "Flag for unsupported key should be unchanged.";
+
+ // Reset.
+ mDevice->reset();
+}
+
+TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRequestsToMappers) {
+ // Configuration.
+ mFakeEventHub->addConfigurationProperty(DEVICE_ID, String8("key"), String8("value"));
+
+ FakeInputMapper* mapper1 = new FakeInputMapper(mDevice, AINPUT_SOURCE_KEYBOARD);
+ mapper1->setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ mapper1->setMetaState(AMETA_ALT_ON);
+ mapper1->addSupportedKeyCode(AKEYCODE_A);
+ mapper1->addSupportedKeyCode(AKEYCODE_B);
+ mapper1->setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN);
+ mapper1->setKeyCodeState(AKEYCODE_B, AKEY_STATE_UP);
+ mapper1->setScanCodeState(2, AKEY_STATE_DOWN);
+ mapper1->setScanCodeState(3, AKEY_STATE_UP);
+ mapper1->setSwitchState(4, AKEY_STATE_DOWN);
+ mDevice->addMapper(mapper1);
+
+ FakeInputMapper* mapper2 = new FakeInputMapper(mDevice, AINPUT_SOURCE_TOUCHSCREEN);
+ mapper2->setMetaState(AMETA_SHIFT_ON);
+ mDevice->addMapper(mapper2);
+
+ mDevice->configure();
+
+ String8 propertyValue;
+ ASSERT_TRUE(mDevice->getConfiguration().tryGetProperty(String8("key"), propertyValue))
+ << "Device should have read configuration during configuration phase.";
+ ASSERT_STREQ("value", propertyValue.string());
+
+ ASSERT_NO_FATAL_FAILURE(mapper1->assertConfigureWasCalled());
+ ASSERT_NO_FATAL_FAILURE(mapper2->assertConfigureWasCalled());
+
+ // Metadata.
+ ASSERT_FALSE(mDevice->isIgnored());
+ ASSERT_EQ(uint32_t(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TOUCHSCREEN), mDevice->getSources());
+
+ InputDeviceInfo info;
+ mDevice->getDeviceInfo(&info);
+ ASSERT_EQ(DEVICE_ID, info.getId());
+ ASSERT_STREQ(DEVICE_NAME, info.getName().string());
+ ASSERT_EQ(AINPUT_KEYBOARD_TYPE_ALPHABETIC, info.getKeyboardType());
+ ASSERT_EQ(uint32_t(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TOUCHSCREEN), info.getSources());
+
+ // State queries.
+ ASSERT_EQ(AMETA_ALT_ON | AMETA_SHIFT_ON, mDevice->getMetaState())
+ << "Should query mappers and combine meta states.";
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+ << "Should return unknown key code state when source not supported.";
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getScanCodeState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+ << "Should return unknown scan code state when source not supported.";
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getSwitchState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+ << "Should return unknown switch state when source not supported.";
+
+ ASSERT_EQ(AKEY_STATE_DOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_KEYBOARD, AKEYCODE_A))
+ << "Should query mapper when source is supported.";
+ ASSERT_EQ(AKEY_STATE_UP, mDevice->getScanCodeState(AINPUT_SOURCE_KEYBOARD, 3))
+ << "Should query mapper when source is supported.";
+ ASSERT_EQ(AKEY_STATE_DOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 4))
+ << "Should query mapper when source is supported.";
+
+ const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 };
+ uint8_t flags[4] = { 0, 0, 0, 1 };
+ ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+ << "Should do nothing when source is unsupported.";
+ ASSERT_EQ(0, flags[0]) << "Flag should be unchanged when source is unsupported.";
+ ASSERT_EQ(0, flags[1]) << "Flag should be unchanged when source is unsupported.";
+ ASSERT_EQ(0, flags[2]) << "Flag should be unchanged when source is unsupported.";
+ ASSERT_EQ(1, flags[3]) << "Flag should be unchanged when source is unsupported.";
+
+ ASSERT_TRUE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, 4, keyCodes, flags))
+ << "Should query mapper when source is supported.";
+ ASSERT_EQ(1, flags[0]) << "Flag for supported key should be set.";
+ ASSERT_EQ(1, flags[1]) << "Flag for supported key should be set.";
+ ASSERT_EQ(0, flags[2]) << "Flag for unsupported key should be unchanged.";
+ ASSERT_EQ(1, flags[3]) << "Flag for unsupported key should be unchanged.";
+
+ // Event handling.
+ RawEvent event;
+ mDevice->process(&event);
+
+ ASSERT_NO_FATAL_FAILURE(mapper1->assertProcessWasCalled());
+ ASSERT_NO_FATAL_FAILURE(mapper2->assertProcessWasCalled());
+
+ // Reset.
+ mDevice->reset();
+
+ ASSERT_NO_FATAL_FAILURE(mapper1->assertResetWasCalled());
+ ASSERT_NO_FATAL_FAILURE(mapper2->assertResetWasCalled());
+}
+
+
+// --- InputMapperTest ---
+
+class InputMapperTest : public testing::Test {
+protected:
+ static const char* DEVICE_NAME;
+ static const int32_t DEVICE_ID;
+
+ sp<FakeEventHub> mFakeEventHub;
+ sp<FakeInputReaderPolicy> mFakePolicy;
+ sp<FakeInputDispatcher> mFakeDispatcher;
+ FakeInputReaderContext* mFakeContext;
+ InputDevice* mDevice;
+
+ virtual void SetUp() {
+ mFakeEventHub = new FakeEventHub();
+ mFakePolicy = new FakeInputReaderPolicy();
+ mFakeDispatcher = new FakeInputDispatcher();
+ mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeDispatcher);
+ mDevice = new InputDevice(mFakeContext, DEVICE_ID, String8(DEVICE_NAME));
+
+ mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0);
+ }
+
+ virtual void TearDown() {
+ delete mDevice;
+ delete mFakeContext;
+ mFakeDispatcher.clear();
+ mFakePolicy.clear();
+ mFakeEventHub.clear();
+ }
+
+ void addConfigurationProperty(const char* key, const char* value) {
+ mFakeEventHub->addConfigurationProperty(DEVICE_ID, String8(key), String8(value));
+ }
+
+ void addMapperAndConfigure(InputMapper* mapper) {
+ mDevice->addMapper(mapper);
+ mDevice->configure();
+ }
+
+ static void process(InputMapper* mapper, nsecs_t when, int32_t deviceId, int32_t type,
+ int32_t scanCode, int32_t keyCode, int32_t value, uint32_t flags) {
+ RawEvent event;
+ event.when = when;
+ event.deviceId = deviceId;
+ event.type = type;
+ event.scanCode = scanCode;
+ event.keyCode = keyCode;
+ event.value = value;
+ event.flags = flags;
+ mapper->process(&event);
+ }
+
+ static void assertMotionRange(const InputDeviceInfo& info,
+ int32_t rangeType, float min, float max, float flat, float fuzz) {
+ const InputDeviceInfo::MotionRange* range = info.getMotionRange(rangeType);
+ ASSERT_TRUE(range != NULL) << "Range: " << rangeType;
+ ASSERT_NEAR(min, range->min, EPSILON) << "Range: " << rangeType;
+ ASSERT_NEAR(max, range->max, EPSILON) << "Range: " << rangeType;
+ ASSERT_NEAR(flat, range->flat, EPSILON) << "Range: " << rangeType;
+ ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Range: " << rangeType;
+ }
+
+ static void assertPointerCoords(const PointerCoords& coords,
+ float x, float y, float pressure, float size,
+ float touchMajor, float touchMinor, float toolMajor, float toolMinor,
+ float orientation) {
+ ASSERT_NEAR(x, coords.x, 1);
+ ASSERT_NEAR(y, coords.y, 1);
+ ASSERT_NEAR(pressure, coords.pressure, EPSILON);
+ ASSERT_NEAR(size, coords.size, EPSILON);
+ ASSERT_NEAR(touchMajor, coords.touchMajor, 1);
+ ASSERT_NEAR(touchMinor, coords.touchMinor, 1);
+ ASSERT_NEAR(toolMajor, coords.toolMajor, 1);
+ ASSERT_NEAR(toolMinor, coords.toolMinor, 1);
+ ASSERT_NEAR(orientation, coords.orientation, EPSILON);
+ }
+};
+
+const char* InputMapperTest::DEVICE_NAME = "device";
+const int32_t InputMapperTest::DEVICE_ID = 1;
+
+
+// --- SwitchInputMapperTest ---
+
+class SwitchInputMapperTest : public InputMapperTest {
+protected:
+};
+
+TEST_F(SwitchInputMapperTest, GetSources) {
+ SwitchInputMapper* mapper = new SwitchInputMapper(mDevice);
+ addMapperAndConfigure(mapper);
+
+ ASSERT_EQ(uint32_t(AINPUT_SOURCE_SWITCH), mapper->getSources());
+}
+
+TEST_F(SwitchInputMapperTest, GetSwitchState) {
+ SwitchInputMapper* mapper = new SwitchInputMapper(mDevice);
+ addMapperAndConfigure(mapper);
+
+ mFakeEventHub->setSwitchState(DEVICE_ID, SW_LID, 1);
+ ASSERT_EQ(1, mapper->getSwitchState(AINPUT_SOURCE_ANY, SW_LID));
+
+ mFakeEventHub->setSwitchState(DEVICE_ID, SW_LID, 0);
+ ASSERT_EQ(0, mapper->getSwitchState(AINPUT_SOURCE_ANY, SW_LID));
+}
+
+TEST_F(SwitchInputMapperTest, Process) {
+ SwitchInputMapper* mapper = new SwitchInputMapper(mDevice);
+ addMapperAndConfigure(mapper);
+
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SW, SW_LID, 0, 1, 0);
+
+ FakeInputDispatcher::NotifySwitchArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifySwitchWasCalled(&args));
+ ASSERT_EQ(ARBITRARY_TIME, args.when);
+ ASSERT_EQ(SW_LID, args.switchCode);
+ ASSERT_EQ(1, args.switchValue);
+ ASSERT_EQ(uint32_t(0), args.policyFlags);
+}
+
+
+// --- KeyboardInputMapperTest ---
+
+class KeyboardInputMapperTest : public InputMapperTest {
+protected:
+ void testDPadKeyRotation(KeyboardInputMapper* mapper,
+ int32_t originalScanCode, int32_t originalKeyCode, int32_t rotatedKeyCode);
+};
+
+void KeyboardInputMapperTest::testDPadKeyRotation(KeyboardInputMapper* mapper,
+ int32_t originalScanCode, int32_t originalKeyCode, int32_t rotatedKeyCode) {
+ FakeInputDispatcher::NotifyKeyArgs args;
+
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, originalKeyCode, 1, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+ ASSERT_EQ(originalScanCode, args.scanCode);
+ ASSERT_EQ(rotatedKeyCode, args.keyCode);
+
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, originalKeyCode, 0, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(originalScanCode, args.scanCode);
+ ASSERT_EQ(rotatedKeyCode, args.keyCode);
+}
+
+
+TEST_F(KeyboardInputMapperTest, GetSources) {
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, mapper->getSources());
+}
+
+TEST_F(KeyboardInputMapperTest, Process_SimpleKeyPress) {
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ // Key down.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_HOME, AKEYCODE_HOME, 1, POLICY_FLAG_WAKE);
+ FakeInputDispatcher::NotifyKeyArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+ ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+ ASSERT_EQ(KEY_HOME, args.scanCode);
+ ASSERT_EQ(AMETA_NONE, args.metaState);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+ ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+ // Key up.
+ process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+ EV_KEY, KEY_HOME, AKEYCODE_HOME, 0, POLICY_FLAG_WAKE);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+ ASSERT_EQ(KEY_HOME, args.scanCode);
+ ASSERT_EQ(AMETA_NONE, args.metaState);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+ ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+}
+
+TEST_F(KeyboardInputMapperTest, Reset_WhenKeysAreNotDown_DoesNotSynthesizeKeyUp) {
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ // Key down.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_HOME, AKEYCODE_HOME, 1, POLICY_FLAG_WAKE);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+ // Key up.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_HOME, AKEYCODE_HOME, 0, POLICY_FLAG_WAKE);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+ // Reset. Since no keys still down, should not synthesize any key ups.
+ mapper->reset();
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+}
+
+TEST_F(KeyboardInputMapperTest, Reset_WhenKeysAreDown_SynthesizesKeyUps) {
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ // Metakey down.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT, 1, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+ // Key down.
+ process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+ EV_KEY, KEY_A, AKEYCODE_A, 1, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+ // Reset. Since two keys are still down, should synthesize two key ups in reverse order.
+ mapper->reset();
+
+ FakeInputDispatcher::NotifyKeyArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(AKEYCODE_A, args.keyCode);
+ ASSERT_EQ(KEY_A, args.scanCode);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+ ASSERT_EQ(uint32_t(0), args.policyFlags);
+ ASSERT_EQ(ARBITRARY_TIME + 1, args.downTime);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(AKEYCODE_SHIFT_LEFT, args.keyCode);
+ ASSERT_EQ(KEY_LEFTSHIFT, args.scanCode);
+ ASSERT_EQ(AMETA_NONE, args.metaState);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+ ASSERT_EQ(uint32_t(0), args.policyFlags);
+ ASSERT_EQ(ARBITRARY_TIME + 1, args.downTime);
+
+ // And that's it.
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+}
+
+TEST_F(KeyboardInputMapperTest, Process_ShouldUpdateMetaState) {
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ // Initial metastate.
+ ASSERT_EQ(AMETA_NONE, mapper->getMetaState());
+
+ // Metakey down.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT, 1, 0);
+ FakeInputDispatcher::NotifyKeyArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState());
+ ASSERT_NO_FATAL_FAILURE(mFakeContext->assertUpdateGlobalMetaStateWasCalled());
+
+ // Key down.
+ process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+ EV_KEY, KEY_A, AKEYCODE_A, 1, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState());
+
+ // Key up.
+ process(mapper, ARBITRARY_TIME + 2, DEVICE_ID,
+ EV_KEY, KEY_A, AKEYCODE_A, 0, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState());
+
+ // Metakey up.
+ process(mapper, ARBITRARY_TIME + 3, DEVICE_ID,
+ EV_KEY, KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT, 0, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AMETA_NONE, args.metaState);
+ ASSERT_EQ(AMETA_NONE, mapper->getMetaState());
+ ASSERT_NO_FATAL_FAILURE(mFakeContext->assertUpdateGlobalMetaStateWasCalled());
+}
+
+TEST_F(KeyboardInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateDPad) {
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ mFakePolicy->setDisplayInfo(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_90);
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_RIGHT));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_DOWN));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_LEFT));
+}
+
+TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) {
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addConfigurationProperty("keyboard.orientationAware", "1");
+ addMapperAndConfigure(mapper);
+
+ mFakePolicy->setDisplayInfo(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_0);
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_RIGHT));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_DOWN));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_LEFT));
+
+ mFakePolicy->setDisplayInfo(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_90);
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN));
+
+ mFakePolicy->setDisplayInfo(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_180);
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_DOWN));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_LEFT));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_UP));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_RIGHT));
+
+ mFakePolicy->setDisplayInfo(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_270);
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_RIGHT));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_DOWN));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_LEFT));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_UP));
+
+ // Special case: if orientation changes while key is down, we still emit the same keycode
+ // in the key up as we did in the key down.
+ FakeInputDispatcher::NotifyKeyArgs args;
+
+ mFakePolicy->setDisplayInfo(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_270);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, AKEYCODE_DPAD_UP, 1, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+ ASSERT_EQ(KEY_UP, args.scanCode);
+ ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode);
+
+ mFakePolicy->setDisplayInfo(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_180);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, AKEYCODE_DPAD_UP, 0, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(KEY_UP, args.scanCode);
+ ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode);
+}
+
+TEST_F(KeyboardInputMapperTest, GetKeyCodeState) {
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ mFakeEventHub->setKeyCodeState(DEVICE_ID, AKEYCODE_A, 1);
+ ASSERT_EQ(1, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
+
+ mFakeEventHub->setKeyCodeState(DEVICE_ID, AKEYCODE_A, 0);
+ ASSERT_EQ(0, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
+}
+
+TEST_F(KeyboardInputMapperTest, GetScanCodeState) {
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ mFakeEventHub->setScanCodeState(DEVICE_ID, KEY_A, 1);
+ ASSERT_EQ(1, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
+
+ mFakeEventHub->setScanCodeState(DEVICE_ID, KEY_A, 0);
+ ASSERT_EQ(0, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
+}
+
+TEST_F(KeyboardInputMapperTest, MarkSupportedKeyCodes) {
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ mFakeEventHub->addKey(DEVICE_ID, KEY_A, AKEYCODE_A, 0);
+
+ const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B };
+ uint8_t flags[2] = { 0, 0 };
+ ASSERT_TRUE(mapper->markSupportedKeyCodes(AINPUT_SOURCE_ANY, 1, keyCodes, flags));
+ ASSERT_TRUE(flags[0]);
+ ASSERT_FALSE(flags[1]);
+}
+
+TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleMetaStateAndLeds) {
+ mFakeEventHub->addLed(DEVICE_ID, LED_CAPSL, true /*initially on*/);
+ mFakeEventHub->addLed(DEVICE_ID, LED_NUML, false /*initially off*/);
+ mFakeEventHub->addLed(DEVICE_ID, LED_SCROLLL, false /*initially off*/);
+
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ // Initialization should have turned all of the lights off.
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+
+ // Toggle caps lock on.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 1, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 0, 0);
+ ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+ ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper->getMetaState());
+
+ // Toggle num lock on.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 1, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 0, 0);
+ ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+ ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+ ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON, mapper->getMetaState());
+
+ // Toggle caps lock off.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 1, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 0, 0);
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+ ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+ ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper->getMetaState());
+
+ // Toggle scroll lock on.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 1, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0, 0);
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+ ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+ ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+ ASSERT_EQ(AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mapper->getMetaState());
+
+ // Toggle num lock off.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 1, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 0, 0);
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+ ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+ ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper->getMetaState());
+
+ // Toggle scroll lock off.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 1, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0, 0);
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+ ASSERT_EQ(AMETA_NONE, mapper->getMetaState());
+}
+
+
+// --- CursorInputMapperTest ---
+
+class CursorInputMapperTest : public InputMapperTest {
+protected:
+ static const int32_t TRACKBALL_MOVEMENT_THRESHOLD;
+
+ sp<FakePointerController> mFakePointerController;
+
+ virtual void SetUp() {
+ InputMapperTest::SetUp();
+
+ mFakePointerController = new FakePointerController();
+ mFakePolicy->setPointerController(DEVICE_ID, mFakePointerController);
+ }
+
+ void testMotionRotation(CursorInputMapper* mapper,
+ int32_t originalX, int32_t originalY, int32_t rotatedX, int32_t rotatedY);
+};
+
+const int32_t CursorInputMapperTest::TRACKBALL_MOVEMENT_THRESHOLD = 6;
+
+void CursorInputMapperTest::testMotionRotation(CursorInputMapper* mapper,
+ int32_t originalX, int32_t originalY, int32_t rotatedX, int32_t rotatedY) {
+ FakeInputDispatcher::NotifyMotionArgs args;
+
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, originalX, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, originalY, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ float(rotatedX) / TRACKBALL_MOVEMENT_THRESHOLD,
+ float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD,
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "pointer");
+ addMapperAndConfigure(mapper);
+
+ ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper->getSources());
+}
+
+TEST_F(CursorInputMapperTest, WhenModeIsNavigation_GetSources_ReturnsTrackball) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addMapperAndConfigure(mapper);
+
+ ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mapper->getSources());
+}
+
+TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeFromPointerController) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "pointer");
+ addMapperAndConfigure(mapper);
+
+ InputDeviceInfo info;
+ mapper->populateDeviceInfo(&info);
+
+ // Initially there may not be a valid motion range.
+ ASSERT_EQ(NULL, info.getMotionRange(AINPUT_MOTION_RANGE_X));
+ ASSERT_EQ(NULL, info.getMotionRange(AINPUT_MOTION_RANGE_Y));
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE,
+ 0.0f, 1.0f, 0.0f, 0.0f));
+
+ // When the bounds are set, then there should be a valid motion range.
+ mFakePointerController->setBounds(1, 2, 800, 480);
+
+ InputDeviceInfo info2;
+ mapper->populateDeviceInfo(&info2);
+
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X,
+ 1, 800, 0.0f, 0.0f));
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y,
+ 2, 480, 0.0f, 0.0f));
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_PRESSURE,
+ 0.0f, 1.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsScaledRange) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addMapperAndConfigure(mapper);
+
+ InputDeviceInfo info;
+ mapper->populateDeviceInfo(&info);
+
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_X,
+ -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_Y,
+ -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE,
+ 0.0f, 1.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaState) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addMapperAndConfigure(mapper);
+
+ mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+ FakeInputDispatcher::NotifyMotionArgs args;
+
+ // Button press.
+ // Mostly testing non x/y behavior here so we don't need to check again elsewhere.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
+ ASSERT_EQ(uint32_t(0), args.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+ ASSERT_EQ(0, args.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+ ASSERT_EQ(0, args.edgeFlags);
+ ASSERT_EQ(uint32_t(1), args.pointerCount);
+ ASSERT_EQ(0, args.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+ ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
+ ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+ // Button release. Should have same down time.
+ process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
+ ASSERT_EQ(uint32_t(0), args.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(0, args.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+ ASSERT_EQ(0, args.edgeFlags);
+ ASSERT_EQ(uint32_t(1), args.pointerCount);
+ ASSERT_EQ(0, args.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+ ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
+ ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+}
+
+TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentXYUpdates) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addMapperAndConfigure(mapper);
+
+ FakeInputDispatcher::NotifyMotionArgs args;
+
+ // Motion in X but not Y.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 1, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ // Motion in Y but not X.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, -2, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+ ASSERT_NEAR(0.0f, args.pointerCoords[0].x, EPSILON);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 0.0f, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addMapperAndConfigure(mapper);
+
+ FakeInputDispatcher::NotifyMotionArgs args;
+
+ // Button press without following sync.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ // Button release without following sync.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addMapperAndConfigure(mapper);
+
+ FakeInputDispatcher::NotifyMotionArgs args;
+
+ // Combined X, Y and Button.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 1, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, -2, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+ 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ // Move X, Y a bit while pressed.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 2, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, 1, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+ 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ // Release Button.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, Reset_WhenButtonIsNotDown_ShouldNotSynthesizeButtonUp) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addMapperAndConfigure(mapper);
+
+ FakeInputDispatcher::NotifyMotionArgs args;
+
+ // Button press.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+
+ // Button release.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+
+ // Reset. Should not synthesize button up since button is not pressed.
+ mapper->reset();
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(CursorInputMapperTest, Reset_WhenButtonIsDown_ShouldSynthesizeButtonUp) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addMapperAndConfigure(mapper);
+
+ FakeInputDispatcher::NotifyMotionArgs args;
+
+ // Button press.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+
+ // Reset. Should synthesize button up.
+ mapper->reset();
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateMotions) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addMapperAndConfigure(mapper);
+
+ mFakePolicy->setDisplayInfo(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_90);
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, -1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, 1));
+}
+
+TEST_F(CursorInputMapperTest, Process_WhenOrientationAware_ShouldRotateMotions) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addConfigurationProperty("cursor.orientationAware", "1");
+ addMapperAndConfigure(mapper);
+
+ mFakePolicy->setDisplayInfo(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0);
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, -1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, 1));
+
+ mFakePolicy->setDisplayInfo(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_90);
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 0, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, -1, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, -1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 0, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, 1, 1));
+
+ mFakePolicy->setDisplayInfo(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_180);
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, -1, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, -1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, -1, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, 1, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, 1, -1));
+
+ mFakePolicy->setDisplayInfo(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_270);
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, -1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, -1, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 0, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, 1, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 0, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, -1));
+}
+
+
+// --- TouchInputMapperTest ---
+
+class TouchInputMapperTest : public InputMapperTest {
+protected:
+ static const int32_t RAW_X_MIN;
+ static const int32_t RAW_X_MAX;
+ static const int32_t RAW_Y_MIN;
+ static const int32_t RAW_Y_MAX;
+ static const int32_t RAW_TOUCH_MIN;
+ static const int32_t RAW_TOUCH_MAX;
+ static const int32_t RAW_TOOL_MIN;
+ static const int32_t RAW_TOOL_MAX;
+ static const int32_t RAW_PRESSURE_MIN;
+ static const int32_t RAW_PRESSURE_MAX;
+ static const int32_t RAW_ORIENTATION_MIN;
+ static const int32_t RAW_ORIENTATION_MAX;
+ static const int32_t RAW_ID_MIN;
+ static const int32_t RAW_ID_MAX;
+ static const float X_PRECISION;
+ static const float Y_PRECISION;
+
+ static const VirtualKeyDefinition VIRTUAL_KEYS[2];
+
+ enum Axes {
+ POSITION = 1 << 0,
+ TOUCH = 1 << 1,
+ TOOL = 1 << 2,
+ PRESSURE = 1 << 3,
+ ORIENTATION = 1 << 4,
+ MINOR = 1 << 5,
+ ID = 1 << 6,
+ };
+
+ void prepareDisplay(int32_t orientation);
+ void prepareVirtualKeys();
+ int32_t toRawX(float displayX);
+ int32_t toRawY(float displayY);
+ float toDisplayX(int32_t rawX);
+ float toDisplayY(int32_t rawY);
+};
+
+const int32_t TouchInputMapperTest::RAW_X_MIN = 25;
+const int32_t TouchInputMapperTest::RAW_X_MAX = 1020;
+const int32_t TouchInputMapperTest::RAW_Y_MIN = 30;
+const int32_t TouchInputMapperTest::RAW_Y_MAX = 1010;
+const int32_t TouchInputMapperTest::RAW_TOUCH_MIN = 0;
+const int32_t TouchInputMapperTest::RAW_TOUCH_MAX = 31;
+const int32_t TouchInputMapperTest::RAW_TOOL_MIN = 0;
+const int32_t TouchInputMapperTest::RAW_TOOL_MAX = 15;
+const int32_t TouchInputMapperTest::RAW_PRESSURE_MIN = RAW_TOUCH_MIN;
+const int32_t TouchInputMapperTest::RAW_PRESSURE_MAX = RAW_TOUCH_MAX;
+const int32_t TouchInputMapperTest::RAW_ORIENTATION_MIN = -7;
+const int32_t TouchInputMapperTest::RAW_ORIENTATION_MAX = 7;
+const int32_t TouchInputMapperTest::RAW_ID_MIN = 0;
+const int32_t TouchInputMapperTest::RAW_ID_MAX = 9;
+const float TouchInputMapperTest::X_PRECISION = float(RAW_X_MAX - RAW_X_MIN) / DISPLAY_WIDTH;
+const float TouchInputMapperTest::Y_PRECISION = float(RAW_Y_MAX - RAW_Y_MIN) / DISPLAY_HEIGHT;
+
+const VirtualKeyDefinition TouchInputMapperTest::VIRTUAL_KEYS[2] = {
+ { KEY_HOME, 60, DISPLAY_HEIGHT + 15, 20, 20 },
+ { KEY_MENU, DISPLAY_HEIGHT - 60, DISPLAY_WIDTH + 15, 20, 20 },
+};
+
+void TouchInputMapperTest::prepareDisplay(int32_t orientation) {
+ mFakePolicy->setDisplayInfo(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation);
+}
+
+void TouchInputMapperTest::prepareVirtualKeys() {
+ mFakeEventHub->addVirtualKeyDefinition(DEVICE_ID, VIRTUAL_KEYS[0]);
+ mFakeEventHub->addVirtualKeyDefinition(DEVICE_ID, VIRTUAL_KEYS[1]);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_HOME, AKEYCODE_HOME, POLICY_FLAG_WAKE);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_MENU, AKEYCODE_MENU, POLICY_FLAG_WAKE);
+}
+
+int32_t TouchInputMapperTest::toRawX(float displayX) {
+ return int32_t(displayX * (RAW_X_MAX - RAW_X_MIN) / DISPLAY_WIDTH + RAW_X_MIN);
+}
+
+int32_t TouchInputMapperTest::toRawY(float displayY) {
+ return int32_t(displayY * (RAW_Y_MAX - RAW_Y_MIN) / DISPLAY_HEIGHT + RAW_Y_MIN);
+}
+
+float TouchInputMapperTest::toDisplayX(int32_t rawX) {
+ return float(rawX - RAW_X_MIN) * DISPLAY_WIDTH / (RAW_X_MAX - RAW_X_MIN);
+}
+
+float TouchInputMapperTest::toDisplayY(int32_t rawY) {
+ return float(rawY - RAW_Y_MIN) * DISPLAY_HEIGHT / (RAW_Y_MAX - RAW_Y_MIN);
+}
+
+
+// --- SingleTouchInputMapperTest ---
+
+class SingleTouchInputMapperTest : public TouchInputMapperTest {
+protected:
+ void prepareAxes(int axes);
+
+ void processDown(SingleTouchInputMapper* mapper, int32_t x, int32_t y);
+ void processMove(SingleTouchInputMapper* mapper, int32_t x, int32_t y);
+ void processUp(SingleTouchInputMapper* mappery);
+ void processPressure(SingleTouchInputMapper* mapper, int32_t pressure);
+ void processToolMajor(SingleTouchInputMapper* mapper, int32_t toolMajor);
+ void processSync(SingleTouchInputMapper* mapper);
+};
+
+void SingleTouchInputMapperTest::prepareAxes(int axes) {
+ if (axes & POSITION) {
+ mFakeEventHub->addAxis(DEVICE_ID, ABS_X, RAW_X_MIN, RAW_X_MAX, 0, 0);
+ mFakeEventHub->addAxis(DEVICE_ID, ABS_Y, RAW_Y_MIN, RAW_Y_MAX, 0, 0);
+ }
+ if (axes & PRESSURE) {
+ mFakeEventHub->addAxis(DEVICE_ID, ABS_PRESSURE, RAW_PRESSURE_MIN, RAW_PRESSURE_MAX, 0, 0);
+ }
+ if (axes & TOOL) {
+ mFakeEventHub->addAxis(DEVICE_ID, ABS_TOOL_WIDTH, RAW_TOOL_MIN, RAW_TOOL_MAX, 0, 0);
+ }
+}
+
+void SingleTouchInputMapperTest::processDown(SingleTouchInputMapper* mapper, int32_t x, int32_t y) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 0, 1, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, 0, x, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, 0, y, 0);
+}
+
+void SingleTouchInputMapperTest::processMove(SingleTouchInputMapper* mapper, int32_t x, int32_t y) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, 0, x, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, 0, y, 0);
+}
+
+void SingleTouchInputMapperTest::processUp(SingleTouchInputMapper* mapper) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 0, 0, 0);
+}
+
+void SingleTouchInputMapperTest::processPressure(
+ SingleTouchInputMapper* mapper, int32_t pressure) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_PRESSURE, 0, pressure, 0);
+}
+
+void SingleTouchInputMapperTest::processToolMajor(
+ SingleTouchInputMapper* mapper, int32_t toolMajor) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TOOL_WIDTH, 0, toolMajor, 0);
+}
+
+void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper* mapper) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+}
+
+
+TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecified_ReturnsTouchPad) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ prepareAxes(POSITION);
+ addMapperAndConfigure(mapper);
+
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper->getSources());
+}
+
+TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchPad_ReturnsTouchPad) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ prepareAxes(POSITION);
+ addConfigurationProperty("touch.deviceType", "touchPad");
+ addMapperAndConfigure(mapper);
+
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper->getSources());
+}
+
+TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_ReturnsTouchScreen) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ prepareAxes(POSITION);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ addMapperAndConfigure(mapper);
+
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper->getSources());
+}
+
+TEST_F(SingleTouchInputMapperTest, GetKeyCodeState) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ // Unknown key.
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
+
+ // Virtual key is down.
+ int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+ int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+ processDown(mapper, x, y);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+ ASSERT_EQ(AKEY_STATE_VIRTUAL, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_HOME));
+
+ // Virtual key is up.
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+ ASSERT_EQ(AKEY_STATE_UP, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_HOME));
+}
+
+TEST_F(SingleTouchInputMapperTest, GetScanCodeState) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ // Unknown key.
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
+
+ // Virtual key is down.
+ int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+ int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+ processDown(mapper, x, y);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+ ASSERT_EQ(AKEY_STATE_VIRTUAL, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_HOME));
+
+ // Virtual key is up.
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+ ASSERT_EQ(AKEY_STATE_UP, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_HOME));
+}
+
+TEST_F(SingleTouchInputMapperTest, MarkSupportedKeyCodes) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ const int32_t keys[2] = { AKEYCODE_HOME, AKEYCODE_A };
+ uint8_t flags[2] = { 0, 0 };
+ ASSERT_TRUE(mapper->markSupportedKeyCodes(AINPUT_SOURCE_ANY, 2, keys, flags));
+ ASSERT_TRUE(flags[0]);
+ ASSERT_FALSE(flags[1]);
+}
+
+TEST_F(SingleTouchInputMapperTest, Reset_WhenVirtualKeysAreDown_SendsUp) {
+ // Note: Ideally we should send cancels but the implementation is more straightforward
+ // with up and this will only happen if a device is forcibly removed.
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+ // Press virtual key.
+ int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+ int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+ processDown(mapper, x, y);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+ // Reset. Since key is down, synthesize key up.
+ mapper->reset();
+
+ FakeInputDispatcher::NotifyKeyArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+ //ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags);
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags);
+ ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+ ASSERT_EQ(KEY_HOME, args.scanCode);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+}
+
+TEST_F(SingleTouchInputMapperTest, Reset_WhenNothingIsPressed_NothingMuchHappens) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ // Press virtual key.
+ int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+ int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+ processDown(mapper, x, y);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+ // Release virtual key.
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled());
+
+ // Reset. Since no key is down, nothing happens.
+ mapper->reset();
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndReleasedNormally_SendsKeyDownAndKeyUp) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+ FakeInputDispatcher::NotifyKeyArgs args;
+
+ // Press virtual key.
+ int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+ int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+ processDown(mapper, x, y);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags);
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags);
+ ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+ ASSERT_EQ(KEY_HOME, args.scanCode);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+ // Release virtual key.
+ processUp(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags);
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags);
+ ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+ ASSERT_EQ(KEY_HOME, args.scanCode);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+ // Should not have sent any motions.
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfBounds_SendsKeyDownAndKeyCancel) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+ FakeInputDispatcher::NotifyKeyArgs keyArgs;
+
+ // Press virtual key.
+ int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+ int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+ processDown(mapper, x, y);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(ARBITRARY_TIME, keyArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, keyArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, keyArgs.source);
+ ASSERT_EQ(POLICY_FLAG_VIRTUAL, keyArgs.policyFlags);
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, keyArgs.flags);
+ ASSERT_EQ(AKEYCODE_HOME, keyArgs.keyCode);
+ ASSERT_EQ(KEY_HOME, keyArgs.scanCode);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, keyArgs.metaState);
+ ASSERT_EQ(ARBITRARY_TIME, keyArgs.downTime);
+
+ // Move out of bounds. This should generate a cancel and a pointer down since we moved
+ // into the display area.
+ y -= 100;
+ processMove(mapper, x, y);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(ARBITRARY_TIME, keyArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, keyArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, keyArgs.source);
+ ASSERT_EQ(POLICY_FLAG_VIRTUAL, keyArgs.policyFlags);
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY
+ | AKEY_EVENT_FLAG_CANCELED, keyArgs.flags);
+ ASSERT_EQ(AKEYCODE_HOME, keyArgs.keyCode);
+ ASSERT_EQ(KEY_HOME, keyArgs.scanCode);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, keyArgs.metaState);
+ ASSERT_EQ(ARBITRARY_TIME, keyArgs.downTime);
+
+ FakeInputDispatcher::NotifyMotionArgs motionArgs;
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Keep moving out of bounds. Should generate a pointer move.
+ y -= 50;
+ processMove(mapper, x, y);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Release out of bounds. Should generate a pointer up.
+ processUp(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Should not have sent any more keys or motions.
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenTouchStartsOutsideDisplayAndMovesIn_SendsDownAsTouchEntersDisplay) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+ FakeInputDispatcher::NotifyMotionArgs motionArgs;
+
+ // Initially go down out of bounds.
+ int32_t x = -10;
+ int32_t y = -10;
+ processDown(mapper, x, y);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+
+ // Move into the display area. Should generate a pointer down.
+ x = 50;
+ y = 75;
+ processMove(mapper, x, y);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Release. Should generate a pointer up.
+ processUp(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Should not have sent any more keys or motions.
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+ FakeInputDispatcher::NotifyMotionArgs motionArgs;
+
+ // Down.
+ int32_t x = 100;
+ int32_t y = 125;
+ processDown(mapper, x, y);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Move.
+ x += 50;
+ y += 75;
+ processMove(mapper, x, y);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Up.
+ processUp(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Should not have sent any more keys or motions.
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_DoesNotRotateMotions) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareAxes(POSITION);
+ addConfigurationProperty("touch.orientationAware", "0");
+ addMapperAndConfigure(mapper);
+
+ FakeInputDispatcher::NotifyMotionArgs args;
+
+ // Rotation 90.
+ prepareDisplay(DISPLAY_ORIENTATION_90);
+ processDown(mapper, toRawX(50), toRawY(75));
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_NEAR(50, args.pointerCoords[0].x, 1);
+ ASSERT_NEAR(75, args.pointerCoords[0].y, 1);
+
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationAware_RotatesMotions) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareAxes(POSITION);
+ addMapperAndConfigure(mapper);
+
+ FakeInputDispatcher::NotifyMotionArgs args;
+
+ // Rotation 0.
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ processDown(mapper, toRawX(50), toRawY(75));
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_NEAR(50, args.pointerCoords[0].x, 1);
+ ASSERT_NEAR(75, args.pointerCoords[0].y, 1);
+
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled());
+
+ // Rotation 90.
+ prepareDisplay(DISPLAY_ORIENTATION_90);
+ processDown(mapper, toRawX(50), toRawY(75));
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_NEAR(75, args.pointerCoords[0].x, 1);
+ ASSERT_NEAR(DISPLAY_WIDTH - 50, args.pointerCoords[0].y, 1);
+
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled());
+
+ // Rotation 180.
+ prepareDisplay(DISPLAY_ORIENTATION_180);
+ processDown(mapper, toRawX(50), toRawY(75));
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_NEAR(DISPLAY_WIDTH - 50, args.pointerCoords[0].x, 1);
+ ASSERT_NEAR(DISPLAY_HEIGHT - 75, args.pointerCoords[0].y, 1);
+
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled());
+
+ // Rotation 270.
+ prepareDisplay(DISPLAY_ORIENTATION_270);
+ processDown(mapper, toRawX(50), toRawY(75));
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_NEAR(DISPLAY_HEIGHT - 75, args.pointerCoords[0].x, 1);
+ ASSERT_NEAR(50, args.pointerCoords[0].y, 1);
+
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | PRESSURE | TOOL);
+ addMapperAndConfigure(mapper);
+
+ // These calculations are based on the input device calibration documentation.
+ int32_t rawX = 100;
+ int32_t rawY = 200;
+ int32_t rawPressure = 10;
+ int32_t rawToolMajor = 12;
+
+ float x = toDisplayX(rawX);
+ float y = toDisplayY(rawY);
+ float pressure = float(rawPressure) / RAW_PRESSURE_MAX;
+ float size = float(rawToolMajor) / RAW_TOOL_MAX;
+ float tool = min(DISPLAY_WIDTH, DISPLAY_HEIGHT) * size;
+ float touch = min(tool * pressure, tool);
+
+ processDown(mapper, rawX, rawY);
+ processPressure(mapper, rawPressure);
+ processToolMajor(mapper, rawToolMajor);
+ processSync(mapper);
+
+ FakeInputDispatcher::NotifyMotionArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ x, y, pressure, size, touch, touch, tool, tool, 0));
+}
+
+
+// --- MultiTouchInputMapperTest ---
+
+class MultiTouchInputMapperTest : public TouchInputMapperTest {
+protected:
+ void prepareAxes(int axes);
+
+ void processPosition(MultiTouchInputMapper* mapper, int32_t x, int32_t y);
+ void processTouchMajor(MultiTouchInputMapper* mapper, int32_t touchMajor);
+ void processTouchMinor(MultiTouchInputMapper* mapper, int32_t touchMinor);
+ void processToolMajor(MultiTouchInputMapper* mapper, int32_t toolMajor);
+ void processToolMinor(MultiTouchInputMapper* mapper, int32_t toolMinor);
+ void processOrientation(MultiTouchInputMapper* mapper, int32_t orientation);
+ void processPressure(MultiTouchInputMapper* mapper, int32_t pressure);
+ void processId(MultiTouchInputMapper* mapper, int32_t id);
+ void processMTSync(MultiTouchInputMapper* mapper);
+ void processSync(MultiTouchInputMapper* mapper);
+};
+
+void MultiTouchInputMapperTest::prepareAxes(int axes) {
+ if (axes & POSITION) {
+ mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX, 0, 0);
+ mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX, 0, 0);
+ }
+ if (axes & TOUCH) {
+ mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_TOUCH_MAJOR, RAW_TOUCH_MIN, RAW_TOUCH_MAX, 0, 0);
+ if (axes & MINOR) {
+ mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_TOUCH_MINOR,
+ RAW_TOUCH_MIN, RAW_TOUCH_MAX, 0, 0);
+ }
+ }
+ if (axes & TOOL) {
+ mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_WIDTH_MAJOR, RAW_TOOL_MIN, RAW_TOOL_MAX, 0, 0);
+ if (axes & MINOR) {
+ mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_WIDTH_MINOR,
+ RAW_TOOL_MAX, RAW_TOOL_MAX, 0, 0);
+ }
+ }
+ if (axes & ORIENTATION) {
+ mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_ORIENTATION,
+ RAW_ORIENTATION_MIN, RAW_ORIENTATION_MAX, 0, 0);
+ }
+ if (axes & PRESSURE) {
+ mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_PRESSURE,
+ RAW_PRESSURE_MIN, RAW_PRESSURE_MAX, 0, 0);
+ }
+ if (axes & ID) {
+ mFakeEventHub->addAxis(DEVICE_ID, ABS_MT_TRACKING_ID,
+ RAW_ID_MIN, RAW_ID_MAX, 0, 0);
+ }
+}
+
+void MultiTouchInputMapperTest::processPosition(
+ MultiTouchInputMapper* mapper, int32_t x, int32_t y) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_X, 0, x, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_Y, 0, y, 0);
+}
+
+void MultiTouchInputMapperTest::processTouchMajor(
+ MultiTouchInputMapper* mapper, int32_t touchMajor) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MAJOR, 0, touchMajor, 0);
+}
+
+void MultiTouchInputMapperTest::processTouchMinor(
+ MultiTouchInputMapper* mapper, int32_t touchMinor) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MINOR, 0, touchMinor, 0);
+}
+
+void MultiTouchInputMapperTest::processToolMajor(
+ MultiTouchInputMapper* mapper, int32_t toolMajor) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MAJOR, 0, toolMajor, 0);
+}
+
+void MultiTouchInputMapperTest::processToolMinor(
+ MultiTouchInputMapper* mapper, int32_t toolMinor) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MINOR, 0, toolMinor, 0);
+}
+
+void MultiTouchInputMapperTest::processOrientation(
+ MultiTouchInputMapper* mapper, int32_t orientation) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_ORIENTATION, 0, orientation, 0);
+}
+
+void MultiTouchInputMapperTest::processPressure(
+ MultiTouchInputMapper* mapper, int32_t pressure) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_PRESSURE, 0, pressure, 0);
+}
+
+void MultiTouchInputMapperTest::processId(
+ MultiTouchInputMapper* mapper, int32_t id) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TRACKING_ID, 0, id, 0);
+}
+
+void MultiTouchInputMapperTest::processMTSync(MultiTouchInputMapper* mapper) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_MT_REPORT, 0, 0, 0);
+}
+
+void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper* mapper) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+}
+
+
+TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackingIds) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+ FakeInputDispatcher::NotifyMotionArgs motionArgs;
+
+ // Two fingers down at once.
+ int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500;
+ processPosition(mapper, x1, y1);
+ processMTSync(mapper);
+ processPosition(mapper, x2, y2);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerIds[0]);
+ ASSERT_EQ(1, motionArgs.pointerIds[1]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Move.
+ x1 += 10; y1 += 15; x2 += 5; y2 -= 10;
+ processPosition(mapper, x1, y1);
+ processMTSync(mapper);
+ processPosition(mapper, x2, y2);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerIds[0]);
+ ASSERT_EQ(1, motionArgs.pointerIds[1]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // First finger up.
+ x2 += 15; y2 -= 20;
+ processPosition(mapper, x2, y2);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerIds[0]);
+ ASSERT_EQ(1, motionArgs.pointerIds[1]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(1, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Move.
+ x2 += 20; y2 -= 25;
+ processPosition(mapper, x2, y2);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(1, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // New finger down.
+ int32_t x3 = 700, y3 = 300;
+ processPosition(mapper, x2, y2);
+ processMTSync(mapper);
+ processPosition(mapper, x3, y3);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerIds[0]);
+ ASSERT_EQ(1, motionArgs.pointerIds[1]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Second finger up.
+ x3 += 30; y3 -= 20;
+ processPosition(mapper, x3, y3);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerIds[0]);
+ ASSERT_EQ(1, motionArgs.pointerIds[1]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Last finger up.
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Should not have sent any more keys or motions.
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingIds) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | ID);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+ FakeInputDispatcher::NotifyMotionArgs motionArgs;
+
+ // Two fingers down at once.
+ int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500;
+ processPosition(mapper, x1, y1);
+ processId(mapper, 1);
+ processMTSync(mapper);
+ processPosition(mapper, x2, y2);
+ processId(mapper, 2);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(1, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(1, motionArgs.pointerIds[0]);
+ ASSERT_EQ(2, motionArgs.pointerIds[1]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+
+ // Move.
+ x1 += 10; y1 += 15; x2 += 5; y2 -= 10;
+ processPosition(mapper, x1, y1);
+ processId(mapper, 1);
+ processMTSync(mapper);
+ processPosition(mapper, x2, y2);
+ processId(mapper, 2);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(1, motionArgs.pointerIds[0]);
+ ASSERT_EQ(2, motionArgs.pointerIds[1]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+
+ // First finger up.
+ x2 += 15; y2 -= 20;
+ processPosition(mapper, x2, y2);
+ processId(mapper, 2);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(1, motionArgs.pointerIds[0]);
+ ASSERT_EQ(2, motionArgs.pointerIds[1]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(2, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+
+ // Move.
+ x2 += 20; y2 -= 25;
+ processPosition(mapper, x2, y2);
+ processId(mapper, 2);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(2, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+
+ // New finger down.
+ int32_t x3 = 700, y3 = 300;
+ processPosition(mapper, x2, y2);
+ processId(mapper, 2);
+ processMTSync(mapper);
+ processPosition(mapper, x3, y3);
+ processId(mapper, 3);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(2, motionArgs.pointerIds[0]);
+ ASSERT_EQ(3, motionArgs.pointerIds[1]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+
+ // Second finger up.
+ x3 += 30; y3 -= 20;
+ processPosition(mapper, x3, y3);
+ processId(mapper, 3);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(2, motionArgs.pointerIds[0]);
+ ASSERT_EQ(3, motionArgs.pointerIds[1]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(3, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+
+ // Last finger up.
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(3, motionArgs.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0));
+
+ // Should not have sent any more keys or motions.
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyKeyWasNotCalled());
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_AllAxes_WithDefaultCalibration) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | TOUCH | TOOL | PRESSURE | ORIENTATION | ID | MINOR);
+ addMapperAndConfigure(mapper);
+
+ // These calculations are based on the input device calibration documentation.
+ int32_t rawX = 100;
+ int32_t rawY = 200;
+ int32_t rawTouchMajor = 7;
+ int32_t rawTouchMinor = 6;
+ int32_t rawToolMajor = 9;
+ int32_t rawToolMinor = 8;
+ int32_t rawPressure = 11;
+ int32_t rawOrientation = 3;
+ int32_t id = 5;
+
+ float x = toDisplayX(rawX);
+ float y = toDisplayY(rawY);
+ float pressure = float(rawPressure) / RAW_PRESSURE_MAX;
+ float size = avg(rawToolMajor, rawToolMinor) / RAW_TOOL_MAX;
+ float toolMajor = float(min(DISPLAY_WIDTH, DISPLAY_HEIGHT)) * rawToolMajor / RAW_TOOL_MAX;
+ float toolMinor = float(min(DISPLAY_WIDTH, DISPLAY_HEIGHT)) * rawToolMinor / RAW_TOOL_MAX;
+ float touchMajor = min(toolMajor * pressure, toolMajor);
+ float touchMinor = min(toolMinor * pressure, toolMinor);
+ float orientation = float(rawOrientation) / RAW_ORIENTATION_MAX * M_PI_2;
+
+ processPosition(mapper, rawX, rawY);
+ processTouchMajor(mapper, rawTouchMajor);
+ processTouchMinor(mapper, rawTouchMinor);
+ processToolMajor(mapper, rawToolMajor);
+ processToolMinor(mapper, rawToolMinor);
+ processPressure(mapper, rawPressure);
+ processOrientation(mapper, rawOrientation);
+ processId(mapper, id);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ FakeInputDispatcher::NotifyMotionArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(id, args.pointerIds[0]);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ x, y, pressure, size, touchMajor, touchMinor, toolMajor, toolMinor, orientation));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_GeometricCalibration) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | TOUCH | TOOL | MINOR);
+ addConfigurationProperty("touch.touchSize.calibration", "geometric");
+ addConfigurationProperty("touch.toolSize.calibration", "geometric");
+ addMapperAndConfigure(mapper);
+
+ // These calculations are based on the input device calibration documentation.
+ int32_t rawX = 100;
+ int32_t rawY = 200;
+ int32_t rawTouchMajor = 140;
+ int32_t rawTouchMinor = 120;
+ int32_t rawToolMajor = 180;
+ int32_t rawToolMinor = 160;
+
+ float x = toDisplayX(rawX);
+ float y = toDisplayY(rawY);
+ float pressure = float(rawTouchMajor) / RAW_TOUCH_MAX;
+ float size = avg(rawToolMajor, rawToolMinor) / RAW_TOOL_MAX;
+ float scale = avg(float(DISPLAY_WIDTH) / (RAW_X_MAX - RAW_X_MIN),
+ float(DISPLAY_HEIGHT) / (RAW_Y_MAX - RAW_Y_MIN));
+ float toolMajor = float(rawToolMajor) * scale;
+ float toolMinor = float(rawToolMinor) * scale;
+ float touchMajor = min(float(rawTouchMajor) * scale, toolMajor);
+ float touchMinor = min(float(rawTouchMinor) * scale, toolMinor);
+
+ processPosition(mapper, rawX, rawY);
+ processTouchMajor(mapper, rawTouchMajor);
+ processTouchMinor(mapper, rawTouchMinor);
+ processToolMajor(mapper, rawToolMajor);
+ processToolMinor(mapper, rawToolMinor);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ FakeInputDispatcher::NotifyMotionArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ x, y, pressure, size, touchMajor, touchMinor, toolMajor, toolMinor, 0));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_TouchToolPressureSizeAxes_SummedLinearCalibration) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | TOUCH | TOOL);
+ addConfigurationProperty("touch.touchSize.calibration", "pressure");
+ addConfigurationProperty("touch.toolSize.calibration", "linear");
+ addConfigurationProperty("touch.toolSize.linearScale", "10");
+ addConfigurationProperty("touch.toolSize.linearBias", "160");
+ addConfigurationProperty("touch.toolSize.isSummed", "1");
+ addConfigurationProperty("touch.pressure.calibration", "amplitude");
+ addConfigurationProperty("touch.pressure.source", "touch");
+ addConfigurationProperty("touch.pressure.scale", "0.01");
+ addMapperAndConfigure(mapper);
+
+ // These calculations are based on the input device calibration documentation.
+ // Note: We only provide a single common touch/tool value because the device is assumed
+ // not to emit separate values for each pointer (isSummed = 1).
+ int32_t rawX = 100;
+ int32_t rawY = 200;
+ int32_t rawX2 = 150;
+ int32_t rawY2 = 250;
+ int32_t rawTouchMajor = 60;
+ int32_t rawToolMajor = 5;
+
+ float x = toDisplayX(rawX);
+ float y = toDisplayY(rawY);
+ float x2 = toDisplayX(rawX2);
+ float y2 = toDisplayY(rawY2);
+ float pressure = float(rawTouchMajor) * 0.01f;
+ float size = float(rawToolMajor) / RAW_TOOL_MAX;
+ float tool = (float(rawToolMajor) * 10.0f + 160.0f) / 2;
+ float touch = min(tool * pressure, tool);
+
+ processPosition(mapper, rawX, rawY);
+ processTouchMajor(mapper, rawTouchMajor);
+ processToolMajor(mapper, rawToolMajor);
+ processMTSync(mapper);
+ processPosition(mapper, rawX2, rawY2);
+ processTouchMajor(mapper, rawTouchMajor);
+ processToolMajor(mapper, rawToolMajor);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ FakeInputDispatcher::NotifyMotionArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ args.action);
+ ASSERT_EQ(size_t(2), args.pointerCount);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ x, y, pressure, size, touch, touch, tool, tool, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[1],
+ x2, y2, pressure, size, touch, touch, tool, tool, 0));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_TouchToolPressureSizeAxes_AreaCalibration) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | TOUCH | TOOL);
+ addConfigurationProperty("touch.touchSize.calibration", "pressure");
+ addConfigurationProperty("touch.toolSize.calibration", "area");
+ addConfigurationProperty("touch.toolSize.areaScale", "22");
+ addConfigurationProperty("touch.toolSize.areaBias", "1");
+ addConfigurationProperty("touch.toolSize.linearScale", "9.2");
+ addConfigurationProperty("touch.toolSize.linearBias", "3");
+ addConfigurationProperty("touch.pressure.calibration", "amplitude");
+ addConfigurationProperty("touch.pressure.source", "touch");
+ addConfigurationProperty("touch.pressure.scale", "0.01");
+ addMapperAndConfigure(mapper);
+
+ // These calculations are based on the input device calibration documentation.
+ int32_t rawX = 100;
+ int32_t rawY = 200;
+ int32_t rawTouchMajor = 60;
+ int32_t rawToolMajor = 5;
+
+ float x = toDisplayX(rawX);
+ float y = toDisplayY(rawY);
+ float pressure = float(rawTouchMajor) * 0.01f;
+ float size = float(rawToolMajor) / RAW_TOOL_MAX;
+ float tool = sqrtf(float(rawToolMajor) * 22.0f + 1.0f) * 9.2f + 3.0f;
+ float touch = min(tool * pressure, tool);
+
+ processPosition(mapper, rawX, rawY);
+ processTouchMajor(mapper, rawTouchMajor);
+ processToolMajor(mapper, rawToolMajor);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ FakeInputDispatcher::NotifyMotionArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeDispatcher->assertNotifyMotionWasCalled(&args));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ x, y, pressure, size, touch, touch, tool, tool, 0));
+}
+
+} // namespace android
diff --git a/services/java/com/android/server/AccessibilityManagerService.java b/services/java/com/android/server/AccessibilityManagerService.java
index 87de79a..04ae490 100644
--- a/services/java/com/android/server/AccessibilityManagerService.java
+++ b/services/java/com/android/server/AccessibilityManagerService.java
@@ -240,10 +240,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
public void onChange(boolean selfChange) {
super.onChange(selfChange);
- mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
-
synchronized (mLock) {
+ mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
if (mIsEnabled) {
manageServicesLocked();
} else {
@@ -269,14 +268,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
});
}
- public void addClient(IAccessibilityManagerClient client) {
+ public boolean addClient(IAccessibilityManagerClient client) {
synchronized (mLock) {
- try {
- client.setEnabled(mIsEnabled);
- mClients.add(client);
- } catch (RemoteException re) {
- Slog.w(LOG_TAG, "Dead AccessibilityManagerClient: " + client, re);
- }
+ mClients.add(client);
+ return mIsEnabled;
}
}
@@ -456,9 +451,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
} catch (RemoteException re) {
if (re instanceof DeadObjectException) {
Slog.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up.");
- synchronized (mLock) {
- removeDeadServiceLocked(service);
- }
+ removeDeadServiceLocked(service);
} else {
Slog.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re);
}
@@ -472,19 +465,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
* @return True if the service was removed, false otherwise.
*/
private boolean removeDeadServiceLocked(Service service) {
- mServices.remove(service);
- mHandler.removeMessages(service.mId);
-
if (Config.DEBUG) {
Slog.i(LOG_TAG, "Dead service " + service.mService + " removed");
}
-
- if (mServices.isEmpty()) {
- mIsEnabled = false;
- updateClientsLocked();
- }
-
- return true;
+ mHandler.removeMessages(service.mId);
+ return mServices.remove(service);
}
/**
@@ -547,11 +532,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
for (int i = 0, count = services.size(); i < count; i++) {
Service service = services.get(i);
-
- service.unbind();
- mComponentNameToServiceMap.remove(service.mComponentName);
+ if (service.unbind()) {
+ i--;
+ count--;
+ }
}
- services.clear();
}
/**
@@ -593,7 +578,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
Set<ComponentName> enabledServices) {
Map<ComponentName, Service> componentNameToServiceMap = mComponentNameToServiceMap;
- List<Service> services = mServices;
boolean isEnabled = mIsEnabled;
for (int i = 0, count = installedServices.size(); i < count; i++) {
@@ -602,15 +586,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
intalledService.name);
Service service = componentNameToServiceMap.get(componentName);
- if (isEnabled && enabledServices.contains(componentName)) {
- if (service == null) {
- new Service(componentName).bind();
+ if (isEnabled) {
+ if (enabledServices.contains(componentName)) {
+ if (service == null) {
+ service = new Service(componentName);
+ }
+ service.bind();
+ } else if (!enabledServices.contains(componentName)) {
+ if (service != null) {
+ service.unbind();
+ }
}
} else {
if (service != null) {
service.unbind();
- componentNameToServiceMap.remove(componentName);
- services.remove(service);
}
}
}
@@ -678,21 +667,31 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
/**
* Binds to the accessibility service.
+ *
+ * @return True if binding is successful.
*/
- public void bind() {
+ public boolean bind() {
if (mService == null) {
- mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE);
+ return mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE);
}
+ return false;
}
/**
* Unbinds form the accessibility service and removes it from the data
* structures for service management.
+ *
+ * @return True if unbinding is successful.
*/
- public void unbind() {
+ public boolean unbind() {
if (mService != null) {
+ mService = null;
mContext.unbindService(this);
+ mComponentNameToServiceMap.remove(mComponentName);
+ mServices.remove(this);
+ return true;
}
+ return false;
}
/**
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index 4931cc7..5a36417 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -661,7 +661,8 @@ class AlarmManagerService extends IAlarmManager.Stub {
remove(mTimeTickSender);
mClockReceiver.scheduleTimeTickEvent();
Intent intent = new Intent(Intent.ACTION_TIME_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+ | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcast(intent);
}
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index 731fb22..ad25657 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -16,6 +16,24 @@
package com.android.server;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
@@ -24,46 +42,38 @@ import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.Intent.FilterComparison;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.TypedValue;
import android.util.Xml;
import android.widget.RemoteViews;
-import java.io.IOException;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.HashMap;
-import java.util.HashSet;
-
-import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.appwidget.IAppWidgetHost;
+import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.util.FastXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
+import com.android.internal.widget.IRemoteViewsAdapterConnection;
class AppWidgetService extends IAppWidgetService.Stub
{
@@ -107,6 +117,46 @@ class AppWidgetService extends IAppWidgetService.Stub
Host host;
}
+ /**
+ * Acts as a proxy between the ServiceConnection and the RemoteViewsAdapterConnection.
+ * This needs to be a static inner class since a reference to the ServiceConnection is held
+ * globally and may lead us to leak AppWidgetService instances (if there were more than one).
+ */
+ static class ServiceConnectionProxy implements ServiceConnection {
+ private final Pair<Integer, Intent.FilterComparison> mKey;
+ private final IBinder mConnectionCb;
+
+ ServiceConnectionProxy(Pair<Integer, Intent.FilterComparison> key, IBinder connectionCb) {
+ mKey = key;
+ mConnectionCb = connectionCb;
+ }
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ final IRemoteViewsAdapterConnection cb =
+ IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb);
+ try {
+ cb.onServiceConnected(service);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ public void onServiceDisconnected(ComponentName name) {
+ disconnect();
+ }
+ public void disconnect() {
+ final IRemoteViewsAdapterConnection cb =
+ IRemoteViewsAdapterConnection.Stub.asInterface(mConnectionCb);
+ try {
+ cb.onServiceDisconnected();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ // Manages connections to RemoteViewsServices
+ private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection>
+ mBoundRemoteViewsServices = new HashMap<Pair<Integer,FilterComparison>,ServiceConnection>();
+
Context mContext;
Locale mLocale;
PackageManager mPackageManager;
@@ -144,6 +194,7 @@ class AppWidgetService extends IAppWidgetService.Stub
// update the provider list.
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
mContext.registerReceiver(mBroadcastReceiver, filter);
@@ -293,6 +344,9 @@ class AppWidgetService extends IAppWidgetService.Stub
}
void deleteAppWidgetLocked(AppWidgetId id) {
+ // We first unbind all services that are bound to this id
+ unbindAppWidgetRemoteViewsServicesLocked(id);
+
Host host = id.host;
host.instances.remove(id);
pruneHostLocked(host);
@@ -375,6 +429,94 @@ class AppWidgetService extends IAppWidgetService.Stub
}
}
+ public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection) {
+ synchronized (mAppWidgetIds) {
+ AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
+ if (id == null) {
+ throw new IllegalArgumentException("bad appWidgetId");
+ }
+ final ComponentName componentName = intent.getComponent();
+ try {
+ final ServiceInfo si = mContext.getPackageManager().getServiceInfo(componentName,
+ PackageManager.GET_PERMISSIONS);
+ if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(si.permission)) {
+ throw new SecurityException("Selected service does not require "
+ + android.Manifest.permission.BIND_REMOTEVIEWS
+ + ": " + componentName);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalArgumentException("Unknown component " + componentName);
+ }
+
+ // If there is already a connection made for this service intent, then disconnect from
+ // that first. (This does not allow multiple connections to the same service under
+ // the same key)
+ ServiceConnectionProxy conn = null;
+ Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
+ new FilterComparison(intent));
+ if (mBoundRemoteViewsServices.containsKey(key)) {
+ conn = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
+ conn.disconnect();
+ mContext.unbindService(conn);
+ mBoundRemoteViewsServices.remove(key);
+ }
+
+ // Bind to the RemoteViewsService (which will trigger a callback to the
+ // RemoteViewsAdapter.onServiceConnected())
+ final long token = Binder.clearCallingIdentity();
+ try {
+ conn = new ServiceConnectionProxy(key, connection);
+ mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE);
+ mBoundRemoteViewsServices.put(key, conn);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ public void unbindRemoteViewsService(int appWidgetId, Intent intent) {
+ synchronized (mAppWidgetIds) {
+ // Unbind from the RemoteViewsService (which will trigger a callback to the bound
+ // RemoteViewsAdapter)
+ Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
+ new FilterComparison(intent));
+ if (mBoundRemoteViewsServices.containsKey(key)) {
+ // We don't need to use the appWidgetId until after we are sure there is something
+ // to unbind. Note that this may mask certain issues with apps calling unbind()
+ // more than necessary.
+ AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
+ if (id == null) {
+ throw new IllegalArgumentException("bad appWidgetId");
+ }
+
+ ServiceConnectionProxy conn =
+ (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
+ conn.disconnect();
+ mContext.unbindService(conn);
+ mBoundRemoteViewsServices.remove(key);
+ } else {
+ Log.e("AppWidgetService", "Error (unbindRemoteViewsService): Connection not bound");
+ }
+ }
+ }
+
+ private void unbindAppWidgetRemoteViewsServicesLocked(AppWidgetId id) {
+ int appWidgetId = id.appWidgetId;
+ // Unbind all connections to Services bound to this AppWidgetId
+ Iterator<Pair<Integer, Intent.FilterComparison>> it =
+ mBoundRemoteViewsServices.keySet().iterator();
+ while (it.hasNext()) {
+ final Pair<Integer, Intent.FilterComparison> key = it.next();
+ if (key.first.intValue() == appWidgetId) {
+ final ServiceConnectionProxy conn = (ServiceConnectionProxy)
+ mBoundRemoteViewsServices.get(key);
+ conn.disconnect();
+ mContext.unbindService(conn);
+ it.remove();
+ }
+ }
+ }
+
public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
synchronized (mAppWidgetIds) {
AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
@@ -426,6 +568,40 @@ class AppWidgetService extends IAppWidgetService.Stub
}
}
+ public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) {
+ if (appWidgetIds == null) {
+ return;
+ }
+ if (appWidgetIds.length == 0) {
+ return;
+ }
+ final int N = appWidgetIds.length;
+
+ synchronized (mAppWidgetIds) {
+ for (int i=0; i<N; i++) {
+ AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]);
+ updateAppWidgetInstanceLocked(id, views, true);
+ }
+ }
+ }
+
+ public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
+ if (appWidgetIds == null) {
+ return;
+ }
+ if (appWidgetIds.length == 0) {
+ return;
+ }
+ final int N = appWidgetIds.length;
+
+ synchronized (mAppWidgetIds) {
+ for (int i=0; i<N; i++) {
+ AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]);
+ notifyAppWidgetViewDataChangedInstanceLocked(id, viewId);
+ }
+ }
+ }
+
public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) {
synchronized (mAppWidgetIds) {
Provider p = lookupProviderLocked(provider);
@@ -443,11 +619,17 @@ class AppWidgetService extends IAppWidgetService.Stub
}
void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) {
+ updateAppWidgetInstanceLocked(id, views, false);
+ }
+
+ void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) {
// allow for stale appWidgetIds and other badness
// lookup also checks that the calling process can access the appWidgetId
// drop unbound appWidgetIds (shouldn't be possible under normal circumstances)
if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) {
- id.views = views;
+
+ // We do not want to save this RemoteViews
+ if (!isPartialUpdate) id.views = views;
// is anyone listening?
if (id.host.callbacks != null) {
@@ -463,6 +645,25 @@ class AppWidgetService extends IAppWidgetService.Stub
}
}
+ void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, int viewId) {
+ // allow for stale appWidgetIds and other badness
+ // lookup also checks that the calling process can access the appWidgetId
+ // drop unbound appWidgetIds (shouldn't be possible under normal circumstances)
+ if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) {
+ // is anyone listening?
+ if (id.host.callbacks != null) {
+ try {
+ // the lock is held, but this is a oneway call
+ id.host.callbacks.viewDataChanged(id.appWidgetId, viewId);
+ } catch (RemoteException e) {
+ // It failed; remove the callback. No need to prune because
+ // we know that this host is still referenced by this instance.
+ id.host.callbacks = null;
+ }
+ }
+ }
+ }
+
public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId,
List<RemoteViews> updatedViews) {
int callingUid = enforceCallingUid(packageName);
@@ -584,6 +785,12 @@ class AppWidgetService extends IAppWidgetService.Stub
}
boolean addProviderLocked(ResolveInfo ri) {
+ if ((ri.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
+ return false;
+ }
+ if (!ri.activityInfo.isEnabled()) {
+ return false;
+ }
Provider p = parseProviderInfoXml(new ComponentName(ri.activityInfo.packageName,
ri.activityInfo.name), ri);
if (p != null) {
@@ -742,6 +949,11 @@ class AppWidgetService extends IAppWidgetService.Stub
}
info.label = activityInfo.loadLabel(mPackageManager).toString();
info.icon = ri.getIconResource();
+ info.previewImage = sa.getResourceId(
+ com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0);
+ info.autoAdvanceViewId = sa.getResourceId(
+ com.android.internal.R.styleable.AppWidgetProviderInfo_autoAdvanceViewId, -1);
+
sa.recycle();
} catch (Exception e) {
// Ok to catch Exception here, because anything going wrong because
@@ -1096,6 +1308,7 @@ class AppWidgetService extends IAppWidgetService.Stub
}
} else {
boolean added = false;
+ boolean changed = false;
String pkgList[] = null;
if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
@@ -1114,14 +1327,16 @@ class AppWidgetService extends IAppWidgetService.Stub
}
pkgList = new String[] { pkgName };
added = Intent.ACTION_PACKAGE_ADDED.equals(action);
+ changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
}
if (pkgList == null || pkgList.length == 0) {
return;
}
- if (added) {
+ if (added || changed) {
synchronized (mAppWidgetIds) {
Bundle extras = intent.getExtras();
- if (extras != null && extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
+ if (changed || (extras != null &&
+ extras.getBoolean(Intent.EXTRA_REPLACING, false))) {
for (String pkgName : pkgList) {
// The package was just upgraded
updateProvidersForPackageLocked(pkgName);
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index f1e226e..c48f360 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -107,6 +107,7 @@ class BackupManagerService extends IBackupManager.Stub {
private static final int MSG_RUN_INITIALIZE = 5;
private static final int MSG_RUN_GET_RESTORE_SETS = 6;
private static final int MSG_TIMEOUT = 7;
+ private static final int MSG_RESTORE_TIMEOUT = 8;
// Timeout interval for deciding that a bind or clear-data has taken too long
static final long TIMEOUT_INTERVAL = 10 * 1000;
@@ -148,9 +149,9 @@ class BackupManagerService extends IBackupManager.Stub {
return "BackupRequest{app=" + appInfo + " full=" + fullBackup + "}";
}
}
- // Backups that we haven't started yet.
- HashMap<ApplicationInfo,BackupRequest> mPendingBackups
- = new HashMap<ApplicationInfo,BackupRequest>();
+ // Backups that we haven't started yet. Keys are package names.
+ HashMap<String,BackupRequest> mPendingBackups
+ = new HashMap<String,BackupRequest>();
// Pseudoname that we use for the Package Manager metadata "package"
static final String PACKAGE_MANAGER_SENTINEL = "@pm@";
@@ -396,6 +397,21 @@ class BackupManagerService extends IBackupManager.Stub {
}
break;
}
+
+ case MSG_RESTORE_TIMEOUT:
+ {
+ synchronized (BackupManagerService.this) {
+ if (mActiveRestoreSession != null) {
+ // Client app left the restore session dangling. We know that it
+ // can't be in the middle of an actual restore operation because
+ // those are executed serially on this same handler thread. Clean
+ // up now.
+ Slog.w(TAG, "Restore session timed out; aborting");
+ post(mActiveRestoreSession.new EndRestoreRunnable(
+ BackupManagerService.this, mActiveRestoreSession));
+ }
+ }
+ }
}
}
}
@@ -590,6 +606,7 @@ class BackupManagerService extends IBackupManager.Stub {
}
}
}
+ tf.close();
} catch (FileNotFoundException fnf) {
// Probably innocuous
Slog.v(TAG, "No ancestral data");
@@ -912,42 +929,48 @@ class BackupManagerService extends IBackupManager.Stub {
// 'packageName' is null, *all* participating apps will be removed.
void removePackageParticipantsLocked(String packageName) {
if (DEBUG) Slog.v(TAG, "removePackageParticipantsLocked: " + packageName);
- List<PackageInfo> allApps = null;
+ List<String> allApps = new ArrayList<String>();
if (packageName != null) {
- allApps = new ArrayList<PackageInfo>();
- try {
- int flags = PackageManager.GET_SIGNATURES;
- allApps.add(mPackageManager.getPackageInfo(packageName, flags));
- } catch (Exception e) {
- // just skip it (???)
- }
+ allApps.add(packageName);
} else {
// all apps with agents
- allApps = allAgentPackages();
+ List<PackageInfo> knownPackages = allAgentPackages();
+ for (PackageInfo pkg : knownPackages) {
+ allApps.add(pkg.packageName);
+ }
}
removePackageParticipantsLockedInner(packageName, allApps);
}
private void removePackageParticipantsLockedInner(String packageName,
- List<PackageInfo> agents) {
+ List<String> allPackageNames) {
if (DEBUG) {
Slog.v(TAG, "removePackageParticipantsLockedInner (" + packageName
- + ") removing " + agents.size() + " entries");
- for (PackageInfo p : agents) {
+ + ") removing " + allPackageNames.size() + " entries");
+ for (String p : allPackageNames) {
Slog.v(TAG, " - " + p);
}
}
- for (PackageInfo pkg : agents) {
- if (packageName == null || pkg.packageName.equals(packageName)) {
- int uid = pkg.applicationInfo.uid;
+ for (String pkg : allPackageNames) {
+ if (packageName == null || pkg.equals(packageName)) {
+ int uid = -1;
+ try {
+ PackageInfo info = mPackageManager.getPackageInfo(packageName, 0);
+ uid = info.applicationInfo.uid;
+ } catch (NameNotFoundException e) {
+ // we don't know this package name, so just skip it for now
+ continue;
+ }
+
HashSet<ApplicationInfo> set = mBackupParticipants.get(uid);
if (set != null) {
// Find the existing entry with the same package name, and remove it.
// We can't just remove(app) because the instances are different.
for (ApplicationInfo entry: set) {
- if (entry.packageName.equals(pkg.packageName)) {
+ if (entry.packageName.equals(pkg)) {
+ if (DEBUG) Slog.v(TAG, " removing participant " + pkg);
set.remove(entry);
- removeEverBackedUp(pkg.packageName);
+ removeEverBackedUp(pkg);
break;
}
}
@@ -997,7 +1020,11 @@ class BackupManagerService extends IBackupManager.Stub {
// brute force but small code size
List<PackageInfo> allApps = allAgentPackages();
- removePackageParticipantsLockedInner(packageName, allApps);
+ List<String> allAppNames = new ArrayList<String>();
+ for (PackageInfo pkg : allApps) {
+ allAppNames.add(pkg.packageName);
+ }
+ removePackageParticipantsLockedInner(packageName, allAppNames);
addPackageParticipantsLockedInner(packageName, allApps);
}
@@ -1364,6 +1391,17 @@ class BackupManagerService extends IBackupManager.Stub {
for (BackupRequest request : mQueue) {
Slog.d(TAG, "starting agent for backup of " + request);
+ // Verify that the requested app exists; it might be something that
+ // requested a backup but was then uninstalled. The request was
+ // journalled and rather than tamper with the journal it's safer
+ // to sanity-check here.
+ try {
+ mPackageManager.getPackageInfo(request.appInfo.packageName, 0);
+ } catch (NameNotFoundException e) {
+ Slog.d(TAG, "Package does not exist; skipping");
+ continue;
+ }
+
IBackupAgent agent = null;
int mode = (request.fullBackup)
? IApplicationThread.BACKUP_MODE_FULL
@@ -1825,6 +1863,11 @@ class BackupManagerService extends IBackupManager.Stub {
} catch (RemoteException e) { /* can't happen */ }
}
+ // Furthermore we need to reset the session timeout clock
+ mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT);
+ mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT,
+ TIMEOUT_RESTORE_INTERVAL);
+
// done; we can finally release the wakelock
mWakelock.release();
}
@@ -2046,7 +2089,7 @@ class BackupManagerService extends IBackupManager.Stub {
// Add the caller to the set of pending backups. If there is
// one already there, then overwrite it, but no harm done.
BackupRequest req = new BackupRequest(app, false);
- if (mPendingBackups.put(app, req) == null) {
+ if (mPendingBackups.put(app.packageName, req) == null) {
// Journal this request in case of crash. The put()
// operation returned null when this package was not already
// in the set; we want to avoid touching the disk redundantly.
@@ -2343,6 +2386,55 @@ class BackupManagerService extends IBackupManager.Stub {
}
}
+ // Supply the configuration Intent for the given transport. If the name is not one
+ // of the available transports, or if the transport does not supply any configuration
+ // UI, the method returns null.
+ public Intent getConfigurationIntent(String transportName) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "getConfigurationIntent");
+
+ synchronized (mTransports) {
+ final IBackupTransport transport = mTransports.get(transportName);
+ if (transport != null) {
+ try {
+ final Intent intent = transport.configurationIntent();
+ if (DEBUG) Slog.d(TAG, "getConfigurationIntent() returning config intent "
+ + intent);
+ return intent;
+ } catch (RemoteException e) {
+ /* fall through to return null */
+ }
+ }
+ }
+
+ return null;
+ }
+
+ // Supply the configuration summary string for the given transport. If the name is
+ // not one of the available transports, or if the transport does not supply any
+ // summary / destination string, the method can return null.
+ //
+ // This string is used VERBATIM as the summary text of the relevant Settings item!
+ public String getDestinationString(String transportName) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "getConfigurationIntent");
+
+ synchronized (mTransports) {
+ final IBackupTransport transport = mTransports.get(transportName);
+ if (transport != null) {
+ try {
+ final String text = transport.currentDestinationString();
+ if (DEBUG) Slog.d(TAG, "getDestinationString() returning " + text);
+ return text;
+ } catch (RemoteException e) {
+ /* fall through to return null */
+ }
+ }
+ }
+
+ return null;
+ }
+
// Callback: a requested backup agent has been instantiated. This should only
// be called from the Activity Manager.
public void agentConnected(String packageName, IBinder agentBinder) {
@@ -2456,10 +2548,23 @@ class BackupManagerService extends IBackupManager.Stub {
return null;
}
mActiveRestoreSession = new ActiveRestoreSession(packageName, transport);
+ mBackupHandler.sendEmptyMessageDelayed(MSG_RESTORE_TIMEOUT, TIMEOUT_RESTORE_INTERVAL);
}
return mActiveRestoreSession;
}
+ void clearRestoreSession(ActiveRestoreSession currentSession) {
+ synchronized(this) {
+ if (currentSession != mActiveRestoreSession) {
+ Slog.e(TAG, "ending non-current restore session");
+ } else {
+ if (DEBUG) Slog.v(TAG, "Clearing restore session and halting timeout");
+ mActiveRestoreSession = null;
+ mBackupHandler.removeMessages(MSG_RESTORE_TIMEOUT);
+ }
+ }
+ }
+
// Note that a currently-active backup agent has notified us that it has
// completed the given outstanding asynchronous backup/restore operation.
public void opComplete(int token) {
@@ -2478,6 +2583,7 @@ class BackupManagerService extends IBackupManager.Stub {
private String mPackageName;
private IBackupTransport mRestoreTransport = null;
RestoreSet[] mRestoreSets = null;
+ boolean mEnded = false;
ActiveRestoreSession(String packageName, String transport) {
mPackageName = packageName;
@@ -2492,6 +2598,10 @@ class BackupManagerService extends IBackupManager.Stub {
throw new IllegalArgumentException("Observer must not be null");
}
+ if (mEnded) {
+ throw new IllegalStateException("Restore session already ended");
+ }
+
long oldId = Binder.clearCallingIdentity();
try {
if (mRestoreTransport == null) {
@@ -2519,6 +2629,10 @@ class BackupManagerService extends IBackupManager.Stub {
if (DEBUG) Slog.d(TAG, "restoreAll token=" + Long.toHexString(token)
+ " observer=" + observer);
+ if (mEnded) {
+ throw new IllegalStateException("Restore session already ended");
+ }
+
if (mRestoreTransport == null || mRestoreSets == null) {
Slog.e(TAG, "Ignoring restoreAll() with no restore set");
return -1;
@@ -2550,6 +2664,10 @@ class BackupManagerService extends IBackupManager.Stub {
public synchronized int restorePackage(String packageName, IRestoreObserver observer) {
if (DEBUG) Slog.v(TAG, "restorePackage pkg=" + packageName + " obs=" + observer);
+ if (mEnded) {
+ throw new IllegalStateException("Restore session already ended");
+ }
+
if (mPackageName != null) {
if (! mPackageName.equals(packageName)) {
Slog.e(TAG, "Ignoring attempt to restore pkg=" + packageName
@@ -2606,34 +2724,59 @@ class BackupManagerService extends IBackupManager.Stub {
return 0;
}
- public synchronized void endRestoreSession() {
- if (DEBUG) Slog.d(TAG, "endRestoreSession");
+ // Posted to the handler to tear down a restore session in a cleanly synchronized way
+ class EndRestoreRunnable implements Runnable {
+ BackupManagerService mBackupManager;
+ ActiveRestoreSession mSession;
- synchronized (this) {
- long oldId = Binder.clearCallingIdentity();
- try {
- if (mRestoreTransport != null) mRestoreTransport.finishRestore();
- } catch (Exception e) {
- Slog.e(TAG, "Error in finishRestore", e);
- } finally {
- mRestoreTransport = null;
- Binder.restoreCallingIdentity(oldId);
- }
+ EndRestoreRunnable(BackupManagerService manager, ActiveRestoreSession session) {
+ mBackupManager = manager;
+ mSession = session;
}
- synchronized (BackupManagerService.this) {
- if (BackupManagerService.this.mActiveRestoreSession == this) {
- BackupManagerService.this.mActiveRestoreSession = null;
- } else {
- Slog.e(TAG, "ending non-current restore session");
+ public void run() {
+ // clean up the session's bookkeeping
+ synchronized (mSession) {
+ try {
+ if (mSession.mRestoreTransport != null) {
+ mSession.mRestoreTransport.finishRestore();
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Error in finishRestore", e);
+ } finally {
+ mSession.mRestoreTransport = null;
+ mSession.mEnded = true;
+ }
}
+
+ // clean up the BackupManagerService side of the bookkeeping
+ // and cancel any pending timeout message
+ mBackupManager.clearRestoreSession(mSession);
}
}
- }
+ public synchronized void endRestoreSession() {
+ if (DEBUG) Slog.d(TAG, "endRestoreSession");
+
+ if (mEnded) {
+ throw new IllegalStateException("Restore session already ended");
+ }
+
+ mBackupHandler.post(new EndRestoreRunnable(BackupManagerService.this, this));
+ }
+ }
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ long identityToken = Binder.clearCallingIdentity();
+ try {
+ dumpInternal(pw);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ }
+
+ private void dumpInternal(PrintWriter pw) {
synchronized (mQueueLock) {
pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled")
+ " / " + (!mProvisioned ? "not " : "") + "provisioned / "
@@ -2647,12 +2790,15 @@ class BackupManagerService extends IBackupManager.Stub {
for (String t : listAllTransports()) {
pw.println((t.equals(mCurrentTransport) ? " * " : " ") + t);
try {
- File dir = new File(mBaseStateDir, getTransport(t).transportDirName());
+ IBackupTransport transport = getTransport(t);
+ File dir = new File(mBaseStateDir, transport.transportDirName());
+ pw.println(" destination: " + transport.currentDestinationString());
+ pw.println(" intent: " + transport.configurationIntent());
for (File f : dir.listFiles()) {
pw.println(" " + f.getName() + " - " + f.length() + " state bytes");
}
- } catch (RemoteException e) {
- Slog.e(TAG, "Error in transportDirName()", e);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error in transport", e);
pw.println(" Error: " + e);
}
}
diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java
index fc4e06f..47599c8 100644
--- a/services/java/com/android/server/BatteryService.java
+++ b/services/java/com/android/server/BatteryService.java
@@ -43,6 +43,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.Arrays;
/**
@@ -76,7 +77,7 @@ class BatteryService extends Binder {
// Used locally for determining when to make a last ditch effort to log
// discharge stats before the device dies.
- private static final int CRITICAL_BATTERY_LEVEL = 4;
+ private int mCriticalBatteryLevel;
private static final int DUMP_MAX_LENGTH = 24 * 1024;
private static final String[] DUMPSYS_ARGS = new String[] { "--checkin", "-u" };
@@ -100,6 +101,7 @@ class BatteryService extends Binder {
private int mBatteryTemperature;
private String mBatteryTechnology;
private boolean mBatteryLevelCritical;
+ private int mInvalidCharger;
private int mLastBatteryStatus;
private int mLastBatteryHealth;
@@ -108,6 +110,7 @@ class BatteryService extends Binder {
private int mLastBatteryVoltage;
private int mLastBatteryTemperature;
private boolean mLastBatteryLevelCritical;
+ private int mLastInvalidCharger;
private int mLowBatteryWarningLevel;
private int mLowBatteryCloseWarningLevel;
@@ -118,18 +121,28 @@ class BatteryService extends Binder {
private long mDischargeStartTime;
private int mDischargeStartLevel;
+ private Led mLed;
+
private boolean mSentLowBatteryBroadcast = false;
- public BatteryService(Context context) {
+ public BatteryService(Context context, LightsService lights) {
mContext = context;
+ mLed = new Led(context, lights);
mBatteryStats = BatteryStatsService.getService();
+ mCriticalBatteryLevel = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_criticalBatteryWarningLevel);
mLowBatteryWarningLevel = mContext.getResources().getInteger(
com.android.internal.R.integer.config_lowBatteryWarningLevel);
mLowBatteryCloseWarningLevel = mContext.getResources().getInteger(
com.android.internal.R.integer.config_lowBatteryCloseWarningLevel);
- mUEventObserver.startObserving("SUBSYSTEM=power_supply");
+ mPowerSupplyObserver.startObserving("SUBSYSTEM=power_supply");
+
+ // watch for invalid charger messages if the invalid_charger switch exists
+ if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) {
+ mInvalidChargerObserver.startObserving("DEVPATH=/devices/virtual/switch/invalid_charger");
+ }
// set initial status
update();
@@ -163,13 +176,24 @@ class BatteryService extends Binder {
return mPlugType;
}
- private UEventObserver mUEventObserver = new UEventObserver() {
+ private UEventObserver mPowerSupplyObserver = new UEventObserver() {
@Override
public void onUEvent(UEventObserver.UEvent event) {
update();
}
};
+ private UEventObserver mInvalidChargerObserver = new UEventObserver() {
+ @Override
+ public void onUEvent(UEventObserver.UEvent event) {
+ int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0;
+ if (mInvalidCharger != invalidCharger) {
+ mInvalidCharger = invalidCharger;
+ update();
+ }
+ }
+ };
+
// returns battery level as a percentage
final int getBatteryLevel() {
return mBatteryLevel;
@@ -207,11 +231,14 @@ class BatteryService extends Binder {
private synchronized final void update() {
native_update();
+ processValues();
+ }
+ private void processValues() {
boolean logOutlier = false;
long dischargeDuration = 0;
- mBatteryLevelCritical = mBatteryLevel <= CRITICAL_BATTERY_LEVEL;
+ mBatteryLevelCritical = mBatteryLevel <= mCriticalBatteryLevel;
if (mAcOnline) {
mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
} else if (mUsbOnline) {
@@ -238,7 +265,8 @@ class BatteryService extends Binder {
mBatteryLevel != mLastBatteryLevel ||
mPlugType != mLastPlugType ||
mBatteryVoltage != mLastBatteryVoltage ||
- mBatteryTemperature != mLastBatteryTemperature) {
+ mBatteryTemperature != mLastBatteryTemperature ||
+ mInvalidCharger != mLastInvalidCharger) {
if (mPlugType != mLastPlugType) {
if (mLastPlugType == BATTERY_PLUGGED_NONE) {
@@ -292,9 +320,9 @@ class BatteryService extends Binder {
* (becomes <= mLowBatteryWarningLevel).
*/
final boolean sendBatteryLow = !plugged
- && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
- && mBatteryLevel <= mLowBatteryWarningLevel
- && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
+ && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
+ && mBatteryLevel <= mLowBatteryWarningLevel
+ && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
sendIntent();
@@ -322,6 +350,9 @@ class BatteryService extends Binder {
mContext.sendBroadcast(statusIntent);
}
+ // Update the battery LED
+ mLed.updateLightsLocked();
+
// This needs to be done after sendIntent() so that we get the lastest battery stats.
if (logOutlier && dischargeDuration != 0) {
logOutlier(dischargeDuration);
@@ -335,6 +366,7 @@ class BatteryService extends Binder {
mLastBatteryVoltage = mBatteryVoltage;
mLastBatteryTemperature = mBatteryTemperature;
mLastBatteryLevelCritical = mBatteryLevelCritical;
+ mLastInvalidCharger = mInvalidCharger;
}
}
@@ -356,16 +388,17 @@ class BatteryService extends Binder {
intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryVoltage);
intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryTemperature);
intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryTechnology);
+ intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger);
- if (false) {
- Slog.d(TAG, "updateBattery level:" + mBatteryLevel +
+ if (true) {
+ Slog.d(TAG, "level:" + mBatteryLevel +
" scale:" + BATTERY_SCALE + " status:" + mBatteryStatus +
" health:" + mBatteryHealth + " present:" + mBatteryPresent +
" voltage: " + mBatteryVoltage +
" temperature: " + mBatteryTemperature +
" technology: " + mBatteryTechnology +
" AC powered:" + mAcOnline + " USB powered:" + mUsbOnline +
- " icon:" + icon );
+ " icon:" + icon + " invalid charger:" + mInvalidCharger);
}
ActivityManagerNative.broadcastStickyIntent(intent, null);
@@ -440,10 +473,15 @@ class BatteryService extends Binder {
private final int getIcon(int level) {
if (mBatteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
return com.android.internal.R.drawable.stat_sys_battery_charge;
- } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING ||
- mBatteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING ||
- mBatteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
+ } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
return com.android.internal.R.drawable.stat_sys_battery;
+ } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
+ || mBatteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
+ if (isPowered() && mBatteryLevel >= 100) {
+ return com.android.internal.R.drawable.stat_sys_battery_charge;
+ } else {
+ return com.android.internal.R.drawable.stat_sys_battery;
+ }
} else {
return com.android.internal.R.drawable.stat_sys_battery_unknown;
}
@@ -460,18 +498,109 @@ class BatteryService extends Binder {
return;
}
- synchronized (this) {
- pw.println("Current Battery Service state:");
- pw.println(" AC powered: " + mAcOnline);
- pw.println(" USB powered: " + mUsbOnline);
- pw.println(" status: " + mBatteryStatus);
- pw.println(" health: " + mBatteryHealth);
- pw.println(" present: " + mBatteryPresent);
- pw.println(" level: " + mBatteryLevel);
- pw.println(" scale: " + BATTERY_SCALE);
- pw.println(" voltage:" + mBatteryVoltage);
- pw.println(" temperature: " + mBatteryTemperature);
- pw.println(" technology: " + mBatteryTechnology);
+ if (args == null || args.length == 0) {
+ synchronized (this) {
+ pw.println("Current Battery Service state:");
+ pw.println(" AC powered: " + mAcOnline);
+ pw.println(" USB powered: " + mUsbOnline);
+ pw.println(" status: " + mBatteryStatus);
+ pw.println(" health: " + mBatteryHealth);
+ pw.println(" present: " + mBatteryPresent);
+ pw.println(" level: " + mBatteryLevel);
+ pw.println(" scale: " + BATTERY_SCALE);
+ pw.println(" voltage:" + mBatteryVoltage);
+ pw.println(" temperature: " + mBatteryTemperature);
+ pw.println(" technology: " + mBatteryTechnology);
+ }
+ } else if (false) {
+ // DO NOT SUBMIT WITH THIS TURNED ON
+ if (args.length == 3 && "set".equals(args[0])) {
+ String key = args[1];
+ String value = args[2];
+ try {
+ boolean update = true;
+ if ("ac".equals(key)) {
+ mAcOnline = Integer.parseInt(value) != 0;
+ } else if ("usb".equals(key)) {
+ mUsbOnline = Integer.parseInt(value) != 0;
+ } else if ("status".equals(key)) {
+ mBatteryStatus = Integer.parseInt(value);
+ } else if ("level".equals(key)) {
+ mBatteryLevel = Integer.parseInt(value);
+ } else if ("invalid".equals(key)) {
+ mInvalidCharger = Integer.parseInt(value);
+ } else {
+ update = false;
+ }
+ if (update) {
+ processValues();
+ }
+ } catch (NumberFormatException ex) {
+ pw.println("Bad value: " + value);
+ }
+ }
+ }
+ }
+
+ class Led {
+ private LightsService mLightsService;
+ private LightsService.Light mBatteryLight;
+
+ private int mBatteryLowARGB;
+ private int mBatteryMediumARGB;
+ private int mBatteryFullARGB;
+ private int mBatteryLedOn;
+ private int mBatteryLedOff;
+
+ private boolean mBatteryCharging;
+ private boolean mBatteryLow;
+ private boolean mBatteryFull;
+
+ Led(Context context, LightsService lights) {
+ mLightsService = lights;
+ mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY);
+
+ mBatteryLowARGB = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_notificationsBatteryLowARGB);
+ mBatteryMediumARGB = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_notificationsBatteryMediumARGB);
+ mBatteryFullARGB = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_notificationsBatteryFullARGB);
+ mBatteryLedOn = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_notificationsBatteryLedOn);
+ mBatteryLedOff = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_notificationsBatteryLedOff);
+ }
+
+ /**
+ * Synchronize on BatteryService.
+ */
+ void updateLightsLocked() {
+ final int level = mBatteryLevel;
+ final int status = mBatteryStatus;
+ if (level < mLowBatteryWarningLevel) {
+ if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
+ // Solid red when battery is charging
+ mBatteryLight.setColor(mBatteryLowARGB);
+ } else {
+ // Flash red when battery is low and not charging
+ mBatteryLight.setFlashing(mBatteryLowARGB, LightsService.LIGHT_FLASH_TIMED,
+ mBatteryLedOn, mBatteryLedOff);
+ }
+ } else if (status == BatteryManager.BATTERY_STATUS_CHARGING
+ || status == BatteryManager.BATTERY_STATUS_FULL) {
+ if (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90) {
+ // Solid green when full or charging and nearly full
+ mBatteryLight.setColor(mBatteryFullARGB);
+ } else {
+ // Solid orange when charging and halfway full
+ mBatteryLight.setColor(mBatteryMediumARGB);
+ }
+ } else {
+ // No lights if not charging and not low
+ mBatteryLight.turnOff();
+ }
}
}
}
+
diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java
index aa8cded..062ab74 100644
--- a/services/java/com/android/server/ClipboardService.java
+++ b/services/java/com/android/server/ClipboardService.java
@@ -16,42 +16,240 @@
package com.android.server;
-import android.text.IClipboard;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.IClipboard;
+import android.content.IOnPrimaryClipChangedListener;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Pair;
+import android.util.Slog;
+
+import java.util.HashSet;
/**
* Implementation of the clipboard for copy and paste.
*/
public class ClipboardService extends IClipboard.Stub {
- private CharSequence mClipboard = "";
+ private final Context mContext;
+ private final IActivityManager mAm;
+ private final PackageManager mPm;
+ private final IBinder mPermissionOwner;
+
+ private final RemoteCallbackList<IOnPrimaryClipChangedListener> mPrimaryClipListeners
+ = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
+
+ private ClipData mPrimaryClip;
+
+ private final HashSet<String> mActivePermissionOwners
+ = new HashSet<String>();
/**
* Instantiates the clipboard.
*/
- public ClipboardService(Context context) { }
+ public ClipboardService(Context context) {
+ mContext = context;
+ mAm = ActivityManagerNative.getDefault();
+ mPm = context.getPackageManager();
+ IBinder permOwner = null;
+ try {
+ permOwner = mAm.newUriPermissionOwner("clipboard");
+ } catch (RemoteException e) {
+ Slog.w("clipboard", "AM dead", e);
+ }
+ mPermissionOwner = permOwner;
+ }
+
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ try {
+ return super.onTransact(code, data, reply, flags);
+ } catch (RuntimeException e) {
+ Slog.w("clipboard", "Exception: ", e);
+ throw e;
+ }
+
+ }
- // javadoc from interface
- public void setClipboardText(CharSequence text) {
+ public void setPrimaryClip(ClipData clip) {
synchronized (this) {
- if (text == null) {
- text = "";
+ if (clip != null && clip.getItemCount() <= 0) {
+ throw new IllegalArgumentException("No items");
+ }
+ checkDataOwnerLocked(clip, Binder.getCallingUid());
+ clearActiveOwnersLocked();
+ mPrimaryClip = clip;
+ final int n = mPrimaryClipListeners.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mPrimaryClipListeners.getBroadcastItem(i).dispatchPrimaryClipChanged();
+ } catch (RemoteException e) {
+
+ // The RemoteCallbackList will take care of removing
+ // the dead object for us.
+ }
}
+ mPrimaryClipListeners.finishBroadcast();
+ }
+ }
- mClipboard = text;
+ public ClipData getPrimaryClip(String pkg) {
+ synchronized (this) {
+ addActiveOwnerLocked(Binder.getCallingUid(), pkg);
+ return mPrimaryClip;
}
}
- // javadoc from interface
- public CharSequence getClipboardText() {
+ public ClipDescription getPrimaryClipDescription() {
synchronized (this) {
- return mClipboard;
+ return mPrimaryClip != null ? mPrimaryClip.getDescription() : null;
+ }
+ }
+
+ public boolean hasPrimaryClip() {
+ synchronized (this) {
+ return mPrimaryClip != null;
+ }
+ }
+
+ public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
+ synchronized (this) {
+ mPrimaryClipListeners.register(listener);
+ }
+ }
+
+ public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
+ synchronized (this) {
+ mPrimaryClipListeners.unregister(listener);
}
}
- // javadoc from interface
public boolean hasClipboardText() {
synchronized (this) {
- return mClipboard.length() > 0;
+ if (mPrimaryClip != null) {
+ CharSequence text = mPrimaryClip.getItemAt(0).getText();
+ return text != null && text.length() > 0;
+ }
+ return false;
+ }
+ }
+
+ private final void checkUriOwnerLocked(Uri uri, int uid) {
+ if (!"content".equals(uri.getScheme())) {
+ return;
+ }
+ long ident = Binder.clearCallingIdentity();
+ boolean allowed = false;
+ try {
+ // This will throw SecurityException for us.
+ mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
+ if (item.getUri() != null) {
+ checkUriOwnerLocked(item.getUri(), uid);
+ }
+ Intent intent = item.getIntent();
+ if (intent != null && intent.getData() != null) {
+ checkUriOwnerLocked(intent.getData(), uid);
+ }
+ }
+
+ private final void checkDataOwnerLocked(ClipData data, int uid) {
+ final int N = data.getItemCount();
+ for (int i=0; i<N; i++) {
+ checkItemOwnerLocked(data.getItemAt(i), uid);
+ }
+ }
+
+ private final void grantUriLocked(Uri uri, String pkg) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private final void grantItemLocked(ClipData.Item item, String pkg) {
+ if (item.getUri() != null) {
+ grantUriLocked(item.getUri(), pkg);
+ }
+ Intent intent = item.getIntent();
+ if (intent != null && intent.getData() != null) {
+ grantUriLocked(intent.getData(), pkg);
+ }
+ }
+
+ private final void addActiveOwnerLocked(int uid, String pkg) {
+ PackageInfo pi;
+ try {
+ pi = mPm.getPackageInfo(pkg, 0);
+ if (pi.applicationInfo.uid != uid) {
+ throw new SecurityException("Calling uid " + uid
+ + " does not own package " + pkg);
+ }
+ } catch (NameNotFoundException e) {
+ throw new IllegalArgumentException("Unknown package " + pkg, e);
+ }
+ if (mPrimaryClip != null && !mActivePermissionOwners.contains(pkg)) {
+ final int N = mPrimaryClip.getItemCount();
+ for (int i=0; i<N; i++) {
+ grantItemLocked(mPrimaryClip.getItemAt(i), pkg);
+ }
+ mActivePermissionOwners.add(pkg);
+ }
+ }
+
+ private final void revokeUriLocked(Uri uri) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private final void revokeItemLocked(ClipData.Item item) {
+ if (item.getUri() != null) {
+ revokeUriLocked(item.getUri());
+ }
+ Intent intent = item.getIntent();
+ if (intent != null && intent.getData() != null) {
+ revokeUriLocked(intent.getData());
+ }
+ }
+
+ private final void clearActiveOwnersLocked() {
+ mActivePermissionOwners.clear();
+ if (mPrimaryClip == null) {
+ return;
+ }
+ final int N = mPrimaryClip.getItemCount();
+ for (int i=0; i<N; i++) {
+ revokeItemLocked(mPrimaryClip.getItemAt(i));
}
}
}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 40cfda9..e689654 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -16,24 +16,31 @@
package com.android.server;
-import android.app.Notification;
-import android.app.NotificationManager;
+import android.bluetooth.BluetoothTetheringDataTracker;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.database.ContentObserver;
import android.net.ConnectivityManager;
+import android.net.DummyDataStateTracker;
import android.net.IConnectivityManager;
+import android.net.LinkProperties;
import android.net.MobileDataStateTracker;
import android.net.NetworkInfo;
import android.net.NetworkStateTracker;
import android.net.NetworkUtils;
+import android.net.Proxy;
+import android.net.ProxyProperties;
+import android.net.vpn.VpnManager;
import android.net.wifi.WifiStateTracker;
import android.os.Binder;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -43,23 +50,26 @@ import android.util.EventLog;
import android.util.Slog;
import com.android.internal.telephony.Phone;
-
import com.android.server.connectivity.Tethering;
import java.io.FileDescriptor;
+import java.io.FileWriter;
+import java.io.IOException;
import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.GregorianCalendar;
import java.util.List;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
/**
* @hide
*/
public class ConnectivityService extends IConnectivityManager.Stub {
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private static final String TAG = "ConnectivityService";
// how long to wait before switching back to a radio's default network
@@ -68,7 +78,6 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private static final String NETWORK_RESTORE_DELAY_PROP_NAME =
"android.telephony.apn-restore";
-
private Tethering mTethering;
private boolean mTetheringConfigValid = false;
@@ -85,6 +94,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/
private List mNetRequestersPids[];
+ private WifiWatchdogService mWifiWatchdogService;
+
// priority order of the nettrackers
// (excluding dynamically set mNetworkPreference)
// TODO - move mNetworkTypePreference into this
@@ -104,6 +115,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private boolean mTestMode;
private static ConnectivityService sServiceInstance;
+ private AtomicBoolean mBackgroundDataEnabled = new AtomicBoolean(true);
+
private static final int ENABLED = 1;
private static final int DISABLED = 0;
@@ -162,6 +175,19 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private static final int EVENT_SET_MOBILE_DATA =
MAX_NETWORK_STATE_TRACKER_EVENT + 7;
+ /**
+ * used internally to clear a wakelock when transitioning
+ * from one net to another
+ */
+ private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK =
+ MAX_NETWORK_STATE_TRACKER_EVENT + 8;
+
+ /**
+ * used internally to reload global proxy settings
+ */
+ private static final int EVENT_APPLY_GLOBAL_HTTP_PROXY =
+ MAX_NETWORK_STATE_TRACKER_EVENT + 9;
+
private Handler mHandler;
// list of DeathRecipients used to make sure features are turned off when
@@ -171,10 +197,25 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private boolean mSystemReady;
private Intent mInitialBroadcast;
+ private PowerManager.WakeLock mNetTransitionWakeLock;
+ private String mNetTransitionWakeLockCausedBy = "";
+ private int mNetTransitionWakeLockSerialNumber;
+ private int mNetTransitionWakeLockTimeout;
+
+ private InetAddress mDefaultDns;
+
// used in DBG mode to track inet condition reports
private static final int INET_CONDITION_LOG_MAX_SIZE = 15;
private ArrayList mInetLog;
+ // track the current default http proxy - tell the world if we get a new one (real change)
+ private ProxyProperties mDefaultProxy = null;
+ // track the global proxy.
+ private ProxyProperties mGlobalProxy = null;
+ private final Object mGlobalProxyLock = new Object();
+
+ private SettingsObserver mSettingsObserver;
+
private static class NetworkAttributes {
/**
* Class for holding settings read from resources.
@@ -210,64 +251,55 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
RadioAttributes[] mRadioAttributes;
- private static class ConnectivityThread extends Thread {
- private Context mContext;
-
- private ConnectivityThread(Context context) {
- super("ConnectivityThread");
- mContext = context;
+ public static synchronized ConnectivityService getInstance(Context context) {
+ if (sServiceInstance == null) {
+ sServiceInstance = new ConnectivityService(context);
}
-
- @Override
- public void run() {
- Looper.prepare();
- synchronized (this) {
- sServiceInstance = new ConnectivityService(mContext);
- notifyAll();
- }
- Looper.loop();
- }
-
- public static ConnectivityService getServiceInstance(Context context) {
- ConnectivityThread thread = new ConnectivityThread(context);
- thread.start();
-
- synchronized (thread) {
- while (sServiceInstance == null) {
- try {
- // Wait until sServiceInstance has been initialized.
- thread.wait();
- } catch (InterruptedException ignore) {
- Slog.e(TAG,
- "Unexpected InterruptedException while waiting"+
- " for ConnectivityService thread");
- }
- }
- }
-
- return sServiceInstance;
- }
- }
-
- public static ConnectivityService getInstance(Context context) {
- return ConnectivityThread.getServiceInstance(context);
+ return sServiceInstance;
}
private ConnectivityService(Context context) {
- if (DBG) Slog.v(TAG, "ConnectivityService starting up");
+ if (DBG) log("ConnectivityService starting up");
+
+ HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
+ handlerThread.start();
+ mHandler = new MyHandler(handlerThread.getLooper());
+
+ mBackgroundDataEnabled.set(Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.BACKGROUND_DATA, 1) == 1);
// setup our unique device name
- String id = Settings.Secure.getString(context.getContentResolver(),
- Settings.Secure.ANDROID_ID);
- if (id != null && id.length() > 0) {
- String name = new String("android_").concat(id);
- SystemProperties.set("net.hostname", name);
+ if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) {
+ String id = Settings.Secure.getString(context.getContentResolver(),
+ Settings.Secure.ANDROID_ID);
+ if (id != null && id.length() > 0) {
+ String name = new String("android_").concat(id);
+ SystemProperties.set("net.hostname", name);
+ }
+ }
+
+ // read our default dns server ip
+ String dns = Settings.Secure.getString(context.getContentResolver(),
+ Settings.Secure.DEFAULT_DNS_SERVER);
+ if (dns == null || dns.length() == 0) {
+ dns = context.getResources().getString(
+ com.android.internal.R.string.config_default_dns_server);
+ }
+ try {
+ mDefaultDns = InetAddress.getByName(dns);
+ } catch (UnknownHostException e) {
+ loge("Error setting defaultDns using " + dns);
}
mContext = context;
+
+ PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+ mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ mNetTransitionWakeLockTimeout = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_networkTransitionTimeout);
+
mNetTrackers = new NetworkStateTracker[
ConnectivityManager.MAX_NETWORK_TYPE+1];
- mHandler = new MyHandler();
mNetworkPreference = getPersistedNetworkPreference();
@@ -280,11 +312,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
for (String raString : raStrings) {
RadioAttributes r = new RadioAttributes(raString);
if (r.mType > ConnectivityManager.MAX_RADIO_TYPE) {
- Slog.e(TAG, "Error in radioAttributes - ignoring attempt to define type " + r.mType);
+ loge("Error in radioAttributes - ignoring attempt to define type " + r.mType);
continue;
}
if (mRadioAttributes[r.mType] != null) {
- Slog.e(TAG, "Error in radioAttributes - ignoring attempt to redefine type " +
+ loge("Error in radioAttributes - ignoring attempt to redefine type " +
r.mType);
continue;
}
@@ -297,17 +329,17 @@ public class ConnectivityService extends IConnectivityManager.Stub {
try {
NetworkAttributes n = new NetworkAttributes(naString);
if (n.mType > ConnectivityManager.MAX_NETWORK_TYPE) {
- Slog.e(TAG, "Error in networkAttributes - ignoring attempt to define type " +
+ loge("Error in networkAttributes - ignoring attempt to define type " +
n.mType);
continue;
}
if (mNetAttributes[n.mType] != null) {
- Slog.e(TAG, "Error in networkAttributes - ignoring attempt to redefine type " +
+ loge("Error in networkAttributes - ignoring attempt to redefine type " +
n.mType);
continue;
}
if (mRadioAttributes[n.mRadio] == null) {
- Slog.e(TAG, "Error in networkAttributes - ignoring attempt to use undefined " +
+ loge("Error in networkAttributes - ignoring attempt to use undefined " +
"radio " + n.mRadio + " in network type " + n.mType);
continue;
}
@@ -359,30 +391,37 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* the number of different network types is not going
* to change very often.
*/
- boolean noMobileData = !getMobileDataEnabled();
for (int netType : mPriorityList) {
switch (mNetAttributes[netType].mRadio) {
case ConnectivityManager.TYPE_WIFI:
- if (DBG) Slog.v(TAG, "Starting Wifi Service.");
- WifiStateTracker wst = new WifiStateTracker(context, mHandler);
- WifiService wifiService = new WifiService(context, wst);
+ if (DBG) log("Starting Wifi Service.");
+ WifiStateTracker wst = new WifiStateTracker();
+ WifiService wifiService = new WifiService(context);
ServiceManager.addService(Context.WIFI_SERVICE, wifiService);
- wifiService.startWifi();
+ wifiService.checkAndStartWifi();
mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst;
- wst.startMonitoring();
+ wst.startMonitoring(context, mHandler);
+
+ //TODO: as part of WWS refactor, create only when needed
+ mWifiWatchdogService = new WifiWatchdogService(context);
break;
case ConnectivityManager.TYPE_MOBILE:
- mNetTrackers[netType] = new MobileDataStateTracker(context, mHandler,
- netType, mNetAttributes[netType].mName);
- mNetTrackers[netType].startMonitoring();
- if (noMobileData) {
- if (DBG) Slog.d(TAG, "tearing down Mobile networks due to setting");
- mNetTrackers[netType].teardown();
- }
+ mNetTrackers[netType] = new MobileDataStateTracker(netType,
+ mNetAttributes[netType].mName);
+ mNetTrackers[netType].startMonitoring(context, mHandler);
+ break;
+ case ConnectivityManager.TYPE_DUMMY:
+ mNetTrackers[netType] = new DummyDataStateTracker(netType,
+ mNetAttributes[netType].mName);
+ mNetTrackers[netType].startMonitoring(context, mHandler);
+ break;
+ case ConnectivityManager.TYPE_BLUETOOTH:
+ mNetTrackers[netType] = BluetoothTetheringDataTracker.getInstance();
+ mNetTrackers[netType].startMonitoring(context, mHandler);
break;
default:
- Slog.e(TAG, "Trying to create a DataStateTracker for an unknown radio type " +
+ loge("Trying to create a DataStateTracker for an unknown radio type " +
mNetAttributes[netType].mRadio);
continue;
}
@@ -392,12 +431,20 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mTetheringConfigValid = (((mNetTrackers[ConnectivityManager.TYPE_MOBILE_DUN] != null) ||
!mTethering.isDunRequired()) &&
(mTethering.getTetherableUsbRegexs().length != 0 ||
- mTethering.getTetherableWifiRegexs().length != 0) &&
+ mTethering.getTetherableWifiRegexs().length != 0 ||
+ mTethering.getTetherableBluetoothRegexs().length != 0) &&
mTethering.getUpstreamIfaceRegexs().length != 0);
if (DBG) {
mInetLog = new ArrayList();
}
+
+ mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY);
+ mSettingsObserver.observe(mContext);
+
+ loadGlobalProxy();
+
+ VpnManager.startVpnService(context);
}
@@ -465,8 +512,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (t != mNetworkPreference && mNetTrackers[t] != null &&
mNetTrackers[t].getNetworkInfo().isConnected()) {
if (DBG) {
- Slog.d(TAG, "tearing down " +
- mNetTrackers[t].getNetworkInfo() +
+ log("tearing down " + mNetTrackers[t].getNetworkInfo() +
" in enforcePreference");
}
teardown(mNetTrackers[t]);
@@ -499,9 +545,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
NetworkStateTracker t = mNetTrackers[type];
NetworkInfo info = t.getNetworkInfo();
if (info.isConnected()) {
- if (DBG && type != mActiveDefaultNetwork) Slog.e(TAG,
- "connected default network is not " +
- "mActiveDefaultNetwork!");
+ if (DBG && type != mActiveDefaultNetwork) {
+ loge("connected default network is not mActiveDefaultNetwork!");
+ }
return info;
}
}
@@ -528,6 +574,38 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return result;
}
+ /**
+ * Return LinkProperties for the active (i.e., connected) default
+ * network interface. It is assumed that at most one default network
+ * is active at a time. If more than one is active, it is indeterminate
+ * which will be returned.
+ * @return the ip properties for the active network, or {@code null} if
+ * none is active
+ */
+ public LinkProperties getActiveLinkProperties() {
+ enforceAccessPermission();
+ for (int type=0; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) {
+ if (mNetAttributes[type] == null || !mNetAttributes[type].isDefault()) {
+ continue;
+ }
+ NetworkStateTracker t = mNetTrackers[type];
+ NetworkInfo info = t.getNetworkInfo();
+ if (info.isConnected()) {
+ return t.getLinkProperties();
+ }
+ }
+ return null;
+ }
+
+ public LinkProperties getLinkProperties(int networkType) {
+ enforceAccessPermission();
+ if (ConnectivityManager.isNetworkTypeValid(networkType)) {
+ NetworkStateTracker t = mNetTrackers[networkType];
+ if (t != null) return t.getLinkProperties();
+ }
+ return null;
+ }
+
public boolean setRadios(boolean turnOn) {
boolean result = true;
enforceChangePermission();
@@ -582,14 +660,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
public void binderDied() {
- Slog.d(TAG, "ConnectivityService FeatureUser binderDied(" +
+ log("ConnectivityService FeatureUser binderDied(" +
mNetworkType + ", " + mFeature + ", " + mBinder + "), created " +
(System.currentTimeMillis() - mCreateTime) + " mSec ago");
stopUsingNetworkFeature(this, false);
}
public void expire() {
- Slog.d(TAG, "ConnectivityService FeatureUser expire(" +
+ log("ConnectivityService FeatureUser expire(" +
mNetworkType + ", " + mFeature + ", " + mBinder +"), created " +
(System.currentTimeMillis() - mCreateTime) + " mSec ago");
stopUsingNetworkFeature(this, false);
@@ -605,8 +683,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
public int startUsingNetworkFeature(int networkType, String feature,
IBinder binder) {
if (DBG) {
- Slog.d(TAG, "startUsingNetworkFeature for net " + networkType +
- ": " + feature);
+ log("startUsingNetworkFeature for net " + networkType + ": " + feature);
}
enforceChangePermission();
if (!ConnectivityManager.isNetworkTypeValid(networkType) ||
@@ -619,15 +696,12 @@ public class ConnectivityService extends IConnectivityManager.Stub {
// TODO - move this into the MobileDataStateTracker
int usedNetworkType = networkType;
if(networkType == ConnectivityManager.TYPE_MOBILE) {
- if (!getMobileDataEnabled()) {
- if (DBG) Slog.d(TAG, "requested special network with data disabled - rejected");
- return Phone.APN_TYPE_NOT_AVAILABLE;
- }
if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS;
} else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL;
- } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN)) {
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN) ||
+ TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN_ALWAYS)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN;
} else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI;
@@ -635,15 +709,18 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
NetworkStateTracker network = mNetTrackers[usedNetworkType];
if (network != null) {
+ Integer currentPid = new Integer(getCallingPid());
if (usedNetworkType != networkType) {
- Integer currentPid = new Integer(getCallingPid());
-
NetworkStateTracker radio = mNetTrackers[networkType];
NetworkInfo ni = network.getNetworkInfo();
if (ni.isAvailable() == false) {
- if (DBG) Slog.d(TAG, "special network not available");
- return Phone.APN_TYPE_NOT_AVAILABLE;
+ if (DBG) log("special network not available");
+ if (!TextUtils.equals(feature,Phone.FEATURE_ENABLE_DUN_ALWAYS)) {
+ return Phone.APN_TYPE_NOT_AVAILABLE;
+ } else {
+ // else make the attempt anyway - probably giving REQUEST_STARTED below
+ }
}
synchronized(this) {
@@ -662,28 +739,29 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (ni.isConnected() == true) {
// add the pid-specific dns
handleDnsConfigurationChange(networkType);
- if (DBG) Slog.d(TAG, "special network already active");
+ if (DBG) log("special network already active");
return Phone.APN_ALREADY_ACTIVE;
}
- if (DBG) Slog.d(TAG, "special network already connecting");
+ if (DBG) log("special network already connecting");
return Phone.APN_REQUEST_STARTED;
}
// check if the radio in play can make another contact
// assume if cannot for now
- if (DBG) Slog.d(TAG, "reconnecting to special network");
+ if (DBG) log("reconnecting to special network");
network.reconnect();
return Phone.APN_REQUEST_STARTED;
} else {
+ // need to remember this unsupported request so we respond appropriately on stop
synchronized(this) {
mFeatureUsers.add(f);
+ if (!mNetRequestersPids[usedNetworkType].contains(currentPid)) {
+ // this gets used for per-pid dns when connected
+ mNetRequestersPids[usedNetworkType].add(currentPid);
+ }
}
- mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_RESTORE_DEFAULT_NETWORK,
- f), getRestoreDefaultNetworkDelay());
-
- return network.startUsingNetworkFeature(feature,
- getCallingPid(), getCallingUid());
+ return -1;
}
}
return Phone.APN_TYPE_NOT_AVAILABLE;
@@ -715,7 +793,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return stopUsingNetworkFeature(u, true);
} else {
// none found!
- if (DBG) Slog.d(TAG, "ignoring stopUsingNetworkFeature - not a live request");
+ if (DBG) log("ignoring stopUsingNetworkFeature - not a live request");
return 1;
}
}
@@ -730,7 +808,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
boolean callTeardown = false; // used to carry our decision outside of sync block
if (DBG) {
- Slog.d(TAG, "stopUsingNetworkFeature for net " + networkType +
+ log("stopUsingNetworkFeature for net " + networkType +
": " + feature);
}
@@ -743,7 +821,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
synchronized(this) {
// check if this process still has an outstanding start request
if (!mFeatureUsers.contains(u)) {
- if (DBG) Slog.d(TAG, "ignoring - this process has no outstanding requests");
+ if (DBG) log("ignoring - this process has no outstanding requests");
return 1;
}
u.unlinkDeathRecipient();
@@ -761,7 +839,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (x.mUid == u.mUid && x.mPid == u.mPid &&
x.mNetworkType == u.mNetworkType &&
TextUtils.equals(x.mFeature, u.mFeature)) {
- if (DBG) Slog.d(TAG, "ignoring stopUsingNetworkFeature as dup is found");
+ if (DBG) log("ignoring stopUsingNetworkFeature as dup is found");
return 1;
}
}
@@ -774,7 +852,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS;
} else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL;
- } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN)) {
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN) ||
+ TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN_ALWAYS)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN;
} else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) {
usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI;
@@ -782,7 +861,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
tracker = mNetTrackers[usedNetworkType];
if (tracker == null) {
- if (DBG) Slog.d(TAG, "ignoring - no known tracker for net type " + usedNetworkType);
+ if (DBG) log("ignoring - no known tracker for net type " + usedNetworkType);
return -1;
}
if (usedNetworkType != networkType) {
@@ -790,20 +869,21 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mNetRequestersPids[usedNetworkType].remove(currentPid);
reassessPidDns(pid, true);
if (mNetRequestersPids[usedNetworkType].size() != 0) {
- if (DBG) Slog.d(TAG, "not tearing down special network - " +
+ if (DBG) log("not tearing down special network - " +
"others still using it");
return 1;
}
callTeardown = true;
+ } else {
+ if (DBG) log("not a known feature - dropping");
}
}
- if (DBG) Slog.d(TAG, "Doing network teardown");
+ if (DBG) log("Doing network teardown");
if (callTeardown) {
tracker.teardown();
return 1;
} else {
- // do it the old fashioned way
- return tracker.stopUsingNetworkFeature(feature, pid, uid);
+ return -1;
}
}
@@ -847,16 +927,42 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (tracker == null || !tracker.getNetworkInfo().isConnected() ||
tracker.isTeardownRequested()) {
if (DBG) {
- Slog.d(TAG, "requestRouteToHostAddress on down network " +
+ log("requestRouteToHostAddress on down network " +
"(" + networkType + ") - dropped");
}
return false;
}
-
try {
- InetAddress inetAddress = InetAddress.getByAddress(hostAddress);
- return tracker.requestRouteToHost(inetAddress);
- } catch (UnknownHostException e) {
+ InetAddress addr = InetAddress.getByAddress(hostAddress);
+ return addHostRoute(tracker, addr);
+ } catch (UnknownHostException e) {}
+ return false;
+ }
+
+ /**
+ * Ensure that a network route exists to deliver traffic to the specified
+ * host via the mobile data network.
+ * @param hostAddress the IP address of the host to which the route is desired,
+ * in network byte order.
+ * TODO - deprecate
+ * @return {@code true} on success, {@code false} on failure
+ */
+ private boolean addHostRoute(NetworkStateTracker nt, InetAddress hostAddress) {
+ if (nt.getNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI) {
+ return false;
+ }
+
+ LinkProperties p = nt.getLinkProperties();
+ if (p == null) return false;
+ String interfaceName = p.getInterfaceName();
+
+ if (DBG) {
+ log("Requested host route to " + hostAddress + "(" + interfaceName + ")");
+ }
+ if (interfaceName != null) {
+ return NetworkUtils.addHostRoute(interfaceName, hostAddress, null);
+ } else {
+ if (DBG) loge("addHostRoute failed due to null interface name");
return false;
}
}
@@ -865,8 +971,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* @see ConnectivityManager#getBackgroundDataSetting()
*/
public boolean getBackgroundDataSetting() {
- return Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.BACKGROUND_DATA, 1) == 1;
+ return mBackgroundDataEnabled.get();
}
/**
@@ -877,6 +982,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
android.Manifest.permission.CHANGE_BACKGROUND_DATA_SETTING,
"ConnectivityService");
+ mBackgroundDataEnabled.set(allowBackgroundDataUsage);
+
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_BACKGROUND_DATA,
(allowBackgroundDataUsage ? ENABLED : DISABLED), 0));
}
@@ -895,10 +1002,13 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* @see ConnectivityManager#getMobileDataEnabled()
*/
public boolean getMobileDataEnabled() {
+ // TODO: This detail should probably be in DataConnectionTracker's
+ // which is where we store the value and maybe make this
+ // asynchronous.
enforceAccessPermission();
boolean retVal = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.MOBILE_DATA, 1) == 1;
- if (DBG) Slog.d(TAG, "getMobileDataEnabled returning " + retVal);
+ if (DBG) log("getMobileDataEnabled returning " + retVal);
return retVal;
}
@@ -907,47 +1017,19 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/
public void setMobileDataEnabled(boolean enabled) {
enforceChangePermission();
- if (DBG) Slog.d(TAG, "setMobileDataEnabled(" + enabled + ")");
+ if (DBG) log("setMobileDataEnabled(" + enabled + ")");
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_MOBILE_DATA,
(enabled ? ENABLED : DISABLED), 0));
}
private void handleSetMobileData(boolean enabled) {
- if (getMobileDataEnabled() == enabled) return;
-
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.MOBILE_DATA, enabled ? 1 : 0);
-
- if (enabled) {
- if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) {
- if (DBG) {
- Slog.d(TAG, "starting up " + mNetTrackers[ConnectivityManager.TYPE_MOBILE]);
- }
- mNetTrackers[ConnectivityManager.TYPE_MOBILE].reconnect();
- }
- } else {
- for (NetworkStateTracker nt : mNetTrackers) {
- if (nt == null) continue;
- int netType = nt.getNetworkInfo().getType();
- if (mNetAttributes[netType].mRadio == ConnectivityManager.TYPE_MOBILE) {
- if (DBG) Slog.d(TAG, "tearing down " + nt);
- nt.teardown();
- }
- }
- }
- }
-
- private int getNumConnectedNetworks() {
- int numConnectedNets = 0;
-
- for (NetworkStateTracker nt : mNetTrackers) {
- if (nt != null && nt.getNetworkInfo().isConnected() &&
- !nt.isTeardownRequested()) {
- ++numConnectedNets;
+ if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) {
+ if (DBG) {
+ Slog.d(TAG, mNetTrackers[ConnectivityManager.TYPE_MOBILE].toString() + enabled);
}
+ mNetTrackers[ConnectivityManager.TYPE_MOBILE].setDataEnable(enabled);
}
- return numConnectedNets;
}
private void enforceAccessPermission() {
@@ -975,6 +1057,12 @@ public class ConnectivityService extends IConnectivityManager.Stub {
"ConnectivityService");
}
+ private void enforceConnectivityInternalPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CONNECTIVITY_INTERNAL,
+ "ConnectivityService");
+ }
+
/**
* Handle a {@code DISCONNECTED} event. If this pertains to the non-active
* network, we ignore it. If it is for the active network, we send out a
@@ -1018,18 +1106,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
info.getExtraInfo());
}
- NetworkStateTracker newNet = null;
if (mNetAttributes[prevNetType].isDefault()) {
- newNet = tryFailover(prevNetType);
- if (newNet != null) {
- NetworkInfo switchTo = newNet.getNetworkInfo();
- if (!switchTo.isConnected()) {
- // if the other net is connected they've already reset this and perhaps even gotten
- // a positive report we don't want to overwrite, but if not we need to clear this now
- // to turn our cellular sig strength white
- mDefaultInetConditionPublished = 0;
- intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
- }
+ tryFailover(prevNetType);
+ if (mActiveDefaultNetwork != -1) {
+ NetworkInfo switchTo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
} else {
mDefaultInetConditionPublished = 0; // we're not connected anymore
@@ -1045,59 +1125,55 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* If the failover network is already connected, then immediately send
* out a followup broadcast indicating successful failover
*/
- if (newNet != null && newNet.getNetworkInfo().isConnected()) {
- sendConnectedBroadcast(newNet.getNetworkInfo());
+ if (mActiveDefaultNetwork != -1) {
+ sendConnectedBroadcast(mNetTrackers[mActiveDefaultNetwork].getNetworkInfo());
}
}
- // returns null if no failover available
- private NetworkStateTracker tryFailover(int prevNetType) {
+ private void tryFailover(int prevNetType) {
/*
- * If this is a default network, check if other defaults are available
- * or active
+ * If this is a default network, check if other defaults are available.
+ * Try to reconnect on all available and let them hash it out when
+ * more than one connects.
*/
- NetworkStateTracker newNet = null;
if (mNetAttributes[prevNetType].isDefault()) {
if (mActiveDefaultNetwork == prevNetType) {
mActiveDefaultNetwork = -1;
}
- int newType = -1;
- int newPriority = -1;
- boolean noMobileData = !getMobileDataEnabled();
+ // don't signal a reconnect for anything lower or equal priority than our
+ // current connected default
+ // TODO - don't filter by priority now - nice optimization but risky
+// int currentPriority = -1;
+// if (mActiveDefaultNetwork != -1) {
+// currentPriority = mNetAttributes[mActiveDefaultNetwork].mPriority;
+// }
for (int checkType=0; checkType <= ConnectivityManager.MAX_NETWORK_TYPE; checkType++) {
if (checkType == prevNetType) continue;
if (mNetAttributes[checkType] == null) continue;
- if (mNetAttributes[checkType].isDefault() == false) continue;
- if (mNetAttributes[checkType].mRadio == ConnectivityManager.TYPE_MOBILE &&
- noMobileData) {
- Slog.e(TAG, "not failing over to mobile type " + checkType +
- " because Mobile Data Disabled");
- continue;
- }
- NetworkStateTracker tracker = mNetTrackers[checkType];
- NetworkInfo info = tracker.getNetworkInfo();
- if (!info.isConnectedOrConnecting() ||
- tracker.isTeardownRequested()) {
- info.setFailover(true);
- tracker.reconnect();
- }
- if (DBG) Slog.d(TAG, "Attempting to switch to " + info.getTypeName());
-
- // figure out if this is the highest priority network
- // so we send an appropriate return value
- if (checkType == mNetworkPreference) {
- newType = checkType;
- }
- if (mNetAttributes[checkType].mPriority > newPriority &&
- newType != mNetworkPreference) {
- newType = checkType;
- newPriority = mNetAttributes[checkType].mPriority;
+ if (!mNetAttributes[checkType].isDefault()) continue;
+
+// Enabling the isAvailable() optimization caused mobile to not get
+// selected if it was in the middle of error handling. Specifically
+// a moble connection that took 30 seconds to complete the DEACTIVATE_DATA_CALL
+// would not be available and we wouldn't get connected to anything.
+// So removing the isAvailable() optimization below for now. TODO: This
+// optimization should work and we need to investigate why it doesn't work.
+// This could be related to how DEACTIVATE_DATA_CALL is reporting its
+// complete before it is really complete.
+// if (!mNetTrackers[checkType].isAvailable()) continue;
+
+// if (currentPriority >= mNetAttributes[checkType].mPriority) continue;
+
+ NetworkStateTracker checkTracker = mNetTrackers[checkType];
+ NetworkInfo checkInfo = checkTracker.getNetworkInfo();
+ if (!checkInfo.isConnectedOrConnecting() || checkTracker.isTeardownRequested()) {
+ checkInfo.setFailover(true);
+ checkTracker.reconnect();
}
+ if (DBG) log("Attempting to switch to " + checkInfo.getTypeName());
}
}
-
- return newNet;
}
private void sendConnectedBroadcast(NetworkInfo info) {
@@ -1142,7 +1218,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
} else {
reasonText = " (" + reason + ").";
}
- Slog.e(TAG, "Attempt to connect to " + info.getTypeName() + " failed" + reasonText);
+ loge("Attempt to connect to " + info.getTypeName() + " failed" + reasonText);
Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
@@ -1160,17 +1236,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
info.setFailover(false);
}
- NetworkStateTracker newNet = null;
if (mNetAttributes[info.getType()].isDefault()) {
- newNet = tryFailover(info.getType());
- if (newNet != null) {
- NetworkInfo switchTo = newNet.getNetworkInfo();
- if (!switchTo.isConnected()) {
- // if the other net is connected they've already reset this and perhaps
- // even gotten a positive report we don't want to overwrite, but if not
- // we need to clear this now to turn our cellular sig strength white
- mDefaultInetConditionPublished = 0;
- }
+ tryFailover(info.getType());
+ if (mActiveDefaultNetwork != -1) {
+ NetworkInfo switchTo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
} else {
mDefaultInetConditionPublished = 0;
@@ -1184,8 +1253,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* If the failover network is already connected, then immediately send
* out a followup broadcast indicating successful failover
*/
- if (newNet != null && newNet.getNetworkInfo().isConnected()) {
- sendConnectedBroadcast(newNet.getNetworkInfo());
+ if (mActiveDefaultNetwork != -1) {
+ sendConnectedBroadcast(mNetTrackers[mActiveDefaultNetwork].getNetworkInfo());
}
}
@@ -1207,6 +1276,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mInitialBroadcast = null;
}
}
+ // load the global proxy at startup
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY));
}
private void handleConnect(NetworkInfo info) {
@@ -1225,24 +1296,35 @@ public class ConnectivityService extends IConnectivityManager.Stub {
mNetAttributes[type].mPriority) ||
mNetworkPreference == mActiveDefaultNetwork) {
// don't accept this one
- if (DBG) Slog.v(TAG, "Not broadcasting CONNECT_ACTION " +
+ if (DBG) {
+ log("Not broadcasting CONNECT_ACTION " +
"to torn down network " + info.getTypeName());
+ }
teardown(thisNet);
return;
} else {
// tear down the other
NetworkStateTracker otherNet =
mNetTrackers[mActiveDefaultNetwork];
- if (DBG) Slog.v(TAG, "Policy requires " +
- otherNet.getNetworkInfo().getTypeName() +
+ if (DBG) {
+ log("Policy requires " + otherNet.getNetworkInfo().getTypeName() +
" teardown");
+ }
if (!teardown(otherNet)) {
- Slog.e(TAG, "Network declined teardown request");
+ loge("Network declined teardown request");
return;
}
- if (isFailover) {
- otherNet.releaseWakeLock();
- }
+ }
+ }
+ synchronized (ConnectivityService.this) {
+ // have a new default network, release the transition wakelock in a second
+ // if it's held. The second pause is to allow apps to reconnect over the
+ // new network
+ if (mNetTransitionWakeLock.isHeld()) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+ mNetTransitionWakeLockSerialNumber, 0),
+ 1000);
}
}
mActiveDefaultNetwork = type;
@@ -1256,36 +1338,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
//reportNetworkCondition(mActiveDefaultNetwork, 100);
}
thisNet.setTeardownRequested(false);
- thisNet.updateNetworkSettings();
+ updateNetworkSettings(thisNet);
handleConnectivityChange(type);
sendConnectedBroadcast(info);
}
- private void handleScanResultsAvailable(NetworkInfo info) {
- int networkType = info.getType();
- if (networkType != ConnectivityManager.TYPE_WIFI) {
- if (DBG) Slog.v(TAG, "Got ScanResultsAvailable for " +
- info.getTypeName() + " network. Don't know how to handle.");
- }
-
- mNetTrackers[networkType].interpretScanResultsAvailable();
- }
-
- private void handleNotificationChange(boolean visible, int id,
- Notification notification) {
- NotificationManager notificationManager = (NotificationManager) mContext
- .getSystemService(Context.NOTIFICATION_SERVICE);
-
- if (visible) {
- notificationManager.notify(id, notification);
- } else {
- notificationManager.cancel(id);
- }
- }
-
/**
- * After a change in the connectivity state of any network, We're mainly
- * concerned with making sure that the list of DNS servers is setupup
+ * After a change in the connectivity state of a network. We're mainly
+ * concerned with making sure that the list of DNS servers is set up
* according to which networks are connected, and ensuring that the
* right routing table entries exist.
*/
@@ -1298,19 +1358,159 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
if (mNetAttributes[netType].isDefault()) {
- mNetTrackers[netType].addDefaultRoute();
+ handleApplyDefaultProxy(netType);
+ addDefaultRoute(mNetTrackers[netType]);
} else {
- mNetTrackers[netType].addPrivateDnsRoutes();
+ addPrivateDnsRoutes(mNetTrackers[netType]);
}
} else {
if (mNetAttributes[netType].isDefault()) {
- mNetTrackers[netType].removeDefaultRoute();
+ removeDefaultRoute(mNetTrackers[netType]);
} else {
- mNetTrackers[netType].removePrivateDnsRoutes();
+ removePrivateDnsRoutes(mNetTrackers[netType]);
+ }
+ }
+ }
+
+ private void addPrivateDnsRoutes(NetworkStateTracker nt) {
+ boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet();
+ LinkProperties p = nt.getLinkProperties();
+ if (p == null) return;
+ String interfaceName = p.getInterfaceName();
+
+ if (DBG) {
+ log("addPrivateDnsRoutes for " + nt +
+ "(" + interfaceName + ") - mPrivateDnsRouteSet = " + privateDnsRouteSet);
+ }
+ if (interfaceName != null && !privateDnsRouteSet) {
+ Collection<InetAddress> dnsList = p.getDnses();
+ for (InetAddress dns : dnsList) {
+ if (DBG) log(" adding " + dns);
+ NetworkUtils.addHostRoute(interfaceName, dns, null);
+ }
+ nt.privateDnsRouteSet(true);
+ }
+ }
+
+ private void removePrivateDnsRoutes(NetworkStateTracker nt) {
+ // TODO - we should do this explicitly but the NetUtils api doesnt
+ // support this yet - must remove all. No worse than before
+ LinkProperties p = nt.getLinkProperties();
+ if (p == null) return;
+ String interfaceName = p.getInterfaceName();
+ boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet();
+ if (interfaceName != null && privateDnsRouteSet) {
+ if (DBG) {
+ log("removePrivateDnsRoutes for " + nt.getNetworkInfo().getTypeName() +
+ " (" + interfaceName + ")");
}
+ NetworkUtils.removeHostRoutes(interfaceName);
+ nt.privateDnsRouteSet(false);
}
}
+
+ private void addDefaultRoute(NetworkStateTracker nt) {
+ LinkProperties p = nt.getLinkProperties();
+ if (p == null) return;
+ String interfaceName = p.getInterfaceName();
+ InetAddress defaultGatewayAddr = p.getGateway();
+
+ if ((interfaceName != null) && (defaultGatewayAddr != null )) {
+ if (!NetworkUtils.addDefaultRoute(interfaceName, defaultGatewayAddr) && DBG) {
+ NetworkInfo networkInfo = nt.getNetworkInfo();
+ log("addDefaultRoute for " + networkInfo.getTypeName() +
+ " (" + interfaceName + "), GatewayAddr=" + defaultGatewayAddr);
+ }
+ }
+ }
+
+
+ public void removeDefaultRoute(NetworkStateTracker nt) {
+ LinkProperties p = nt.getLinkProperties();
+ if (p == null) return;
+ String interfaceName = p.getInterfaceName();
+
+ if (interfaceName != null) {
+ if ((NetworkUtils.removeDefaultRoute(interfaceName) >= 0) && DBG) {
+ NetworkInfo networkInfo = nt.getNetworkInfo();
+ log("removeDefaultRoute for " + networkInfo.getTypeName() + " (" +
+ interfaceName + ")");
+ }
+ }
+ }
+
+ /**
+ * Reads the network specific TCP buffer sizes from SystemProperties
+ * net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system
+ * wide use
+ */
+ public void updateNetworkSettings(NetworkStateTracker nt) {
+ String key = nt.getTcpBufferSizesPropName();
+ String bufferSizes = SystemProperties.get(key);
+
+ if (bufferSizes.length() == 0) {
+ loge(key + " not found in system properties. Using defaults");
+
+ // Setting to default values so we won't be stuck to previous values
+ key = "net.tcp.buffersize.default";
+ bufferSizes = SystemProperties.get(key);
+ }
+
+ // Set values in kernel
+ if (bufferSizes.length() != 0) {
+ if (DBG) {
+ log("Setting TCP values: [" + bufferSizes
+ + "] which comes from [" + key + "]");
+ }
+ setBufferSize(bufferSizes);
+ }
+ }
+
+ /**
+ * Writes TCP buffer sizes to /sys/kernel/ipv4/tcp_[r/w]mem_[min/def/max]
+ * which maps to /proc/sys/net/ipv4/tcp_rmem and tcpwmem
+ *
+ * @param bufferSizes in the format of "readMin, readInitial, readMax,
+ * writeMin, writeInitial, writeMax"
+ */
+ private void setBufferSize(String bufferSizes) {
+ try {
+ String[] values = bufferSizes.split(",");
+
+ if (values.length == 6) {
+ final String prefix = "/sys/kernel/ipv4/tcp_";
+ stringToFile(prefix + "rmem_min", values[0]);
+ stringToFile(prefix + "rmem_def", values[1]);
+ stringToFile(prefix + "rmem_max", values[2]);
+ stringToFile(prefix + "wmem_min", values[3]);
+ stringToFile(prefix + "wmem_def", values[4]);
+ stringToFile(prefix + "wmem_max", values[5]);
+ } else {
+ loge("Invalid buffersize string: " + bufferSizes);
+ }
+ } catch (IOException e) {
+ loge("Can't set tcp buffer sizes:" + e);
+ }
+ }
+
+ /**
+ * Writes string to file. Basically same as "echo -n $string > $filename"
+ *
+ * @param filename
+ * @param string
+ * @throws IOException
+ */
+ private void stringToFile(String filename, String string) throws IOException {
+ FileWriter out = new FileWriter(filename);
+ try {
+ out.write(string);
+ } finally {
+ out.close();
+ }
+ }
+
+
/**
* Adjust the per-process dns entries (net.dns<x>.<pid>) based
* on the highest priority active net which this process requested.
@@ -1318,7 +1518,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
*/
private void reassessPidDns(int myPid, boolean doBump)
{
- if (DBG) Slog.d(TAG, "reassessPidDns for pid " + myPid);
+ if (DBG) log("reassessPidDns for pid " + myPid);
for(int i : mPriorityList) {
if (mNetAttributes[i].isDefault()) {
continue;
@@ -1326,12 +1526,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
NetworkStateTracker nt = mNetTrackers[i];
if (nt.getNetworkInfo().isConnected() &&
!nt.isTeardownRequested()) {
+ LinkProperties p = nt.getLinkProperties();
+ if (p == null) continue;
List pids = mNetRequestersPids[i];
for (int j=0; j<pids.size(); j++) {
Integer pid = (Integer)pids.get(j);
if (pid.intValue() == myPid) {
- String[] dnsList = nt.getNameServers();
- writePidDns(dnsList, myPid);
+ Collection<InetAddress> dnses = p.getDnses();
+ writePidDns(dnses, myPid);
if (doBump) {
bumpDns();
}
@@ -1353,13 +1555,18 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
- private void writePidDns(String[] dnsList, int pid) {
+ // return true if results in a change
+ private boolean writePidDns(Collection <InetAddress> dnses, int pid) {
int j = 1;
- for (String dns : dnsList) {
- if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) {
- SystemProperties.set("net.dns" + j++ + "." + pid, dns);
+ boolean changed = false;
+ for (InetAddress dns : dnses) {
+ String dnsString = dns.getHostAddress();
+ if (changed || !dnsString.equals(SystemProperties.get("net.dns" + j + "." + pid))) {
+ changed = true;
+ SystemProperties.set("net.dns" + j++ + "." + pid, dns.getHostAddress());
}
}
+ return changed;
}
private void bumpDns() {
@@ -1375,27 +1582,59 @@ public class ConnectivityService extends IConnectivityManager.Stub {
} catch (NumberFormatException e) {}
}
SystemProperties.set("net.dnschange", "" + (n+1));
+ /*
+ * Tell the VMs to toss their DNS caches
+ */
+ Intent intent = new Intent(Intent.ACTION_CLEAR_DNS_CACHE);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ /*
+ * Connectivity events can happen before boot has completed ...
+ */
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcast(intent);
}
private void handleDnsConfigurationChange(int netType) {
// add default net's dns entries
NetworkStateTracker nt = mNetTrackers[netType];
if (nt != null && nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) {
- String[] dnsList = nt.getNameServers();
+ LinkProperties p = nt.getLinkProperties();
+ if (p == null) return;
+ Collection<InetAddress> dnses = p.getDnses();
+ boolean changed = false;
if (mNetAttributes[netType].isDefault()) {
int j = 1;
- for (String dns : dnsList) {
- if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) {
+ if (dnses.size() == 0 && mDefaultDns != null) {
+ String dnsString = mDefaultDns.getHostAddress();
+ if (!dnsString.equals(SystemProperties.get("net.dns1"))) {
+ if (DBG) {
+ log("no dns provided - using " + dnsString);
+ }
+ changed = true;
+ SystemProperties.set("net.dns1", dnsString);
+ }
+ j++;
+ } else {
+ for (InetAddress dns : dnses) {
+ String dnsString = dns.getHostAddress();
+ if (!changed && dnsString.equals(SystemProperties.get("net.dns" + j))) {
+ j++;
+ continue;
+ }
if (DBG) {
- Slog.d(TAG, "adding dns " + dns + " for " +
+ log("adding dns " + dns + " for " +
nt.getNetworkInfo().getTypeName());
}
- SystemProperties.set("net.dns" + j++, dns);
+ changed = true;
+ SystemProperties.set("net.dns" + j++, dnsString);
}
}
for (int k=j ; k<mNumDnsEntries; k++) {
- if (DBG) Slog.d(TAG, "erasing net.dns" + k);
- SystemProperties.set("net.dns" + k, "");
+ if (changed || !TextUtils.isEmpty(SystemProperties.get("net.dns" + k))) {
+ if (DBG) log("erasing net.dns" + k);
+ changed = true;
+ SystemProperties.set("net.dns" + k, "");
+ }
}
mNumDnsEntries = j;
} else {
@@ -1403,11 +1642,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
List pids = mNetRequestersPids[netType];
for (int y=0; y< pids.size(); y++) {
Integer pid = (Integer)pids.get(y);
- writePidDns(dnsList, pid.intValue());
+ changed = writePidDns(dnses, pid.intValue());
}
}
+ if (changed) bumpDns();
}
- bumpDns();
}
private int getRestoreDefaultNetworkDelay() {
@@ -1462,6 +1701,13 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
pw.println();
+ synchronized (this) {
+ pw.println("NetworkTranstionWakeLock is currently " +
+ (mNetTransitionWakeLock.isHeld() ? "" : "not ") + "held.");
+ pw.println("It was last requested for "+mNetTransitionWakeLockCausedBy);
+ }
+ pw.println();
+
mTethering.dump(fd, pw, args);
if (mInetLog != null) {
@@ -1475,6 +1721,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
// must be stateless - things change under us.
private class MyHandler extends Handler {
+ public MyHandler(Looper looper) {
+ super(looper);
+ }
+
@Override
public void handleMessage(Message msg) {
NetworkInfo info;
@@ -1493,7 +1743,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (DBG) {
// TODO - remove this after we validate the dropping doesn't break
// anything
- Slog.d(TAG, "Dropping ConnectivityChange for " +
+ log("Dropping ConnectivityChange for " +
info.getTypeName() + ": " +
state + "/" + info.getDetailedState());
}
@@ -1501,7 +1751,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
mNetAttributes[type].mLastState = state;
- if (DBG) Slog.d(TAG, "ConnectivityChange for " +
+ if (DBG) log("ConnectivityChange for " +
info.getTypeName() + ": " +
state + "/" + info.getDetailedState());
@@ -1536,29 +1786,23 @@ public class ConnectivityService extends IConnectivityManager.Stub {
handleConnect(info);
}
break;
-
- case NetworkStateTracker.EVENT_SCAN_RESULTS_AVAILABLE:
- info = (NetworkInfo) msg.obj;
- handleScanResultsAvailable(info);
- break;
-
- case NetworkStateTracker.EVENT_NOTIFICATION_CHANGED:
- handleNotificationChange(msg.arg1 == 1, msg.arg2,
- (Notification) msg.obj);
- break;
-
case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED:
info = (NetworkInfo) msg.obj;
type = info.getType();
- handleDnsConfigurationChange(type);
+ handleConnectivityChange(type);
break;
-
- case NetworkStateTracker.EVENT_ROAMING_CHANGED:
- // fill me in
- break;
-
- case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED:
- // fill me in
+ case EVENT_CLEAR_NET_TRANSITION_WAKELOCK:
+ String causedBy = null;
+ synchronized (ConnectivityService.this) {
+ if (msg.arg1 == mNetTransitionWakeLockSerialNumber &&
+ mNetTransitionWakeLock.isHeld()) {
+ mNetTransitionWakeLock.release();
+ causedBy = mNetTransitionWakeLockCausedBy;
+ }
+ }
+ if (causedBy != null) {
+ log("NetTransition Wakelock for " + causedBy + " released by timeout");
+ }
break;
case EVENT_RESTORE_DEFAULT_NETWORK:
FeatureUser u = (FeatureUser)msg.obj;
@@ -1596,6 +1840,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
handleSetMobileData(enabled);
break;
}
+ case EVENT_APPLY_GLOBAL_HTTP_PROXY:
+ {
+ handleDeprecatedGlobalHttpProxy();
+ }
}
}
}
@@ -1652,6 +1900,15 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
+ public String[] getTetherableBluetoothRegexs() {
+ enforceTetherAccessPermission();
+ if (isTetheringSupported()) {
+ return mTethering.getTetherableBluetoothRegexs();
+ } else {
+ return new String[0];
+ }
+ }
+
// TODO - move iface listing, queries, etc to new module
// javadoc from interface
public String[] getTetherableIfaces() {
@@ -1680,9 +1937,28 @@ public class ConnectivityService extends IConnectivityManager.Stub {
return tetherEnabledInSettings && mTetheringConfigValid;
}
+ // An API NetworkStateTrackers can call when they lose their network.
+ // This will automatically be cleared after X seconds or a network becomes CONNECTED,
+ // whichever happens first. The timer is started by the first caller and not
+ // restarted by subsequent callers.
+ public void requestNetworkTransitionWakelock(String forWhom) {
+ enforceConnectivityInternalPermission();
+ synchronized (this) {
+ if (mNetTransitionWakeLock.isHeld()) return;
+ mNetTransitionWakeLockSerialNumber++;
+ mNetTransitionWakeLock.acquire();
+ mNetTransitionWakeLockCausedBy = forWhom;
+ }
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+ mNetTransitionWakeLockSerialNumber, 0),
+ mNetTransitionWakeLockTimeout);
+ return;
+ }
+
// 100 percent is full good, 0 is full bad.
public void reportInetCondition(int networkType, int percentage) {
- if (DBG) Slog.d(TAG, "reportNetworkCondition(" + networkType + ", " + percentage + ")");
+ if (DBG) log("reportNetworkCondition(" + networkType + ", " + percentage + ")");
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.STATUS_BAR,
"ConnectivityService");
@@ -1704,22 +1980,22 @@ public class ConnectivityService extends IConnectivityManager.Stub {
private void handleInetConditionChange(int netType, int condition) {
if (DBG) {
- Slog.d(TAG, "Inet connectivity change, net=" +
+ log("Inet connectivity change, net=" +
netType + ", condition=" + condition +
",mActiveDefaultNetwork=" + mActiveDefaultNetwork);
}
if (mActiveDefaultNetwork == -1) {
- if (DBG) Slog.d(TAG, "no active default network - aborting");
+ if (DBG) log("no active default network - aborting");
return;
}
if (mActiveDefaultNetwork != netType) {
- if (DBG) Slog.d(TAG, "given net not default - aborting");
+ if (DBG) log("given net not default - aborting");
return;
}
mDefaultInetCondition = condition;
int delay;
if (mInetConditionChangeInFlight == false) {
- if (DBG) Slog.d(TAG, "starting a change hold");
+ if (DBG) log("starting a change hold");
// setup a new hold to debounce this
if (mDefaultInetCondition > 50) {
delay = Settings.Secure.getInt(mContext.getContentResolver(),
@@ -1734,37 +2010,169 @@ public class ConnectivityService extends IConnectivityManager.Stub {
} else {
// we've set the new condition, when this hold ends that will get
// picked up
- if (DBG) Slog.d(TAG, "currently in hold - not setting new end evt");
+ if (DBG) log("currently in hold - not setting new end evt");
}
}
private void handleInetConditionHoldEnd(int netType, int sequence) {
if (DBG) {
- Slog.d(TAG, "Inet hold end, net=" + netType +
+ log("Inet hold end, net=" + netType +
", condition =" + mDefaultInetCondition +
", published condition =" + mDefaultInetConditionPublished);
}
mInetConditionChangeInFlight = false;
if (mActiveDefaultNetwork == -1) {
- if (DBG) Slog.d(TAG, "no active default network - aborting");
+ if (DBG) log("no active default network - aborting");
return;
}
if (mDefaultConnectionSequence != sequence) {
- if (DBG) Slog.d(TAG, "event hold for obsolete network - aborting");
+ if (DBG) log("event hold for obsolete network - aborting");
return;
}
if (mDefaultInetConditionPublished == mDefaultInetCondition) {
- if (DBG) Slog.d(TAG, "no change in condition - aborting");
+ if (DBG) log("no change in condition - aborting");
return;
}
NetworkInfo networkInfo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
if (networkInfo.isConnected() == false) {
- if (DBG) Slog.d(TAG, "default network not connected - aborting");
+ if (DBG) log("default network not connected - aborting");
return;
}
mDefaultInetConditionPublished = mDefaultInetCondition;
sendInetConditionBroadcast(networkInfo);
return;
}
+
+ public synchronized ProxyProperties getProxy() {
+ if (mGlobalProxy != null) return mGlobalProxy;
+ if (mDefaultProxy != null) return mDefaultProxy;
+ return null;
+ }
+
+ public void setGlobalProxy(ProxyProperties proxyProperties) {
+ enforceChangePermission();
+ synchronized (mGlobalProxyLock) {
+ if (proxyProperties == mGlobalProxy) return;
+ if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return;
+ if (mGlobalProxy != null && mGlobalProxy.equals(proxyProperties)) return;
+
+ String host = "";
+ int port = 0;
+ String exclList = "";
+ if (proxyProperties != null && !TextUtils.isEmpty(proxyProperties.getHost())) {
+ mGlobalProxy = new ProxyProperties(proxyProperties);
+ host = mGlobalProxy.getHost();
+ port = mGlobalProxy.getPort();
+ exclList = mGlobalProxy.getExclusionList();
+ } else {
+ mGlobalProxy = null;
+ }
+ ContentResolver res = mContext.getContentResolver();
+ Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_HOST, host);
+ Settings.Secure.putInt(res, Settings.Secure.GLOBAL_HTTP_PROXY_PORT, port);
+ Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
+ exclList);
+ }
+
+ if (mGlobalProxy == null) {
+ proxyProperties = mDefaultProxy;
+ }
+ sendProxyBroadcast(proxyProperties);
+ }
+
+ private void loadGlobalProxy() {
+ ContentResolver res = mContext.getContentResolver();
+ String host = Settings.Secure.getString(res, Settings.Secure.GLOBAL_HTTP_PROXY_HOST);
+ int port = Settings.Secure.getInt(res, Settings.Secure.GLOBAL_HTTP_PROXY_PORT, 0);
+ String exclList = Settings.Secure.getString(res,
+ Settings.Secure.GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
+ if (!TextUtils.isEmpty(host)) {
+ ProxyProperties proxyProperties = new ProxyProperties(host, port, exclList);
+ synchronized (mGlobalProxyLock) {
+ mGlobalProxy = proxyProperties;
+ }
+ }
+ }
+
+ public ProxyProperties getGlobalProxy() {
+ synchronized (mGlobalProxyLock) {
+ return mGlobalProxy;
+ }
+ }
+
+ private void handleApplyDefaultProxy(int type) {
+ // check if new default - push it out to all VM if so
+ ProxyProperties proxy = mNetTrackers[type].getLinkProperties().getHttpProxy();
+ synchronized (this) {
+ if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return;
+ if (mDefaultProxy == proxy) return;
+ if (!TextUtils.isEmpty(proxy.getHost())) {
+ mDefaultProxy = proxy;
+ } else {
+ mDefaultProxy = null;
+ }
+ }
+ if (DBG) log("changing default proxy to " + proxy);
+ if ((proxy == null && mGlobalProxy == null) || proxy.equals(mGlobalProxy)) return;
+ if (mGlobalProxy != null) return;
+ sendProxyBroadcast(proxy);
+ }
+
+ private void handleDeprecatedGlobalHttpProxy() {
+ String proxy = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.HTTP_PROXY);
+ if (!TextUtils.isEmpty(proxy)) {
+ String data[] = proxy.split(":");
+ String proxyHost = data[0];
+ int proxyPort = 8080;
+ if (data.length > 1) {
+ try {
+ proxyPort = Integer.parseInt(data[1]);
+ } catch (NumberFormatException e) {
+ return;
+ }
+ }
+ ProxyProperties p = new ProxyProperties(data[0], proxyPort, "");
+ setGlobalProxy(p);
+ }
+ }
+
+ private void sendProxyBroadcast(ProxyProperties proxy) {
+ if (proxy == null) proxy = new ProxyProperties("", 0, "");
+ log("sending Proxy Broadcast for " + proxy);
+ Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra(Proxy.EXTRA_PROXY_INFO, proxy);
+ mContext.sendStickyBroadcast(intent);
+ }
+
+ private static class SettingsObserver extends ContentObserver {
+ private int mWhat;
+ private Handler mHandler;
+ SettingsObserver(Handler handler, int what) {
+ super(handler);
+ mHandler = handler;
+ mWhat = what;
+ }
+
+ void observe(Context context) {
+ ContentResolver resolver = context.getContentResolver();
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.HTTP_PROXY), false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mHandler.obtainMessage(mWhat).sendToTarget();
+ }
+ }
+
+ private void log(String s) {
+ Slog.d(TAG, s);
+ }
+
+ private void loge(String s) {
+ Slog.e(TAG, s);
+ }
}
diff --git a/services/java/com/android/server/CountryDetectorService.java b/services/java/com/android/server/CountryDetectorService.java
new file mode 100644
index 0000000..3081ebe
--- /dev/null
+++ b/services/java/com/android/server/CountryDetectorService.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.server;
+
+import java.util.HashMap;
+
+import com.android.server.location.ComprehensiveCountryDetector;
+
+import android.content.Context;
+import android.location.Country;
+import android.location.CountryListener;
+import android.location.ICountryDetector;
+import android.location.ICountryListener;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * This class detects the country that the user is in through
+ * {@link ComprehensiveCountryDetector}.
+ *
+ * @hide
+ */
+public class CountryDetectorService extends ICountryDetector.Stub implements Runnable {
+
+ /**
+ * The class represents the remote listener, it will also removes itself
+ * from listener list when the remote process was died.
+ */
+ private final class Receiver implements IBinder.DeathRecipient {
+ private final ICountryListener mListener;
+ private final IBinder mKey;
+
+ public Receiver(ICountryListener listener) {
+ mListener = listener;
+ mKey = listener.asBinder();
+ }
+
+ public void binderDied() {
+ removeListener(mKey);
+ }
+
+ @Override
+ public boolean equals(Object otherObj) {
+ if (otherObj instanceof Receiver) {
+ return mKey.equals(((Receiver) otherObj).mKey);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mKey.hashCode();
+ }
+
+ public ICountryListener getListener() {
+ return mListener;
+ }
+ }
+
+ private final static String TAG = "CountryDetectorService";
+
+ private final HashMap<IBinder, Receiver> mReceivers;
+ private final Context mContext;
+ private ComprehensiveCountryDetector mCountryDetector;
+ private boolean mSystemReady;
+ private Handler mHandler;
+ private CountryListener mLocationBasedDetectorListener;
+
+ public CountryDetectorService(Context context) {
+ super();
+ mReceivers = new HashMap<IBinder, Receiver>();
+ mContext = context;
+ }
+
+ @Override
+ public Country detectCountry() throws RemoteException {
+ if (!mSystemReady) {
+ throw new RemoteException();
+ }
+ return mCountryDetector.detectCountry();
+ }
+
+ /**
+ * Add the ICountryListener into the listener list.
+ */
+ @Override
+ public void addCountryListener(ICountryListener listener) throws RemoteException {
+ if (!mSystemReady) {
+ throw new RemoteException();
+ }
+ addListener(listener);
+ }
+
+ /**
+ * Remove the ICountryListener from the listener list.
+ */
+ @Override
+ public void removeCountryListener(ICountryListener listener) throws RemoteException {
+ if (!mSystemReady) {
+ throw new RemoteException();
+ }
+ removeListener(listener.asBinder());
+ }
+
+ private void addListener(ICountryListener listener) {
+ synchronized (mReceivers) {
+ Receiver r = new Receiver(listener);
+ try {
+ listener.asBinder().linkToDeath(r, 0);
+ mReceivers.put(listener.asBinder(), r);
+ if (mReceivers.size() == 1) {
+ Slog.d(TAG, "The first listener is added");
+ setCountryListener(mLocationBasedDetectorListener);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "linkToDeath failed:", e);
+ }
+ }
+ }
+
+ private void removeListener(IBinder key) {
+ synchronized (mReceivers) {
+ mReceivers.remove(key);
+ if (mReceivers.isEmpty()) {
+ setCountryListener(null);
+ Slog.d(TAG, "No listener is left");
+ }
+ }
+ }
+
+
+ protected void notifyReceivers(Country country) {
+ synchronized(mReceivers) {
+ for (Receiver receiver : mReceivers.values()) {
+ try {
+ receiver.getListener().onCountryDetected(country);
+ } catch (RemoteException e) {
+ // TODO: Shall we remove the receiver?
+ Slog.e(TAG, "notifyReceivers failed:", e);
+ }
+ }
+ }
+ }
+
+ void systemReady() {
+ // Shall we wait for the initialization finish.
+ Thread thread = new Thread(this, "CountryDetectorService");
+ thread.start();
+ }
+
+ private void initialize() {
+ mCountryDetector = new ComprehensiveCountryDetector(mContext);
+ mLocationBasedDetectorListener = new CountryListener() {
+ public void onCountryDetected(final Country country) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ notifyReceivers(country);
+ }
+ });
+ }
+ };
+ }
+
+ public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ Looper.prepare();
+ mHandler = new Handler();
+ initialize();
+ mSystemReady = true;
+ Looper.loop();
+ }
+
+ protected void setCountryListener(final CountryListener listener) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mCountryDetector.setCountryListener(listener);
+ }
+ });
+ }
+
+ // For testing
+ boolean isSystemReady() {
+ return mSystemReady;
+ }
+}
diff --git a/services/java/com/android/server/DemoDataSet.java b/services/java/com/android/server/DemoDataSet.java
deleted file mode 100644
index 277985f..0000000
--- a/services/java/com/android/server/DemoDataSet.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.AssetManager;
-import android.net.Uri;
-import android.os.Environment;
-import android.provider.Contacts;
-import android.provider.Settings;
-import android.provider.MediaStore.Images;
-import android.util.Config;
-import android.util.Slog;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-public class DemoDataSet
-{
- private final static String LOG_TAG = "DemoDataSet";
-
- private ContentResolver mContentResolver;
-
- public final void add(Context context)
- {
- mContentResolver = context.getContentResolver();
-
- // Remove all the old data
- mContentResolver.delete(Contacts.People.CONTENT_URI, null, null);
-
- // Add the new data
- addDefaultData();
-
- // Add images from /android/images
- addDefaultImages();
- }
-
- private final void addDefaultImages()
- {
- File rootDirectory = Environment.getRootDirectory();
- String [] files
- = new File(rootDirectory, "images").list();
- int count = files.length;
-
- if (count == 0) {
- Slog.i(LOG_TAG, "addDefaultImages: no images found!");
- return;
- }
-
- for (int i = 0; i < count; i++)
- {
- String name = files[i];
- String path = rootDirectory + "/" + name;
-
- try {
- Images.Media.insertImage(mContentResolver, path, name, null);
- } catch (FileNotFoundException e) {
- Slog.e(LOG_TAG, "Failed to import image " + path, e);
- }
- }
- }
-
- private final void addDefaultData()
- {
- Slog.i(LOG_TAG, "Adding default data...");
-
-// addImage("Violet", "images/violet.png");
-// addImage("Corky", "images/corky.png");
-
- // PENDING: should this be done here?!?!
- Intent intent = new Intent(
- Intent.ACTION_CALL, Uri.fromParts("voicemail", "", null));
- addShortcut("1", intent);
- }
-
- private final Uri addImage(String name, Uri file)
- {
- ContentValues imagev = new ContentValues();
- imagev.put("name", name);
-
- Uri url = null;
-
- AssetManager ass = AssetManager.getSystem();
- InputStream in = null;
- OutputStream out = null;
-
- try
- {
- in = ass.open(file.toString());
-
- url = mContentResolver.insert(Images.Media.INTERNAL_CONTENT_URI, imagev);
- out = mContentResolver.openOutputStream(url);
-
- final int size = 8 * 1024;
- byte[] buf = new byte[size];
-
- int count = 0;
- do
- {
- count = in.read(buf, 0, size);
- if (count > 0) {
- out.write(buf, 0, count);
- }
- } while (count > 0);
- }
- catch (Exception e)
- {
- Slog.e(LOG_TAG, "Failed to insert image '" + file + "'", e);
- url = null;
- }
-
- return url;
- }
-
- private final Uri addShortcut(String shortcut, Intent intent)
- {
- if (Config.LOGV) Slog.v(LOG_TAG, "addShortcut: shortcut=" + shortcut + ", intent=" + intent);
- return Settings.Bookmarks.add(mContentResolver, intent, null, null,
- shortcut != null ? shortcut.charAt(0) : 0, 0);
- }
-}
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 1538003..b2d534b 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -28,18 +28,23 @@ import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.app.Activity;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.app.admin.IDevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.os.IPowerManager;
import android.os.PowerManager;
@@ -48,9 +53,11 @@ import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.util.Slog;
+import android.os.SystemProperties;
+import android.provider.Settings;
import android.util.PrintWriterPrinter;
import android.util.Printer;
+import android.util.Slog;
import android.util.Xml;
import android.view.WindowManagerPolicy;
@@ -61,47 +68,97 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
+import java.text.DateFormat;
import java.util.ArrayList;
+import java.util.Date;
import java.util.HashMap;
import java.util.List;
+import java.util.Set;
/**
* Implementation of the device policy APIs.
*/
public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
+ private static final int REQUEST_EXPIRE_PASSWORD = 5571;
+
static final String TAG = "DevicePolicyManagerService";
-
+
+ private static final long EXPIRATION_GRACE_PERIOD_MS = 5 * 86400 * 1000; // 5 days, in ms
+
+ protected static final String ACTION_EXPIRED_PASSWORD_NOTIFICATION
+ = "com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION";
+
+ private static final long MS_PER_DAY = 86400 * 1000;
+
final Context mContext;
final MyPackageMonitor mMonitor;
final PowerManager.WakeLock mWakeLock;
IPowerManager mIPowerManager;
-
+
int mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
int mActivePasswordLength = 0;
+ int mActivePasswordUpperCase = 0;
+ int mActivePasswordLowerCase = 0;
+ int mActivePasswordLetters = 0;
+ int mActivePasswordNumeric = 0;
+ int mActivePasswordSymbols = 0;
+ int mActivePasswordNonLetter = 0;
int mFailedPasswordAttempts = 0;
-
+
int mPasswordOwner = -1;
-
+ Handler mHandler = new Handler();
+
final HashMap<ComponentName, ActiveAdmin> mAdminMap
= new HashMap<ComponentName, ActiveAdmin>();
final ArrayList<ActiveAdmin> mAdminList
= new ArrayList<ActiveAdmin>();
-
+
+ BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_BOOT_COMPLETED.equals(action)
+ || ACTION_EXPIRED_PASSWORD_NOTIFICATION.equals(action)) {
+ Slog.v(TAG, "Sending password expiration notifications for action " + action);
+ mHandler.post(new Runnable() {
+ public void run() {
+ handlePasswordExpirationNotification();
+ }
+ });
+ }
+ }
+ };
+
static class ActiveAdmin {
final DeviceAdminInfo info;
-
+
int passwordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
int minimumPasswordLength = 0;
+ int passwordHistoryLength = 0;
+ int minimumPasswordUpperCase = 0;
+ int minimumPasswordLowerCase = 0;
+ int minimumPasswordLetters = 1;
+ int minimumPasswordNumeric = 1;
+ int minimumPasswordSymbols = 1;
+ int minimumPasswordNonLetter = 0;
long maximumTimeToUnlock = 0;
int maximumFailedPasswordsForWipe = 0;
-
+ long passwordExpirationTimeout = 0L;
+ long passwordExpirationDate = 0L;
+ boolean encryptionRequested = false;
+
+ // TODO: review implementation decisions with frameworks team
+ boolean specifiesGlobalProxy = false;
+ String globalProxySpec = null;
+ String globalProxyExclusionList = null;
+
ActiveAdmin(DeviceAdminInfo _info) {
info = _info;
}
-
+
int getUid() { return info.getActivityInfo().applicationInfo.uid; }
-
+
void writeToXml(XmlSerializer out)
throws IllegalArgumentException, IllegalStateException, IOException {
out.startTag(null, "policies");
@@ -114,10 +171,45 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (minimumPasswordLength > 0) {
out.startTag(null, "min-password-length");
out.attribute(null, "value", Integer.toString(minimumPasswordLength));
- out.endTag(null, "mn-password-length");
+ out.endTag(null, "min-password-length");
+ }
+ if(passwordHistoryLength > 0) {
+ out.startTag(null, "password-history-length");
+ out.attribute(null, "value", Integer.toString(passwordHistoryLength));
+ out.endTag(null, "password-history-length");
+ }
+ if (minimumPasswordUpperCase > 0) {
+ out.startTag(null, "min-password-uppercase");
+ out.attribute(null, "value", Integer.toString(minimumPasswordUpperCase));
+ out.endTag(null, "min-password-uppercase");
+ }
+ if (minimumPasswordLowerCase > 0) {
+ out.startTag(null, "min-password-lowercase");
+ out.attribute(null, "value", Integer.toString(minimumPasswordLowerCase));
+ out.endTag(null, "min-password-lowercase");
+ }
+ if (minimumPasswordLetters > 0) {
+ out.startTag(null, "min-password-letters");
+ out.attribute(null, "value", Integer.toString(minimumPasswordLetters));
+ out.endTag(null, "min-password-letters");
+ }
+ if (minimumPasswordNumeric > 0) {
+ out.startTag(null, "min-password-numeric");
+ out.attribute(null, "value", Integer.toString(minimumPasswordNumeric));
+ out.endTag(null, "min-password-numeric");
+ }
+ if (minimumPasswordSymbols > 0) {
+ out.startTag(null, "min-password-symbols");
+ out.attribute(null, "value", Integer.toString(minimumPasswordSymbols));
+ out.endTag(null, "min-password-symbols");
+ }
+ if (minimumPasswordNonLetter > 0) {
+ out.startTag(null, "min-password-nonletter");
+ out.attribute(null, "value", Integer.toString(minimumPasswordNonLetter));
+ out.endTag(null, "min-password-nonletter");
}
}
- if (maximumTimeToUnlock != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+ if (maximumTimeToUnlock != 0) {
out.startTag(null, "max-time-to-unlock");
out.attribute(null, "value", Long.toString(maximumTimeToUnlock));
out.endTag(null, "max-time-to-unlock");
@@ -127,8 +219,38 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
out.attribute(null, "value", Integer.toString(maximumFailedPasswordsForWipe));
out.endTag(null, "max-failed-password-wipe");
}
+ if (specifiesGlobalProxy) {
+ out.startTag(null, "specifies-global-proxy");
+ out.attribute(null, "value", Boolean.toString(specifiesGlobalProxy));
+ out.endTag(null, "specifies_global_proxy");
+ if (globalProxySpec != null) {
+ out.startTag(null, "global-proxy-spec");
+ out.attribute(null, "value", globalProxySpec);
+ out.endTag(null, "global-proxy-spec");
+ }
+ if (globalProxyExclusionList != null) {
+ out.startTag(null, "global-proxy-exclusion-list");
+ out.attribute(null, "value", globalProxyExclusionList);
+ out.endTag(null, "global-proxy-exclusion-list");
+ }
+ }
+ if (passwordExpirationTimeout != 0L) {
+ out.startTag(null, "password-expiration-timeout");
+ out.attribute(null, "value", Long.toString(passwordExpirationTimeout));
+ out.endTag(null, "password-expiration-timeout");
+ }
+ if (passwordExpirationDate != 0L) {
+ out.startTag(null, "password-expiration-date");
+ out.attribute(null, "value", Long.toString(passwordExpirationDate));
+ out.endTag(null, "password-expiration-date");
+ }
+ if (encryptionRequested) {
+ out.startTag(null, "encryption-requested");
+ out.attribute(null, "value", Boolean.toString(encryptionRequested));
+ out.endTag(null, "encryption-requested");
+ }
}
-
+
void readFromXml(XmlPullParser parser)
throws XmlPullParserException, IOException {
int outerDepth = parser.getDepth();
@@ -147,19 +269,58 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
} else if ("min-password-length".equals(tag)) {
minimumPasswordLength = Integer.parseInt(
parser.getAttributeValue(null, "value"));
+ } else if ("password-history-length".equals(tag)) {
+ passwordHistoryLength = Integer.parseInt(
+ parser.getAttributeValue(null, "value"));
+ } else if ("min-password-uppercase".equals(tag)) {
+ minimumPasswordUpperCase = Integer.parseInt(
+ parser.getAttributeValue(null, "value"));
+ } else if ("min-password-lowercase".equals(tag)) {
+ minimumPasswordLowerCase = Integer.parseInt(
+ parser.getAttributeValue(null, "value"));
+ } else if ("min-password-letters".equals(tag)) {
+ minimumPasswordLetters = Integer.parseInt(
+ parser.getAttributeValue(null, "value"));
+ } else if ("min-password-numeric".equals(tag)) {
+ minimumPasswordNumeric = Integer.parseInt(
+ parser.getAttributeValue(null, "value"));
+ } else if ("min-password-symbols".equals(tag)) {
+ minimumPasswordSymbols = Integer.parseInt(
+ parser.getAttributeValue(null, "value"));
+ } else if ("min-password-nonletter".equals(tag)) {
+ minimumPasswordNonLetter = Integer.parseInt(
+ parser.getAttributeValue(null, "value"));
} else if ("max-time-to-unlock".equals(tag)) {
maximumTimeToUnlock = Long.parseLong(
parser.getAttributeValue(null, "value"));
} else if ("max-failed-password-wipe".equals(tag)) {
maximumFailedPasswordsForWipe = Integer.parseInt(
parser.getAttributeValue(null, "value"));
+ } else if ("specifies-global-proxy".equals(tag)) {
+ specifiesGlobalProxy = Boolean.parseBoolean(
+ parser.getAttributeValue(null, "value"));
+ } else if ("global-proxy-spec".equals(tag)) {
+ globalProxySpec =
+ parser.getAttributeValue(null, "value");
+ } else if ("global-proxy-exclusion-list".equals(tag)) {
+ globalProxyExclusionList =
+ parser.getAttributeValue(null, "value");
+ } else if ("password-expiration-timeout".equals(tag)) {
+ passwordExpirationTimeout = Long.parseLong(
+ parser.getAttributeValue(null, "value"));
+ } else if ("password-expiration-date".equals(tag)) {
+ passwordExpirationDate = Long.parseLong(
+ parser.getAttributeValue(null, "value"));
+ } else if ("encryption-requested".equals(tag)) {
+ encryptionRequested = Boolean.parseBoolean(
+ parser.getAttributeValue(null, "value"));
} else {
Slog.w(TAG, "Unknown admin tag: " + tag);
}
XmlUtils.skipCurrentTag(parser);
}
}
-
+
void dump(String prefix, PrintWriter pw) {
pw.print(prefix); pw.print("uid="); pw.println(getUid());
pw.print(prefix); pw.println("policies:");
@@ -170,23 +331,54 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
pw.print(prefix); pw.print("passwordQuality=0x");
- pw.print(Integer.toHexString(passwordQuality));
- pw.print(" minimumPasswordLength=");
+ pw.println(Integer.toHexString(passwordQuality));
+ pw.print(prefix); pw.print("minimumPasswordLength=");
pw.println(minimumPasswordLength);
+ pw.print(prefix); pw.print("passwordHistoryLength=");
+ pw.println(passwordHistoryLength);
+ pw.print(prefix); pw.print("minimumPasswordUpperCase=");
+ pw.println(minimumPasswordUpperCase);
+ pw.print(prefix); pw.print("minimumPasswordLowerCase=");
+ pw.println(minimumPasswordLowerCase);
+ pw.print(prefix); pw.print("minimumPasswordLetters=");
+ pw.println(minimumPasswordLetters);
+ pw.print(prefix); pw.print("minimumPasswordNumeric=");
+ pw.println(minimumPasswordNumeric);
+ pw.print(prefix); pw.print("minimumPasswordSymbols=");
+ pw.println(minimumPasswordSymbols);
+ pw.print(prefix); pw.print("minimumPasswordNonLetter=");
+ pw.println(minimumPasswordNonLetter);
pw.print(prefix); pw.print("maximumTimeToUnlock=");
pw.println(maximumTimeToUnlock);
pw.print(prefix); pw.print("maximumFailedPasswordsForWipe=");
pw.println(maximumFailedPasswordsForWipe);
+ pw.print(prefix); pw.print("specifiesGlobalProxy=");
+ pw.println(specifiesGlobalProxy);
+ pw.print(prefix); pw.print("passwordExpirationTimeout=");
+ pw.println(passwordExpirationTimeout);
+ pw.print(prefix); pw.print("passwordExpirationDate=");
+ pw.println(passwordExpirationDate);
+ if (globalProxySpec != null) {
+ pw.print(prefix); pw.print("globalProxySpec=");
+ pw.println(globalProxySpec);
+ }
+ if (globalProxyExclusionList != null) {
+ pw.print(prefix); pw.print("globalProxyEclusionList=");
+ pw.println(globalProxyExclusionList);
+ }
+ pw.print(prefix); pw.print("encryptionRequested=");
+ pw.println(encryptionRequested);
}
}
-
+
class MyPackageMonitor extends PackageMonitor {
+ @Override
public void onSomePackagesChanged() {
synchronized (DevicePolicyManagerService.this) {
boolean removed = false;
for (int i=mAdminList.size()-1; i>=0; i--) {
ActiveAdmin aa = mAdminList.get(i);
- int change = isPackageDisappearing(aa.info.getPackageName());
+ int change = isPackageDisappearing(aa.info.getPackageName());
if (change == PACKAGE_PERMANENT_CHANGE
|| change == PACKAGE_TEMPORARY_CHANGE) {
Slog.w(TAG, "Admin unexpectedly uninstalled: "
@@ -211,7 +403,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
/**
* Instantiates the service.
*/
@@ -221,6 +413,50 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
mMonitor.register(context, true);
mWakeLock = ((PowerManager)context.getSystemService(Context.POWER_SERVICE))
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "DPM");
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_BOOT_COMPLETED);
+ filter.addAction(ACTION_EXPIRED_PASSWORD_NOTIFICATION);
+ context.registerReceiver(mReceiver, filter);
+ }
+
+ /**
+ * Set an alarm for an upcoming event - expiration warning, expiration, or post-expiration
+ * reminders. Clears alarm if no expirations are configured.
+ */
+ protected void setExpirationAlarmCheckLocked(Context context) {
+ final long expiration = getPasswordExpirationLocked(null);
+ final long now = System.currentTimeMillis();
+ final long timeToExpire = expiration - now;
+ final long alarmTime;
+ if (expiration == 0) {
+ // No expirations are currently configured: Cancel alarm.
+ alarmTime = 0;
+ } else if (timeToExpire <= 0) {
+ // The password has already expired: Repeat every 24 hours.
+ alarmTime = now + MS_PER_DAY;
+ } else {
+ // Selecting the next alarm time: Roll forward to the next 24 hour multiple before
+ // the expiration time.
+ long alarmInterval = timeToExpire % MS_PER_DAY;
+ if (alarmInterval == 0) {
+ alarmInterval = MS_PER_DAY;
+ }
+ alarmTime = now + alarmInterval;
+ }
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ PendingIntent pi = PendingIntent.getBroadcast(context, REQUEST_EXPIRE_PASSWORD,
+ new Intent(ACTION_EXPIRED_PASSWORD_NOTIFICATION),
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
+ am.cancel(pi);
+ if (alarmTime != 0) {
+ am.set(AlarmManager.RTC, alarmTime, pi);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
private IPowerManager getIPowerManager() {
@@ -230,7 +466,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
return mIPowerManager;
}
-
+
ActiveAdmin getActiveAdminUncheckedLocked(ComponentName who) {
ActiveAdmin admin = mAdminMap.get(who);
if (admin != null
@@ -240,7 +476,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
return null;
}
-
+
ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy)
throws SecurityException {
final int callingUid = Binder.getCallingUid();
@@ -271,13 +507,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
+ Binder.getCallingUid() + " for policy #" + reqPolicy);
}
}
-
+
void sendAdminCommandLocked(ActiveAdmin admin, String action) {
Intent intent = new Intent(action);
intent.setComponent(admin.info.getComponent());
+ if (action.equals(DeviceAdminReceiver.ACTION_PASSWORD_EXPIRING)) {
+ intent.putExtra("expiration", admin.passwordExpirationDate);
+ }
mContext.sendBroadcast(intent);
}
-
+
void sendAdminCommandLocked(String action, int reqPolicy) {
final int N = mAdminList.size();
if (N > 0) {
@@ -289,19 +528,24 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
void removeActiveAdminLocked(ComponentName adminReceiver) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver);
if (admin != null) {
+ boolean doProxyCleanup =
+ admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_SETS_GLOBAL_PROXY);
sendAdminCommandLocked(admin,
DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED);
// XXX need to wait for it to complete.
mAdminList.remove(admin);
mAdminMap.remove(adminReceiver);
validatePasswordOwnerLocked();
+ if (doProxyCleanup) {
+ resetGlobalProxy();
+ }
}
}
-
+
public DeviceAdminInfo findAdmin(ComponentName adminName) {
Intent resolveIntent = new Intent();
resolveIntent.setComponent(adminName);
@@ -310,7 +554,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (infos == null || infos.size() <= 0) {
throw new IllegalArgumentException("Unknown admin: " + adminName);
}
-
+
try {
return new DeviceAdminInfo(mContext, infos.get(0));
} catch (XmlPullParserException e) {
@@ -321,7 +565,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return null;
}
}
-
+
private static JournaledFile makeJournaledFile() {
final String base = "/data/system/device_policies.xml";
return new JournaledFile(new File(base), new File(base + ".tmp"));
@@ -337,7 +581,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
out.startDocument(null, true);
out.startTag(null, "policies");
-
+
final int N = mAdminList.size();
for (int i=0; i<N; i++) {
ActiveAdmin ap = mAdminList.get(i);
@@ -348,26 +592,36 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
out.endTag(null, "admin");
}
}
-
+
if (mPasswordOwner >= 0) {
out.startTag(null, "password-owner");
out.attribute(null, "value", Integer.toString(mPasswordOwner));
out.endTag(null, "password-owner");
}
-
+
if (mFailedPasswordAttempts != 0) {
out.startTag(null, "failed-password-attempts");
out.attribute(null, "value", Integer.toString(mFailedPasswordAttempts));
out.endTag(null, "failed-password-attempts");
}
-
- if (mActivePasswordQuality != 0 || mActivePasswordLength != 0) {
+
+ if (mActivePasswordQuality != 0 || mActivePasswordLength != 0
+ || mActivePasswordUpperCase != 0 || mActivePasswordLowerCase != 0
+ || mActivePasswordLetters != 0 || mActivePasswordNumeric != 0
+ || mActivePasswordSymbols != 0 || mActivePasswordNonLetter != 0) {
out.startTag(null, "active-password");
out.attribute(null, "quality", Integer.toString(mActivePasswordQuality));
out.attribute(null, "length", Integer.toString(mActivePasswordLength));
+ out.attribute(null, "uppercase", Integer.toString(mActivePasswordUpperCase));
+ out.attribute(null, "lowercase", Integer.toString(mActivePasswordLowerCase));
+ out.attribute(null, "letters", Integer.toString(mActivePasswordLetters));
+ out.attribute(null, "numeric", Integer
+ .toString(mActivePasswordNumeric));
+ out.attribute(null, "symbols", Integer.toString(mActivePasswordSymbols));
+ out.attribute(null, "nonletter", Integer.toString(mActivePasswordNonLetter));
out.endTag(null, "active-password");
}
-
+
out.endTag(null, "policies");
out.endDocument();
@@ -445,6 +699,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
parser.getAttributeValue(null, "quality"));
mActivePasswordLength = Integer.parseInt(
parser.getAttributeValue(null, "length"));
+ mActivePasswordUpperCase = Integer.parseInt(
+ parser.getAttributeValue(null, "uppercase"));
+ mActivePasswordLowerCase = Integer.parseInt(
+ parser.getAttributeValue(null, "lowercase"));
+ mActivePasswordLetters = Integer.parseInt(
+ parser.getAttributeValue(null, "letters"));
+ mActivePasswordNumeric = Integer.parseInt(
+ parser.getAttributeValue(null, "numeric"));
+ mActivePasswordSymbols = Integer.parseInt(
+ parser.getAttributeValue(null, "symbols"));
+ mActivePasswordNonLetter = Integer.parseInt(
+ parser.getAttributeValue(null, "nonletter"));
XmlUtils.skipCurrentTag(parser);
} else {
Slog.w(TAG, "Unknown tag: " + tag);
@@ -484,10 +750,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
+ Integer.toHexString(utils.getActivePasswordQuality()));
mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
mActivePasswordLength = 0;
+ mActivePasswordUpperCase = 0;
+ mActivePasswordLowerCase = 0;
+ mActivePasswordLetters = 0;
+ mActivePasswordNumeric = 0;
+ mActivePasswordSymbols = 0;
+ mActivePasswordNonLetter = 0;
}
-
+
validatePasswordOwnerLocked();
-
+
long timeMs = getMaximumTimeToLock(null);
if (timeMs <= 0) {
timeMs = Integer.MAX_VALUE;
@@ -506,12 +778,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
+ case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
return;
}
throw new IllegalArgumentException("Invalid quality constant: 0x"
+ Integer.toHexString(quality));
}
-
+
void validatePasswordOwnerLocked() {
if (mPasswordOwner >= 0) {
boolean haveOwner = false;
@@ -528,17 +801,41 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
public void systemReady() {
synchronized (this) {
loadSettingsLocked();
}
}
-
- public void setActiveAdmin(ComponentName adminReceiver) {
+
+ private void handlePasswordExpirationNotification() {
+ synchronized (this) {
+ final long now = System.currentTimeMillis();
+ final int N = mAdminList.size();
+ if (N <= 0) {
+ return;
+ }
+ for (int i=0; i < N; i++) {
+ ActiveAdmin admin = mAdminList.get(i);
+ if (admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)
+ && admin.passwordExpirationTimeout > 0L
+ && admin.passwordExpirationDate > 0L
+ && now >= admin.passwordExpirationDate - EXPIRATION_GRACE_PERIOD_MS) {
+ sendAdminCommandLocked(admin, DeviceAdminReceiver.ACTION_PASSWORD_EXPIRING);
+ }
+ }
+ setExpirationAlarmCheckLocked(mContext);
+ }
+ }
+
+ /**
+ * @param adminReceiver The admin to add
+ * @param refreshing true = update an active admin, no error
+ */
+ public void setActiveAdmin(ComponentName adminReceiver, boolean refreshing) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-
+
DeviceAdminInfo info = findAdmin(adminReceiver);
if (info == null) {
throw new IllegalArgumentException("Bad admin: " + adminReceiver);
@@ -546,27 +843,51 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
- if (getActiveAdminUncheckedLocked(adminReceiver) != null) {
+ if (!refreshing && getActiveAdminUncheckedLocked(adminReceiver) != null) {
throw new IllegalArgumentException("Admin is already added");
}
- ActiveAdmin admin = new ActiveAdmin(info);
- mAdminMap.put(adminReceiver, admin);
- mAdminList.add(admin);
+ ActiveAdmin newAdmin = new ActiveAdmin(info);
+ mAdminMap.put(adminReceiver, newAdmin);
+ int replaceIndex = -1;
+ if (refreshing) {
+ final int N = mAdminList.size();
+ for (int i=0; i < N; i++) {
+ ActiveAdmin oldAdmin = mAdminList.get(i);
+ if (oldAdmin.info.getComponent().equals(adminReceiver)) {
+ replaceIndex = i;
+ break;
+ }
+ }
+ }
+ if (replaceIndex == -1) {
+ mAdminList.add(newAdmin);
+ } else {
+ mAdminList.set(replaceIndex, newAdmin);
+ }
saveSettingsLocked();
- sendAdminCommandLocked(admin,
- DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED);
+ sendAdminCommandLocked(newAdmin, DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
-
+
public boolean isAdminActive(ComponentName adminReceiver) {
synchronized (this) {
return getActiveAdminUncheckedLocked(adminReceiver) != null;
}
}
-
+
+ public boolean hasGrantedPolicy(ComponentName adminReceiver, int policyId) {
+ synchronized (this) {
+ ActiveAdmin administrator = getActiveAdminUncheckedLocked(adminReceiver);
+ if (administrator == null) {
+ throw new SecurityException("No active admin " + adminReceiver);
+ }
+ return administrator.info.usesPolicy(policyId);
+ }
+ }
+
public List<ComponentName> getActiveAdmins() {
synchronized (this) {
final int N = mAdminList.size();
@@ -580,7 +901,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return res;
}
}
-
+
public boolean packageHasActiveAdmins(String packageName) {
synchronized (this) {
final int N = mAdminList.size();
@@ -592,7 +913,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return false;
}
}
-
+
public void removeActiveAdmin(ComponentName adminReceiver) {
synchronized (this) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver);
@@ -611,10 +932,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
public void setPasswordQuality(ComponentName who, int quality) {
validateQualityConstant(quality);
-
+
synchronized (this) {
if (who == null) {
throw new NullPointerException("ComponentName is null");
@@ -627,16 +948,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
public int getPasswordQuality(ComponentName who) {
synchronized (this) {
int mode = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
+
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
return admin != null ? admin.passwordQuality : mode;
}
-
+
final int N = mAdminList.size();
for (int i=0; i<N; i++) {
ActiveAdmin admin = mAdminList.get(i);
@@ -647,7 +968,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return mode;
}
}
-
+
public void setPasswordMinimumLength(ComponentName who, int length) {
synchronized (this) {
if (who == null) {
@@ -661,16 +982,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
public int getPasswordMinimumLength(ComponentName who) {
synchronized (this) {
int length = 0;
-
+
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
return admin != null ? admin.minimumPasswordLength : length;
}
-
+
final int N = mAdminList.size();
for (int i=0; i<N; i++) {
ActiveAdmin admin = mAdminList.get(i);
@@ -681,18 +1002,343 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return length;
}
}
-
+
+ public void setPasswordHistoryLength(ComponentName who, int length) {
+ synchronized (this) {
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ if (ap.passwordHistoryLength != length) {
+ ap.passwordHistoryLength = length;
+ saveSettingsLocked();
+ }
+ }
+ }
+
+ public int getPasswordHistoryLength(ComponentName who) {
+ synchronized (this) {
+ int length = 0;
+
+ if (who != null) {
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+ return admin != null ? admin.passwordHistoryLength : length;
+ }
+
+ final int N = mAdminList.size();
+ for (int i = 0; i < N; i++) {
+ ActiveAdmin admin = mAdminList.get(i);
+ if (length < admin.passwordHistoryLength) {
+ length = admin.passwordHistoryLength;
+ }
+ }
+ return length;
+ }
+ }
+
+ public void setPasswordExpirationTimeout(ComponentName who, long timeout) {
+ synchronized (this) {
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+ if (timeout < 0) {
+ throw new IllegalArgumentException("Timeout must be >= 0 ms");
+ }
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD);
+ // Calling this API automatically bumps the expiration date
+ final long expiration = timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L;
+ ap.passwordExpirationDate = expiration;
+ ap.passwordExpirationTimeout = timeout;
+ if (timeout > 0L) {
+ Slog.w(TAG, "setPasswordExpiration(): password will expire on "
+ + DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT)
+ .format(new Date(expiration)));
+ }
+ saveSettingsLocked();
+ setExpirationAlarmCheckLocked(mContext); // in case this is the first one
+ }
+ }
+
+ /**
+ * Return a single admin's expiration cycle time, or the min of all cycle times.
+ * Returns 0 if not configured.
+ */
+ public long getPasswordExpirationTimeout(ComponentName who) {
+ synchronized (this) {
+ if (who != null) {
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+ return admin != null ? admin.passwordExpirationTimeout : 0L;
+ }
+
+ long timeout = 0L;
+ final int N = mAdminList.size();
+ for (int i = 0; i < N; i++) {
+ ActiveAdmin admin = mAdminList.get(i);
+ if (timeout == 0L || (admin.passwordExpirationTimeout != 0L
+ && timeout > admin.passwordExpirationTimeout)) {
+ timeout = admin.passwordExpirationTimeout;
+ }
+ }
+ return timeout;
+ }
+ }
+
+ /**
+ * Return a single admin's expiration date/time, or the min (soonest) for all admins.
+ * Returns 0 if not configured.
+ */
+ private long getPasswordExpirationLocked(ComponentName who) {
+ if (who != null) {
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+ return admin != null ? admin.passwordExpirationDate : 0L;
+ }
+
+ long timeout = 0L;
+ final int N = mAdminList.size();
+ for (int i = 0; i < N; i++) {
+ ActiveAdmin admin = mAdminList.get(i);
+ if (timeout == 0L || (admin.passwordExpirationDate != 0
+ && timeout > admin.passwordExpirationDate)) {
+ timeout = admin.passwordExpirationDate;
+ }
+ }
+ return timeout;
+ }
+
+ public long getPasswordExpiration(ComponentName who) {
+ synchronized (this) {
+ return getPasswordExpirationLocked(who);
+ }
+ }
+
+ public void setPasswordMinimumUpperCase(ComponentName who, int length) {
+ synchronized (this) {
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ if (ap.minimumPasswordUpperCase != length) {
+ ap.minimumPasswordUpperCase = length;
+ saveSettingsLocked();
+ }
+ }
+ }
+
+ public int getPasswordMinimumUpperCase(ComponentName who) {
+ synchronized (this) {
+ int length = 0;
+
+ if (who != null) {
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+ return admin != null ? admin.minimumPasswordUpperCase : length;
+ }
+
+ final int N = mAdminList.size();
+ for (int i=0; i<N; i++) {
+ ActiveAdmin admin = mAdminList.get(i);
+ if (length < admin.minimumPasswordUpperCase) {
+ length = admin.minimumPasswordUpperCase;
+ }
+ }
+ return length;
+ }
+ }
+
+ public void setPasswordMinimumLowerCase(ComponentName who, int length) {
+ synchronized (this) {
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ if (ap.minimumPasswordLowerCase != length) {
+ ap.minimumPasswordLowerCase = length;
+ saveSettingsLocked();
+ }
+ }
+ }
+
+ public int getPasswordMinimumLowerCase(ComponentName who) {
+ synchronized (this) {
+ int length = 0;
+
+ if (who != null) {
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+ return admin != null ? admin.minimumPasswordLowerCase : length;
+ }
+
+ final int N = mAdminList.size();
+ for (int i=0; i<N; i++) {
+ ActiveAdmin admin = mAdminList.get(i);
+ if (length < admin.minimumPasswordLowerCase) {
+ length = admin.minimumPasswordLowerCase;
+ }
+ }
+ return length;
+ }
+ }
+
+ public void setPasswordMinimumLetters(ComponentName who, int length) {
+ synchronized (this) {
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ if (ap.minimumPasswordLetters != length) {
+ ap.minimumPasswordLetters = length;
+ saveSettingsLocked();
+ }
+ }
+ }
+
+ public int getPasswordMinimumLetters(ComponentName who) {
+ synchronized (this) {
+ int length = 0;
+
+ if (who != null) {
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+ return admin != null ? admin.minimumPasswordLetters : length;
+ }
+
+ final int N = mAdminList.size();
+ for (int i=0; i<N; i++) {
+ ActiveAdmin admin = mAdminList.get(i);
+ if (length < admin.minimumPasswordLetters) {
+ length = admin.minimumPasswordLetters;
+ }
+ }
+ return length;
+ }
+ }
+
+ public void setPasswordMinimumNumeric(ComponentName who, int length) {
+ synchronized (this) {
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ if (ap.minimumPasswordNumeric != length) {
+ ap.minimumPasswordNumeric = length;
+ saveSettingsLocked();
+ }
+ }
+ }
+
+ public int getPasswordMinimumNumeric(ComponentName who) {
+ synchronized (this) {
+ int length = 0;
+
+ if (who != null) {
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+ return admin != null ? admin.minimumPasswordNumeric : length;
+ }
+
+ final int N = mAdminList.size();
+ for (int i = 0; i < N; i++) {
+ ActiveAdmin admin = mAdminList.get(i);
+ if (length < admin.minimumPasswordNumeric) {
+ length = admin.minimumPasswordNumeric;
+ }
+ }
+ return length;
+ }
+ }
+
+ public void setPasswordMinimumSymbols(ComponentName who, int length) {
+ synchronized (this) {
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ if (ap.minimumPasswordSymbols != length) {
+ ap.minimumPasswordSymbols = length;
+ saveSettingsLocked();
+ }
+ }
+ }
+
+ public int getPasswordMinimumSymbols(ComponentName who) {
+ synchronized (this) {
+ int length = 0;
+
+ if (who != null) {
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+ return admin != null ? admin.minimumPasswordSymbols : length;
+ }
+
+ final int N = mAdminList.size();
+ for (int i=0; i<N; i++) {
+ ActiveAdmin admin = mAdminList.get(i);
+ if (length < admin.minimumPasswordSymbols) {
+ length = admin.minimumPasswordSymbols;
+ }
+ }
+ return length;
+ }
+ }
+
+ public void setPasswordMinimumNonLetter(ComponentName who, int length) {
+ synchronized (this) {
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
+ if (ap.minimumPasswordNonLetter != length) {
+ ap.minimumPasswordNonLetter = length;
+ saveSettingsLocked();
+ }
+ }
+ }
+
+ public int getPasswordMinimumNonLetter(ComponentName who) {
+ synchronized (this) {
+ int length = 0;
+
+ if (who != null) {
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
+ return admin != null ? admin.minimumPasswordNonLetter : length;
+ }
+
+ final int N = mAdminList.size();
+ for (int i=0; i<N; i++) {
+ ActiveAdmin admin = mAdminList.get(i);
+ if (length < admin.minimumPasswordNonLetter) {
+ length = admin.minimumPasswordNonLetter;
+ }
+ }
+ return length;
+ }
+ }
+
public boolean isActivePasswordSufficient() {
synchronized (this) {
// This API can only be called by an active device admin,
// so try to retrieve it to check that the caller is one.
getActiveAdminForCallerLocked(null,
DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
- return mActivePasswordQuality >= getPasswordQuality(null)
- && mActivePasswordLength >= getPasswordMinimumLength(null);
+ if (mActivePasswordQuality < getPasswordQuality(null)
+ || mActivePasswordLength < getPasswordMinimumLength(null)) {
+ return false;
+ }
+ if(mActivePasswordQuality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) {
+ return true;
+ }
+ return mActivePasswordUpperCase >= getPasswordMinimumUpperCase(null)
+ && mActivePasswordLowerCase >= getPasswordMinimumLowerCase(null)
+ && mActivePasswordLetters >= getPasswordMinimumLetters(null)
+ && mActivePasswordNumeric >= getPasswordMinimumNumeric(null)
+ && mActivePasswordSymbols >= getPasswordMinimumSymbols(null)
+ && mActivePasswordNonLetter >= getPasswordMinimumNonLetter(null);
}
}
-
+
public int getCurrentFailedPasswordAttempts() {
synchronized (this) {
// This API can only be called by an active device admin,
@@ -702,7 +1348,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return mFailedPasswordAttempts;
}
}
-
+
public void setMaximumFailedPasswordsForWipe(ComponentName who, int num) {
synchronized (this) {
// This API can only be called by an active device admin,
@@ -717,16 +1363,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
public int getMaximumFailedPasswordsForWipe(ComponentName who) {
synchronized (this) {
int count = 0;
-
+
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
return admin != null ? admin.maximumFailedPasswordsForWipe : count;
}
-
+
final int N = mAdminList.size();
for (int i=0; i<N; i++) {
ActiveAdmin admin = mAdminList.get(i);
@@ -740,7 +1386,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return count;
}
}
-
+
public boolean resetPassword(String password, int flags) {
int quality;
synchronized (this) {
@@ -751,14 +1397,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
quality = getPasswordQuality(null);
if (quality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
int realQuality = LockPatternUtils.computePasswordQuality(password);
- if (realQuality < quality) {
+ if (realQuality < quality
+ && quality != DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) {
Slog.w(TAG, "resetPassword: password quality 0x"
+ Integer.toHexString(quality)
+ " does not meet required quality 0x"
+ Integer.toHexString(quality));
return false;
}
- quality = realQuality;
+ quality = Math.max(realQuality, quality);
}
int length = getPasswordMinimumLength(null);
if (password.length() < length) {
@@ -766,14 +1413,86 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
+ " does not meet required length " + length);
return false;
}
+ if (quality == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX) {
+ int letters = 0;
+ int uppercase = 0;
+ int lowercase = 0;
+ int numbers = 0;
+ int symbols = 0;
+ int nonletter = 0;
+ for (int i = 0; i < password.length(); i++) {
+ char c = password.charAt(i);
+ if (c >= 'A' && c <= 'Z') {
+ letters++;
+ uppercase++;
+ } else if (c >= 'a' && c <= 'z') {
+ letters++;
+ lowercase++;
+ } else if (c >= '0' && c <= '9') {
+ numbers++;
+ nonletter++;
+ } else {
+ symbols++;
+ nonletter++;
+ }
+ }
+ int neededLetters = getPasswordMinimumLetters(null);
+ if(letters < neededLetters) {
+ Slog.w(TAG, "resetPassword: number of letters " + letters
+ + " does not meet required number of letters " + neededLetters);
+ return false;
+ }
+ int neededNumbers = getPasswordMinimumNumeric(null);
+ if (numbers < neededNumbers) {
+ Slog
+ .w(TAG, "resetPassword: number of numerical digits " + numbers
+ + " does not meet required number of numerical digits "
+ + neededNumbers);
+ return false;
+ }
+ int neededLowerCase = getPasswordMinimumLowerCase(null);
+ if (lowercase < neededLowerCase) {
+ Slog.w(TAG, "resetPassword: number of lowercase letters " + lowercase
+ + " does not meet required number of lowercase letters "
+ + neededLowerCase);
+ return false;
+ }
+ int neededUpperCase = getPasswordMinimumUpperCase(null);
+ if (uppercase < neededUpperCase) {
+ Slog.w(TAG, "resetPassword: number of uppercase letters " + uppercase
+ + " does not meet required number of uppercase letters "
+ + neededUpperCase);
+ return false;
+ }
+ int neededSymbols = getPasswordMinimumSymbols(null);
+ if (symbols < neededSymbols) {
+ Slog.w(TAG, "resetPassword: number of special symbols " + symbols
+ + " does not meet required number of special symbols " + neededSymbols);
+ return false;
+ }
+ int neededNonLetter = getPasswordMinimumNonLetter(null);
+ if (nonletter < neededNonLetter) {
+ Slog.w(TAG, "resetPassword: number of non-letter characters " + nonletter
+ + " does not meet required number of non-letter characters "
+ + neededNonLetter);
+ return false;
+ }
+ }
+
+ LockPatternUtils utils = new LockPatternUtils(mContext);
+ if(utils.checkPasswordHistory(password)) {
+ Slog.w(TAG, "resetPassword: password is the same as one of the last "
+ + getPasswordHistoryLength(null) + " passwords");
+ return false;
+ }
}
-
+
int callingUid = Binder.getCallingUid();
if (mPasswordOwner >= 0 && mPasswordOwner != callingUid) {
Slog.w(TAG, "resetPassword: already set by another uid and not entered by user");
return false;
}
-
+
// Don't do this with the lock held, because it is going to call
// back in to the service.
long ident = Binder.clearCallingIdentity();
@@ -791,10 +1510,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
} finally {
Binder.restoreCallingIdentity(ident);
}
-
+
return true;
}
-
+
public void setMaximumTimeToLock(ComponentName who, long timeMs) {
synchronized (this) {
if (who == null) {
@@ -804,16 +1523,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
DeviceAdminInfo.USES_POLICY_FORCE_LOCK);
if (ap.maximumTimeToUnlock != timeMs) {
ap.maximumTimeToUnlock = timeMs;
-
+
long ident = Binder.clearCallingIdentity();
try {
saveSettingsLocked();
-
+
timeMs = getMaximumTimeToLock(null);
if (timeMs <= 0) {
timeMs = Integer.MAX_VALUE;
}
-
+
try {
getIPowerManager().setMaximumScreenOffTimeount((int)timeMs);
} catch (RemoteException e) {
@@ -825,16 +1544,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
public long getMaximumTimeToLock(ComponentName who) {
synchronized (this) {
long time = 0;
-
+
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who);
return admin != null ? admin.maximumTimeToUnlock : time;
}
-
+
final int N = mAdminList.size();
for (int i=0; i<N; i++) {
ActiveAdmin admin = mAdminList.get(i);
@@ -848,7 +1567,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return time;
}
}
-
+
public void lockNow() {
synchronized (this) {
// This API can only be called by an active device admin,
@@ -865,7 +1584,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
void wipeDataLocked(int flags) {
if ((flags&DevicePolicyManager.WIPE_EXTERNAL_STORAGE) != 0) {
Intent intent = new Intent(ExternalStorageFormatter.FORMAT_AND_FACTORY_RESET);
@@ -880,7 +1599,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
public void wipeData(int flags) {
synchronized (this) {
// This API can only be called by an active device admin,
@@ -895,11 +1614,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
public void getRemoveWarning(ComponentName comp, final RemoteCallback result) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-
+
synchronized (this) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(comp);
if (admin == null) {
@@ -922,22 +1641,34 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}, null, Activity.RESULT_OK, null, null);
}
}
-
- public void setActivePasswordState(int quality, int length) {
+
+ public void setActivePasswordState(int quality, int length, int letters, int uppercase,
+ int lowercase, int numbers, int symbols, int nonletter) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-
+
validateQualityConstant(quality);
-
+
synchronized (this) {
if (mActivePasswordQuality != quality || mActivePasswordLength != length
- || mFailedPasswordAttempts != 0) {
+ || mFailedPasswordAttempts != 0 || mActivePasswordLetters != letters
+ || mActivePasswordUpperCase != uppercase
+ || mActivePasswordLowerCase != lowercase || mActivePasswordNumeric != numbers
+ || mActivePasswordSymbols != symbols || mActivePasswordNonLetter != nonletter) {
long ident = Binder.clearCallingIdentity();
try {
mActivePasswordQuality = quality;
mActivePasswordLength = length;
+ mActivePasswordLetters = letters;
+ mActivePasswordLowerCase = lowercase;
+ mActivePasswordUpperCase = uppercase;
+ mActivePasswordNumeric = numbers;
+ mActivePasswordSymbols = symbols;
+ mActivePasswordNonLetter = nonletter;
mFailedPasswordAttempts = 0;
saveSettingsLocked();
+ updatePasswordExpirationsLocked();
+ setExpirationAlarmCheckLocked(mContext);
sendAdminCommandLocked(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED,
DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
} finally {
@@ -946,11 +1677,29 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
+ /**
+ * Called any time the device password is updated. Resets all password expiration clocks.
+ */
+ private void updatePasswordExpirationsLocked() {
+ final int N = mAdminList.size();
+ if (N > 0) {
+ for (int i=0; i<N; i++) {
+ ActiveAdmin admin = mAdminList.get(i);
+ if (admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) {
+ long timeout = admin.passwordExpirationTimeout;
+ long expiration = timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L;
+ admin.passwordExpirationDate = expiration;
+ }
+ }
+ saveSettingsLocked();
+ }
+ }
+
public void reportFailedPasswordAttempt() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-
+
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
@@ -967,11 +1716,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
public void reportSuccessfulPasswordAttempt() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-
+
synchronized (this) {
if (mFailedPasswordAttempts != 0 || mPasswordOwner >= 0) {
long ident = Binder.clearCallingIdentity();
@@ -987,7 +1736,212 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
+ public ComponentName setGlobalProxy(ComponentName who, String proxySpec,
+ String exclusionList) {
+ synchronized(this) {
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+
+ ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_SETS_GLOBAL_PROXY);
+
+ // Scan through active admins and find if anyone has already
+ // set the global proxy.
+ final int N = mAdminList.size();
+ Set<ComponentName> compSet = mAdminMap.keySet();
+ for (ComponentName component : compSet) {
+ ActiveAdmin ap = mAdminMap.get(component);
+ if ((ap.specifiesGlobalProxy) && (!component.equals(who))) {
+ // Another admin already sets the global proxy
+ // Return it to the caller.
+ return component;
+ }
+ }
+ if (proxySpec == null) {
+ admin.specifiesGlobalProxy = false;
+ admin.globalProxySpec = null;
+ admin.globalProxyExclusionList = null;
+ } else {
+
+ admin.specifiesGlobalProxy = true;
+ admin.globalProxySpec = proxySpec;
+ admin.globalProxyExclusionList = exclusionList;
+ }
+
+ // Reset the global proxy accordingly
+ // Do this using system permissions, as apps cannot write to secure settings
+ long origId = Binder.clearCallingIdentity();
+ resetGlobalProxy();
+ Binder.restoreCallingIdentity(origId);
+ return null;
+ }
+ }
+
+ public ComponentName getGlobalProxyAdmin() {
+ synchronized(this) {
+ // Scan through active admins and find if anyone has already
+ // set the global proxy.
+ final int N = mAdminList.size();
+ for (int i = 0; i < N; i++) {
+ ActiveAdmin ap = mAdminList.get(i);
+ if (ap.specifiesGlobalProxy) {
+ // Device admin sets the global proxy
+ // Return it to the caller.
+ return ap.info.getComponent();
+ }
+ }
+ }
+ // No device admin sets the global proxy.
+ return null;
+ }
+
+ private void resetGlobalProxy() {
+ final int N = mAdminList.size();
+ for (int i = 0; i < N; i++) {
+ ActiveAdmin ap = mAdminList.get(i);
+ if (ap.specifiesGlobalProxy) {
+ saveGlobalProxy(ap.globalProxySpec, ap.globalProxyExclusionList);
+ return;
+ }
+ }
+ // No device admins defining global proxies - reset global proxy settings to none
+ saveGlobalProxy(null, null);
+ }
+
+ private void saveGlobalProxy(String proxySpec, String exclusionList) {
+ if (exclusionList == null) {
+ exclusionList = "";
+ }
+ if (proxySpec == null) {
+ proxySpec = "";
+ }
+ // Remove white spaces
+ proxySpec = proxySpec.trim();
+ String data[] = proxySpec.split(":");
+ int proxyPort = 8080;
+ if (data.length > 1) {
+ try {
+ proxyPort = Integer.parseInt(data[1]);
+ } catch (NumberFormatException e) {}
+ }
+ exclusionList = exclusionList.trim();
+ ContentResolver res = mContext.getContentResolver();
+ Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_HOST, data[0]);
+ Settings.Secure.putInt(res, Settings.Secure.GLOBAL_HTTP_PROXY_PORT, proxyPort);
+ Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
+ exclusionList);
+ }
+
+ /**
+ * Set the storage encryption request for a single admin. Returns the new total request
+ * status (for all admins).
+ */
+ public int setStorageEncryption(ComponentName who, boolean encrypt) {
+ synchronized (this) {
+ // Check for permissions
+ if (who == null) {
+ throw new NullPointerException("ComponentName is null");
+ }
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_ENCRYPTED_STORAGE);
+
+ // Quick exit: If the filesystem does not support encryption, we can exit early.
+ if (!isEncryptionSupported()) {
+ return DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED;
+ }
+
+ // (1) Record the value for the admin so it's sticky
+ if (ap.encryptionRequested != encrypt) {
+ ap.encryptionRequested = encrypt;
+ saveSettingsLocked();
+ }
+
+ // (2) Compute "max" for all admins
+ boolean newRequested = false;
+ final int N = mAdminList.size();
+ for (int i = 0; i < N; i++) {
+ newRequested |= mAdminList.get(i).encryptionRequested;
+ }
+
+ // Notify OS of new request
+ setEncryptionRequested(newRequested);
+
+ // Return the new global request status
+ return newRequested
+ ? DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE
+ : DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE;
+ }
+ }
+
+ /**
+ * Get the current storage encryption request status for a given admin, or aggregate of all
+ * active admins.
+ */
+ public boolean getStorageEncryption(ComponentName who) {
+ synchronized (this) {
+ // Check for permissions if a particular caller is specified
+ if (who != null) {
+ // When checking for a single caller, status is based on caller's request
+ ActiveAdmin ap = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_ENCRYPTED_STORAGE);
+ return ap.encryptionRequested;
+ }
+
+ // If no particular caller is specified, return the aggregate set of requests.
+ // This is short circuited by returning true on the first hit.
+ final int N = mAdminList.size();
+ for (int i = 0; i < N; i++) {
+ if (mAdminList.get(i).encryptionRequested) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Get the current encryption status of the device.
+ */
+ public int getStorageEncryptionStatus() {
+ return getEncryptionStatus();
+ }
+
+ /**
+ * Hook to low-levels: This should report if the filesystem supports encrypted storage.
+ */
+ private boolean isEncryptionSupported() {
+ // Note, this can be implemented as
+ // return getEncryptionStatus() != DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED;
+ // But is provided as a separate internal method if there's a faster way to do a
+ // simple check for supported-or-not.
+ return getEncryptionStatus() != DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED;
+ }
+
+ /**
+ * Hook to low-levels: Reporting the current status of encryption.
+ * @return A value such as {@link DevicePolicyManager#ENCRYPTION_STATUS_UNSUPPORTED} or
+ * {@link DevicePolicyManager#ENCRYPTION_STATUS_INACTIVE} or
+ * {@link DevicePolicyManager#ENCRYPTION_STATUS_ACTIVE}.
+ */
+ private int getEncryptionStatus() {
+ String status = SystemProperties.get("ro.crypto.state", "unsupported");
+ if ("encrypted".equalsIgnoreCase(status)) {
+ return DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE;
+ } else if ("unencrypted".equalsIgnoreCase(status)) {
+ return DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE;
+ } else {
+ return DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED;
+ }
+ }
+
+ /**
+ * Hook to low-levels: If needed, record the new admin setting for encryption.
+ */
+ private void setEncryptionRequested(boolean encrypt) {
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -998,12 +1952,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
+ ", uid=" + Binder.getCallingUid());
return;
}
-
+
final Printer p = new PrintWriterPrinter(pw);
-
+
synchronized (this) {
p.println("Current Device Policy Manager state:");
-
+
p.println(" Enabled Device Admins:");
final int N = mAdminList.size();
for (int i=0; i<N; i++) {
@@ -1014,11 +1968,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
ap.dump(" ", pw);
}
}
-
+
pw.println(" ");
pw.print(" mActivePasswordQuality=0x");
pw.println(Integer.toHexString(mActivePasswordQuality));
pw.print(" mActivePasswordLength="); pw.println(mActivePasswordLength);
+ pw.print(" mActivePasswordUpperCase="); pw.println(mActivePasswordUpperCase);
+ pw.print(" mActivePasswordLowerCase="); pw.println(mActivePasswordLowerCase);
+ pw.print(" mActivePasswordLetters="); pw.println(mActivePasswordLetters);
+ pw.print(" mActivePasswordNumeric="); pw.println(mActivePasswordNumeric);
+ pw.print(" mActivePasswordSymbols="); pw.println(mActivePasswordSymbols);
+ pw.print(" mActivePasswordNonLetter="); pw.println(mActivePasswordNonLetter);
pw.print(" mFailedPasswordAttempts="); pw.println(mFailedPasswordAttempts);
pw.print(" mPasswordOwner="); pw.println(mPasswordOwner);
}
diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java
index 0b1a4a3..0fba7c3 100644
--- a/services/java/com/android/server/DeviceStorageMonitorService.java
+++ b/services/java/com/android/server/DeviceStorageMonitorService.java
@@ -16,7 +16,6 @@
package com.android.server;
-import com.android.server.am.ActivityManagerService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -38,7 +37,6 @@ import android.provider.Settings;
import android.util.Config;
import android.util.EventLog;
import android.util.Slog;
-import android.provider.Settings;
/**
* This class implements a service to monitor the amount of disk
@@ -66,6 +64,7 @@ class DeviceStorageMonitorService extends Binder {
private static final int MONITOR_INTERVAL = 1; //in minutes
private static final int LOW_MEMORY_NOTIFICATION_ID = 1;
private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
+ private static final int DEFAULT_THRESHOLD_MAX_BYTES = 500*1024*1024; // 500MB
private static final int DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES = 12*60; //in minutes
private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB
private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000;
@@ -271,13 +270,18 @@ class DeviceStorageMonitorService extends Binder {
* any way
*/
private long getMemThreshold() {
- int value = Settings.Secure.getInt(
+ long value = Settings.Secure.getInt(
mContentResolver,
Settings.Secure.SYS_STORAGE_THRESHOLD_PERCENTAGE,
DEFAULT_THRESHOLD_PERCENTAGE);
if(localLOGV) Slog.v(TAG, "Threshold Percentage="+value);
+ value *= mTotalMemory;
+ long maxValue = Settings.Secure.getInt(
+ mContentResolver,
+ Settings.Secure.SYS_STORAGE_THRESHOLD_MAX_BYTES,
+ DEFAULT_THRESHOLD_MAX_BYTES);
//evaluate threshold value
- return mTotalMemory*value;
+ return value < maxValue ? value : maxValue;
}
/*
diff --git a/services/java/com/android/server/DockObserver.java b/services/java/com/android/server/DockObserver.java
index bee8872..dea9007 100644
--- a/services/java/com/android/server/DockObserver.java
+++ b/services/java/com/android/server/DockObserver.java
@@ -102,8 +102,8 @@ class DockObserver extends UEventObserver {
try {
FileReader file = new FileReader(DOCK_STATE_PATH);
int len = file.read(buffer, 0, 1024);
+ file.close();
mPreviousDockState = mDockState = Integer.valueOf((new String(buffer, 0, len)).trim());
-
} catch (FileNotFoundException e) {
Slog.w(TAG, "This kernel does not have dock station support");
} catch (Exception e) {
@@ -158,13 +158,17 @@ class DockObserver extends UEventObserver {
{
String whichSound = null;
if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
- if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) {
+ if ((mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
+ (mPreviousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
+ (mPreviousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) {
whichSound = Settings.System.DESK_UNDOCK_SOUND;
} else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) {
whichSound = Settings.System.CAR_UNDOCK_SOUND;
}
} else {
- if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) {
+ if ((mDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
+ (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
+ (mDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) {
whichSound = Settings.System.DESK_DOCK_SOUND;
} else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
whichSound = Settings.System.CAR_DOCK_SOUND;
diff --git a/services/java/com/android/server/DropBoxManagerService.java b/services/java/com/android/server/DropBoxManagerService.java
index 3981525..5c878c9 100644
--- a/services/java/com/android/server/DropBoxManagerService.java
+++ b/services/java/com/android/server/DropBoxManagerService.java
@@ -97,10 +97,18 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
// Ensure that all log entries have a unique timestamp
private long mLastTimestamp = 0;
+ private volatile boolean mBooted = false;
+
/** Receives events that might indicate a need to clean up files. */
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ if (intent != null && Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+ mBooted = true;
+ return;
+ }
+
+ // Else, for ACTION_DEVICE_STORAGE_LOW:
mCachedQuotaUptimeMillis = 0; // Force a re-check of quota size
// Run the initialization in the background (not this main thread).
@@ -132,7 +140,11 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
// Set up intent receivers
mContext = context;
mContentResolver = context.getContentResolver();
- context.registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW));
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
+ filter.addAction(Intent.ACTION_BOOT_COMPLETED);
+ context.registerReceiver(mReceiver, filter);
mContentResolver.registerContentObserver(
Settings.Secure.CONTENT_URI, true,
@@ -224,6 +236,9 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag);
dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time);
+ if (!mBooted) {
+ dropboxIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ }
mContext.sendBroadcast(dropboxIntent, android.Manifest.permission.READ_LOGS);
} catch (IOException e) {
@@ -349,16 +364,17 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
if ((entry.flags & DropBoxManager.IS_TEXT) != 0 && (doPrint || !doFile)) {
DropBoxManager.Entry dbe = null;
+ InputStreamReader isr = null;
try {
dbe = new DropBoxManager.Entry(
entry.tag, entry.timestampMillis, entry.file, entry.flags);
if (doPrint) {
- InputStreamReader r = new InputStreamReader(dbe.getInputStream());
+ isr = new InputStreamReader(dbe.getInputStream());
char[] buf = new char[4096];
boolean newline = false;
for (;;) {
- int n = r.read(buf);
+ int n = isr.read(buf);
if (n <= 0) break;
out.append(buf, 0, n);
newline = (buf[n - 1] == '\n');
@@ -382,6 +398,12 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
Slog.e(TAG, "Can't read: " + entry.file, e);
} finally {
if (dbe != null) dbe.close();
+ if (isr != null) {
+ try {
+ isr.close();
+ } catch (IOException unused) {
+ }
+ }
}
}
diff --git a/services/java/com/android/server/HeadsetObserver.java b/services/java/com/android/server/HeadsetObserver.java
deleted file mode 100644
index 6f0a91d..0000000
--- a/services/java/com/android/server/HeadsetObserver.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.app.ActivityManagerNative;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.os.UEventObserver;
-import android.util.Slog;
-import android.media.AudioManager;
-
-import java.io.FileReader;
-import java.io.FileNotFoundException;
-
-/**
- * <p>HeadsetObserver monitors for a wired headset.
- */
-class HeadsetObserver extends UEventObserver {
- private static final String TAG = HeadsetObserver.class.getSimpleName();
- private static final boolean LOG = true;
-
- private static final String HEADSET_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/h2w";
- private static final String HEADSET_STATE_PATH = "/sys/class/switch/h2w/state";
- private static final String HEADSET_NAME_PATH = "/sys/class/switch/h2w/name";
-
- private static final int BIT_HEADSET = (1 << 0);
- private static final int BIT_HEADSET_NO_MIC = (1 << 1);
- private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC);
- private static final int HEADSETS_WITH_MIC = BIT_HEADSET;
-
- private int mHeadsetState;
- private int mPrevHeadsetState;
- private String mHeadsetName;
-
- private final Context mContext;
- private final WakeLock mWakeLock; // held while there is a pending route change
-
- public HeadsetObserver(Context context) {
- mContext = context;
- PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetObserver");
- mWakeLock.setReferenceCounted(false);
-
- startObserving(HEADSET_UEVENT_MATCH);
-
- init(); // set initial status
- }
-
- @Override
- public void onUEvent(UEventObserver.UEvent event) {
- if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());
-
- try {
- update(event.get("SWITCH_NAME"), Integer.parseInt(event.get("SWITCH_STATE")));
- } catch (NumberFormatException e) {
- Slog.e(TAG, "Could not parse switch state from event " + event);
- }
- }
-
- private synchronized final void init() {
- char[] buffer = new char[1024];
-
- String newName = mHeadsetName;
- int newState = mHeadsetState;
- mPrevHeadsetState = mHeadsetState;
- try {
- FileReader file = new FileReader(HEADSET_STATE_PATH);
- int len = file.read(buffer, 0, 1024);
- newState = Integer.valueOf((new String(buffer, 0, len)).trim());
-
- file = new FileReader(HEADSET_NAME_PATH);
- len = file.read(buffer, 0, 1024);
- newName = new String(buffer, 0, len).trim();
-
- } catch (FileNotFoundException e) {
- Slog.w(TAG, "This kernel does not have wired headset support");
- } catch (Exception e) {
- Slog.e(TAG, "" , e);
- }
-
- update(newName, newState);
- }
-
- private synchronized final void update(String newName, int newState) {
- // Retain only relevant bits
- int headsetState = newState & SUPPORTED_HEADSETS;
- int newOrOld = headsetState | mHeadsetState;
- int delay = 0;
- // reject all suspect transitions: only accept state changes from:
- // - a: 0 heaset to 1 headset
- // - b: 1 headset to 0 headset
- if (mHeadsetState == headsetState || ((newOrOld & (newOrOld - 1)) != 0)) {
- return;
- }
-
- mHeadsetName = newName;
- mPrevHeadsetState = mHeadsetState;
- mHeadsetState = headsetState;
-
- if (headsetState == 0) {
- Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
- mContext.sendBroadcast(intent);
- // It can take hundreds of ms flush the audio pipeline after
- // apps pause audio playback, but audio route changes are
- // immediate, so delay the route change by 1000ms.
- // This could be improved once the audio sub-system provides an
- // interface to clear the audio pipeline.
- delay = 1000;
- } else {
- // Insert the same delay for headset connection so that the connection event is not
- // broadcast before the disconnection event in case of fast removal/insertion
- if (mHandler.hasMessages(0)) {
- delay = 1000;
- }
- }
- mWakeLock.acquire();
- mHandler.sendMessageDelayed(mHandler.obtainMessage(0,
- mHeadsetState,
- mPrevHeadsetState,
- mHeadsetName),
- delay);
- }
-
- private synchronized final void sendIntents(int headsetState, int prevHeadsetState, String headsetName) {
- int allHeadsets = SUPPORTED_HEADSETS;
- for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) {
- if ((curHeadset & allHeadsets) != 0) {
- sendIntent(curHeadset, headsetState, prevHeadsetState, headsetName);
- allHeadsets &= ~curHeadset;
- }
- }
- }
-
- private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) {
- if ((headsetState & headset) != (prevHeadsetState & headset)) {
- // Pack up the values and broadcast them to everyone
- Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- int state = 0;
- int microphone = 0;
-
- if ((headset & HEADSETS_WITH_MIC) != 0) {
- microphone = 1;
- }
- if ((headsetState & headset) != 0) {
- state = 1;
- }
- intent.putExtra("state", state);
- intent.putExtra("name", headsetName);
- intent.putExtra("microphone", microphone);
-
- if (LOG) Slog.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone);
- // TODO: Should we require a permission?
- ActivityManagerNative.broadcastStickyIntent(intent, null);
- }
- }
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- sendIntents(msg.arg1, msg.arg2, (String)msg.obj);
- mWakeLock.release();
- }
- };
-}
diff --git a/services/java/com/android/server/InputApplication.java b/services/java/com/android/server/InputApplication.java
index 38420d4..ae09484 100644
--- a/services/java/com/android/server/InputApplication.java
+++ b/services/java/com/android/server/InputApplication.java
@@ -18,16 +18,19 @@ package com.android.server;
/**
* Describes input-related application properties for use by the input dispatcher.
- *
* @hide
*/
public final class InputApplication {
+ // Application handle.
+ public InputApplicationHandle inputApplicationHandle;
+
// Application name.
public String name;
-
+
// Dispatching timeout.
public long dispatchingTimeoutNanos;
-
- // The application window token.
- public Object token;
+
+ public void recycle() {
+ inputApplicationHandle = null;
+ }
}
diff --git a/services/java/com/android/server/InputApplicationHandle.java b/services/java/com/android/server/InputApplicationHandle.java
new file mode 100644
index 0000000..d396d11
--- /dev/null
+++ b/services/java/com/android/server/InputApplicationHandle.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+/**
+ * Functions as a handle for an application that can receive input.
+ * Enables the native input dispatcher to refer indirectly to the window manager's
+ * application window token.
+ * @hide
+ */
+public final class InputApplicationHandle {
+ // Pointer to the native input application handle.
+ // This field is lazily initialized via JNI.
+ @SuppressWarnings("unused")
+ private int ptr;
+
+ // The window manager's application window token.
+ public final WindowManagerService.AppWindowToken appWindowToken;
+
+ private native void nativeDispose();
+
+ public InputApplicationHandle(WindowManagerService.AppWindowToken appWindowToken) {
+ this.appWindowToken = appWindowToken;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ nativeDispose();
+ super.finalize();
+ }
+}
diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java
index ba39c57..8d249ff 100644
--- a/services/java/com/android/server/InputManager.java
+++ b/services/java/com/android/server/InputManager.java
@@ -19,10 +19,17 @@ package com.android.server;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.os.SystemProperties;
import android.util.Slog;
@@ -30,7 +37,9 @@ import android.util.Xml;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
+import android.view.KeyEvent;
import android.view.Surface;
+import android.view.WindowManager;
import java.io.BufferedReader;
import java.io.File;
@@ -41,7 +50,6 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Properties;
/*
* Wraps the C++ InputManager and provides its callbacks.
@@ -69,7 +77,7 @@ public class InputManager {
private static native boolean nativeHasKeys(int deviceId, int sourceMask,
int[] keyCodes, boolean[] keyExists);
private static native void nativeRegisterInputChannel(InputChannel inputChannel,
- boolean monitor);
+ InputWindowHandle inputWindowHandle, boolean monitor);
private static native void nativeUnregisterInputChannel(InputChannel inputChannel);
private static native int nativeInjectInputEvent(InputEvent event,
int injectorPid, int injectorUid, int syncMode, int timeoutMillis);
@@ -79,6 +87,8 @@ public class InputManager {
private static native InputDevice nativeGetInputDevice(int deviceId);
private static native void nativeGetInputConfiguration(Configuration configuration);
private static native int[] nativeGetInputDeviceIds();
+ private static native boolean nativeTransferTouchFocus(InputChannel fromChannel,
+ InputChannel toChannel);
private static native String nativeDump();
// Input event injection constants defined in InputDispatcher.h.
@@ -230,7 +240,7 @@ public class InputManager {
}
InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
- nativeRegisterInputChannel(inputChannels[0], true);
+ nativeRegisterInputChannel(inputChannels[0], null, true);
inputChannels[0].dispose(); // don't need to retain the Java object reference
return inputChannels[1];
}
@@ -238,13 +248,16 @@ public class InputManager {
/**
* Registers an input channel so that it can be used as an input event target.
* @param inputChannel The input channel to register.
+ * @param inputWindowHandle The handle of the input window associated with the
+ * input channel, or null if none.
*/
- public void registerInputChannel(InputChannel inputChannel) {
+ public void registerInputChannel(InputChannel inputChannel,
+ InputWindowHandle inputWindowHandle) {
if (inputChannel == null) {
throw new IllegalArgumentException("inputChannel must not be null.");
}
- nativeRegisterInputChannel(inputChannel, false);
+ nativeRegisterInputChannel(inputChannel, inputWindowHandle, false);
}
/**
@@ -326,28 +339,78 @@ public class InputManager {
nativeSetInputDispatchMode(enabled, frozen);
}
+ /**
+ * Atomically transfers touch focus from one window to another as identified by
+ * their input channels. It is possible for multiple windows to have
+ * touch focus if they support split touch dispatch
+ * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
+ * method only transfers touch focus of the specified window without affecting
+ * other windows that may also have touch focus at the same time.
+ * @param fromChannel The channel of a window that currently has touch focus.
+ * @param toChannel The channel of the window that should receive touch focus in
+ * place of the first.
+ * @return True if the transfer was successful. False if the window with the
+ * specified channel did not actually have touch focus at the time of the request.
+ */
+ public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) {
+ if (fromChannel == null) {
+ throw new IllegalArgumentException("fromChannel must not be null.");
+ }
+ if (toChannel == null) {
+ throw new IllegalArgumentException("toChannel must not be null.");
+ }
+ return nativeTransferTouchFocus(fromChannel, toChannel);
+ }
+
public void dump(PrintWriter pw) {
String dumpStr = nativeDump();
if (dumpStr != null) {
pw.println(dumpStr);
}
}
-
- private static final class VirtualKeyDefinition {
- public int scanCode;
-
- // configured position data, specified in display coords
- public int centerX;
- public int centerY;
- public int width;
- public int height;
- }
-
- private static final class InputDeviceCalibration {
- public String[] keys;
- public String[] values;
+
+ private static final class PointerIcon {
+ public Bitmap bitmap;
+ public float hotSpotX;
+ public float hotSpotY;
+
+ public static PointerIcon load(Resources resources, int resourceId) {
+ PointerIcon icon = new PointerIcon();
+
+ XmlResourceParser parser = resources.getXml(resourceId);
+ final int bitmapRes;
+ try {
+ XmlUtils.beginDocument(parser, "pointer-icon");
+
+ TypedArray a = resources.obtainAttributes(
+ parser, com.android.internal.R.styleable.PointerIcon);
+ bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
+ icon.hotSpotX = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
+ icon.hotSpotY = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
+ a.recycle();
+ } catch (Exception ex) {
+ Slog.e(TAG, "Exception parsing pointer icon resource.", ex);
+ return null;
+ } finally {
+ parser.close();
+ }
+
+ if (bitmapRes == 0) {
+ Slog.e(TAG, "<pointer-icon> is missing bitmap attribute");
+ return null;
+ }
+
+ Drawable drawable = resources.getDrawable(bitmapRes);
+ if (!(drawable instanceof BitmapDrawable)) {
+ Slog.e(TAG, "<pointer-icon> bitmap attribute must refer to a bitmap drawable");
+ return null;
+ }
+
+ icon.bitmap = ((BitmapDrawable)drawable).getBitmap();
+ return icon;
+ }
}
-
+
/*
* Callbacks from native.
*/
@@ -360,7 +423,7 @@ public class InputManager {
@SuppressWarnings("unused")
public void notifyConfigurationChanged(long whenNanos) {
- mWindowManagerService.sendNewConfiguration();
+ mWindowManagerService.mInputMonitor.notifyConfigurationChanged();
}
@SuppressWarnings("unused")
@@ -369,28 +432,35 @@ public class InputManager {
}
@SuppressWarnings("unused")
- public void notifyInputChannelBroken(InputChannel inputChannel) {
- mWindowManagerService.mInputMonitor.notifyInputChannelBroken(inputChannel);
+ public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) {
+ mWindowManagerService.mInputMonitor.notifyInputChannelBroken(inputWindowHandle);
}
@SuppressWarnings("unused")
- public long notifyANR(Object token, InputChannel inputChannel) {
- return mWindowManagerService.mInputMonitor.notifyANR(token, inputChannel);
+ public long notifyANR(InputApplicationHandle inputApplicationHandle,
+ InputWindowHandle inputWindowHandle) {
+ return mWindowManagerService.mInputMonitor.notifyANR(
+ inputApplicationHandle, inputWindowHandle);
}
@SuppressWarnings("unused")
- public int interceptKeyBeforeQueueing(long whenNanos, int action, int flags,
- int keyCode, int scanCode, int policyFlags, boolean isScreenOn) {
+ public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) {
return mWindowManagerService.mInputMonitor.interceptKeyBeforeQueueing(
- whenNanos, action, flags, keyCode, scanCode, policyFlags, isScreenOn);
+ event, policyFlags, isScreenOn);
+ }
+
+ @SuppressWarnings("unused")
+ public boolean interceptKeyBeforeDispatching(InputWindowHandle focus,
+ KeyEvent event, int policyFlags) {
+ return mWindowManagerService.mInputMonitor.interceptKeyBeforeDispatching(
+ focus, event, policyFlags);
}
@SuppressWarnings("unused")
- public boolean interceptKeyBeforeDispatching(InputChannel focus, int action,
- int flags, int keyCode, int scanCode, int metaState, int repeatCount,
- int policyFlags) {
- return mWindowManagerService.mInputMonitor.interceptKeyBeforeDispatching(focus,
- action, flags, keyCode, scanCode, metaState, repeatCount, policyFlags);
+ public KeyEvent dispatchUnhandledKey(InputWindowHandle focus,
+ KeyEvent event, int policyFlags) {
+ return mWindowManagerService.mInputMonitor.dispatchUnhandledKey(
+ focus, event, policyFlags);
}
@SuppressWarnings("unused")
@@ -419,79 +489,6 @@ public class InputManager {
}
@SuppressWarnings("unused")
- public VirtualKeyDefinition[] getVirtualKeyDefinitions(String deviceName) {
- ArrayList<VirtualKeyDefinition> keys = new ArrayList<VirtualKeyDefinition>();
-
- try {
- FileInputStream fis = new FileInputStream(
- "/sys/board_properties/virtualkeys." + deviceName);
- InputStreamReader isr = new InputStreamReader(fis);
- BufferedReader br = new BufferedReader(isr, 2048);
- String str = br.readLine();
- if (str != null) {
- String[] it = str.split(":");
- if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "***** VIRTUAL KEYS: " + it);
- final int N = it.length-6;
- for (int i=0; i<=N; i+=6) {
- if (!"0x01".equals(it[i])) {
- Slog.w(TAG, "Unknown virtual key type at elem #"
- + i + ": " + it[i] + " for device " + deviceName);
- continue;
- }
- try {
- VirtualKeyDefinition key = new VirtualKeyDefinition();
- key.scanCode = Integer.parseInt(it[i+1]);
- key.centerX = Integer.parseInt(it[i+2]);
- key.centerY = Integer.parseInt(it[i+3]);
- key.width = Integer.parseInt(it[i+4]);
- key.height = Integer.parseInt(it[i+5]);
- if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Virtual key "
- + key.scanCode + ": center=" + key.centerX + ","
- + key.centerY + " size=" + key.width + "x"
- + key.height);
- keys.add(key);
- } catch (NumberFormatException e) {
- Slog.w(TAG, "Bad number in virtual key definition at region "
- + i + " in: " + str + " for device " + deviceName, e);
- }
- }
- }
- br.close();
- } catch (FileNotFoundException e) {
- Slog.i(TAG, "No virtual keys found for device " + deviceName + ".");
- } catch (IOException e) {
- Slog.w(TAG, "Error reading virtual keys for device " + deviceName + ".", e);
- }
-
- return keys.toArray(new VirtualKeyDefinition[keys.size()]);
- }
-
- @SuppressWarnings("unused")
- public InputDeviceCalibration getInputDeviceCalibration(String deviceName) {
- // Calibration is specified as a sequence of colon-delimited key value pairs.
- Properties properties = new Properties();
- File calibrationFile = new File(Environment.getRootDirectory(),
- CALIBRATION_DIR_PATH + deviceName + ".idc");
- if (calibrationFile.exists()) {
- try {
- properties.load(new FileInputStream(calibrationFile));
- } catch (IOException ex) {
- Slog.w(TAG, "Error reading input device calibration properties for device "
- + deviceName + " from " + calibrationFile + ".", ex);
- }
- } else {
- Slog.i(TAG, "No input device calibration properties found for device "
- + deviceName + ".");
- return null;
- }
-
- InputDeviceCalibration calibration = new InputDeviceCalibration();
- calibration.keys = properties.keySet().toArray(new String[properties.size()]);
- calibration.values = properties.values().toArray(new String[properties.size()]);
- return calibration;
- }
-
- @SuppressWarnings("unused")
public String[] getExcludedDeviceNames() {
ArrayList<String> names = new ArrayList<String>();
@@ -539,5 +536,19 @@ public class InputManager {
}
return result;
}
+
+ @SuppressWarnings("unused")
+ public int getPointerLayer() {
+ return mWindowManagerService.mPolicy.windowTypeToLayerLw(
+ WindowManager.LayoutParams.TYPE_POINTER)
+ * WindowManagerService.TYPE_LAYER_MULTIPLIER
+ + WindowManagerService.TYPE_LAYER_OFFSET;
+ }
+
+ @SuppressWarnings("unused")
+ public PointerIcon getPointerIcon() {
+ return PointerIcon.load(mContext.getResources(),
+ com.android.internal.R.drawable.pointer_arrow_icon);
+ }
}
}
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index ecad3cc..6636fb7 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -49,6 +49,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.ContentObserver;
+import android.inputmethodservice.InputMethodService;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -59,20 +60,24 @@ import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.provider.Settings.Secure;
+import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
import android.util.EventLog;
+import android.util.Pair;
import android.util.Slog;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.view.IWindowManager;
import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodSubtype;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -80,6 +85,7 @@ import java.io.PrintWriter;
import java.text.Collator;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@@ -93,6 +99,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
static final String TAG = "InputManagerService";
static final int MSG_SHOW_IM_PICKER = 1;
+ static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2;
+ static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3;
+ static final int MSG_SHOW_IM_CONFIG = 4;
static final int MSG_UNBIND_INPUT = 1000;
static final int MSG_BIND_INPUT = 1010;
@@ -109,8 +118,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
static final long TIME_TO_RECONNECT = 10*1000;
+ private static final int NOT_A_SUBTYPE_ID = -1;
+ private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
+ private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
+ private static final String SUBTYPE_MODE_VOICE = "voice";
+
+ // TODO: Will formalize this value as API
+ private static final String SUBTYPE_EXTRAVALUE_EXCLUDE_FROM_LAST_IME =
+ "excludeFromLastInputMethod";
+
final Context mContext;
+ final Resources mRes;
final Handler mHandler;
+ final InputMethodSettings mSettings;
final SettingsObserver mSettingsObserver;
final StatusBarManagerService mStatusBar;
final IWindowManager mIWindowManager;
@@ -120,13 +140,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// All known input methods. mMethodMap also serves as the global
// lock for this class.
- final ArrayList<InputMethodInfo> mMethodList
- = new ArrayList<InputMethodInfo>();
- final HashMap<String, InputMethodInfo> mMethodMap
- = new HashMap<String, InputMethodInfo>();
-
- final TextUtils.SimpleStringSplitter mStringColonSplitter
- = new TextUtils.SimpleStringSplitter(':');
+ final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>();
+ final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>();
class SessionState {
final ClientState client;
@@ -224,6 +239,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
String mCurId;
/**
+ * The current subtype of the current input method.
+ */
+ private InputMethodSubtype mCurrentSubtype;
+
+ // This list contains the pairs of InputMethodInfo and InputMethodSubtype.
+ private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>
+ mShortcutInputMethodsAndSubtypes =
+ new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>();
+
+ /**
* Set to true if our ServiceConnection is currently actively bound to
* a service (whether or not we have gotten its IBinder back yet).
*/
@@ -288,10 +313,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
*/
boolean mScreenOn = true;
+ int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
+ int mImeWindowVis;
+ long mOldSystemSettingsVersion;
+
AlertDialog.Builder mDialogBuilder;
AlertDialog mSwitchingDialog;
InputMethodInfo[] mIms;
CharSequence[] mItems;
+ int[] mSubtypeIds;
class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler) {
@@ -299,6 +329,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this);
}
@Override public void onChange(boolean selfChange) {
@@ -351,9 +383,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (!doit) {
return true;
}
-
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD, "");
+ resetSelectedInputMethodAndSubtypeLocked("");
chooseNewDefaultIMELocked();
return true;
}
@@ -406,19 +436,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// Uh oh, current input method is no longer around!
// Pick another one...
Slog.i(TAG, "Current input method removed: " + curInputMethodId);
+ mImeWindowVis = 0;
+ mStatusBar.setImeWindowStatus(mCurToken, mImeWindowVis,
+ mBackDisposition);
if (!chooseNewDefaultIMELocked()) {
changed = true;
curIm = null;
- curInputMethodId = "";
Slog.i(TAG, "Unsetting current input method");
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD,
- curInputMethodId);
+ resetSelectedInputMethodAndSubtypeLocked("");
}
}
}
}
-
+
if (curIm == null) {
// We currently don't have a default input method... is
// one now available?
@@ -449,6 +479,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
public InputMethodManagerService(Context context, StatusBarManagerService statusBar) {
mContext = context;
+ mRes = context.getResources();
mHandler = new Handler(this);
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
@@ -457,6 +488,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
handleMessage(msg);
}
});
+ // Initialize the system settings version to undefined.
+ mOldSystemSettingsVersion = -1;
(new MyPackageMonitor()).register(mContext, true);
@@ -469,27 +502,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mStatusBar = statusBar;
statusBar.setIconVisibility("ime", false);
+ // mSettings should be created before buildInputMethodListLocked
+ mSettings = new InputMethodSettings(
+ mRes, context.getContentResolver(), mMethodMap, mMethodList);
buildInputMethodListLocked(mMethodList, mMethodMap);
+ mSettings.enableAllIMEsIfThereIsNoEnabledIME();
- final String enabledStr = Settings.Secure.getString(
- mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS);
- Slog.i(TAG, "Enabled input methods: " + enabledStr);
- final String defaultIme = Settings.Secure.getString(mContext
- .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
- if (enabledStr == null || TextUtils.isEmpty(defaultIme)) {
- Slog.i(TAG, "Enabled input methods or default IME has not been set, enabling all");
+ if (TextUtils.isEmpty(Settings.Secure.getString(
+ mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD))) {
InputMethodInfo defIm = null;
- StringBuilder sb = new StringBuilder(256);
- final int N = mMethodList.size();
- for (int i=0; i<N; i++) {
- InputMethodInfo imi = mMethodList.get(i);
- Slog.i(TAG, "Adding: " + imi.getId());
- if (i > 0) sb.append(':');
- sb.append(imi.getId());
+ for (InputMethodInfo imi: mMethodList) {
if (defIm == null && imi.getIsDefaultResourceId() != 0) {
try {
- Resources res = mContext.createPackageContext(
+ Resources res = context.createPackageContext(
imi.getPackageName(), 0).getResources();
if (res.getBoolean(imi.getIsDefaultResourceId())) {
defIm = imi;
@@ -500,15 +525,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
}
- if (defIm == null && N > 0) {
+ if (defIm == null && mMethodList.size() > 0) {
defIm = mMethodList.get(0);
Slog.i(TAG, "No default found, using " + defIm.getId());
}
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS, sb.toString());
if (defIm != null) {
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD, defIm.getId());
+ setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
}
}
@@ -552,29 +574,40 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
public List<InputMethodInfo> getEnabledInputMethodList() {
synchronized (mMethodMap) {
- return getEnabledInputMethodListLocked();
+ return mSettings.getEnabledInputMethodListLocked();
}
}
- List<InputMethodInfo> getEnabledInputMethodListLocked() {
- final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
-
- final String enabledStr = Settings.Secure.getString(
- mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS);
- if (enabledStr != null) {
- final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
- splitter.setString(enabledStr);
+ private HashMap<InputMethodInfo, List<InputMethodSubtype>>
+ getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked() {
+ HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
+ new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
+ for (InputMethodInfo imi: getEnabledInputMethodList()) {
+ enabledInputMethodAndSubtypes.put(
+ imi, getEnabledInputMethodSubtypeListLocked(imi, true));
+ }
+ return enabledInputMethodAndSubtypes;
+ }
- while (splitter.hasNext()) {
- InputMethodInfo info = mMethodMap.get(splitter.next());
- if (info != null) {
- res.add(info);
- }
- }
+ public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi,
+ boolean allowsImplicitlySelectedSubtypes) {
+ if (imi == null && mCurMethodId != null) {
+ imi = mMethodMap.get(mCurMethodId);
+ }
+ final List<InputMethodSubtype> enabledSubtypes =
+ mSettings.getEnabledInputMethodSubtypeListLocked(imi);
+ if (!allowsImplicitlySelectedSubtypes || enabledSubtypes.size() > 0) {
+ return enabledSubtypes;
+ } else {
+ return getApplicableSubtypesLocked(mRes, getSubtypes(imi));
}
+ }
- return res;
+ public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
+ boolean allowsImplicitlySelectedSubtypes) {
+ synchronized (mMethodMap) {
+ return getEnabledInputMethodSubtypeListLocked(imi, allowsImplicitlySelectedSubtypes);
+ }
}
public void addClient(IInputMethodClient client,
@@ -959,21 +992,54 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
+ public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
+ int uid = Binder.getCallingUid();
+ long ident = Binder.clearCallingIdentity();
+ try {
+ if (token == null || mCurToken != token) {
+ Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token);
+ return;
+ }
+
+ synchronized (mMethodMap) {
+ mImeWindowVis = vis;
+ mBackDisposition = backDisposition;
+ mStatusBar.setImeWindowStatus(token, vis, backDisposition);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ // TODO: Investigate and fix why are settings changes getting processed before the settings seq
+ // number is updated?
+ // TODO: Change this stuff to not rely on modifying settings for normal user interactions.
void updateFromSettingsLocked() {
+ long newSystemSettingsVersion = getSystemSettingsVersion();
+ // This is a workaround to avoid a situation that old cached value in Settings.Secure
+ // will be handled.
+ if (newSystemSettingsVersion == mOldSystemSettingsVersion) return;
+
// We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
// enabled.
String id = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD);
- if (id != null && id.length() > 0) {
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+ // There is no input method selected, try to choose new applicable input method.
+ if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
+ id = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+ }
+ if (!TextUtils.isEmpty(id)) {
try {
- setInputMethodLocked(id);
+ setInputMethodLocked(id, getSelectedInputMethodSubtypeId(id));
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Unknown input method from prefs: " + id, e);
mCurMethodId = null;
unbindCurrentMethodLocked(true);
}
+ mShortcutInputMethodsAndSubtypes.clear();
} else {
// There is no longer an input method set, so stop any current one.
mCurMethodId = null;
@@ -981,21 +1047,59 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
- void setInputMethodLocked(String id) {
+ /* package */ void setInputMethodLocked(String id, int subtypeId) {
InputMethodInfo info = mMethodMap.get(id);
if (info == null) {
throw new IllegalArgumentException("Unknown id: " + id);
}
if (id.equals(mCurMethodId)) {
+ InputMethodSubtype subtype = null;
+ if (subtypeId >= 0 && subtypeId < info.getSubtypeCount()) {
+ subtype = info.getSubtypeAt(subtypeId);
+ }
+ if (subtype != mCurrentSubtype) {
+ synchronized (mMethodMap) {
+ if (subtype != null) {
+ setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
+ }
+ if (mCurMethod != null) {
+ try {
+ final Configuration conf = mRes.getConfiguration();
+ final boolean haveHardKeyboard = conf.keyboard
+ != Configuration.KEYBOARD_NOKEYS;
+ final boolean hardKeyShown = haveHardKeyboard
+ && conf.hardKeyboardHidden
+ != Configuration.HARDKEYBOARDHIDDEN_YES;
+ mImeWindowVis = (mInputShown || hardKeyShown) ? (
+ InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE)
+ : 0;
+ mStatusBar.setImeWindowStatus(mCurToken, mImeWindowVis,
+ mBackDisposition);
+ // If subtype is null, try to find the most applicable one from
+ // getCurrentInputMethodSubtype.
+ if (subtype == null) {
+ subtype = getCurrentInputMethodSubtype();
+ }
+ mCurMethod.changeInputMethodSubtype(subtype);
+ } catch (RemoteException e) {
+ return;
+ }
+ }
+ }
+ }
return;
}
final long ident = Binder.clearCallingIdentity();
try {
+ // Set a subtype to this input method.
+ // subtypeId the name of a subtype which will be set.
+ setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
+ // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
+ // because mCurMethodId is stored as a history in
+ // setSelectedInputMethodAndSubtypeLocked().
mCurMethodId = id;
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD, id);
if (ActivityManagerNative.isSystemReady()) {
Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
@@ -1089,9 +1193,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (!mIWindowManager.inputMethodClientHasFocus(client)) {
if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
+ uid + ": " + client);
+ mImeWindowVis = 0;
+ mStatusBar.setImeWindowStatus(mCurToken, mImeWindowVis,
+ mBackDisposition);
return false;
}
} catch (RemoteException e) {
+ mImeWindowVis = 0;
+ mStatusBar.setImeWindowStatus(mCurToken, mImeWindowVis, mBackDisposition);
return false;
}
}
@@ -1164,11 +1273,22 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
mCurFocusedWindow = windowToken;
+ // Should we auto-show the IME even if the caller has not
+ // specified what should be done with it?
+ // We only do this automatically if the window can resize
+ // to accommodate the IME (so what the user sees will give
+ // them good context without input information being obscured
+ // by the IME) or if running on a large screen where there
+ // is more room for the target window + IME.
+ final boolean doAutoShow =
+ (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
+ == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
+ || mRes.getConfiguration().isLayoutSizeAtLeast(
+ Configuration.SCREENLAYOUT_SIZE_LARGE);
+
switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
- if (!isTextEditor || (softInputMode &
- WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
- != WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
+ if (!isTextEditor || !doAutoShow) {
if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
// There is no focus view, and this window will
// be behind any soft input window, so hide the
@@ -1176,13 +1296,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
}
- } else if (isTextEditor && (softInputMode &
- WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
- == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
- && (softInputMode &
- WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
+ } else if (isTextEditor && doAutoShow && (softInputMode &
+ WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
// There is a focus view, and we are navigating forward
// into the window, so show the input window for the user.
+ // We only do this automatically if the window an resize
+ // to accomodate the IME (so what the user sees will give
+ // them good context without input information being obscured
+ // by the IME) or if running on a large screen where there
+ // is more room for the target window + IME.
if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
}
@@ -1223,15 +1345,72 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
synchronized (mMethodMap) {
if (mCurClient == null || client == null
|| mCurClient.client.asBinder() != client.asBinder()) {
- Slog.w(TAG, "Ignoring showInputMethodDialogFromClient of uid "
+ Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
+ Binder.getCallingUid() + ": " + client);
}
- mHandler.sendEmptyMessage(MSG_SHOW_IM_PICKER);
+ // Always call subtype picker, because subtype picker is a superset of input method
+ // picker.
+ mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER);
}
}
public void setInputMethod(IBinder token, String id) {
+ setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
+ }
+
+ public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
+ synchronized (mMethodMap) {
+ if (subtype != null) {
+ setInputMethodWithSubtypeId(token, id, getSubtypeIdFromHashCode(
+ mMethodMap.get(id), subtype.hashCode()));
+ } else {
+ setInputMethod(token, id);
+ }
+ }
+ }
+
+ public void showInputMethodAndSubtypeEnablerFromClient(
+ IInputMethodClient client, String inputMethodId) {
+ synchronized (mMethodMap) {
+ if (mCurClient == null || client == null
+ || mCurClient.client.asBinder() != client.asBinder()) {
+ Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client);
+ }
+ executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
+ MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
+ }
+ }
+
+ public boolean switchToLastInputMethod(IBinder token) {
+ synchronized (mMethodMap) {
+ final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
+ if (lastIme == null) return false;
+ final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
+ if (lastImi == null) return false;
+
+ final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId);
+ final int lastSubtypeHash = Integer.valueOf(lastIme.second);
+ // If the last IME is the same as the current IME and the last subtype is not defined,
+ // there is no need to switch to the last IME.
+ if (imiIdIsSame && lastSubtypeHash == NOT_A_SUBTYPE_ID) return false;
+
+ int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
+ : mCurrentSubtype.hashCode();
+ if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
+ if (DEBUG) {
+ Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second + ", from: "
+ + mCurMethodId + ", " + currentSubtypeHash);
+ }
+ setInputMethodWithSubtypeId(token, lastIme.first, getSubtypeIdFromHashCode(
+ lastImi, lastSubtypeHash));
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
synchronized (mMethodMap) {
if (token == null) {
if (mContext.checkCallingOrSelfPermission(
@@ -1249,7 +1428,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
long ident = Binder.clearCallingIdentity();
try {
- setInputMethodLocked(id);
+ setInputMethodLocked(id, subtypeId);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1315,6 +1494,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
showInputMethodMenu();
return true;
+ case MSG_SHOW_IM_SUBTYPE_PICKER:
+ showInputMethodSubtypeMenu();
+ return true;
+
+ case MSG_SHOW_IM_SUBTYPE_ENABLER:
+ args = (HandlerCaller.SomeArgs)msg.obj;
+ showInputMethodAndSubtypeEnabler((String)args.arg1);
+ return true;
+
+ case MSG_SHOW_IM_CONFIG:
+ showConfigureInputMethods();
+ return true;
+
// ---------------------------------------------------------
case MSG_UNBIND_INPUT:
@@ -1413,8 +1605,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
& ApplicationInfo.FLAG_SYSTEM) != 0;
}
+ private static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
+ ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ subtypes.add(imi.getSubtypeAt(i));
+ }
+ return subtypes;
+ }
+
private boolean chooseNewDefaultIMELocked() {
- List<InputMethodInfo> enabled = getEnabledInputMethodListLocked();
+ List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
if (enabled != null && enabled.size() > 0) {
// We'd prefer to fall back on a system IME, since that is safer.
int i=enabled.size();
@@ -1425,9 +1626,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
break;
}
}
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD,
- enabled.get(i).getId());
+ InputMethodInfo imi = enabled.get(i);
+ if (DEBUG) {
+ Slog.d(TAG, "New default IME was selected: " + imi.getId());
+ }
+ resetSelectedInputMethodAndSubtypeLocked(imi.getId());
return true;
}
@@ -1440,7 +1643,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
map.clear();
PackageManager pm = mContext.getPackageManager();
- final Configuration config = mContext.getResources().getConfiguration();
+ final Configuration config = mRes.getConfiguration();
final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY;
String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(),
Secure.DISABLED_SYSTEM_INPUT_METHODS);
@@ -1498,7 +1701,34 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// ----------------------------------------------------------------------
- void showInputMethodMenu() {
+ private void showInputMethodMenu() {
+ showInputMethodMenuInternal(false);
+ }
+
+ private void showInputMethodSubtypeMenu() {
+ showInputMethodMenuInternal(true);
+ }
+
+ private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
+ Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ if (!TextUtils.isEmpty(inputMethodId)) {
+ intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId);
+ }
+ mContext.startActivity(intent);
+ }
+
+ private void showConfigureInputMethods() {
+ Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivity(intent);
+ }
+
+ private void showInputMethodMenuInternal(boolean showSubtypes) {
if (DEBUG) Slog.v(TAG, "Show switching menu");
final Context context = mContext;
@@ -1507,48 +1737,81 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
String lastInputMethodId = Settings.Secure.getString(context
.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+ int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
- final List<InputMethodInfo> immis = getEnabledInputMethodList();
-
- if (immis == null) {
- return;
- }
-
synchronized (mMethodMap) {
- hideInputMethodMenuLocked();
+ final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
+ getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked();
+ if (immis == null || immis.size() == 0) {
+ return;
+ }
- int N = immis.size();
+ hideInputMethodMenuLocked();
- final Map<CharSequence, InputMethodInfo> imMap =
- new TreeMap<CharSequence, InputMethodInfo>(Collator.getInstance());
+ final Map<CharSequence, Pair<InputMethodInfo, Integer>> imMap =
+ new TreeMap<CharSequence, Pair<InputMethodInfo, Integer>>(Collator.getInstance());
- for (int i = 0; i < N; ++i) {
- InputMethodInfo property = immis.get(i);
- if (property == null) {
- continue;
+ for (InputMethodInfo imi: immis.keySet()) {
+ if (imi == null) continue;
+ List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi);
+ HashSet<String> enabledSubtypeSet = new HashSet<String>();
+ for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) {
+ enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
+ }
+ ArrayList<InputMethodSubtype> subtypes = getSubtypes(imi);
+ CharSequence label = imi.loadLabel(pm);
+ if (showSubtypes && enabledSubtypeSet.size() > 0) {
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int j = 0; j < subtypeCount; ++j) {
+ InputMethodSubtype subtype = imi.getSubtypeAt(j);
+ if (enabledSubtypeSet.contains(String.valueOf(subtype.hashCode()))) {
+ CharSequence title;
+ int nameResId = subtype.getNameResId();
+ String mode = subtype.getMode();
+ if (nameResId != 0) {
+ title = pm.getText(imi.getPackageName(), nameResId,
+ imi.getServiceInfo().applicationInfo);
+ } else {
+ CharSequence language = subtype.getLocale();
+ // TODO: Use more friendly Title and UI
+ title = label + "," + (mode == null ? "" : mode) + ","
+ + (language == null ? "" : language);
+ }
+ imMap.put(title, new Pair<InputMethodInfo, Integer>(imi, j));
+ }
+ }
+ } else {
+ imMap.put(label,
+ new Pair<InputMethodInfo, Integer>(imi, NOT_A_SUBTYPE_ID));
}
- imMap.put(property.loadLabel(pm), property);
}
- N = imMap.size();
+ final int N = imMap.size();
mItems = imMap.keySet().toArray(new CharSequence[N]);
- mIms = imMap.values().toArray(new InputMethodInfo[N]);
-
+ mIms = new InputMethodInfo[N];
+ mSubtypeIds = new int[N];
int checkedItem = 0;
for (int i = 0; i < N; ++i) {
+ Pair<InputMethodInfo, Integer> value = imMap.get(mItems[i]);
+ mIms[i] = value.first;
+ mSubtypeIds[i] = value.second;
if (mIms[i].getId().equals(lastInputMethodId)) {
- checkedItem = i;
- break;
+ int subtypeId = mSubtypeIds[i];
+ if ((subtypeId == NOT_A_SUBTYPE_ID)
+ || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
+ || (subtypeId == lastInputMethodSubtypeId)) {
+ checkedItem = i;
+ }
}
}
-
+
AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
hideInputMethodMenu();
}
};
-
+
TypedArray a = context.obtainStyledAttributes(null,
com.android.internal.R.styleable.DialogPreference,
com.android.internal.R.attr.alertDialogStyle, 0);
@@ -1562,23 +1825,38 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
.setIcon(a.getDrawable(
com.android.internal.R.styleable.DialogPreference_dialogTitle));
a.recycle();
-
+
mDialogBuilder.setSingleChoiceItems(mItems, checkedItem,
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
synchronized (mMethodMap) {
- if (mIms == null || mIms.length <= which) {
+ if (mIms == null || mIms.length <= which
+ || mSubtypeIds == null || mSubtypeIds.length <= which) {
return;
}
InputMethodInfo im = mIms[which];
+ int subtypeId = mSubtypeIds[which];
hideInputMethodMenu();
if (im != null) {
- setInputMethodLocked(im.getId());
+ if ((subtypeId < 0)
+ || (subtypeId >= im.getSubtypeCount())) {
+ subtypeId = NOT_A_SUBTYPE_ID;
+ }
+ setInputMethodLocked(im.getId(), subtypeId);
}
}
}
});
+ if (showSubtypes) {
+ mDialogBuilder.setPositiveButton(
+ com.android.internal.R.string.configure_input_methods,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ showConfigureInputMethods();
+ }
+ });
+ }
mSwitchingDialog = mDialogBuilder.create();
mSwitchingDialog.getWindow().setType(
WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
@@ -1630,80 +1908,800 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// Make sure this is a valid input method.
InputMethodInfo imm = mMethodMap.get(id);
if (imm == null) {
- if (imm == null) {
- throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
+ throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
+ }
+
+ List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
+ .getEnabledInputMethodsAndSubtypeListLocked();
+
+ if (enabled) {
+ for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
+ if (pair.first.equals(id)) {
+ // We are enabling this input method, but it is already enabled.
+ // Nothing to do. The previous state was enabled.
+ return true;
+ }
+ }
+ mSettings.appendAndPutEnabledInputMethodLocked(id, false);
+ // Previous state was disabled.
+ return false;
+ } else {
+ StringBuilder builder = new StringBuilder();
+ if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+ builder, enabledInputMethodsList, id)) {
+ // Disabled input method is currently selected, switch to another one.
+ String selId = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+ if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
+ Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
+ resetSelectedInputMethodAndSubtypeLocked("");
+ }
+ // Previous state was enabled.
+ return true;
+ } else {
+ // We are disabling the input method but it is already disabled.
+ // Nothing to do. The previous state was disabled.
+ return false;
+ }
+ }
+ }
+
+ private boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
+ if (subtype == null) return true;
+ String[] extraValues = subtype.getExtraValue().split(",");
+ final int N = extraValues.length;
+ for (int i = 0; i < N; ++i) {
+ if (SUBTYPE_EXTRAVALUE_EXCLUDE_FROM_LAST_IME.equals(extraValues[i])) {
+ return false;
}
}
+ return true;
+ }
- StringBuilder builder = new StringBuilder(256);
+ private void saveCurrentInputMethodAndSubtypeToHistory() {
+ String subtypeId = NOT_A_SUBTYPE_ID_STR;
+ if (mCurrentSubtype != null) {
+ subtypeId = String.valueOf(mCurrentSubtype.hashCode());
+ }
+ if (canAddToLastInputMethod(mCurrentSubtype)) {
+ mSettings.addSubtypeToHistory(mCurMethodId, subtypeId);
+ }
+ }
- boolean removed = false;
- String firstId = null;
+ private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
+ boolean setSubtypeOnly) {
+ mOldSystemSettingsVersion = getSystemSettingsVersion();
+ // Update the history of InputMethod and Subtype
+ saveCurrentInputMethodAndSubtypeToHistory();
- // Look through the currently enabled input methods.
- String enabledStr = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS);
- if (enabledStr != null) {
- final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
- splitter.setString(enabledStr);
- while (splitter.hasNext()) {
- String curId = splitter.next();
- if (curId.equals(id)) {
- if (enabled) {
- // We are enabling this input method, but it is
- // already enabled. Nothing to do. The previous
- // state was enabled.
- return true;
+ // Set Subtype here
+ if (imi == null || subtypeId < 0) {
+ mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+ mCurrentSubtype = null;
+ } else {
+ if (subtypeId < imi.getSubtypeCount()) {
+ InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
+ mSettings.putSelectedSubtype(subtype.hashCode());
+ mCurrentSubtype = subtype;
+ } else {
+ mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+ mCurrentSubtype = null;
+ }
+ }
+
+ if (!setSubtypeOnly) {
+ // Set InputMethod here
+ mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
+ }
+ }
+
+ private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
+ InputMethodInfo imi = mMethodMap.get(newDefaultIme);
+ int lastSubtypeId = NOT_A_SUBTYPE_ID;
+ // newDefaultIme is empty when there is no candidate for the selected IME.
+ if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
+ String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
+ if (subtypeHashCode != null) {
+ try {
+ lastSubtypeId = getSubtypeIdFromHashCode(
+ imi, Integer.valueOf(subtypeHashCode));
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
+ }
+ }
+ }
+ setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
+ }
+
+ private int getSelectedInputMethodSubtypeId(String id) {
+ InputMethodInfo imi = mMethodMap.get(id);
+ if (imi == null) {
+ return NOT_A_SUBTYPE_ID;
+ }
+ int subtypeId;
+ try {
+ subtypeId = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
+ } catch (SettingNotFoundException e) {
+ return NOT_A_SUBTYPE_ID;
+ }
+ return getSubtypeIdFromHashCode(imi, subtypeId);
+ }
+
+ private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
+ if (imi != null) {
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ InputMethodSubtype ims = imi.getSubtypeAt(i);
+ if (subtypeHashCode == ims.hashCode()) {
+ return i;
+ }
+ }
+ }
+ return NOT_A_SUBTYPE_ID;
+ }
+
+ private static ArrayList<InputMethodSubtype> getApplicableSubtypesLocked(
+ Resources res, List<InputMethodSubtype> subtypes) {
+ final String systemLocale = res.getConfiguration().locale.toString();
+ if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
+ HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
+ new HashMap<String, InputMethodSubtype>();
+ final int N = subtypes.size();
+ boolean containsKeyboardSubtype = false;
+ for (int i = 0; i < N; ++i) {
+ InputMethodSubtype subtype = subtypes.get(i);
+ final String locale = subtype.getLocale();
+ final String mode = subtype.getMode();
+ // When system locale starts with subtype's locale, that subtype will be applicable
+ // for system locale
+ // For instance, it's clearly applicable for cases like system locale = en_US and
+ // subtype = en, but it is not necessarily considered applicable for cases like system
+ // locale = en and subtype = en_US.
+ // We just call systemLocale.startsWith(locale) in this function because there is no
+ // need to find applicable subtypes aggressively unlike
+ // findLastResortApplicableSubtypeLocked.
+ if (systemLocale.startsWith(locale)) {
+ InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
+ // If more applicable subtypes are contained, skip.
+ if (applicableSubtype != null
+ && systemLocale.equals(applicableSubtype.getLocale())) continue;
+ applicableModeAndSubtypesMap.put(mode, subtype);
+ if (!containsKeyboardSubtype
+ && SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())) {
+ containsKeyboardSubtype = true;
+ }
+ }
+ }
+ ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
+ applicableModeAndSubtypesMap.values());
+ if (!containsKeyboardSubtype) {
+ InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
+ res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
+ if (lastResortKeyboardSubtype != null) {
+ applicableSubtypes.add(lastResortKeyboardSubtype);
+ }
+ }
+ return applicableSubtypes;
+ }
+
+ /**
+ * If there are no selected subtypes, tries finding the most applicable one according to the
+ * given locale.
+ * @param subtypes this function will search the most applicable subtype in subtypes
+ * @param mode subtypes will be filtered by mode
+ * @param locale subtypes will be filtered by locale
+ * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
+ * it will return the first subtype matched with mode
+ * @return the most applicable subtypeId
+ */
+ private static InputMethodSubtype findLastResortApplicableSubtypeLocked(
+ Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
+ boolean canIgnoreLocaleAsLastResort) {
+ if (subtypes == null || subtypes.size() == 0) {
+ return null;
+ }
+ if (TextUtils.isEmpty(locale)) {
+ locale = res.getConfiguration().locale.toString();
+ }
+ final String language = locale.substring(0, 2);
+ boolean partialMatchFound = false;
+ InputMethodSubtype applicableSubtype = null;
+ InputMethodSubtype firstMatchedModeSubtype = null;
+ final int N = subtypes.size();
+ for (int i = 0; i < N; ++i) {
+ InputMethodSubtype subtype = subtypes.get(i);
+ final String subtypeLocale = subtype.getLocale();
+ // An applicable subtype should match "mode". If mode is null, mode will be ignored,
+ // and all subtypes with all modes can be candidates.
+ if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
+ if (firstMatchedModeSubtype == null) {
+ firstMatchedModeSubtype = subtype;
+ }
+ if (locale.equals(subtypeLocale)) {
+ // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
+ applicableSubtype = subtype;
+ break;
+ } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
+ // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
+ applicableSubtype = subtype;
+ partialMatchFound = true;
+ }
+ }
+ }
+
+ if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
+ return firstMatchedModeSubtype;
+ }
+
+ // The first subtype applicable to the system locale will be defined as the most applicable
+ // subtype.
+ if (DEBUG) {
+ if (applicableSubtype != null) {
+ Slog.d(TAG, "Applicable InputMethodSubtype was found: "
+ + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
+ }
+ }
+ return applicableSubtype;
+ }
+
+ // If there are no selected shortcuts, tries finding the most applicable ones.
+ private Pair<InputMethodInfo, InputMethodSubtype>
+ findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
+ List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
+ InputMethodInfo mostApplicableIMI = null;
+ InputMethodSubtype mostApplicableSubtype = null;
+ boolean foundInSystemIME = false;
+
+ // Search applicable subtype for each InputMethodInfo
+ for (InputMethodInfo imi: imis) {
+ final String imiId = imi.getId();
+ if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
+ continue;
+ }
+ InputMethodSubtype subtype = null;
+ final List<InputMethodSubtype> enabledSubtypes =
+ getEnabledInputMethodSubtypeList(imi, true);
+ // 1. Search by the current subtype's locale from enabledSubtypes.
+ if (mCurrentSubtype != null) {
+ subtype = findLastResortApplicableSubtypeLocked(
+ mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
+ }
+ // 2. Search by the system locale from enabledSubtypes.
+ // 3. Search the first enabled subtype matched with mode from enabledSubtypes.
+ if (subtype == null) {
+ subtype = findLastResortApplicableSubtypeLocked(
+ mRes, enabledSubtypes, mode, null, true);
+ }
+ // 4. Search by the current subtype's locale from all subtypes.
+ if (subtype == null && mCurrentSubtype != null) {
+ subtype = findLastResortApplicableSubtypeLocked(
+ mRes, getSubtypes(imi), mode, mCurrentSubtype.getLocale(), false);
+ }
+ // 5. Search by the system locale from all subtypes.
+ // 6. Search the first enabled subtype matched with mode from all subtypes.
+ if (subtype == null) {
+ subtype = findLastResortApplicableSubtypeLocked(
+ mRes, getSubtypes(imi), mode, null, true);
+ }
+ if (subtype != null) {
+ if (imiId.equals(mCurMethodId)) {
+ // The current input method is the most applicable IME.
+ mostApplicableIMI = imi;
+ mostApplicableSubtype = subtype;
+ break;
+ } else if (!foundInSystemIME) {
+ // The system input method is 2nd applicable IME.
+ mostApplicableIMI = imi;
+ mostApplicableSubtype = subtype;
+ if ((imi.getServiceInfo().applicationInfo.flags
+ & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ foundInSystemIME = true;
}
+ }
+ }
+ }
+ if (DEBUG) {
+ if (mostApplicableIMI != null) {
+ Slog.w(TAG, "Most applicable shortcut input method was:"
+ + mostApplicableIMI.getId());
+ if (mostApplicableSubtype != null) {
+ Slog.w(TAG, "Most applicable shortcut input method subtype was:"
+ + "," + mostApplicableSubtype.getMode() + ","
+ + mostApplicableSubtype.getLocale());
+ }
+ }
+ }
+ if (mostApplicableIMI != null) {
+ return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI,
+ mostApplicableSubtype);
+ } else {
+ return null;
+ }
+ }
+
+ private static long getSystemSettingsVersion() {
+ return SystemProperties.getLong(Settings.Secure.SYS_PROP_SETTING_VERSION, 0);
+ }
+
+ /**
+ * @return Return the current subtype of this input method.
+ */
+ public InputMethodSubtype getCurrentInputMethodSubtype() {
+ boolean subtypeIsSelected = false;
+ try {
+ subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID;
+ } catch (SettingNotFoundException e) {
+ }
+ synchronized (mMethodMap) {
+ if (!subtypeIsSelected || mCurrentSubtype == null) {
+ String lastInputMethodId = Settings.Secure.getString(
+ mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+ int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
+ if (subtypeId == NOT_A_SUBTYPE_ID) {
+ InputMethodInfo imi = mMethodMap.get(lastInputMethodId);
+ if (imi != null) {
+ // If there are no selected subtypes, the framework will try to find
+ // the most applicable subtype from explicitly or implicitly enabled
+ // subtypes.
+ List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
+ getEnabledInputMethodSubtypeList(imi, true);
+ // If there is only one explicitly or implicitly enabled subtype,
+ // just returns it.
+ if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
+ mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
+ } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
+ mCurrentSubtype = findLastResortApplicableSubtypeLocked(
+ mRes, explicitlyOrImplicitlyEnabledSubtypes,
+ SUBTYPE_MODE_KEYBOARD, null, true);
+ if (mCurrentSubtype == null) {
+ mCurrentSubtype = findLastResortApplicableSubtypeLocked(
+ mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
+ true);
+ }
+ }
+ }
+ } else {
+ mCurrentSubtype =
+ getSubtypes(mMethodMap.get(lastInputMethodId)).get(subtypeId);
+ }
+ }
+ return mCurrentSubtype;
+ }
+ }
+
+ private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
+ InputMethodSubtype subtype) {
+ if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) {
+ mShortcutInputMethodsAndSubtypes.get(imi).add(subtype);
+ } else {
+ ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+ subtypes.add(subtype);
+ mShortcutInputMethodsAndSubtypes.put(imi, subtypes);
+ }
+ }
+
+ // TODO: We should change the return type from List to List<Parcelable>
+ public List getShortcutInputMethodsAndSubtypes() {
+ synchronized (mMethodMap) {
+ ArrayList<Object> ret = new ArrayList<Object>();
+ if (mShortcutInputMethodsAndSubtypes.size() == 0) {
+ // If there are no selected shortcut subtypes, the framework will try to find
+ // the most applicable subtype from all subtypes whose mode is
+ // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
+ Pair<InputMethodInfo, InputMethodSubtype> info =
+ findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
+ SUBTYPE_MODE_VOICE);
+ if (info != null) {
+ ret.add(info.first);
+ ret.add(info.second);
+ }
+ return ret;
+ }
+ for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
+ ret.add(imi);
+ for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
+ ret.add(subtype);
+ }
+ }
+ return ret;
+ }
+ }
+
+ public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
+ synchronized (mMethodMap) {
+ if (subtype != null && mCurMethodId != null) {
+ InputMethodInfo imi = mMethodMap.get(mCurMethodId);
+ int subtypeId = getSubtypeIdFromHashCode(imi, subtype.hashCode());
+ if (subtypeId != NOT_A_SUBTYPE_ID) {
+ setInputMethodLocked(mCurMethodId, subtypeId);
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Utility class for putting and getting settings for InputMethod
+ * TODO: Move all putters and getters of settings to this class.
+ */
+ private static class InputMethodSettings {
+ // The string for enabled input method is saved as follows:
+ // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
+ private static final char INPUT_METHOD_SEPARATER = ':';
+ private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
+ private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
+
+ private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
+
+ private final Resources mRes;
+ private final ContentResolver mResolver;
+ private final HashMap<String, InputMethodInfo> mMethodMap;
+ private final ArrayList<InputMethodInfo> mMethodList;
+
+ private String mEnabledInputMethodsStrCache;
+
+ private static void buildEnabledInputMethodsSettingString(
+ StringBuilder builder, Pair<String, ArrayList<String>> pair) {
+ String id = pair.first;
+ ArrayList<String> subtypes = pair.second;
+ builder.append(id);
+ // Inputmethod and subtypes are saved in the settings as follows:
+ // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
+ for (String subtypeId: subtypes) {
+ builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
+ }
+ }
+
+ public InputMethodSettings(
+ Resources res, ContentResolver resolver,
+ HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList) {
+ mRes = res;
+ mResolver = resolver;
+ mMethodMap = methodMap;
+ mMethodList = methodList;
+ }
+
+ public List<InputMethodInfo> getEnabledInputMethodListLocked() {
+ return createEnabledInputMethodListLocked(
+ getEnabledInputMethodsAndSubtypeListLocked());
+ }
+
+ public List<Pair<InputMethodInfo, ArrayList<String>>>
+ getEnabledInputMethodAndSubtypeHashCodeListLocked() {
+ return createEnabledInputMethodAndSubtypeHashCodeListLocked(
+ getEnabledInputMethodsAndSubtypeListLocked());
+ }
+
+ public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
+ InputMethodInfo imi) {
+ List<Pair<String, ArrayList<String>>> imsList =
+ getEnabledInputMethodsAndSubtypeListLocked();
+ ArrayList<InputMethodSubtype> enabledSubtypes =
+ new ArrayList<InputMethodSubtype>();
+ if (imi != null) {
+ for (Pair<String, ArrayList<String>> imsPair : imsList) {
+ InputMethodInfo info = mMethodMap.get(imsPair.first);
+ if (info != null && info.getId().equals(imi.getId())) {
+ final int subtypeCount = info.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ InputMethodSubtype ims = info.getSubtypeAt(i);
+ for (String s: imsPair.second) {
+ if (String.valueOf(ims.hashCode()).equals(s)) {
+ enabledSubtypes.add(ims);
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ return enabledSubtypes;
+ }
+
+ // At the initial boot, the settings for input methods are not set,
+ // so we need to enable IME in that case.
+ public void enableAllIMEsIfThereIsNoEnabledIME() {
+ if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
+ StringBuilder sb = new StringBuilder();
+ final int N = mMethodList.size();
+ for (int i = 0; i < N; i++) {
+ InputMethodInfo imi = mMethodList.get(i);
+ Slog.i(TAG, "Adding: " + imi.getId());
+ if (i > 0) sb.append(':');
+ sb.append(imi.getId());
+ }
+ putEnabledInputMethodsStr(sb.toString());
+ }
+ }
+
+ private List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
+ ArrayList<Pair<String, ArrayList<String>>> imsList
+ = new ArrayList<Pair<String, ArrayList<String>>>();
+ final String enabledInputMethodsStr = getEnabledInputMethodsStr();
+ if (TextUtils.isEmpty(enabledInputMethodsStr)) {
+ return imsList;
+ }
+ mInputMethodSplitter.setString(enabledInputMethodsStr);
+ while (mInputMethodSplitter.hasNext()) {
+ String nextImsStr = mInputMethodSplitter.next();
+ mSubtypeSplitter.setString(nextImsStr);
+ if (mSubtypeSplitter.hasNext()) {
+ ArrayList<String> subtypeHashes = new ArrayList<String>();
+ // The first element is ime id.
+ String imeId = mSubtypeSplitter.next();
+ while (mSubtypeSplitter.hasNext()) {
+ subtypeHashes.add(mSubtypeSplitter.next());
+ }
+ imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
+ }
+ }
+ return imsList;
+ }
+
+ public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
+ if (reloadInputMethodStr) {
+ getEnabledInputMethodsStr();
+ }
+ if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
+ // Add in the newly enabled input method.
+ putEnabledInputMethodsStr(id);
+ } else {
+ putEnabledInputMethodsStr(
+ mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
+ }
+ }
+
+ /**
+ * Build and put a string of EnabledInputMethods with removing specified Id.
+ * @return the specified id was removed or not.
+ */
+ public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+ StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
+ boolean isRemoved = false;
+ boolean needsAppendSeparator = false;
+ for (Pair<String, ArrayList<String>> ims: imsList) {
+ String curId = ims.first;
+ if (curId.equals(id)) {
// We are disabling this input method, and it is
// currently enabled. Skip it to remove from the
// new list.
- removed = true;
- } else if (!enabled) {
- // We are building a new list of input methods that
- // doesn't contain the given one.
- if (firstId == null) firstId = curId;
- if (builder.length() > 0) builder.append(':');
- builder.append(curId);
+ isRemoved = true;
+ } else {
+ if (needsAppendSeparator) {
+ builder.append(INPUT_METHOD_SEPARATER);
+ } else {
+ needsAppendSeparator = true;
+ }
+ buildEnabledInputMethodsSettingString(builder, ims);
}
}
+ if (isRemoved) {
+ // Update the setting with the new list of input methods.
+ putEnabledInputMethodsStr(builder.toString());
+ }
+ return isRemoved;
}
- if (!enabled) {
- if (!removed) {
- // We are disabling the input method but it is already
- // disabled. Nothing to do. The previous state was
- // disabled.
- return false;
+ private List<InputMethodInfo> createEnabledInputMethodListLocked(
+ List<Pair<String, ArrayList<String>>> imsList) {
+ final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
+ for (Pair<String, ArrayList<String>> ims: imsList) {
+ InputMethodInfo info = mMethodMap.get(ims.first);
+ if (info != null) {
+ res.add(info);
+ }
}
- // Update the setting with the new list of input methods.
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
- // We the disabled input method is currently selected, switch
- // to another one.
- String selId = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD);
- if (id.equals(selId)) {
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_INPUT_METHOD,
- firstId != null ? firstId : "");
+ return res;
+ }
+
+ private List<Pair<InputMethodInfo, ArrayList<String>>>
+ createEnabledInputMethodAndSubtypeHashCodeListLocked(
+ List<Pair<String, ArrayList<String>>> imsList) {
+ final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
+ = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
+ for (Pair<String, ArrayList<String>> ims : imsList) {
+ InputMethodInfo info = mMethodMap.get(ims.first);
+ if (info != null) {
+ res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
+ }
}
- // Previous state was enabled.
- return true;
+ return res;
}
- // Add in the newly enabled input method.
- if (enabledStr == null || enabledStr.length() == 0) {
- enabledStr = id;
- } else {
- enabledStr = enabledStr + ':' + id;
+ private void putEnabledInputMethodsStr(String str) {
+ Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str);
+ mEnabledInputMethodsStrCache = str;
}
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS, enabledStr);
+ private String getEnabledInputMethodsStr() {
+ mEnabledInputMethodsStrCache = Settings.Secure.getString(
+ mResolver, Settings.Secure.ENABLED_INPUT_METHODS);
+ if (DEBUG) {
+ Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache);
+ }
+ return mEnabledInputMethodsStrCache;
+ }
- // Previous state was disabled.
- return false;
+ private void saveSubtypeHistory(
+ List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
+ StringBuilder builder = new StringBuilder();
+ boolean isImeAdded = false;
+ if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
+ builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
+ newSubtypeId);
+ isImeAdded = true;
+ }
+ for (Pair<String, String> ime: savedImes) {
+ String imeId = ime.first;
+ String subtypeId = ime.second;
+ if (TextUtils.isEmpty(subtypeId)) {
+ subtypeId = NOT_A_SUBTYPE_ID_STR;
+ }
+ if (isImeAdded) {
+ builder.append(INPUT_METHOD_SEPARATER);
+ } else {
+ isImeAdded = true;
+ }
+ builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
+ subtypeId);
+ }
+ // Remove the last INPUT_METHOD_SEPARATER
+ putSubtypeHistoryStr(builder.toString());
+ }
+
+ public void addSubtypeToHistory(String imeId, String subtypeId) {
+ List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
+ for (Pair<String, String> ime: subtypeHistory) {
+ if (ime.first.equals(imeId)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
+ + ime.second);
+ }
+ // We should break here
+ subtypeHistory.remove(ime);
+ break;
+ }
+ }
+ if (DEBUG) {
+ Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
+ }
+ saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
+ }
+
+ private void putSubtypeHistoryStr(String str) {
+ if (DEBUG) {
+ Slog.d(TAG, "putSubtypeHistoryStr: " + str);
+ }
+ Settings.Secure.putString(
+ mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
+ }
+
+ public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
+ // Gets the first one from the history
+ return getLastSubtypeForInputMethodLockedInternal(null);
+ }
+
+ public String getLastSubtypeForInputMethodLocked(String imeId) {
+ Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
+ if (ime != null) {
+ return ime.second;
+ } else {
+ return null;
+ }
+ }
+
+ private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
+ List<Pair<String, ArrayList<String>>> enabledImes =
+ getEnabledInputMethodsAndSubtypeListLocked();
+ List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
+ for (Pair<String, String> imeAndSubtype: subtypeHistory) {
+ final String imeInTheHistory = imeAndSubtype.first;
+ // If imeId is empty, returns the first IME and subtype in the history
+ if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
+ final String subtypeInTheHistory = imeAndSubtype.second;
+ final String subtypeHashCode =
+ getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
+ enabledImes, imeInTheHistory, subtypeInTheHistory);
+ if (!TextUtils.isEmpty(subtypeHashCode)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
+ }
+ return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
+ }
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "No enabled IME found in the history");
+ }
+ return null;
+ }
+
+ private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
+ ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
+ for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
+ if (enabledIme.first.equals(imeId)) {
+ final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
+ if (explicitlyEnabledSubtypes.size() == 0) {
+ // If there are no explicitly enabled subtypes, applicable subtypes are
+ // enabled implicitly.
+ InputMethodInfo ime = mMethodMap.get(imeId);
+ // If IME is enabled and no subtypes are enabled, applicable subtypes
+ // are enabled implicitly, so needs to treat them to be enabled.
+ if (ime != null && ime.getSubtypeCount() > 0) {
+ List<InputMethodSubtype> implicitlySelectedSubtypes =
+ getApplicableSubtypesLocked(mRes, getSubtypes(ime));
+ if (implicitlySelectedSubtypes != null) {
+ final int N = implicitlySelectedSubtypes.size();
+ for (int i = 0; i < N; ++i) {
+ final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
+ if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
+ return subtypeHashCode;
+ }
+ }
+ }
+ }
+ } else {
+ for (String s: explicitlyEnabledSubtypes) {
+ if (s.equals(subtypeHashCode)) {
+ // If both imeId and subtypeId are enabled, return subtypeId.
+ return s;
+ }
+ }
+ }
+ // If imeId was enabled but subtypeId was disabled.
+ return NOT_A_SUBTYPE_ID_STR;
+ }
+ }
+ // If both imeId and subtypeId are disabled, return null
+ return null;
+ }
+
+ private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
+ ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
+ final String subtypeHistoryStr = getSubtypeHistoryStr();
+ if (TextUtils.isEmpty(subtypeHistoryStr)) {
+ return imsList;
+ }
+ mInputMethodSplitter.setString(subtypeHistoryStr);
+ while (mInputMethodSplitter.hasNext()) {
+ String nextImsStr = mInputMethodSplitter.next();
+ mSubtypeSplitter.setString(nextImsStr);
+ if (mSubtypeSplitter.hasNext()) {
+ String subtypeId = NOT_A_SUBTYPE_ID_STR;
+ // The first element is ime id.
+ String imeId = mSubtypeSplitter.next();
+ while (mSubtypeSplitter.hasNext()) {
+ subtypeId = mSubtypeSplitter.next();
+ break;
+ }
+ imsList.add(new Pair<String, String>(imeId, subtypeId));
+ }
+ }
+ return imsList;
+ }
+
+ private String getSubtypeHistoryStr() {
+ if (DEBUG) {
+ Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString(
+ mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY));
+ }
+ return Settings.Secure.getString(
+ mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY);
+ }
+
+ public void putSelectedInputMethod(String imeId) {
+ Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
+ }
+
+ public void putSelectedSubtype(int subtypeId) {
+ Settings.Secure.putInt(
+ mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
+ }
}
// ----------------------------------------------------------------------
diff --git a/services/java/com/android/server/InputWindow.java b/services/java/com/android/server/InputWindow.java
index befc770..2c2cdfe 100644
--- a/services/java/com/android/server/InputWindow.java
+++ b/services/java/com/android/server/InputWindow.java
@@ -16,68 +16,63 @@
package com.android.server;
+import android.graphics.Region;
import android.view.InputChannel;
/**
* Describes input-related window properties for use by the input dispatcher.
- *
* @hide
*/
public final class InputWindow {
+ // The window handle.
+ public InputWindowHandle inputWindowHandle;
+
// The input channel associated with the window.
public InputChannel inputChannel;
-
+
// The window name.
public String name;
-
+
// Window layout params attributes. (WindowManager.LayoutParams)
public int layoutParamsFlags;
public int layoutParamsType;
-
+
// Dispatching timeout.
public long dispatchingTimeoutNanos;
-
- // Window frame area.
+
+ // Window frame.
public int frameLeft;
public int frameTop;
public int frameRight;
public int frameBottom;
-
- // Window visible frame area.
- public int visibleFrameLeft;
- public int visibleFrameTop;
- public int visibleFrameRight;
- public int visibleFrameBottom;
-
- // Window touchable area.
- public int touchableAreaLeft;
- public int touchableAreaTop;
- public int touchableAreaRight;
- public int touchableAreaBottom;
-
+
+ // Window touchable region.
+ public final Region touchableRegion = new Region();
+
// Window is visible.
public boolean visible;
-
+
// Window can receive keys.
public boolean canReceiveKeys;
-
+
// Window has focus.
public boolean hasFocus;
-
+
// Window has wallpaper. (window is the current wallpaper target)
public boolean hasWallpaper;
-
+
// Input event dispatching is paused.
public boolean paused;
-
+
// Window layer.
public int layer;
-
+
// Id of process and user that owns the window.
public int ownerPid;
public int ownerUid;
-
+
public void recycle() {
+ inputWindowHandle = null;
inputChannel = null;
}
}
diff --git a/services/java/com/android/server/InputWindowHandle.java b/services/java/com/android/server/InputWindowHandle.java
new file mode 100644
index 0000000..4b92939
--- /dev/null
+++ b/services/java/com/android/server/InputWindowHandle.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.view.WindowManagerPolicy;
+
+/**
+ * Functions as a handle for a window that can receive input.
+ * Enables the native input dispatcher to refer indirectly to the window manager's window state.
+ * @hide
+ */
+public final class InputWindowHandle {
+ // Pointer to the native input window handle.
+ // This field is lazily initialized via JNI.
+ @SuppressWarnings("unused")
+ private int ptr;
+
+ // The input application handle.
+ public final InputApplicationHandle inputApplicationHandle;
+
+ // The window manager's window state.
+ public final WindowManagerPolicy.WindowState windowState;
+
+ private native void nativeDispose();
+
+ public InputWindowHandle(InputApplicationHandle inputApplicationHandle,
+ WindowManagerPolicy.WindowState windowState) {
+ this.inputApplicationHandle = inputApplicationHandle;
+ this.windowState = windowState;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ nativeDispose();
+ super.finalize();
+ }
+}
diff --git a/services/java/com/android/server/LightsService.java b/services/java/com/android/server/LightsService.java
index 9b57735..21f2bcf 100644
--- a/services/java/com/android/server/LightsService.java
+++ b/services/java/com/android/server/LightsService.java
@@ -24,12 +24,12 @@ import android.os.ServiceManager;
import android.os.Message;
import android.util.Slog;
-import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class LightsService {
private static final String TAG = "LightsService";
+ private static final boolean DEBUG = false;
static final int LIGHT_ID_BACKLIGHT = 0;
static final int LIGHT_ID_KEYBOARD = 1;
@@ -115,6 +115,8 @@ public class LightsService {
private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) {
if (color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS) {
+ if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#"
+ + Integer.toHexString(color));
mColor = color;
mMode = mode;
mOnMS = onMS;
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 10107c6..656ec4d 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -479,14 +479,17 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
mEnabledProviders.add(passiveProvider.getName());
// initialize external network location and geocoder services
- if (mNetworkLocationProviderPackageName != null) {
+ PackageManager pm = mContext.getPackageManager();
+ if (mNetworkLocationProviderPackageName != null &&
+ pm.resolveService(new Intent(mNetworkLocationProviderPackageName), 0) != null) {
mNetworkLocationProvider =
new LocationProviderProxy(mContext, LocationManager.NETWORK_PROVIDER,
mNetworkLocationProviderPackageName, mLocationHandler);
addProvider(mNetworkLocationProvider);
}
- if (mGeocodeProviderPackageName != null) {
+ if (mGeocodeProviderPackageName != null &&
+ pm.resolveService(new Intent(mGeocodeProviderPackageName), 0) != null) {
mGeocodeProvider = new GeocoderProxy(mContext, mGeocodeProviderPackageName);
}
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index e6e3e5f..436eff0 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -19,6 +19,7 @@ package com.android.server;
import com.android.internal.app.IMediaContainerService;
import com.android.server.am.ActivityManagerService;
+import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -45,6 +46,7 @@ import android.os.storage.IMountShutdownObserver;
import android.os.storage.IObbActionListener;
import android.os.storage.OnObbStateChangeListener;
import android.os.storage.StorageResultCode;
+import android.text.TextUtils;
import android.util.Slog;
import java.io.FileDescriptor;
@@ -73,8 +75,8 @@ import javax.crypto.spec.PBEKeySpec;
* @hide - Applications should use android.os.storage.StorageManager
* to access the MountService.
*/
-class MountService extends IMountService.Stub
- implements INativeDaemonConnectorCallbacks {
+class MountService extends IMountService.Stub implements INativeDaemonConnectorCallbacks {
+
private static final boolean LOCAL_LOGD = false;
private static final boolean DEBUG_UNMOUNT = false;
private static final boolean DEBUG_EVENTS = false;
@@ -151,6 +153,8 @@ class MountService extends IMountService.Stub
private boolean mBooted = false;
private boolean mReady = false;
private boolean mSendUmsConnectedOnBoot = false;
+ // true if we should fake MEDIA_MOUNTED state for external storage
+ private boolean mEmulateExternalStorage = false;
/**
* Private hash of currently mounted secure containers.
@@ -332,6 +336,7 @@ class MountService extends IMountService.Stub
super(l);
}
+ @Override
public void handleMessage(Message msg) {
switch (msg.what) {
case H_UNMOUNT_PM_UPDATE: {
@@ -425,6 +430,7 @@ class MountService extends IMountService.Stub
}
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
@@ -440,12 +446,15 @@ class MountService extends IMountService.Stub
return;
}
new Thread() {
+ @Override
public void run() {
try {
String path = Environment.getExternalStorageDirectory().getPath();
String state = getVolumeState(path);
- if (state.equals(Environment.MEDIA_UNMOUNTED)) {
+ if (mEmulateExternalStorage) {
+ notifyVolumeStateChange(null, path, VolumeState.NoMedia, VolumeState.Mounted);
+ } else if (state.equals(Environment.MEDIA_UNMOUNTED)) {
int rc = doMountVolume(path);
if (rc != StorageResultCode.OperationSucceeded) {
Slog.e(TAG, String.format("Boot-time mount failed (%d)", rc));
@@ -516,21 +525,21 @@ class MountService extends IMountService.Stub
Slog.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
return;
}
+ // Update state on PackageManager, but only of real events
+ if (!mEmulateExternalStorage) {
+ if (Environment.MEDIA_UNMOUNTED.equals(state)) {
+ mPms.updateExternalMediaStatus(false, false);
- if (Environment.MEDIA_UNMOUNTED.equals(state)) {
- // Tell the package manager the media is gone.
- mPms.updateExternalMediaStatus(false, false);
-
- /*
- * Some OBBs might have been unmounted when this volume was
- * unmounted, so send a message to the handler to let it know to
- * remove those from the list of mounted OBBS.
- */
- mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_FLUSH_MOUNT_STATE,
- path));
- } else if (Environment.MEDIA_MOUNTED.equals(state)) {
- // Tell the package manager the media is available for use.
- mPms.updateExternalMediaStatus(true, false);
+ /*
+ * Some OBBs might have been unmounted when this volume was
+ * unmounted, so send a message to the handler to let it know to
+ * remove those from the list of mounted OBBS.
+ */
+ mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_FLUSH_MOUNT_STATE,
+ path));
+ } else if (Environment.MEDIA_MOUNTED.equals(state)) {
+ mPms.updateExternalMediaStatus(true, false);
+ }
}
String oldState = mLegacyState;
@@ -561,6 +570,7 @@ class MountService extends IMountService.Stub
* we need to do our work in a new thread.
*/
new Thread() {
+ @Override
public void run() {
/**
* Determine media state and UMS detection status
@@ -610,7 +620,7 @@ class MountService extends IMountService.Stub
Slog.w(TAG, "Failed to get share availability");
}
/*
- * Now that we've done our initialization, release
+ * Now that we've done our initialization, release
* the hounds!
*/
mReady = true;
@@ -674,6 +684,7 @@ class MountService extends IMountService.Stub
if (code == VoldResponseCode.VolumeDiskInserted) {
new Thread() {
+ @Override
public void run() {
try {
int rc;
@@ -1003,6 +1014,7 @@ class MountService extends IMountService.Stub
* USB mass storage disconnected while enabled
*/
new Thread() {
+ @Override
public void run() {
try {
int rc;
@@ -1040,6 +1052,13 @@ class MountService extends IMountService.Stub
public MountService(Context context) {
mContext = context;
+ mEmulateExternalStorage = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_emulateExternalStorage);
+ if (mEmulateExternalStorage) {
+ Slog.d(TAG, "using emulated external storage");
+ mLegacyState = Environment.MEDIA_MOUNTED;
+ }
+
// XXX: This will go away soon in favor of IMountServiceObserver
mPms = (PackageManagerService) ServiceManager.getService("package");
@@ -1220,7 +1239,7 @@ class MountService extends IMountService.Stub
waitForReady();
return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums");
}
-
+
/**
* @return state of the volume at the specified mount point
*/
@@ -1237,6 +1256,10 @@ class MountService extends IMountService.Stub
return mLegacyState;
}
+ public boolean isExternalStorageEmulated() {
+ return mEmulateExternalStorage;
+ }
+
public int mountVolume(String path) {
validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
@@ -1386,7 +1409,7 @@ class MountService extends IMountService.Stub
return rc;
}
-
+
public int mountSecureContainer(String id, String key, int ownerUid) {
validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
waitForReady();
@@ -1474,7 +1497,7 @@ class MountService extends IMountService.Stub
synchronized (mAsecMountSet) {
/*
- * Because a mounted container has active internal state which cannot be
+ * Because a mounted container has active internal state which cannot be
* changed while active, we must ensure both ids are not currently mounted.
*/
if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
@@ -1609,6 +1632,101 @@ class MountService extends IMountService.Stub
Slog.i(TAG, "Send to OBB handler: " + action.toString());
}
+ public int decryptStorage(String password) {
+ if (TextUtils.isEmpty(password)) {
+ throw new IllegalArgumentException("password cannot be empty");
+ }
+
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+ "no permission to access the crypt keeper");
+
+ waitForReady();
+
+ if (DEBUG_EVENTS) {
+ Slog.i(TAG, "decrypting storage...");
+ }
+
+ try {
+ ArrayList<String> rsp = mConnector.doCommand("cryptfs checkpw " + password);
+ String[] tokens = rsp.get(0).split(" ");
+
+ if (tokens == null || tokens.length != 2) {
+ return -1;
+ }
+
+ int code = Integer.parseInt(tokens[1]);
+
+ if (code == 0) {
+ // Decrypt was successful. Post a delayed message before restarting in order
+ // to let the UI to clear itself
+ mHandler.postDelayed(new Runnable() {
+ public void run() {
+ mConnector.doCommand(String.format("cryptfs restart"));
+ }
+ }, 1000); // 1 second
+ }
+
+ return code;
+ } catch (NativeDaemonConnectorException e) {
+ // Decryption failed
+ return e.getCode();
+ }
+ }
+
+ public int encryptStorage(String password) {
+ if (TextUtils.isEmpty(password)) {
+ throw new IllegalArgumentException("password cannot be empty");
+ }
+
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+ "no permission to access the crypt keeper");
+
+ waitForReady();
+
+ if (DEBUG_EVENTS) {
+ Slog.i(TAG, "encrypting storage...");
+ }
+
+ try {
+ mConnector.doCommand(String.format("cryptfs enablecrypto inplace %s", password));
+ } catch (NativeDaemonConnectorException e) {
+ // Encryption failed
+ return e.getCode();
+ }
+
+ return 0;
+ }
+
+ public int changeEncryptionPassword(String password) {
+ if (TextUtils.isEmpty(password)) {
+ throw new IllegalArgumentException("password cannot be empty");
+ }
+
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+ "no permission to access the crypt keeper");
+
+ waitForReady();
+
+ if (DEBUG_EVENTS) {
+ Slog.i(TAG, "changing encryption password...");
+ }
+
+ try {
+ ArrayList<String> response = mConnector.doCommand("cryptfs changepw " + password);
+
+ String[] tokens = response.get(0).split(" ");
+
+ if (tokens == null || tokens.length != 2) {
+ return -1;
+ }
+
+ return Integer.parseInt(tokens[1]);
+ } catch (NativeDaemonConnectorException e) {
+ // Encryption failed
+ return e.getCode();
+ }
+ }
+
private void addObbStateLocked(ObbState obbState) throws RemoteException {
final IBinder binder = obbState.getBinder();
List<ObbState> obbStates = mObbMounts.get(binder);
@@ -1896,6 +2014,7 @@ class MountService extends IMountService.Stub
mKey = key;
}
+ @Override
public void handleExecute() throws IOException, RemoteException {
waitForReady();
warnOnNotMounted();
@@ -1976,6 +2095,7 @@ class MountService extends IMountService.Stub
}
}
+ @Override
public void handleError() {
sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
}
@@ -2005,6 +2125,7 @@ class MountService extends IMountService.Stub
mForceUnmount = force;
}
+ @Override
public void handleExecute() throws IOException {
waitForReady();
warnOnNotMounted();
@@ -2059,6 +2180,7 @@ class MountService extends IMountService.Stub
}
}
+ @Override
public void handleError() {
sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
}
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index f0acdc0..152605f 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -219,28 +219,6 @@ class NetworkManagementService extends INetworkManagementService.Stub {
}
}
- private static int stringToIpAddr(String addrString) throws UnknownHostException {
- try {
- String[] parts = addrString.split("\\.");
- if (parts.length != 4) {
- throw new UnknownHostException(addrString);
- }
-
- int a = Integer.parseInt(parts[0]) ;
- int b = Integer.parseInt(parts[1]) << 8;
- int c = Integer.parseInt(parts[2]) << 16;
- int d = Integer.parseInt(parts[3]) << 24;
-
- return a | b | c | d;
- } catch (NumberFormatException ex) {
- throw new UnknownHostException(addrString);
- }
- }
-
- public static String intToIpString(int i) {
- return ((i >> 24 ) & 0xFF) + "." + ((i >> 16 ) & 0xFF) + "." + ((i >> 8 ) & 0xFF) + "." +
- (i & 0xFF);
- }
//
// INetworkManagementService members
@@ -288,18 +266,17 @@ class NetworkManagementService extends INetworkManagementService.Stub {
cfg = new InterfaceConfiguration();
cfg.hwAddr = st.nextToken(" ");
try {
- cfg.ipAddr = stringToIpAddr(st.nextToken(" "));
+ cfg.addr = InetAddress.getByName(st.nextToken(" "));
} catch (UnknownHostException uhe) {
Slog.e(TAG, "Failed to parse ipaddr", uhe);
- cfg.ipAddr = 0;
}
try {
- cfg.netmask = stringToIpAddr(st.nextToken(" "));
+ cfg.mask = InetAddress.getByName(st.nextToken(" "));
} catch (UnknownHostException uhe) {
Slog.e(TAG, "Failed to parse netmask", uhe);
- cfg.netmask = 0;
}
+
cfg.interfaceFlags = st.nextToken("]").trim() +"]";
} catch (NoSuchElementException nsee) {
throw new IllegalStateException(
@@ -312,12 +289,13 @@ class NetworkManagementService extends INetworkManagementService.Stub {
public void setInterfaceConfig(
String iface, InterfaceConfiguration cfg) throws IllegalStateException {
String cmd = String.format("interface setcfg %s %s %s %s", iface,
- intToIpString(cfg.ipAddr), intToIpString(cfg.netmask), cfg.interfaceFlags);
+ cfg.addr.getHostAddress(), cfg.mask.getHostAddress(),
+ cfg.interfaceFlags);
try {
mConnector.doCommand(cmd);
} catch (NativeDaemonConnectorException e) {
throw new IllegalStateException(
- "Unable to communicate with native daemon to interface setcfg");
+ "Unable to communicate with native daemon to interface setcfg - " + e);
}
}
diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java
new file mode 100644
index 0000000..52f84eb
--- /dev/null
+++ b/services/java/com/android/server/NetworkTimeUpdateService.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import com.android.internal.telephony.TelephonyIntents;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.SntpClient;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Monitors the network time and updates the system time if it is out of sync
+ * and there hasn't been any NITZ update from the carrier recently.
+ * If looking up the network time fails for some reason, it tries a few times with a short
+ * interval and then resets to checking on longer intervals.
+ * <p>
+ * If the user enables AUTO_TIME, it will check immediately for the network time, if NITZ wasn't
+ * available.
+ * </p>
+ */
+public class NetworkTimeUpdateService {
+
+ private static final String TAG = "NetworkTimeUpdateService";
+ private static final boolean DBG = false;
+
+ private static final int EVENT_AUTO_TIME_CHANGED = 1;
+ private static final int EVENT_POLL_NETWORK_TIME = 2;
+
+ /** Normal polling frequency */
+ private static final long POLLING_INTERVAL_MS = 24L * 60 * 60 * 1000; // 24 hrs
+ /** Try-again polling interval, in case the network request failed */
+ private static final long POLLING_INTERVAL_SHORTER_MS = 60 * 1000L; // 60 seconds
+ /** Number of times to try again */
+ private static final int TRY_AGAIN_TIMES_MAX = 3;
+ /** How long to wait for the NTP server to respond. */
+ private static final int MAX_NTP_FETCH_WAIT_MS = 20 * 1000;
+ /** If the time difference is greater than this threshold, then update the time. */
+ private static final int TIME_ERROR_THRESHOLD_MS = 5 * 1000;
+
+ private static final String ACTION_POLL =
+ "com.android.server.NetworkTimeUpdateService.action.POLL";
+ private static final String PROPERTIES_FILE = "/etc/gps.conf";
+ private static int POLL_REQUEST = 0;
+
+ private static final long NOT_SET = -1;
+ private long mNitzTimeSetTime = NOT_SET;
+ // TODO: Have a way to look up the timezone we are in
+ private long mNitzZoneSetTime = NOT_SET;
+
+ private Context mContext;
+ // NTP lookup is done on this thread and handler
+ private Handler mHandler;
+ private HandlerThread mThread;
+ private AlarmManager mAlarmManager;
+ private PendingIntent mPendingPollIntent;
+ private SettingsObserver mSettingsObserver;
+ // Address of the NTP server
+ private String mNtpServer;
+ // The last time that we successfully fetched the NTP time.
+ private long mLastNtpFetchTime = NOT_SET;
+ // Keeps track of how many quick attempts were made to fetch NTP time.
+ // During bootup, the network may not have been up yet, or it's taking time for the
+ // connection to happen.
+ private int mTryAgainCounter;
+
+ public NetworkTimeUpdateService(Context context) {
+ mContext = context;
+ mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ Intent pollIntent = new Intent(ACTION_POLL, null);
+ mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
+ }
+
+ /** Initialize the receivers and initiate the first NTP request */
+ public void systemReady() {
+ mNtpServer = getNtpServerAddress();
+ if (mNtpServer == null) {
+ Slog.e(TAG, "NTP server address not found, not syncing to NTP time");
+ return;
+ }
+
+ registerForTelephonyIntents();
+ registerForAlarms();
+
+ mThread = new HandlerThread(TAG);
+ mThread.start();
+ mHandler = new MyHandler(mThread.getLooper());
+ // Check the network time on the new thread
+ mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
+
+ mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
+ mSettingsObserver.observe(mContext);
+ }
+
+ private String getNtpServerAddress() {
+ String serverAddress = null;
+ FileInputStream stream = null;
+ try {
+ Properties properties = new Properties();
+ File file = new File(PROPERTIES_FILE);
+ stream = new FileInputStream(file);
+ properties.load(stream);
+ serverAddress = properties.getProperty("NTP_SERVER", null);
+ } catch (IOException e) {
+ Slog.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE);
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (Exception e) {}
+ }
+ }
+ return serverAddress;
+ }
+
+ private void registerForTelephonyIntents() {
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME);
+ intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
+ mContext.registerReceiver(mNitzReceiver, intentFilter);
+ }
+
+ private void registerForAlarms() {
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
+ }
+ }, new IntentFilter(ACTION_POLL));
+ }
+
+ private void onPollNetworkTime(int event) {
+ // If Automatic time is not set, don't bother.
+ if (!isAutomaticTimeRequested()) return;
+
+ final long refTime = SystemClock.elapsedRealtime();
+ // If NITZ time was received less than POLLING_INTERVAL_MS time ago,
+ // no need to sync to NTP.
+ if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < POLLING_INTERVAL_MS) {
+ resetAlarm(POLLING_INTERVAL_MS);
+ return;
+ }
+ final long currentTime = System.currentTimeMillis();
+ if (DBG) Log.d(TAG, "System time = " + currentTime);
+ // Get the NTP time
+ if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + POLLING_INTERVAL_MS
+ || event == EVENT_AUTO_TIME_CHANGED) {
+ if (DBG) Log.d(TAG, "Before Ntp fetch");
+ long ntp = getNtpTime();
+ if (DBG) Log.d(TAG, "Ntp = " + ntp);
+ if (ntp > 0) {
+ mTryAgainCounter = 0;
+ mLastNtpFetchTime = SystemClock.elapsedRealtime();
+ if (Math.abs(ntp - currentTime) > TIME_ERROR_THRESHOLD_MS) {
+ // Set the system time
+ if (DBG) Log.d(TAG, "Ntp time to be set = " + ntp);
+ // Make sure we don't overflow, since it's going to be converted to an int
+ if (ntp / 1000 < Integer.MAX_VALUE) {
+ SystemClock.setCurrentTimeMillis(ntp);
+ }
+ } else {
+ if (DBG) Log.d(TAG, "Ntp time is close enough = " + ntp);
+ }
+ } else {
+ // Try again shortly
+ mTryAgainCounter++;
+ if (mTryAgainCounter <= TRY_AGAIN_TIMES_MAX) {
+ resetAlarm(POLLING_INTERVAL_SHORTER_MS);
+ } else {
+ // Try much later
+ mTryAgainCounter = 0;
+ resetAlarm(POLLING_INTERVAL_MS);
+ }
+ return;
+ }
+ }
+ resetAlarm(POLLING_INTERVAL_MS);
+ }
+
+ /**
+ * Cancel old alarm and starts a new one for the specified interval.
+ *
+ * @param interval when to trigger the alarm, starting from now.
+ */
+ private void resetAlarm(long interval) {
+ mAlarmManager.cancel(mPendingPollIntent);
+ long now = SystemClock.elapsedRealtime();
+ long next = now + interval;
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent);
+ }
+
+ private long getNtpTime() {
+ SntpClient client = new SntpClient();
+ if (client.requestTime(mNtpServer, MAX_NTP_FETCH_WAIT_MS)) {
+ return client.getNtpTime();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Checks if the user prefers to automatically set the time.
+ */
+ private boolean isAutomaticTimeRequested() {
+ return Settings.System.getInt(mContext.getContentResolver(), Settings.System.AUTO_TIME, 0)
+ != 0;
+ }
+
+ /** Receiver for Nitz time events */
+ private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) {
+ mNitzTimeSetTime = SystemClock.elapsedRealtime();
+ } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) {
+ mNitzZoneSetTime = SystemClock.elapsedRealtime();
+ }
+ }
+ };
+
+ /** Handler to do the network accesses on */
+ private class MyHandler extends Handler {
+
+ public MyHandler(Looper l) {
+ super(l);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_AUTO_TIME_CHANGED:
+ case EVENT_POLL_NETWORK_TIME:
+ onPollNetworkTime(msg.what);
+ break;
+ }
+ }
+ }
+
+ /** Observer to watch for changes to the AUTO_TIME setting */
+ private static class SettingsObserver extends ContentObserver {
+
+ private int mMsg;
+ private Handler mHandler;
+
+ SettingsObserver(Handler handler, int msg) {
+ super(handler);
+ mHandler = handler;
+ mMsg = msg;
+ }
+
+ void observe(Context context) {
+ ContentResolver resolver = context.getContentResolver();
+ resolver.registerContentObserver(Settings.System.getUriFor(Settings.System.AUTO_TIME),
+ false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mHandler.obtainMessage(mMsg).sendToTarget();
+ }
+ }
+}
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 4da5eb2..47dce41 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -17,7 +17,6 @@
package com.android.server;
import com.android.internal.statusbar.StatusBarNotification;
-import com.android.server.StatusBarManagerService;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
@@ -38,16 +37,14 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.database.ContentObserver;
-import android.hardware.Usb;
+import android.hardware.UsbManager;
import android.media.AudioManager;
import android.net.Uri;
-import android.os.BatteryManager;
-import android.os.Bundle;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
-import android.os.Power;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -56,8 +53,8 @@ import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.EventLog;
-import android.util.Slog;
import android.util.Log;
+import android.util.Slog;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
@@ -91,8 +88,6 @@ public class NotificationManagerService extends INotificationManager.Stub
private WorkerHandler mHandler;
private StatusBarManagerService mStatusBar;
- private LightsService mLightsService;
- private LightsService.Light mBatteryLight;
private LightsService.Light mNotificationLight;
private LightsService.Light mAttentionLight;
@@ -127,18 +122,8 @@ public class NotificationManagerService extends INotificationManager.Stub
private ArrayList<ToastRecord> mToastQueue;
private ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
-
- private boolean mBatteryCharging;
- private boolean mBatteryLow;
- private boolean mBatteryFull;
private NotificationRecord mLedNotification;
- private static int mBatteryLowARGB;
- private static int mBatteryMediumARGB;
- private static int mBatteryFullARGB;
- private static int mBatteryLedOn;
- private static int mBatteryLedOff;
-
private static String idDebugString(Context baseContext, String packageName, int id) {
Context c = null;
@@ -171,12 +156,11 @@ public class NotificationManagerService extends INotificationManager.Stub
final int id;
final int uid;
final int initialPid;
- ITransientNotification callback;
- int duration;
+ final int priority;
final Notification notification;
IBinder statusBarKey;
- NotificationRecord(String pkg, String tag, int id, int uid, int initialPid,
+ NotificationRecord(String pkg, String tag, int id, int uid, int initialPid, int priority,
Notification notification)
{
this.pkg = pkg;
@@ -184,6 +168,7 @@ public class NotificationManagerService extends INotificationManager.Stub
this.id = id;
this.uid = uid;
this.initialPid = initialPid;
+ this.priority = priority;
this.notification = notification;
}
@@ -211,7 +196,9 @@ public class NotificationManagerService extends INotificationManager.Stub
+ Integer.toHexString(System.identityHashCode(this))
+ " pkg=" + pkg
+ " id=" + Integer.toHexString(id)
- + " tag=" + tag + "}";
+ + " tag=" + tag
+ + " pri=" + priority
+ + "}";
}
}
@@ -282,7 +269,13 @@ public class NotificationManagerService extends INotificationManager.Stub
public void onNotificationClick(String pkg, String tag, int id) {
cancelNotification(pkg, tag, id, Notification.FLAG_AUTO_CANCEL,
- Notification.FLAG_FOREGROUND_SERVICE);
+ Notification.FLAG_FOREGROUND_SERVICE, true);
+ }
+
+ public void onNotificationClear(String pkg, String tag, int id) {
+ cancelNotification(pkg, tag, id, 0,
+ Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
+ true);
}
public void onPanelRevealed() {
@@ -318,7 +311,7 @@ public class NotificationManagerService extends INotificationManager.Stub
int uid, int initialPid, String message) {
Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id
+ "; will crashApplication(uid=" + uid + ", pid=" + initialPid + ")");
- cancelNotification(pkg, tag, id, 0, 0);
+ cancelNotification(pkg, tag, id, 0, 0, false);
long ident = Binder.clearCallingIdentity();
try {
ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg,
@@ -337,29 +330,12 @@ public class NotificationManagerService extends INotificationManager.Stub
boolean queryRestart = false;
- if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
- boolean batteryCharging = (intent.getIntExtra("plugged", 0) != 0);
- int level = intent.getIntExtra("level", -1);
- boolean batteryLow = (level >= 0 && level <= Power.LOW_BATTERY_THRESHOLD);
- int status = intent.getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN);
- boolean batteryFull = (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90);
-
- if (batteryCharging != mBatteryCharging ||
- batteryLow != mBatteryLow ||
- batteryFull != mBatteryFull) {
- mBatteryCharging = batteryCharging;
- mBatteryLow = batteryLow;
- mBatteryFull = batteryFull;
- updateLights();
- }
- } else if (action.equals(Usb.ACTION_USB_STATE)) {
+ if (action.equals(UsbManager.ACTION_USB_STATE)) {
Bundle extras = intent.getExtras();
- boolean usbConnected = extras.getBoolean(Usb.USB_CONNECTED);
- boolean adbEnabled = (Usb.USB_FUNCTION_ENABLED.equals(
- extras.getString(Usb.USB_FUNCTION_ADB)));
+ boolean usbConnected = extras.getBoolean(UsbManager.USB_CONNECTED);
+ boolean adbEnabled = (UsbManager.USB_FUNCTION_ENABLED.equals(
+ extras.getString(UsbManager.USB_FUNCTION_ADB)));
updateAdbNotification(usbConnected && adbEnabled);
- } else if (action.equals(Usb.ACTION_USB_DISCONNECTED)) {
- updateAdbNotification(false);
} else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
|| action.equals(Intent.ACTION_PACKAGE_RESTARTED)
|| (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
@@ -430,7 +406,6 @@ public class NotificationManagerService extends INotificationManager.Stub
{
super();
mContext = context;
- mLightsService = lights;
mAm = ActivityManagerNative.getDefault();
mSound = new NotificationPlayer(TAG);
mSound.setUsesWakeLock(context);
@@ -440,7 +415,6 @@ public class NotificationManagerService extends INotificationManager.Stub
mStatusBar = statusBar;
statusBar.setNotificationCallbacks(mNotificationCallbacks);
- mBatteryLight = lights.getLight(LightsService.LIGHT_ID_BATTERY);
mNotificationLight = lights.getLight(LightsService.LIGHT_ID_NOTIFICATIONS);
mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION);
@@ -452,17 +426,6 @@ public class NotificationManagerService extends INotificationManager.Stub
mDefaultNotificationLedOff = resources.getInteger(
com.android.internal.R.integer.config_defaultNotificationLedOff);
- mBatteryLowARGB = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_notificationsBatteryLowARGB);
- mBatteryMediumARGB = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_notificationsBatteryMediumARGB);
- mBatteryFullARGB = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_notificationsBatteryFullARGB);
- mBatteryLedOn = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_notificationsBatteryLedOn);
- mBatteryLedOff = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_notificationsBatteryLedOff);
-
// Don't start allowing notifications until the setup wizard has run once.
// After that, including subsequent boots, init with notifications turned on.
// This works on the first boot because the setup wizard will toggle this
@@ -472,10 +435,9 @@ public class NotificationManagerService extends INotificationManager.Stub
mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
}
- // register for battery changed notifications
+ // register for various Intents
IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_BATTERY_CHANGED);
- filter.addAction(Usb.ACTION_USB_STATE);
+ filter.addAction(UsbManager.ACTION_USB_STATE);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
@@ -678,6 +640,7 @@ public class NotificationManagerService extends INotificationManager.Stub
// Notifications
// ============================================================================
+ @Deprecated
public void enqueueNotification(String pkg, int id, Notification notification, int[] idOut)
{
enqueueNotificationWithTag(pkg, null /* tag */, id, notification, idOut);
@@ -690,11 +653,27 @@ public class NotificationManagerService extends INotificationManager.Stub
tag, id, notification, idOut);
}
+ public void enqueueNotificationWithTagPriority(String pkg, String tag, int id, int priority,
+ Notification notification, int[] idOut)
+ {
+ enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(),
+ tag, id, priority, notification, idOut);
+ }
+
// Not exposed via Binder; for system use only (otherwise malicious apps could spoof the
// uid/pid of another application)
public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
String tag, int id, Notification notification, int[] idOut)
{
+ enqueueNotificationInternal(pkg, callingUid, callingPid, tag, id,
+ ((notification.flags & Notification.FLAG_ONGOING_EVENT) != 0)
+ ? StatusBarNotification.PRIORITY_ONGOING
+ : StatusBarNotification.PRIORITY_NORMAL,
+ notification, idOut);
+ }
+ public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
+ String tag, int id, int priority, Notification notification, int[] idOut)
+ {
checkIncomingCall(pkg);
// Limit the number of notifications that any given package except the android
@@ -733,15 +712,13 @@ public class NotificationManagerService extends INotificationManager.Stub
throw new IllegalArgumentException("contentView required: pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
- if (notification.contentIntent == null) {
- throw new IllegalArgumentException("contentIntent required: pkg=" + pkg
- + " id=" + id + " notification=" + notification);
- }
}
synchronized (mNotificationList) {
- NotificationRecord r = new NotificationRecord(pkg, tag, id,
- callingUid, callingPid, notification);
+ NotificationRecord r = new NotificationRecord(pkg, tag, id,
+ callingUid, callingPid,
+ priority,
+ notification);
NotificationRecord old = null;
int index = indexOfNotificationLocked(pkg, tag, id);
@@ -767,6 +744,8 @@ public class NotificationManagerService extends INotificationManager.Stub
if (notification.icon != 0) {
StatusBarNotification n = new StatusBarNotification(pkg, id, tag,
r.uid, r.initialPid, notification);
+ n.priority = r.priority;
+
if (old != null && old.statusBarKey != null) {
r.statusBarKey = old.statusBarKey;
long identity = Binder.clearCallingIdentity();
@@ -788,6 +767,7 @@ public class NotificationManagerService extends INotificationManager.Stub
}
sendAccessibilityEvent(notification, pkg);
} else {
+ Slog.e(TAG, "Ignoring notification with icon==0: " + notification);
if (old != null && old.statusBarKey != null) {
long identity = Binder.clearCallingIdentity();
try {
@@ -894,7 +874,20 @@ public class NotificationManagerService extends INotificationManager.Stub
manager.sendAccessibilityEvent(event);
}
- private void cancelNotificationLocked(NotificationRecord r) {
+ private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete) {
+ // tell the app
+ if (sendDelete) {
+ if (r.notification.deleteIntent != null) {
+ try {
+ r.notification.deleteIntent.send();
+ } catch (PendingIntent.CanceledException ex) {
+ // do nothing - there's no relevant way to recover, and
+ // no reason to let this propagate
+ Slog.w(TAG, "canceled PendingIntent for " + r.pkg, ex);
+ }
+ }
+ }
+
// status bar
if (r.notification.icon != 0) {
long identity = Binder.clearCallingIdentity();
@@ -943,7 +936,7 @@ public class NotificationManagerService extends INotificationManager.Stub
* and none of the {@code mustNotHaveFlags}.
*/
private void cancelNotification(String pkg, String tag, int id, int mustHaveFlags,
- int mustNotHaveFlags) {
+ int mustNotHaveFlags, boolean sendDelete) {
EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, mustHaveFlags);
synchronized (mNotificationList) {
@@ -960,7 +953,7 @@ public class NotificationManagerService extends INotificationManager.Stub
mNotificationList.remove(index);
- cancelNotificationLocked(r);
+ cancelNotificationLocked(r, sendDelete);
updateLightsLocked();
}
}
@@ -993,7 +986,7 @@ public class NotificationManagerService extends INotificationManager.Stub
return true;
}
mNotificationList.remove(i);
- cancelNotificationLocked(r);
+ cancelNotificationLocked(r, false);
}
if (canceledSomething) {
updateLightsLocked();
@@ -1002,7 +995,7 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
-
+ @Deprecated
public void cancelNotification(String pkg, int id) {
cancelNotificationWithTag(pkg, null /* tag */, id);
}
@@ -1012,7 +1005,7 @@ public class NotificationManagerService extends INotificationManager.Stub
// Don't allow client applications to cancel foreground service notis.
cancelNotification(pkg, tag, id, 0,
Binder.getCallingUid() == Process.SYSTEM_UID
- ? 0 : Notification.FLAG_FOREGROUND_SERVICE);
+ ? 0 : Notification.FLAG_FOREGROUND_SERVICE, false);
}
public void cancelAllNotifications(String pkg) {
@@ -1048,17 +1041,8 @@ public class NotificationManagerService extends INotificationManager.Stub
if ((r.notification.flags & (Notification.FLAG_ONGOING_EVENT
| Notification.FLAG_NO_CLEAR)) == 0) {
- if (r.notification.deleteIntent != null) {
- try {
- r.notification.deleteIntent.send();
- } catch (PendingIntent.CanceledException ex) {
- // do nothing - there's no relevant way to recover, and
- // no reason to let this propagate
- Slog.w(TAG, "canceled PendingIntent for " + r.pkg, ex);
- }
- }
mNotificationList.remove(i);
- cancelNotificationLocked(r);
+ cancelNotificationLocked(r, true);
}
}
@@ -1066,34 +1050,9 @@ public class NotificationManagerService extends INotificationManager.Stub
}
}
- private void updateLights() {
- synchronized (mNotificationList) {
- updateLightsLocked();
- }
- }
-
// lock on mNotificationList
private void updateLightsLocked()
{
- // Battery low always shows, other states only show if charging.
- if (mBatteryLow) {
- if (mBatteryCharging) {
- mBatteryLight.setColor(mBatteryLowARGB);
- } else {
- // Flash when battery is low and not charging
- mBatteryLight.setFlashing(mBatteryLowARGB, LightsService.LIGHT_FLASH_TIMED,
- mBatteryLedOn, mBatteryLedOff);
- }
- } else if (mBatteryCharging) {
- if (mBatteryFull) {
- mBatteryLight.setColor(mBatteryFullARGB);
- } else {
- mBatteryLight.setColor(mBatteryMediumARGB);
- }
- } else {
- mBatteryLight.turnOff();
- }
-
// clear pending pulse notification if screen is on
if (mScreenOn || mLedNotification == null) {
mPendingPulseNotification = false;
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 8c74566..9ee71e8 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -1050,7 +1050,7 @@ class PackageManagerService extends IPackageManager.Stub {
void cleanupInstallFailedPackage(PackageSetting ps) {
Slog.i(TAG, "Cleaning up incompletely installed app: " + ps.name);
if (mInstaller != null) {
- boolean useSecureFS = useEncryptedFilesystemForPackage(ps.pkg);
+ boolean useSecureFS = false;
int retCode = mInstaller.remove(ps.name, useSecureFS);
if (retCode < 0) {
Slog.w(TAG, "Couldn't remove app data directory for package: "
@@ -1225,6 +1225,7 @@ class PackageManagerService extends IPackageManager.Stub {
}
}
+ permReader.close();
} catch (XmlPullParserException e) {
Slog.w(TAG, "Got execption parsing permissions.", e);
} catch (IOException e) {
@@ -2086,7 +2087,7 @@ class PackageManagerService extends IPackageManager.Stub {
return (List<ResolveInfo>) mActivities.queryIntentForPackage(intent,
resolvedType, flags, pkg.activities);
}
- return null;
+ return new ArrayList<ResolveInfo>();
}
}
@@ -2779,11 +2780,6 @@ class PackageManagerService extends IPackageManager.Stub {
return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
}
- private static boolean useEncryptedFilesystemForPackage(PackageParser.Package pkg) {
- return Environment.isEncryptedFilesystemEnabled() &&
- ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_NEVER_ENCRYPT) == 0);
- }
-
private boolean verifyPackageUpdate(PackageSetting oldPkg, PackageParser.Package newPkg) {
if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
Slog.w(TAG, "Unable to update from " + oldPkg.name
@@ -2800,7 +2796,7 @@ class PackageManagerService extends IPackageManager.Stub {
}
private File getDataPathForPackage(PackageParser.Package pkg) {
- boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(pkg);
+ boolean useEncryptedFSDir = false;
File dataPath;
if (useEncryptedFSDir) {
dataPath = new File(mSecureAppDataDir, pkg.packageName);
@@ -2852,7 +2848,7 @@ class PackageManagerService extends IPackageManager.Stub {
mResolveActivity.processName = mAndroidApplication.processName;
mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
- mResolveActivity.theme = com.android.internal.R.style.Theme_Dialog_Alert;
+ mResolveActivity.theme = com.android.internal.R.style.Theme_Holo_Dialog_Alert;
mResolveActivity.exported = true;
mResolveActivity.enabled = true;
mResolveInfo.activityInfo = mResolveActivity;
@@ -3136,7 +3132,7 @@ class PackageManagerService extends IPackageManager.Stub {
pkg.applicationInfo.dataDir = dataPath.getPath();
} else {
// This is a normal package, need to make its data directory.
- boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(pkg);
+ boolean useEncryptedFSDir = false;
dataPath = getDataPathForPackage(pkg);
boolean uidError = false;
@@ -4391,10 +4387,19 @@ class PackageManagerService extends IPackageManager.Stub {
}
}
}
-
+
+ /**
+ * Check if the external storage media is available. This is true if there
+ * is a mounted external storage medium or if the external storage is
+ * emulated.
+ */
+ private boolean isExternalMediaAvailable() {
+ return mMediaMounted || Environment.isExternalStorageEmulated();
+ }
+
public String nextPackageToClean(String lastPackage) {
synchronized (mPackages) {
- if (!mMediaMounted) {
+ if (!isExternalMediaAvailable()) {
// If the external storage is no longer mounted at this point,
// the caller may not have been able to delete all of this
// packages files and can not delete any more. Bail.
@@ -4414,7 +4419,7 @@ class PackageManagerService extends IPackageManager.Stub {
void startCleaningPackages() {
synchronized (mPackages) {
- if (!mMediaMounted) {
+ if (!isExternalMediaAvailable()) {
return;
}
if (mSettings.mPackagesToBeCleaned.size() <= 0) {
@@ -4549,6 +4554,80 @@ class PackageManagerService extends IPackageManager.Stub {
mHandler.sendMessage(msg);
}
+ public void setInstallerPackageName(String targetPackage,
+ String installerPackageName) {
+ PackageSetting pkgSetting;
+ final int uid = Binder.getCallingUid();
+ final int permission = mContext.checkCallingPermission(
+ android.Manifest.permission.INSTALL_PACKAGES);
+ final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
+ synchronized (mPackages) {
+ PackageSetting targetPackageSetting = mSettings.mPackages.get(targetPackage);
+ if (targetPackageSetting == null) {
+ throw new IllegalArgumentException("Unknown target package: " + targetPackage);
+ }
+
+ PackageSetting installerPackageSetting;
+ if (installerPackageName != null) {
+ installerPackageSetting = mSettings.mPackages.get(installerPackageName);
+ if (installerPackageSetting == null) {
+ throw new IllegalArgumentException("Unknown installer package: "
+ + installerPackageName);
+ }
+ } else {
+ installerPackageSetting = null;
+ }
+
+ Signature[] callerSignature;
+ Object obj = mSettings.getUserIdLP(uid);
+ if (obj != null) {
+ if (obj instanceof SharedUserSetting) {
+ callerSignature = ((SharedUserSetting)obj).signatures.mSignatures;
+ } else if (obj instanceof PackageSetting) {
+ callerSignature = ((PackageSetting)obj).signatures.mSignatures;
+ } else {
+ throw new SecurityException("Bad object " + obj + " for uid " + uid);
+ }
+ } else {
+ throw new SecurityException("Unknown calling uid " + uid);
+ }
+
+ // Verify: can't set installerPackageName to a package that is
+ // not signed with the same cert as the caller.
+ if (installerPackageSetting != null) {
+ if (checkSignaturesLP(callerSignature,
+ installerPackageSetting.signatures.mSignatures)
+ != PackageManager.SIGNATURE_MATCH) {
+ throw new SecurityException(
+ "Caller does not have same cert as new installer package "
+ + installerPackageName);
+ }
+ }
+
+ // Verify: if target already has an installer package, it must
+ // be signed with the same cert as the caller.
+ if (targetPackageSetting.installerPackageName != null) {
+ PackageSetting setting = mSettings.mPackages.get(
+ targetPackageSetting.installerPackageName);
+ // If the currently set package isn't valid, then it's always
+ // okay to change it.
+ if (setting != null) {
+ if (checkSignaturesLP(callerSignature,
+ setting.signatures.mSignatures)
+ != PackageManager.SIGNATURE_MATCH) {
+ throw new SecurityException(
+ "Caller does not have same cert as old installer package "
+ + targetPackageSetting.installerPackageName);
+ }
+ }
+ }
+
+ // Okay!
+ targetPackageSetting.installerPackageName = installerPackageName;
+ scheduleWriteSettingsLocked();
+ }
+ }
+
private void processPendingInstall(final InstallArgs args, final int currentStatus) {
// Queue up an async operation since the package installation may take a little while.
mHandler.post(new Runnable() {
@@ -4656,6 +4735,79 @@ class PackageManagerService extends IPackageManager.Stub {
abstract void handleReturnCode();
}
+ class MeasureParams extends HandlerParams {
+ private final PackageStats mStats;
+ private boolean mSuccess;
+
+ private final IPackageStatsObserver mObserver;
+
+ public MeasureParams(PackageStats stats, boolean success,
+ IPackageStatsObserver observer) {
+ mObserver = observer;
+ mStats = stats;
+ mSuccess = success;
+ }
+
+ @Override
+ void handleStartCopy() throws RemoteException {
+ final boolean mounted;
+
+ if (Environment.isExternalStorageEmulated()) {
+ mounted = true;
+ } else {
+ final String status = Environment.getExternalStorageState();
+
+ mounted = status.equals(Environment.MEDIA_MOUNTED)
+ || status.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
+ }
+
+ if (mounted) {
+ final File externalCacheDir = Environment
+ .getExternalStorageAppCacheDirectory(mStats.packageName);
+ final long externalCacheSize = mContainerService
+ .calculateDirectorySize(externalCacheDir.getPath());
+ mStats.externalCacheSize = externalCacheSize;
+
+ final File externalDataDir = Environment
+ .getExternalStorageAppDataDirectory(mStats.packageName);
+ long externalDataSize = mContainerService.calculateDirectorySize(externalDataDir
+ .getPath());
+
+ if (externalCacheDir.getParentFile().equals(externalDataDir)) {
+ externalDataSize -= externalCacheSize;
+ }
+ mStats.externalDataSize = externalDataSize;
+
+ final File externalMediaDir = Environment
+ .getExternalStorageAppMediaDirectory(mStats.packageName);
+ mStats.externalMediaSize = mContainerService
+ .calculateDirectorySize(externalCacheDir.getPath());
+
+ final File externalObbDir = Environment
+ .getExternalStorageAppObbDirectory(mStats.packageName);
+ mStats.externalObbSize = mContainerService.calculateDirectorySize(externalObbDir
+ .getPath());
+ }
+ }
+
+ @Override
+ void handleReturnCode() {
+ if (mObserver != null) {
+ try {
+ mObserver.onGetStatsCompleted(mStats, mSuccess);
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Observer no longer exists.");
+ }
+ }
+ }
+
+ @Override
+ void handleServiceError() {
+ Slog.e(TAG, "Could not measure application " + mStats.packageName
+ + " external storage");
+ }
+ }
+
class InstallParams extends HandlerParams {
final IPackageInstallObserver observer;
int flags;
@@ -6141,7 +6293,7 @@ class PackageManagerService extends IPackageManager.Stub {
deletedPs = mSettings.mPackages.get(packageName);
}
if ((flags&PackageManager.DONT_DELETE_DATA) == 0) {
- boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p);
+ boolean useEncryptedFSDir = false;
if (mInstaller != null) {
int retCode = mInstaller.remove(packageName, useEncryptedFSDir);
if (retCode < 0) {
@@ -6385,7 +6537,7 @@ class PackageManagerService extends IPackageManager.Stub {
}
boolean useEncryptedFSDir = false;
- if(!dataOnly) {
+ if (!dataOnly) {
//need to check this only for fully installed applications
if (p == null) {
Slog.w(TAG, "Package named '" + packageName +"' doesn't exist.");
@@ -6396,7 +6548,6 @@ class PackageManagerService extends IPackageManager.Stub {
Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
return false;
}
- useEncryptedFSDir = useEncryptedFilesystemForPackage(p);
}
if (mInstaller != null) {
int retCode = mInstaller.clearUserData(packageName, useEncryptedFSDir);
@@ -6450,7 +6601,7 @@ class PackageManagerService extends IPackageManager.Stub {
Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
return false;
}
- boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p);
+ boolean useEncryptedFSDir = false;
if (mInstaller != null) {
int retCode = mInstaller.deleteCacheFiles(packageName, useEncryptedFSDir);
if (retCode < 0) {
@@ -6470,18 +6621,16 @@ class PackageManagerService extends IPackageManager.Stub {
mHandler.post(new Runnable() {
public void run() {
mHandler.removeCallbacks(this);
- PackageStats lStats = new PackageStats(packageName);
- final boolean succeded;
+ PackageStats stats = new PackageStats(packageName);
+
+ final boolean success;
synchronized (mInstallLock) {
- succeded = getPackageSizeInfoLI(packageName, lStats);
+ success = getPackageSizeInfoLI(packageName, stats);
}
- if(observer != null) {
- try {
- observer.onGetStatsCompleted(lStats, succeded);
- } catch (RemoteException e) {
- Log.i(TAG, "Observer no longer exists.");
- }
- } //end if observer
+
+ Message msg = mHandler.obtainMessage(INIT_COPY);
+ msg.obj = new MeasureParams(stats, success, observer);
+ mHandler.sendMessage(msg);
} //end run
});
}
@@ -6514,7 +6663,7 @@ class PackageManagerService extends IPackageManager.Stub {
}
publicSrcDir = isForwardLocked(p) ? applicationInfo.publicSourceDir : null;
}
- boolean useEncryptedFSDir = useEncryptedFilesystemForPackage(p);
+ boolean useEncryptedFSDir = false;
if (mInstaller != null) {
int res = mInstaller.getSizeInfo(packageName, p.mPath,
publicSrcDir, pStats, useEncryptedFSDir);
@@ -7278,7 +7427,7 @@ class PackageManagerService extends IPackageManager.Stub {
pw.println(" ");
pw.println("Package warning messages:");
File fname = getSettingsProblemFile();
- FileInputStream in;
+ FileInputStream in = null;
try {
in = new FileInputStream(fname);
int avail = in.available();
@@ -7287,6 +7436,13 @@ class PackageManagerService extends IPackageManager.Stub {
pw.print(new String(data));
} catch (FileNotFoundException e) {
} catch (IOException e) {
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ }
+ }
}
}
}
@@ -7679,8 +7835,7 @@ class PackageManagerService extends IPackageManager.Stub {
this.pkgFlags = pkgFlags & (
ApplicationInfo.FLAG_SYSTEM |
ApplicationInfo.FLAG_FORWARD_LOCK |
- ApplicationInfo.FLAG_EXTERNAL_STORAGE |
- ApplicationInfo.FLAG_NEVER_ENCRYPT);
+ ApplicationInfo.FLAG_EXTERNAL_STORAGE);
}
}
@@ -9715,7 +9870,8 @@ class PackageManagerService extends IPackageManager.Stub {
* Update media status on PackageManager.
*/
public void updateExternalMediaStatus(final boolean mediaStatus, final boolean reportStatus) {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
throw new SecurityException("Media status can only be updated by the system");
}
synchronized (mPackages) {
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index a6daaef..caf6376 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -219,6 +219,8 @@ class PowerManagerService extends IPowerManager.Stub
private float mLightSensorValue = -1;
private boolean mProxIgnoredBecauseScreenTurnedOff = false;
private int mHighestLightSensorValue = -1;
+ private boolean mLightSensorPendingDecrease = false;
+ private boolean mLightSensorPendingIncrease = false;
private float mLightSensorPendingValue = -1;
private int mLightSensorScreenBrightness = -1;
private int mLightSensorButtonBrightness = -1;
@@ -1141,6 +1143,8 @@ class PowerManagerService extends IPowerManager.Stub
pw.println(" mLightSensorEnabled=" + mLightSensorEnabled);
pw.println(" mLightSensorValue=" + mLightSensorValue
+ " mLightSensorPendingValue=" + mLightSensorPendingValue);
+ pw.println(" mLightSensorPendingDecrease=" + mLightSensorPendingDecrease
+ + " mLightSensorPendingIncrease=" + mLightSensorPendingIncrease);
pw.println(" mLightSensorScreenBrightness=" + mLightSensorScreenBrightness
+ " mLightSensorButtonBrightness=" + mLightSensorButtonBrightness
+ " mLightSensorKeyboardBrightness=" + mLightSensorKeyboardBrightness);
@@ -1171,10 +1175,8 @@ class PowerManagerService extends IPowerManager.Stub
pw.println("mPokeLocks.size=" + mPokeLocks.size() + ":");
for (PokeLock p: mPokeLocks.values()) {
pw.println(" poke lock '" + p.tag + "':"
- + ((p.pokey & POKE_LOCK_IGNORE_CHEEK_EVENTS) != 0
- ? " POKE_LOCK_IGNORE_CHEEK_EVENTS" : "")
- + ((p.pokey & POKE_LOCK_IGNORE_TOUCH_AND_CHEEK_EVENTS) != 0
- ? " POKE_LOCK_IGNORE_TOUCH_AND_CHEEK_EVENTS" : "")
+ + ((p.pokey & POKE_LOCK_IGNORE_TOUCH_EVENTS) != 0
+ ? " POKE_LOCK_IGNORE_TOUCH_EVENTS" : "")
+ ((p.pokey & POKE_LOCK_SHORT_TIMEOUT) != 0
? " POKE_LOCK_SHORT_TIMEOUT" : "")
+ ((p.pokey & POKE_LOCK_MEDIUM_TIMEOUT) != 0
@@ -1609,7 +1611,7 @@ class PowerManagerService extends IPowerManager.Stub
if (err == 0) {
mLastScreenOnTime = (on ? SystemClock.elapsedRealtime() : 0);
if (mUseSoftwareAutoBrightness) {
- enableLightSensor(on);
+ enableLightSensorLocked(on);
if (!on) {
// make sure button and key backlights are off too
mButtonLight.turnOff();
@@ -1742,6 +1744,8 @@ class PowerManagerService extends IPowerManager.Stub
} else {
// cancel light sensor task
mHandler.removeCallbacks(mAutoBrightnessTask);
+ mLightSensorPendingDecrease = false;
+ mLightSensorPendingIncrease = false;
mScreenOffTime = SystemClock.elapsedRealtime();
long identity = Binder.clearCallingIdentity();
try {
@@ -2212,31 +2216,13 @@ class PowerManagerService extends IPowerManager.Stub
private void userActivity(long time, long timeoutOverride, boolean noChangeLights,
int eventType, boolean force) {
- if (((mPokey & POKE_LOCK_IGNORE_CHEEK_EVENTS) != 0)
- && (eventType == CHEEK_EVENT)) {
- if (false) {
- Slog.d(TAG, "dropping cheek event mPokey=0x" + Integer.toHexString(mPokey));
- }
- return;
- }
-
- if (((mPokey & POKE_LOCK_IGNORE_TOUCH_AND_CHEEK_EVENTS) != 0)
- && (eventType == TOUCH_EVENT || eventType == TOUCH_UP_EVENT
- || eventType == LONG_TOUCH_EVENT || eventType == CHEEK_EVENT)) {
+ if (((mPokey & POKE_LOCK_IGNORE_TOUCH_EVENTS) != 0) && (eventType == TOUCH_EVENT)) {
if (false) {
Slog.d(TAG, "dropping touch mPokey=0x" + Integer.toHexString(mPokey));
}
return;
}
- if (false) {
- if (((mPokey & POKE_LOCK_IGNORE_CHEEK_EVENTS) != 0)) {
- Slog.d(TAG, "userActivity !!!");//, new RuntimeException());
- } else {
- Slog.d(TAG, "mPokey=0x" + Integer.toHexString(mPokey));
- }
- }
-
synchronized (mLocks) {
if (mSpew) {
Slog.d(TAG, "userActivity mLastEventTime=" + mLastEventTime + " time=" + time
@@ -2325,9 +2311,10 @@ class PowerManagerService extends IPowerManager.Stub
private Runnable mAutoBrightnessTask = new Runnable() {
public void run() {
synchronized (mLocks) {
- int value = (int)mLightSensorPendingValue;
- if (value >= 0) {
- mLightSensorPendingValue = -1;
+ if (mLightSensorPendingDecrease || mLightSensorPendingIncrease) {
+ int value = (int)mLightSensorPendingValue;
+ mLightSensorPendingDecrease = false;
+ mLightSensorPendingIncrease = false;
lightSensorChangedLocked(value);
}
}
@@ -2569,16 +2556,12 @@ class PowerManagerService extends IPowerManager.Stub
}
private void setScreenBrightnessMode(int mode) {
- boolean enabled = (mode == SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- if (mUseSoftwareAutoBrightness && mAutoBrightessEnabled != enabled) {
- mAutoBrightessEnabled = enabled;
- if (isScreenOn()) {
- // force recompute of backlight values
- if (mLightSensorValue >= 0) {
- int value = (int)mLightSensorValue;
- mLightSensorValue = -1;
- lightSensorChangedLocked(value);
- }
+ synchronized (mLocks) {
+ boolean enabled = (mode == SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ if (mUseSoftwareAutoBrightness && mAutoBrightessEnabled != enabled) {
+ mAutoBrightessEnabled = enabled;
+ // This will get us a new value
+ enableLightSensorLocked(mAutoBrightessEnabled && isScreenOn());
}
}
}
@@ -2604,7 +2587,8 @@ class PowerManagerService extends IPowerManager.Stub
}
mKeylightDelay = LONG_KEYLIGHT_DELAY;
if (totalDelay < 0) {
- mScreenOffDelay = Integer.MAX_VALUE;
+ // negative number means stay on as long as possible.
+ mScreenOffDelay = mMaximumScreenOffTimeout;
} else if (mKeylightDelay < totalDelay) {
// subtract the time that the keylight delay. This will give us the
// remainder of the time that we need to sleep to get the accurate
@@ -2729,7 +2713,6 @@ class PowerManagerService extends IPowerManager.Stub
// don't bother with the light sensor if auto brightness is handled in hardware
if (mUseSoftwareAutoBrightness) {
mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
- enableLightSensor(true);
}
// wait until sensors are enabled before turning on screen.
@@ -2747,6 +2730,8 @@ class PowerManagerService extends IPowerManager.Stub
Slog.d(TAG, "system ready!");
mDoneBooting = true;
+ enableLightSensorLocked(mUseSoftwareAutoBrightness && mAutoBrightessEnabled);
+
long identity = Binder.clearCallingIdentity();
try {
mBatteryStats.noteScreenBrightness(getPreferredBrightness());
@@ -2901,9 +2886,13 @@ class PowerManagerService extends IPowerManager.Stub
}
}
- private void enableLightSensor(boolean enable) {
+ private void enableLightSensorLocked(boolean enable) {
if (mDebugLightSensor) {
- Slog.d(TAG, "enableLightSensor " + enable);
+ Slog.d(TAG, "enableLightSensorLocked enable=" + enable
+ + " mAutoBrightessEnabled=" + mAutoBrightessEnabled);
+ }
+ if (!mAutoBrightessEnabled) {
+ enable = false;
}
if (mSensorManager != null && mLightSensorEnabled != enable) {
mLightSensorEnabled = enable;
@@ -2980,19 +2969,29 @@ class PowerManagerService extends IPowerManager.Stub
if (mDebugLightSensor) {
Slog.d(TAG, "onSensorChanged: light value: " + value);
}
- mHandler.removeCallbacks(mAutoBrightnessTask);
- if (mLightSensorValue != value) {
- if (mLightSensorValue == -1 ||
- milliseconds < mLastScreenOnTime + mLightSensorWarmupTime) {
- // process the value immediately if screen has just turned on
- lightSensorChangedLocked(value);
- } else {
+ if (mLightSensorValue == -1 ||
+ milliseconds < mLastScreenOnTime + mLightSensorWarmupTime) {
+ // process the value immediately if screen has just turned on
+ mHandler.removeCallbacks(mAutoBrightnessTask);
+ mLightSensorPendingDecrease = false;
+ mLightSensorPendingIncrease = false;
+ lightSensorChangedLocked(value);
+ } else {
+ if ((value > mLightSensorValue && mLightSensorPendingDecrease) ||
+ (value < mLightSensorValue && mLightSensorPendingIncrease) ||
+ (value == mLightSensorValue) ||
+ (!mLightSensorPendingDecrease && !mLightSensorPendingIncrease)) {
// delay processing to debounce the sensor
+ mHandler.removeCallbacks(mAutoBrightnessTask);
+ mLightSensorPendingDecrease = (value < mLightSensorValue);
+ mLightSensorPendingIncrease = (value > mLightSensorValue);
+ if (mLightSensorPendingDecrease || mLightSensorPendingIncrease) {
+ mLightSensorPendingValue = value;
+ mHandler.postDelayed(mAutoBrightnessTask, LIGHT_SENSOR_DELAY);
+ }
+ } else {
mLightSensorPendingValue = value;
- mHandler.postDelayed(mAutoBrightnessTask, LIGHT_SENSOR_DELAY);
}
- } else {
- mLightSensorPendingValue = -1;
}
}
}
diff --git a/services/java/com/android/server/ProcessStats.java b/services/java/com/android/server/ProcessStats.java
index 43dbcc0..1a12a84 100644
--- a/services/java/com/android/server/ProcessStats.java
+++ b/services/java/com/android/server/ProcessStats.java
@@ -799,8 +799,9 @@ public class ProcessStats {
}
private String readFile(String file, char endChar) {
+ FileInputStream is = null;
try {
- FileInputStream is = new FileInputStream(file);
+ is = new FileInputStream(file);
int len = is.read(mBuffer);
is.close();
@@ -815,6 +816,13 @@ public class ProcessStats {
}
} catch (java.io.FileNotFoundException e) {
} catch (java.io.IOException e) {
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (java.io.IOException e) {
+ }
+ }
}
return null;
}
@@ -841,4 +849,3 @@ public class ProcessStats {
}
}
}
-
diff --git a/services/java/com/android/server/SamplingProfilerService.java b/services/java/com/android/server/SamplingProfilerService.java
new file mode 100644
index 0000000..61267d0
--- /dev/null
+++ b/services/java/com/android/server/SamplingProfilerService.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.ContentResolver;
+import android.os.DropBoxManager;
+import android.os.FileObserver;
+import android.os.Binder;
+
+import android.util.Slog;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import com.android.internal.os.SamplingProfilerIntegration;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+public class SamplingProfilerService extends Binder {
+
+ private static final String TAG = "SamplingProfilerService";
+ private static final boolean LOCAL_LOGV = false;
+ public static final String SNAPSHOT_DIR = SamplingProfilerIntegration.SNAPSHOT_DIR;
+
+ private FileObserver snapshotObserver;
+
+ public SamplingProfilerService(Context context) {
+ registerSettingObserver(context);
+ startWorking(context);
+ }
+
+ private void startWorking(Context context) {
+ if (LOCAL_LOGV) Slog.v(TAG, "starting SamplingProfilerService!");
+
+ final DropBoxManager dropbox =
+ (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE);
+
+ // before FileObserver is ready, there could have already been some snapshots
+ // in the directory, we don't want to miss them
+ File[] snapshotFiles = new File(SNAPSHOT_DIR).listFiles();
+ for (int i = 0; snapshotFiles != null && i < snapshotFiles.length; i++) {
+ handleSnapshotFile(snapshotFiles[i], dropbox);
+ }
+
+ // detect new snapshot and put it in dropbox
+ // delete it afterwards no matter what happened before
+ // Note: needs listening at event ATTRIB rather than CLOSE_WRITE, because we set the
+ // readability of snapshot files after writing them!
+ snapshotObserver = new FileObserver(SNAPSHOT_DIR, FileObserver.ATTRIB) {
+ @Override
+ public void onEvent(int event, String path) {
+ handleSnapshotFile(new File(SNAPSHOT_DIR, path), dropbox);
+ }
+ };
+ snapshotObserver.startWatching();
+
+ if (LOCAL_LOGV) Slog.v(TAG, "SamplingProfilerService activated");
+ }
+
+ private void handleSnapshotFile(File file, DropBoxManager dropbox) {
+ try {
+ dropbox.addFile(TAG, file, 0);
+ if (LOCAL_LOGV) Slog.v(TAG, file.getPath() + " added to dropbox");
+ } catch (IOException e) {
+ Slog.e(TAG, "Can't add " + file.getPath() + " to dropbox", e);
+ } finally {
+ file.delete();
+ }
+ }
+
+ private void registerSettingObserver(Context context) {
+ ContentResolver contentResolver = context.getContentResolver();
+ contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.SAMPLING_PROFILER_MS),
+ false, new SamplingProfilerSettingsObserver(contentResolver));
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("SamplingProfilerService:");
+ pw.println("Watching directory: " + SNAPSHOT_DIR);
+ }
+
+ private class SamplingProfilerSettingsObserver extends ContentObserver {
+ private ContentResolver mContentResolver;
+ public SamplingProfilerSettingsObserver(ContentResolver contentResolver) {
+ super(null);
+ mContentResolver = contentResolver;
+ onChange(false);
+ }
+ @Override
+ public void onChange(boolean selfChange) {
+ Integer samplingProfilerMs = Settings.Secure.getInt(
+ mContentResolver, Settings.Secure.SAMPLING_PROFILER_MS, 0);
+ // setting this secure property will start or stop sampling profiler,
+ // as well as adjust the the time between taking snapshots.
+ SystemProperties.set("persist.sys.profiler_ms", samplingProfilerMs.toString());
+ }
+ }
+}
diff --git a/services/java/com/android/server/ScreenRotationAnimation.java b/services/java/com/android/server/ScreenRotationAnimation.java
new file mode 100644
index 0000000..ef00b08
--- /dev/null
+++ b/services/java/com/android/server/ScreenRotationAnimation.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server; // TODO: use com.android.server.wm, once things move there
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.view.Display;
+import android.view.Surface;
+import android.view.SurfaceSession;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Transformation;
+
+class ScreenRotationAnimation {
+ static final String TAG = "ScreenRotationAnimation";
+ static final boolean DEBUG = false;
+
+ static final int FREEZE_LAYER = WindowManagerService.TYPE_LAYER_MULTIPLIER * 200;
+
+ class BlackSurface {
+ final int left;
+ final int top;
+ final Surface surface;
+
+ BlackSurface(SurfaceSession session, int layer, int l, int t, int w, int h)
+ throws Surface.OutOfResourcesException {
+ left = l;
+ top = t;
+ surface = new Surface(session, 0, "BlackSurface",
+ -1, w, h, PixelFormat.OPAQUE, Surface.FX_SURFACE_DIM);
+ surface.setAlpha(1.0f);
+ surface.setLayer(FREEZE_LAYER);
+ }
+
+ void setMatrix(Matrix matrix) {
+ mTmpMatrix.setTranslate(left, top);
+ mTmpMatrix.postConcat(matrix);
+ mTmpMatrix.getValues(mTmpFloats);
+ surface.setPosition((int)mTmpFloats[Matrix.MTRANS_X],
+ (int)mTmpFloats[Matrix.MTRANS_Y]);
+ surface.setMatrix(
+ mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
+ mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
+ if (false) {
+ Slog.i(TAG, "Black Surface @ (" + left + "," + top + "): ("
+ + mTmpFloats[Matrix.MTRANS_X] + ","
+ + mTmpFloats[Matrix.MTRANS_Y] + ") matrix=["
+ + mTmpFloats[Matrix.MSCALE_X] + ","
+ + mTmpFloats[Matrix.MSCALE_Y] + "]["
+ + mTmpFloats[Matrix.MSKEW_X] + ","
+ + mTmpFloats[Matrix.MSKEW_Y] + "]");
+ }
+ }
+ }
+
+ final Context mContext;
+ final Display mDisplay;
+ Surface mSurface;
+ BlackSurface[] mBlackSurfaces;
+ int mWidth, mHeight;
+
+ int mSnapshotRotation;
+ int mSnapshotDeltaRotation;
+ int mOriginalRotation;
+ int mOriginalWidth, mOriginalHeight;
+ int mCurRotation;
+
+ Animation mExitAnimation;
+ final Transformation mExitTransformation = new Transformation();
+ Animation mEnterAnimation;
+ final Transformation mEnterTransformation = new Transformation();
+ boolean mStarted;
+
+ final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+ final Matrix mSnapshotInitialMatrix = new Matrix();
+ final Matrix mSnapshotFinalMatrix = new Matrix();
+ final Matrix mTmpMatrix = new Matrix();
+ final float[] mTmpFloats = new float[9];
+
+ public ScreenRotationAnimation(Context context, Display display, SurfaceSession session,
+ boolean inTransaction) {
+ mContext = context;
+ mDisplay = display;
+
+ display.getMetrics(mDisplayMetrics);
+
+ Bitmap screenshot = Surface.screenshot(0, 0);
+
+ if (screenshot != null) {
+ // Screenshot does NOT include rotation!
+ mSnapshotRotation = 0;
+ mWidth = screenshot.getWidth();
+ mHeight = screenshot.getHeight();
+ } else {
+ // Just in case.
+ mSnapshotRotation = display.getRotation();
+ mWidth = mDisplayMetrics.widthPixels;
+ mHeight = mDisplayMetrics.heightPixels;
+ }
+
+ mOriginalRotation = display.getRotation();
+ mOriginalWidth = mDisplayMetrics.widthPixels;
+ mOriginalHeight = mDisplayMetrics.heightPixels;
+
+ if (!inTransaction) {
+ if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG,
+ ">>> OPEN TRANSACTION ScreenRotationAnimation");
+ Surface.openTransaction();
+ }
+
+ try {
+ try {
+ mSurface = new Surface(session, 0, "FreezeSurface",
+ -1, mWidth, mHeight, PixelFormat.OPAQUE, 0);
+ mSurface.setLayer(FREEZE_LAYER + 1);
+ } catch (Surface.OutOfResourcesException e) {
+ Slog.w(TAG, "Unable to allocate freeze surface", e);
+ }
+
+ setRotation(display.getRotation());
+
+ if (mSurface != null) {
+ Rect dirty = new Rect(0, 0, mWidth, mHeight);
+ Canvas c = null;
+ try {
+ c = mSurface.lockCanvas(dirty);
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "Unable to lock surface", e);
+ return;
+ } catch (Surface.OutOfResourcesException e) {
+ Slog.w(TAG, "Unable to lock surface", e);
+ return;
+ }
+ if (c == null) {
+ Slog.w(TAG, "Null surface");
+ return;
+ }
+
+ if (screenshot != null) {
+ Paint paint = new Paint(0);
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+ c.drawBitmap(screenshot, 0, 0, paint);
+ } else {
+ c.drawColor(Color.BLACK, PorterDuff.Mode.SRC);
+ }
+
+ mSurface.unlockCanvasAndPost(c);
+ }
+ } finally {
+ if (!inTransaction) {
+ Surface.closeTransaction();
+ if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG,
+ "<<< CLOSE TRANSACTION ScreenRotationAnimation");
+ }
+
+ if (screenshot != null) {
+ screenshot.recycle();
+ }
+ }
+ }
+
+ static int deltaRotation(int oldRotation, int newRotation) {
+ int delta = newRotation - oldRotation;
+ if (delta < 0) delta += 4;
+ return delta;
+ }
+
+ void setSnapshotTransform(Matrix matrix, float alpha) {
+ if (mSurface != null) {
+ matrix.getValues(mTmpFloats);
+ mSurface.setPosition((int)mTmpFloats[Matrix.MTRANS_X],
+ (int)mTmpFloats[Matrix.MTRANS_Y]);
+ mSurface.setMatrix(
+ mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
+ mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
+ mSurface.setAlpha(alpha);
+ if (DEBUG) {
+ float[] srcPnts = new float[] { 0, 0, mWidth, mHeight };
+ float[] dstPnts = new float[4];
+ matrix.mapPoints(dstPnts, srcPnts);
+ Slog.i(TAG, "Original : (" + srcPnts[0] + "," + srcPnts[1]
+ + ")-(" + srcPnts[2] + "," + srcPnts[3] + ")");
+ Slog.i(TAG, "Transformed: (" + dstPnts[0] + "," + dstPnts[1]
+ + ")-(" + dstPnts[2] + "," + dstPnts[3] + ")");
+ }
+ }
+ }
+
+ public static void createRotationMatrix(int rotation, int width, int height,
+ Matrix outMatrix) {
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ outMatrix.reset();
+ break;
+ case Surface.ROTATION_90:
+ outMatrix.setRotate(90, 0, 0);
+ outMatrix.postTranslate(height, 0);
+ break;
+ case Surface.ROTATION_180:
+ outMatrix.setRotate(180, 0, 0);
+ outMatrix.postTranslate(width, height);
+ break;
+ case Surface.ROTATION_270:
+ outMatrix.setRotate(270, 0, 0);
+ outMatrix.postTranslate(0, width);
+ break;
+ }
+ }
+
+ // Must be called while in a transaction.
+ public void setRotation(int rotation) {
+ mCurRotation = rotation;
+
+ // Compute the transformation matrix that must be applied
+ // to the snapshot to make it stay in the same original position
+ // with the current screen rotation.
+ int delta = deltaRotation(rotation, mSnapshotRotation);
+ createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
+
+ if (DEBUG) Slog.v(TAG, "**** ROTATION: " + delta);
+ setSnapshotTransform(mSnapshotInitialMatrix, 1.0f);
+ }
+
+ /**
+ * Returns true if animating.
+ */
+ public boolean dismiss(SurfaceSession session, long maxAnimationDuration,
+ float animationScale) {
+ // Figure out how the screen has moved from the original rotation.
+ int delta = deltaRotation(mCurRotation, mOriginalRotation);
+ if (false && delta == 0) {
+ // Nothing changed, just remove the snapshot.
+ if (mSurface != null) {
+ mSurface.destroy();
+ mSurface = null;
+ }
+ return false;
+ }
+
+ switch (delta) {
+ case Surface.ROTATION_0:
+ mExitAnimation = AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.screen_rotate_0_exit);
+ mEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.screen_rotate_0_enter);
+ break;
+ case Surface.ROTATION_90:
+ mExitAnimation = AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.screen_rotate_plus_90_exit);
+ mEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.screen_rotate_plus_90_enter);
+ break;
+ case Surface.ROTATION_180:
+ mExitAnimation = AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.screen_rotate_180_exit);
+ mEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.screen_rotate_180_enter);
+ break;
+ case Surface.ROTATION_270:
+ mExitAnimation = AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.screen_rotate_minus_90_exit);
+ mEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.screen_rotate_minus_90_enter);
+ break;
+ }
+
+ mDisplay.getMetrics(mDisplayMetrics);
+
+ // Initialize the animations. This is a hack, redefining what "parent"
+ // means to allow supplying the last and next size. In this definition
+ // "%p" is the original (let's call it "previous") size, and "%" is the
+ // screen's current/new size.
+ mEnterAnimation.initialize(mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
+ mOriginalWidth, mOriginalHeight);
+ mExitAnimation.initialize(mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
+ mOriginalWidth, mOriginalHeight);
+ mStarted = false;
+
+ mExitAnimation.restrictDuration(maxAnimationDuration);
+ mExitAnimation.scaleCurrentDuration(animationScale);
+ mEnterAnimation.restrictDuration(maxAnimationDuration);
+ mEnterAnimation.scaleCurrentDuration(animationScale);
+
+ if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG,
+ ">>> OPEN TRANSACTION ScreenRotationAnimation.dismiss");
+ Surface.openTransaction();
+
+ mBlackSurfaces = new BlackSurface[4];
+ try {
+ final int w = mDisplayMetrics.widthPixels;
+ final int h = mDisplayMetrics.heightPixels;
+ mBlackSurfaces[0] = new BlackSurface(session, FREEZE_LAYER, -w, -h, w, h*2);
+ mBlackSurfaces[1] = new BlackSurface(session, FREEZE_LAYER, 0, -h, w*2, h);
+ mBlackSurfaces[2] = new BlackSurface(session, FREEZE_LAYER, w, 0, w, h*2);
+ mBlackSurfaces[3] = new BlackSurface(session, FREEZE_LAYER, -w, h, w*2, h);
+ } catch (Surface.OutOfResourcesException e) {
+ Slog.w(TAG, "Unable to allocate black surface", e);
+ } finally {
+ Surface.closeTransaction();
+ if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG,
+ "<<< CLOSE TRANSACTION ScreenRotationAnimation.dismiss");
+ }
+
+ return true;
+ }
+
+ public void kill() {
+ if (mSurface != null) {
+ mSurface.destroy();
+ mSurface = null;
+ }
+ if (mBlackSurfaces != null) {
+ for (int i=0; i<mBlackSurfaces.length; i++) {
+ if (mBlackSurfaces[i] != null) {
+ mBlackSurfaces[i].surface.destroy();
+ }
+ }
+ mBlackSurfaces = null;
+ }
+ if (mExitAnimation != null) {
+ mExitAnimation.cancel();
+ mExitAnimation = null;
+ }
+ if (mEnterAnimation != null) {
+ mEnterAnimation.cancel();
+ mEnterAnimation = null;
+ }
+ }
+
+ public boolean isAnimating() {
+ return mEnterAnimation != null || mExitAnimation != null;
+ }
+
+ public boolean stepAnimation(long now) {
+ if (mEnterAnimation == null && mExitAnimation == null) {
+ return false;
+ }
+
+ if (!mStarted) {
+ mEnterAnimation.setStartTime(now);
+ mExitAnimation.setStartTime(now);
+ mStarted = true;
+ }
+
+ mExitTransformation.clear();
+ boolean moreExit = false;
+ if (mExitAnimation != null) {
+ moreExit = mExitAnimation.getTransformation(now, mExitTransformation);
+ if (DEBUG) Slog.v(TAG, "Stepped exit: " + mExitTransformation);
+ if (!moreExit) {
+ if (DEBUG) Slog.v(TAG, "Exit animation done!");
+ mExitAnimation.cancel();
+ mExitAnimation = null;
+ mExitTransformation.clear();
+ if (mSurface != null) {
+ mSurface.hide();
+ }
+ }
+ }
+
+ mEnterTransformation.clear();
+ boolean moreEnter = false;
+ if (mEnterAnimation != null) {
+ moreEnter = mEnterAnimation.getTransformation(now, mEnterTransformation);
+ if (!moreEnter) {
+ mEnterAnimation.cancel();
+ mEnterAnimation = null;
+ mEnterTransformation.clear();
+ if (mBlackSurfaces != null) {
+ for (int i=0; i<mBlackSurfaces.length; i++) {
+ if (mBlackSurfaces[i] != null) {
+ mBlackSurfaces[i].surface.hide();
+ }
+ }
+ }
+ } else {
+ if (mBlackSurfaces != null) {
+ for (int i=0; i<mBlackSurfaces.length; i++) {
+ if (mBlackSurfaces[i] != null) {
+ mBlackSurfaces[i].setMatrix(mEnterTransformation.getMatrix());
+ }
+ }
+ }
+ }
+ }
+
+ mSnapshotFinalMatrix.setConcat(mExitTransformation.getMatrix(), mSnapshotInitialMatrix);
+ setSnapshotTransform(mSnapshotFinalMatrix, mExitTransformation.getAlpha());
+
+ return moreEnter || moreExit;
+ }
+
+ public Transformation getEnterTransformation() {
+ return mEnterTransformation;
+ }
+}
diff --git a/services/java/com/android/server/ShutdownActivity.java b/services/java/com/android/server/ShutdownActivity.java
index 64b9c5d..c9d4d01 100644
--- a/services/java/com/android/server/ShutdownActivity.java
+++ b/services/java/com/android/server/ShutdownActivity.java
@@ -27,19 +27,26 @@ import com.android.internal.app.ShutdownThread;
public class ShutdownActivity extends Activity {
private static final String TAG = "ShutdownActivity";
+ private boolean mReboot;
private boolean mConfirm;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mConfirm = getIntent().getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false);
+ Intent intent = getIntent();
+ mReboot = Intent.ACTION_REBOOT.equals(intent.getAction());
+ mConfirm = intent.getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false);
Slog.i(TAG, "onCreate(): confirm=" + mConfirm);
Handler h = new Handler();
h.post(new Runnable() {
public void run() {
- ShutdownThread.shutdown(ShutdownActivity.this, mConfirm);
+ if (mReboot) {
+ ShutdownThread.reboot(ShutdownActivity.this, null, mConfirm);
+ } else {
+ ShutdownThread.shutdown(ShutdownActivity.this, mConfirm);
+ }
}
});
}
diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java
index e5dfb18..5ada77b 100644
--- a/services/java/com/android/server/StatusBarManagerService.java
+++ b/services/java/com/android/server/StatusBarManagerService.java
@@ -32,6 +32,7 @@ import android.os.Binder;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Slog;
+import android.view.View;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.IStatusBarService;
@@ -52,11 +53,13 @@ import java.util.Map;
* if they are local, that they just enqueue messages to not deadlock.
*/
public class StatusBarManagerService extends IStatusBarService.Stub
+ implements WindowManagerService.OnHardKeyboardStatusChangeListener
{
static final String TAG = "StatusBarManagerService";
static final boolean SPEW = false;
final Context mContext;
+ final WindowManagerService mWindowManager;
Handler mHandler = new Handler();
NotificationCallbacks mNotificationCallbacks;
volatile IStatusBar mBar;
@@ -66,8 +69,17 @@ public class StatusBarManagerService extends IStatusBarService.Stub
// for disabling the status bar
ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>();
+ IBinder mSysUiVisToken = new Binder();
int mDisabled = 0;
+ Object mLock = new Object();
+ // We usually call it lights out mode, but double negatives are annoying
+ boolean mLightsOn = true;
+ boolean mMenuVisible = false;
+ int mImeWindowVis = 0;
+ int mImeBackDisposition;
+ IBinder mImeToken = null;
+
private class DisableRecord implements IBinder.DeathRecipient {
String pkg;
int what;
@@ -84,6 +96,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
void onSetDisabled(int status);
void onClearAll();
void onNotificationClick(String pkg, String tag, int id);
+ void onNotificationClear(String pkg, String tag, int id);
void onPanelRevealed();
void onNotificationError(String pkg, String tag, int id,
int uid, int initialPid, String message);
@@ -92,8 +105,10 @@ public class StatusBarManagerService extends IStatusBarService.Stub
/**
* Construct the service, add the status bar view to the window manager
*/
- public StatusBarManagerService(Context context) {
+ public StatusBarManagerService(Context context, WindowManagerService windowManager) {
mContext = context;
+ mWindowManager = windowManager;
+ mWindowManager.setOnHardKeyboardStatusChangeListener(this);
final Resources res = context.getResources();
mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.config_statusBarIcons));
@@ -104,22 +119,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
// ================================================================================
- // Constructing the view
- // ================================================================================
-
- public void systemReady() {
- }
-
- public void systemReady2() {
- ComponentName cn = ComponentName.unflattenFromString(
- mContext.getString(com.android.internal.R.string.config_statusBarComponent));
- Intent intent = new Intent();
- intent.setComponent(cn);
- Slog.i(TAG, "Starting service: " + cn);
- mContext.startService(intent);
- }
-
- // ================================================================================
// From IStatusBarService
// ================================================================================
public void expand() {
@@ -147,25 +146,29 @@ public class StatusBarManagerService extends IStatusBarService.Stub
public void disable(int what, IBinder token, String pkg) {
enforceStatusBar();
+ synchronized (mLock) {
+ disableLocked(what, token, pkg);
+ }
+ }
+
+ private void disableLocked(int what, IBinder token, String pkg) {
// It's important that the the callback and the call to mBar get done
// in the same order when multiple threads are calling this function
// so they are paired correctly. The messages on the handler will be
// handled in the order they were enqueued, but will be outside the lock.
- synchronized (mDisableRecords) {
- manageDisableListLocked(what, token, pkg);
- final int net = gatherDisableActionsLocked();
- if (net != mDisabled) {
- mDisabled = net;
- mHandler.post(new Runnable() {
- public void run() {
- mNotificationCallbacks.onSetDisabled(net);
- }
- });
- if (mBar != null) {
- try {
- mBar.disable(net);
- } catch (RemoteException ex) {
+ manageDisableListLocked(what, token, pkg);
+ final int net = gatherDisableActionsLocked();
+ if (net != mDisabled) {
+ mDisabled = net;
+ mHandler.post(new Runnable() {
+ public void run() {
+ mNotificationCallbacks.onSetDisabled(net);
}
+ });
+ if (mBar != null) {
+ try {
+ mBar.disable(net);
+ } catch (RemoteException ex) {
}
}
}
@@ -240,6 +243,109 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
}
+ /**
+ * Hide or show the on-screen Menu key. Only call this from the window manager, typically in
+ * response to a window with FLAG_NEEDS_MENU_KEY set.
+ */
+ public void setMenuKeyVisible(final boolean visible) {
+ enforceStatusBar();
+
+ if (SPEW) Slog.d(TAG, (visible?"showing":"hiding") + " MENU key");
+
+ synchronized(mLock) {
+ if (mMenuVisible != visible) {
+ mMenuVisible = visible;
+ mHandler.post(new Runnable() {
+ public void run() {
+ if (mBar != null) {
+ try {
+ mBar.setMenuKeyVisible(visible);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ });
+ }
+ }
+ }
+
+ public void setImeWindowStatus(final IBinder token, final int vis, final int backDisposition) {
+ enforceStatusBar();
+
+ if (SPEW) {
+ Slog.d(TAG, "swetImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition);
+ }
+
+ synchronized(mLock) {
+ // In case of IME change, we need to call up setImeWindowStatus() regardless of
+ // mImeWindowVis because mImeWindowVis may not have been set to false when the
+ // previous IME was destroyed.
+ mImeWindowVis = vis;
+ mImeBackDisposition = backDisposition;
+ mImeToken = token;
+ mHandler.post(new Runnable() {
+ public void run() {
+ if (mBar != null) {
+ try {
+ mBar.setImeWindowStatus(token, vis, backDisposition);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ });
+ }
+ }
+
+ public void setSystemUiVisibility(int vis) {
+ // also allows calls from window manager which is in this process.
+ enforceStatusBarService();
+
+ synchronized (mLock) {
+ final boolean lightsOn = (vis & View.STATUS_BAR_HIDDEN) == 0;
+ updateLightsOnLocked(lightsOn);
+ disableLocked(vis & StatusBarManager.DISABLE_MASK, mSysUiVisToken,
+ "WindowManager.LayoutParams");
+ }
+ }
+
+ private void updateLightsOnLocked(final boolean lightsOn) {
+ if (mLightsOn != lightsOn) {
+ mLightsOn = lightsOn;
+ mHandler.post(new Runnable() {
+ public void run() {
+ if (mBar != null) {
+ try {
+ mBar.setLightsOn(lightsOn);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ });
+ }
+ }
+
+ public void setHardKeyboardEnabled(final boolean enabled) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ mWindowManager.setHardKeyboardEnabled(enabled);
+ }
+ });
+ }
+
+ @Override
+ public void onHardKeyboardStatusChange(final boolean available, final boolean enabled) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ if (mBar != null) {
+ try {
+ mBar.setHardKeyboardStatus(available, enabled);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ });
+ }
+
private void enforceStatusBar() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR,
"StatusBarManagerService");
@@ -255,12 +361,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub
"StatusBarManagerService");
}
-
// ================================================================================
// Callbacks from the status bar service.
// ================================================================================
public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,
- List<IBinder> notificationKeys, List<StatusBarNotification> notifications) {
+ List<IBinder> notificationKeys, List<StatusBarNotification> notifications,
+ int switches[], List<IBinder> binders) {
enforceStatusBarService();
Slog.i(TAG, "registerStatusBar bar=" + bar);
@@ -274,6 +380,16 @@ public class StatusBarManagerService extends IStatusBarService.Stub
notifications.add(e.getValue());
}
}
+ synchronized (mLock) {
+ switches[0] = gatherDisableActionsLocked();
+ switches[1] = mLightsOn ? 1 : 0;
+ switches[2] = mMenuVisible ? 1 : 0;
+ switches[3] = mImeWindowVis;
+ switches[4] = mImeBackDisposition;
+ binders.add(mImeToken);
+ }
+ switches[5] = mWindowManager.isHardKeyboardAvailable() ? 1 : 0;
+ switches[6] = mWindowManager.isHardKeyboardEnabled() ? 1 : 0;
}
/**
@@ -301,6 +417,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub
mNotificationCallbacks.onNotificationError(pkg, tag, id, uid, initialPid, message);
}
+ public void onNotificationClear(String pkg, String tag, int id) {
+ enforceStatusBarService();
+
+ mNotificationCallbacks.onNotificationClear(pkg, tag, id);
+ }
+
public void onClearAllNotifications() {
enforceStatusBarService();
@@ -364,37 +486,35 @@ public class StatusBarManagerService extends IStatusBarService.Stub
Slog.d(TAG, "manageDisableList what=0x" + Integer.toHexString(what) + " pkg=" + pkg);
}
// update the list
- synchronized (mDisableRecords) {
- final int N = mDisableRecords.size();
- DisableRecord tok = null;
- int i;
- for (i=0; i<N; i++) {
- DisableRecord t = mDisableRecords.get(i);
- if (t.token == token) {
- tok = t;
- break;
- }
+ final int N = mDisableRecords.size();
+ DisableRecord tok = null;
+ int i;
+ for (i=0; i<N; i++) {
+ DisableRecord t = mDisableRecords.get(i);
+ if (t.token == token) {
+ tok = t;
+ break;
+ }
+ }
+ if (what == 0 || !token.isBinderAlive()) {
+ if (tok != null) {
+ mDisableRecords.remove(i);
+ tok.token.unlinkToDeath(tok, 0);
}
- if (what == 0 || !token.isBinderAlive()) {
- if (tok != null) {
- mDisableRecords.remove(i);
- tok.token.unlinkToDeath(tok, 0);
+ } else {
+ if (tok == null) {
+ tok = new DisableRecord();
+ try {
+ token.linkToDeath(tok, 0);
}
- } else {
- if (tok == null) {
- tok = new DisableRecord();
- try {
- token.linkToDeath(tok, 0);
- }
- catch (RemoteException ex) {
- return; // give up
- }
- mDisableRecords.add(tok);
+ catch (RemoteException ex) {
+ return; // give up
}
- tok.what = what;
- tok.token = token;
- tok.pkg = pkg;
+ mDisableRecords.add(tok);
}
+ tok.what = what;
+ tok.token = token;
+ tok.pkg = pkg;
}
}
@@ -435,7 +555,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub
}
}
- synchronized (mDisableRecords) {
+ synchronized (mLock) {
final int N = mDisableRecords.size();
pw.println(" mDisableRecords.size=" + N
+ " mDisabled=0x" + Integer.toHexString(mDisabled));
diff --git a/services/java/com/android/server/StrictModeFlash.java b/services/java/com/android/server/StrictModeFlash.java
new file mode 100644
index 0000000..0a6c625
--- /dev/null
+++ b/services/java/com/android/server/StrictModeFlash.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server; // TODO: use com.android.server.wm, once things move there
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.view.Display;
+import android.view.Surface;
+import android.view.SurfaceSession;
+
+class StrictModeFlash {
+ private static final String TAG = "StrictModeFlash";
+
+ Surface mSurface;
+ int mLastDW;
+ int mLastDH;
+ boolean mDrawNeeded;
+ final int mThickness = 20;
+
+ public StrictModeFlash(Display display, SurfaceSession session) {
+ final DisplayMetrics dm = new DisplayMetrics();
+ display.getMetrics(dm);
+
+ try {
+ mSurface = new Surface(session, 0, "StrictModeFlash", -1, 1, 1, PixelFormat.TRANSLUCENT, 0);
+ } catch (Surface.OutOfResourcesException e) {
+ return;
+ }
+
+ mSurface.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); // one more than Watermark? arbitrary.
+ mSurface.setPosition(0, 0);
+ mDrawNeeded = true;
+ }
+
+ private void drawIfNeeded() {
+ if (!mDrawNeeded) {
+ return;
+ }
+ mDrawNeeded = false;
+ final int dw = mLastDW;
+ final int dh = mLastDH;
+
+ Rect dirty = new Rect(0, 0, dw, dh);
+ Canvas c = null;
+ try {
+ c = mSurface.lockCanvas(dirty);
+ } catch (IllegalArgumentException e) {
+ } catch (Surface.OutOfResourcesException e) {
+ }
+ if (c == null) {
+ return;
+ }
+
+ // Top
+ c.clipRect(new Rect(0, 0, dw, mThickness), Region.Op.REPLACE);
+ c.drawColor(Color.RED);
+ // Left
+ c.clipRect(new Rect(0, 0, mThickness, dh), Region.Op.REPLACE);
+ c.drawColor(Color.RED);
+ // Right
+ c.clipRect(new Rect(dw - mThickness, 0, dw, dh), Region.Op.REPLACE);
+ c.drawColor(Color.RED);
+ // Bottom
+ c.clipRect(new Rect(0, dh - mThickness, dw, dh), Region.Op.REPLACE);
+ c.drawColor(Color.RED);
+
+ mSurface.unlockCanvasAndPost(c);
+ }
+
+ // Note: caller responsible for being inside
+ // Surface.openTransaction() / closeTransaction()
+ public void setVisibility(boolean on) {
+ if (mSurface == null) {
+ return;
+ }
+ drawIfNeeded();
+ if (on) {
+ mSurface.show();
+ } else {
+ mSurface.hide();
+ }
+ }
+
+ void positionSurface(int dw, int dh) {
+ if (mLastDW == dw && mLastDH == dh) {
+ return;
+ }
+ mLastDW = dw;
+ mLastDH = dh;
+ mSurface.setSize(dw, dh);
+ mDrawNeeded = true;
+ }
+
+}
diff --git a/services/java/com/android/server/SystemBackupAgent.java b/services/java/com/android/server/SystemBackupAgent.java
index fff1874..a1f43b4 100644
--- a/services/java/com/android/server/SystemBackupAgent.java
+++ b/services/java/com/android/server/SystemBackupAgent.java
@@ -16,18 +16,16 @@
package com.android.server;
-import android.app.backup.AbsoluteFileBackupHelper;
import android.app.backup.BackupDataInput;
-import android.app.backup.BackupDataInputStream;
import android.app.backup.BackupDataOutput;
-import android.app.backup.BackupHelper;
import android.app.backup.BackupAgentHelper;
+import android.app.backup.WallpaperBackupHelper;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.os.ServiceManager;
-import android.os.SystemService;
import android.util.Slog;
+
import java.io.File;
import java.io.IOException;
@@ -54,7 +52,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
// TODO: Send a delete for any stored wallpaper image in this case?
files = new String[] { WALLPAPER_INFO };
}
- addHelper("wallpaper", new AbsoluteFileBackupHelper(SystemBackupAgent.this, files));
+ addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this, files));
super.onBackup(oldState, data, newState);
}
@@ -62,12 +60,11 @@ public class SystemBackupAgent extends BackupAgentHelper {
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
throws IOException {
// On restore, we also support a previous data schema "system_files"
- addHelper("wallpaper", new AbsoluteFileBackupHelper(SystemBackupAgent.this,
+ addHelper("wallpaper", new WallpaperBackupHelper(SystemBackupAgent.this,
new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO }));
- addHelper("system_files", new AbsoluteFileBackupHelper(SystemBackupAgent.this,
+ addHelper("system_files", new WallpaperBackupHelper(SystemBackupAgent.this,
new String[] { WALLPAPER_IMAGE }));
- boolean success = false;
try {
super.onRestore(data, appVersionCode, newState);
@@ -75,7 +72,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
Context.WALLPAPER_SERVICE);
wallpaper.settingsRestored();
} catch (IOException ex) {
- // If there was a failure, delete everything for the wallpaper, this is too aggresive,
+ // If there was a failure, delete everything for the wallpaper, this is too aggressive,
// but this is hopefully a rare failure.
Slog.d(TAG, "restore failed", ex);
(new File(WALLPAPER_IMAGE)).delete();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index df69b76..33e9908 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -24,6 +24,7 @@ import com.android.internal.os.SamplingProfilerIntegration;
import dalvik.system.VMRuntime;
import dalvik.system.Zygote;
+import android.accounts.AccountManagerService;
import android.app.ActivityManagerNative;
import android.bluetooth.BluetoothAdapter;
import android.content.ComponentName;
@@ -32,9 +33,10 @@ import android.content.ContentService;
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
+import android.content.res.Configuration;
import android.database.ContentObserver;
-import android.database.Cursor;
import android.media.AudioService;
+import android.os.Build;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -46,10 +48,12 @@ import android.provider.Settings;
import android.server.BluetoothA2dpService;
import android.server.BluetoothService;
import android.server.search.SearchManagerService;
+import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
-import android.accounts.AccountManagerService;
+import android.view.Display;
+import android.view.WindowManager;
import java.io.File;
import java.util.Timer;
@@ -57,11 +61,8 @@ import java.util.TimerTask;
class ServerThread extends Thread {
private static final String TAG = "SystemServer";
- private final static boolean INCLUDE_DEMO = false;
- private static final int LOG_BOOT_PROGRESS_SYSTEM_RUN = 3010;
-
- private ContentResolver mContentResolver;
+ ContentResolver mContentResolver;
private class AdbSettingsObserver extends ContentObserver {
public AdbSettingsObserver() {
@@ -120,12 +121,13 @@ class ServerThread extends Thread {
WindowManagerService wm = null;
BluetoothService bluetooth = null;
BluetoothA2dpService bluetoothA2dp = null;
- HeadsetObserver headset = null;
+ WiredAccessoryObserver wiredAccessory = null;
DockObserver dock = null;
- UsbObserver usb = null;
+ UsbService usb = null;
UiModeManagerService uiMode = null;
RecognitionManagerService recognition = null;
ThrottleService throttle = null;
+ NetworkTimeUpdateService networkTimeUpdater = null;
// Critical services...
try {
@@ -168,13 +170,13 @@ class ServerThread extends Thread {
Slog.i(TAG, "System Content Providers");
ActivityManagerService.installSystemProviders();
- Slog.i(TAG, "Battery Service");
- battery = new BatteryService(context);
- ServiceManager.addService("battery", battery);
-
Slog.i(TAG, "Lights Service");
lights = new LightsService(context);
+ Slog.i(TAG, "Battery Service");
+ battery = new BatteryService(context, lights);
+ ServiceManager.addService("battery", battery);
+
Slog.i(TAG, "Vibrator Service");
ServiceManager.addService("vibrator", new VibratorService(context));
@@ -202,11 +204,9 @@ class ServerThread extends Thread {
// TODO: Use a more reliable check to see if this product should
// support Bluetooth - see bug 988521
if (SystemProperties.get("ro.kernel.qemu").equals("1")) {
- Slog.i(TAG, "Registering null Bluetooth Service (emulator)");
- ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, null);
+ Slog.i(TAG, "No Bluetooh Service (emulator)");
} else if (factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL) {
- Slog.i(TAG, "Registering null Bluetooth Service (factory test)");
- ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, null);
+ Slog.i(TAG, "No Bluetooth Service (factory test)");
} else {
Slog.i(TAG, "Bluetooth Service");
bluetooth = new BluetoothService(context);
@@ -215,6 +215,7 @@ class ServerThread extends Thread {
bluetoothA2dp = new BluetoothA2dpService(context, bluetooth);
ServiceManager.addService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE,
bluetoothA2dp);
+ bluetooth.initAfterA2dpRegistration();
int bluetoothOn = Settings.Secure.getInt(mContentResolver,
Settings.Secure.BLUETOOTH_ON, 0);
@@ -234,6 +235,7 @@ class ServerThread extends Thread {
NotificationManagerService notification = null;
WallpaperManagerService wallpaper = null;
LocationManagerService location = null;
+ CountryDetectorService countryDetector = null;
if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
try {
@@ -246,7 +248,7 @@ class ServerThread extends Thread {
try {
Slog.i(TAG, "Status Bar");
- statusBar = new StatusBarManagerService(context);
+ statusBar = new StatusBarManagerService(context, wm);
ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar);
} catch (Throwable e) {
Slog.e(TAG, "Failure starting StatusBarManagerService", e);
@@ -345,6 +347,14 @@ class ServerThread extends Thread {
}
try {
+ Slog.i(TAG, "Country Detector");
+ countryDetector = new CountryDetectorService(context);
+ ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting Country Detector", e);
+ }
+
+ try {
Slog.i(TAG, "Search Service");
ServiceManager.addService(Context.SEARCH_SERVICE,
new SearchManagerService(context));
@@ -352,11 +362,6 @@ class ServerThread extends Thread {
Slog.e(TAG, "Failure starting Search Service", e);
}
- if (INCLUDE_DEMO) {
- Slog.i(TAG, "Installing demo data...");
- (new DemoThread(context)).start();
- }
-
try {
Slog.i(TAG, "DropBox Service");
ServiceManager.addService(Context.DROPBOX_SERVICE,
@@ -381,14 +386,6 @@ class ServerThread extends Thread {
}
try {
- Slog.i(TAG, "Headset Observer");
- // Listen for wired headset changes
- headset = new HeadsetObserver(context);
- } catch (Throwable e) {
- Slog.e(TAG, "Failure starting HeadsetObserver", e);
- }
-
- try {
Slog.i(TAG, "Dock Observer");
// Listen for dock station changes
dock = new DockObserver(context, power);
@@ -397,11 +394,19 @@ class ServerThread extends Thread {
}
try {
+ Slog.i(TAG, "Wired Accessory Observer");
+ // Listen for wired headset changes
+ wiredAccessory = new WiredAccessoryObserver(context);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting WiredAccessoryObserver", e);
+ }
+
+ try {
Slog.i(TAG, "USB Observer");
// Listen for USB changes
- usb = new UsbObserver(context);
+ usb = new UsbService(context);
} catch (Throwable e) {
- Slog.e(TAG, "Failure starting UsbObserver", e);
+ Slog.e(TAG, "Failure starting UsbService", e);
}
try {
@@ -434,13 +439,32 @@ class ServerThread extends Thread {
} catch (Throwable e) {
Slog.e(TAG, "Failure starting Recognition Service", e);
}
-
+
try {
Slog.i(TAG, "DiskStats Service");
ServiceManager.addService("diskstats", new DiskStatsService(context));
} catch (Throwable e) {
Slog.e(TAG, "Failure starting DiskStats Service", e);
}
+
+ try {
+ // need to add this service even if SamplingProfilerIntegration.isEnabled()
+ // is false, because it is this service that detects system property change and
+ // turns on SamplingProfilerIntegration. Plus, when sampling profiler doesn't work,
+ // there is little overhead for running this service.
+ Slog.i(TAG, "SamplingProfiler Service");
+ ServiceManager.addService("samplingprofiler",
+ new SamplingProfilerService(context));
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting SamplingProfiler Service", e);
+ }
+
+ try {
+ Slog.i(TAG, "NetworkTimeUpdateService");
+ networkTimeUpdater = new NetworkTimeUpdateService(context);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting NetworkTimeUpdate service");
+ }
}
// make sure the ADB_ENABLED setting value matches the secure property value
@@ -455,14 +479,11 @@ class ServerThread extends Thread {
// we are in safe mode.
final boolean safeMode = wm.detectSafeMode();
if (safeMode) {
- try {
- ActivityManagerNative.getDefault().enterSafeMode();
- // Post the safe mode state in the Zygote class
- Zygote.systemInSafeMode = true;
- // Disable the JIT for the system_server process
- VMRuntime.getRuntime().disableJitCompilation();
- } catch (RemoteException e) {
- }
+ ActivityManagerService.self().enterSafeMode();
+ // Post the safe mode state in the Zygote class
+ Zygote.systemInSafeMode = true;
+ // Disable the JIT for the system_server process
+ VMRuntime.getRuntime().disableJitCompilation();
} else {
// Enable the JIT for the system_server process
VMRuntime.getRuntime().startJitCompilation();
@@ -478,10 +499,21 @@ class ServerThread extends Thread {
notification.systemReady();
}
- if (statusBar != null) {
- statusBar.systemReady();
- }
wm.systemReady();
+
+ if (safeMode) {
+ ActivityManagerService.self().showSafeModeOverlay();
+ }
+
+ // Update the configuration for this context by hand, because we're going
+ // to start using it before the config change done in wm.systemReady() will
+ // propagate to it.
+ Configuration config = wm.computeNewConfiguration();
+ DisplayMetrics metrics = new DisplayMetrics();
+ WindowManager w = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ w.getDefaultDisplay().getMetrics(metrics);
+ context.getResources().updateConfiguration(config, metrics);
+
power.systemReady();
try {
pm.systemReady();
@@ -489,11 +521,11 @@ class ServerThread extends Thread {
}
// These are needed to propagate to the runnable below.
- final StatusBarManagerService statusBarF = statusBar;
+ final Context contextF = context;
final BatteryService batteryF = battery;
final ConnectivityService connectivityF = connectivity;
final DockObserver dockF = dock;
- final UsbObserver usbF = usb;
+ final UsbService usbF = usb;
final ThrottleService throttleF = throttle;
final UiModeManagerService uiModeF = uiMode;
final AppWidgetService appWidgetF = appWidget;
@@ -501,6 +533,8 @@ class ServerThread extends Thread {
final InputMethodManagerService immF = imm;
final RecognitionManagerService recognitionF = recognition;
final LocationManagerService locationF = location;
+ final CountryDetectorService countryDetectorF = countryDetector;
+ final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;
// We now tell the activity manager it is okay to run third party
// code. It will call back into us once it has gotten to the state
@@ -512,7 +546,7 @@ class ServerThread extends Thread {
public void run() {
Slog.i(TAG, "Making services ready");
- if (statusBarF != null) statusBarF.systemReady2();
+ startSystemUi(contextF);
if (batteryF != null) batteryF.systemReady();
if (connectivityF != null) connectivityF.systemReady();
if (dockF != null) dockF.systemReady();
@@ -528,7 +562,9 @@ class ServerThread extends Thread {
if (wallpaperF != null) wallpaperF.systemReady();
if (immF != null) immF.systemReady();
if (locationF != null) locationF.systemReady();
+ if (countryDetectorF != null) countryDetectorF.systemReady();
if (throttleF != null) throttleF.systemReady();
+ if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemReady();
}
});
@@ -540,39 +576,17 @@ class ServerThread extends Thread {
Looper.loop();
Slog.d(TAG, "System ServerThread is exiting!");
}
-}
-class DemoThread extends Thread
-{
- DemoThread(Context context)
- {
- mContext = context;
+ static final void startSystemUi(Context context) {
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName("com.android.systemui",
+ "com.android.systemui.SystemUIService"));
+ Slog.d(TAG, "Starting service: " + intent);
+ context.startService(intent);
}
-
- @Override
- public void run()
- {
- try {
- Cursor c = mContext.getContentResolver().query(People.CONTENT_URI, null, null, null, null);
- boolean hasData = c != null && c.moveToFirst();
- if (c != null) {
- c.deactivate();
- }
- if (!hasData) {
- DemoDataSet dataset = new DemoDataSet();
- dataset.add(mContext);
- }
- } catch (Throwable e) {
- Slog.e("SystemServer", "Failure installing demo data", e);
- }
-
- }
-
- Context mContext;
}
-public class SystemServer
-{
+public class SystemServer {
private static final String TAG = "SystemServer";
public static final int FACTORY_TEST_OFF = 0;
@@ -610,15 +624,18 @@ public class SystemServer
timer.schedule(new TimerTask() {
@Override
public void run() {
- SamplingProfilerIntegration.writeSnapshot("system_server");
+ SamplingProfilerIntegration.writeSnapshot("system_server", null);
}
}, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL);
}
+ // Mmmmmm... more memory!
+ dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
+
// The system server has to run all of the time, so it needs to be
// as efficient as possible with its memory usage.
VMRuntime.getRuntime().setTargetHeapUtilization(0.8f);
-
+
System.loadLibrary("android_servers");
init1(args);
}
diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java
index 3370279..eb14180 100644
--- a/services/java/com/android/server/TelephonyRegistry.java
+++ b/services/java/com/android/server/TelephonyRegistry.java
@@ -19,7 +19,8 @@ package com.android.server;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.net.NetworkUtils;
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -35,12 +36,14 @@ import android.util.Slog;
import java.util.ArrayList;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.net.NetworkInterface;
import com.android.internal.app.IBatteryStats;
import com.android.internal.telephony.ITelephonyRegistry;
import com.android.internal.telephony.IPhoneStateListener;
import com.android.internal.telephony.DefaultPhoneNotifier;
import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.TelephonyIntents;
import com.android.server.am.BatteryStatsService;
@@ -63,7 +66,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
private final Context mContext;
- private final ArrayList<Record> mRecords = new ArrayList();
+ // access should be inside synchronized (mRecords) for these two fields
+ private final ArrayList<IBinder> mRemoveList = new ArrayList<IBinder>();
+ private final ArrayList<Record> mRecords = new ArrayList<Record>();
private final IBatteryStats mBatteryStats;
@@ -81,7 +86,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
private int mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE;
- private int mDataConnectionState = TelephonyManager.DATA_CONNECTED;
+ private int mDataConnectionState = TelephonyManager.DATA_UNKNOWN;
private boolean mDataConnectionPossible = false;
@@ -89,14 +94,18 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
private String mDataConnectionApn = "";
- private String[] mDataConnectionApnTypes = null;
+ private ArrayList<String> mConnectedApns;
- private String mDataConnectionInterfaceName = "";
+ private LinkProperties mDataConnectionLinkProperties;
+
+ private LinkCapabilities mDataConnectionLinkCapabilities;
private Bundle mCellLocation = new Bundle();
private int mDataConnectionNetworkType;
+ private int mOtaspMode = ServiceStateTracker.OTASP_UNKNOWN;
+
static final int PHONE_STATE_PERMISSION_MASK =
PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR |
PhoneStateListener.LISTEN_CALL_STATE |
@@ -121,6 +130,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
mContext = context;
mBatteryStats = BatteryStatsService.getService();
+ mConnectedApns = new ArrayList<String>();
}
public void listen(String pkgForDebug, IPhoneStateListener callback, int events,
@@ -153,7 +163,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
r.events = events;
if (notifyNow) {
if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
- sendServiceState(r, mServiceState);
+ try {
+ r.callback.onServiceStateChanged(new ServiceState(mServiceState));
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
}
if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) {
try {
@@ -179,7 +193,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
if ((events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) {
- sendCellLocation(r, mCellLocation);
+ try {
+ r.callback.onCellLocationChanged(new Bundle(mCellLocation));
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
}
if ((events & PhoneStateListener.LISTEN_CALL_STATE) != 0) {
try {
@@ -210,6 +228,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
remove(r.binder);
}
}
+ if ((events & PhoneStateListener.LISTEN_OTASP_CHANGED) != 0) {
+ try {
+ r.callback.onOtaspChanged(mOtaspMode);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
}
}
} else {
@@ -236,16 +261,16 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
synchronized (mRecords) {
mCallState = state;
mCallIncomingNumber = incomingNumber;
- for (int i = mRecords.size() - 1; i >= 0; i--) {
- Record r = mRecords.get(i);
+ for (Record r : mRecords) {
if ((r.events & PhoneStateListener.LISTEN_CALL_STATE) != 0) {
try {
r.callback.onCallStateChanged(state, incomingNumber);
} catch (RemoteException ex) {
- remove(r.binder);
+ mRemoveList.add(r.binder);
}
}
}
+ handleRemoveListLocked();
}
broadcastCallStateChanged(state, incomingNumber);
}
@@ -257,12 +282,16 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
Slog.i(TAG, "notifyServiceState: " + state);
synchronized (mRecords) {
mServiceState = state;
- for (int i = mRecords.size() - 1; i >= 0; i--) {
- Record r = mRecords.get(i);
+ for (Record r : mRecords) {
if ((r.events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
- sendServiceState(r, state);
+ try {
+ r.callback.onServiceStateChanged(new ServiceState(state));
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
}
+ handleRemoveListLocked();
}
broadcastServiceStateChanged(state);
}
@@ -273,10 +302,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
synchronized (mRecords) {
mSignalStrength = signalStrength;
- for (int i = mRecords.size() - 1; i >= 0; i--) {
- Record r = mRecords.get(i);
+ for (Record r : mRecords) {
if ((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
- sendSignalStrength(r, signalStrength);
+ try {
+ r.callback.onSignalStrengthsChanged(new SignalStrength(signalStrength));
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
if ((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) {
try {
@@ -284,10 +316,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
: gsmSignalStrength));
} catch (RemoteException ex) {
- remove(r.binder);
+ mRemoveList.add(r.binder);
}
}
}
+ handleRemoveListLocked();
}
broadcastSignalStrengthChanged(signalStrength);
}
@@ -296,19 +329,18 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (!checkNotifyPermission("notifyMessageWaitingChanged()")) {
return;
}
- Slog.i(TAG, "notifyMessageWaitingChanged: " + mwi);
synchronized (mRecords) {
mMessageWaiting = mwi;
- for (int i = mRecords.size() - 1; i >= 0; i--) {
- Record r = mRecords.get(i);
+ for (Record r : mRecords) {
if ((r.events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
try {
r.callback.onMessageWaitingIndicatorChanged(mwi);
} catch (RemoteException ex) {
- remove(r.binder);
+ mRemoveList.add(r.binder);
}
}
}
+ handleRemoveListLocked();
}
}
@@ -316,19 +348,18 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (!checkNotifyPermission("notifyCallForwardingChanged()")) {
return;
}
- Slog.i(TAG, "notifyCallForwardingChanged: " + cfi);
synchronized (mRecords) {
mCallForwarding = cfi;
- for (int i = mRecords.size() - 1; i >= 0; i--) {
- Record r = mRecords.get(i);
+ for (Record r : mRecords) {
if ((r.events & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) {
try {
r.callback.onCallForwardingIndicatorChanged(cfi);
} catch (RemoteException ex) {
- remove(r.binder);
+ mRemoveList.add(r.binder);
}
}
}
+ handleRemoveListLocked();
}
}
@@ -338,58 +369,83 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
synchronized (mRecords) {
mDataActivity = state;
- for (int i = mRecords.size() - 1; i >= 0; i--) {
- Record r = mRecords.get(i);
+ for (Record r : mRecords) {
if ((r.events & PhoneStateListener.LISTEN_DATA_ACTIVITY) != 0) {
try {
r.callback.onDataActivity(state);
} catch (RemoteException ex) {
- remove(r.binder);
+ mRemoveList.add(r.binder);
}
}
}
+ handleRemoveListLocked();
}
}
public void notifyDataConnection(int state, boolean isDataConnectivityPossible,
- String reason, String apn, String[] apnTypes, String interfaceName, int networkType,
- String gateway) {
+ String reason, String apn, String apnType, LinkProperties linkProperties,
+ LinkCapabilities linkCapabilities, int networkType) {
if (!checkNotifyPermission("notifyDataConnection()" )) {
return;
}
Slog.i(TAG, "notifyDataConnection: state=" + state + " isDataConnectivityPossible="
- + isDataConnectivityPossible + " reason=" + reason
- + " interfaceName=" + interfaceName + " networkType=" + networkType);
+ + isDataConnectivityPossible + " reason='" + reason
+ + "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType);
synchronized (mRecords) {
- mDataConnectionState = state;
+ boolean modified = false;
+ if (state == TelephonyManager.DATA_CONNECTED) {
+ if (!mConnectedApns.contains(apnType)) {
+ mConnectedApns.add(apnType);
+ if (mDataConnectionState != state) {
+ mDataConnectionState = state;
+ modified = true;
+ }
+ }
+ } else {
+ if (mConnectedApns.remove(apnType)) {
+ if (mConnectedApns.isEmpty()) {
+ mDataConnectionState = state;
+ modified = true;
+ } else {
+ // leave mDataConnectionState as is and
+ // send out the new status for the APN in question.
+ }
+ }
+ }
mDataConnectionPossible = isDataConnectivityPossible;
mDataConnectionReason = reason;
- mDataConnectionApn = apn;
- mDataConnectionApnTypes = apnTypes;
- mDataConnectionInterfaceName = interfaceName;
- mDataConnectionNetworkType = networkType;
- for (int i = mRecords.size() - 1; i >= 0; i--) {
- Record r = mRecords.get(i);
- if ((r.events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) {
- try {
- r.callback.onDataConnectionStateChanged(state, networkType);
- } catch (RemoteException ex) {
- remove(r.binder);
+ mDataConnectionLinkProperties = linkProperties;
+ mDataConnectionLinkCapabilities = linkCapabilities;
+ if (mDataConnectionNetworkType != networkType) {
+ mDataConnectionNetworkType = networkType;
+ // need to tell registered listeners about the new network type
+ modified = true;
+ }
+ if (modified) {
+ Slog.d(TAG, "onDataConnectionStateChanged(" + state + ", " + networkType + ")");
+ for (Record r : mRecords) {
+ if ((r.events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) {
+ try {
+ r.callback.onDataConnectionStateChanged(state, networkType);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
}
+ handleRemoveListLocked();
}
}
broadcastDataConnectionStateChanged(state, isDataConnectivityPossible, reason, apn,
- apnTypes, interfaceName, gateway);
+ apnType, linkProperties, linkCapabilities);
}
- public void notifyDataConnectionFailed(String reason) {
+ public void notifyDataConnectionFailed(String reason, String apnType) {
if (!checkNotifyPermission("notifyDataConnectionFailed()")) {
return;
}
/*
- * This is commented out because there is on onDataConnectionFailed callback
- * on PhoneStateListener. There should be
+ * This is commented out because there is no onDataConnectionFailed callback
+ * in PhoneStateListener. There should be.
synchronized (mRecords) {
mDataConnectionFailedReason = reason;
final int N = mRecords.size();
@@ -401,7 +457,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
*/
- broadcastDataConnectionFailed(reason);
+ broadcastDataConnectionFailed(reason, apnType);
}
public void notifyCellLocation(Bundle cellLocation) {
@@ -410,39 +466,36 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
synchronized (mRecords) {
mCellLocation = cellLocation;
- for (int i = mRecords.size() - 1; i >= 0; i--) {
- Record r = mRecords.get(i);
+ for (Record r : mRecords) {
if ((r.events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) {
- sendCellLocation(r, cellLocation);
+ try {
+ r.callback.onCellLocationChanged(new Bundle(cellLocation));
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+
}
}
+ handleRemoveListLocked();
}
}
- /**
- * Copy the service state object so they can't mess it up in the local calls
- */
- public void sendServiceState(Record r, ServiceState state) {
- try {
- r.callback.onServiceStateChanged(new ServiceState(state));
- } catch (RemoteException ex) {
- remove(r.binder);
- }
- }
-
- private void sendCellLocation(Record r, Bundle cellLocation) {
- try {
- r.callback.onCellLocationChanged(new Bundle(cellLocation));
- } catch (RemoteException ex) {
- remove(r.binder);
+ public void notifyOtaspChanged(int otaspMode) {
+ if (!checkNotifyPermission("notifyOtaspChanged()" )) {
+ return;
}
- }
-
- private void sendSignalStrength(Record r, SignalStrength signalStrength) {
- try {
- r.callback.onSignalStrengthsChanged(new SignalStrength(signalStrength));
- } catch (RemoteException ex) {
- remove(r.binder);
+ synchronized (mRecords) {
+ mOtaspMode = otaspMode;
+ for (Record r : mRecords) {
+ if ((r.events & PhoneStateListener.LISTEN_OTASP_CHANGED) != 0) {
+ try {
+ r.callback.onOtaspChanged(otaspMode);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ handleRemoveListLocked();
}
}
@@ -468,11 +521,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
pw.println(" mDataConnectionPossible=" + mDataConnectionPossible);
pw.println(" mDataConnectionReason=" + mDataConnectionReason);
pw.println(" mDataConnectionApn=" + mDataConnectionApn);
- pw.println(" mDataConnectionInterfaceName=" + mDataConnectionInterfaceName);
+ pw.println(" mDataConnectionLinkProperties=" + mDataConnectionLinkProperties);
+ pw.println(" mDataConnectionLinkCapabilities=" + mDataConnectionLinkCapabilities);
pw.println(" mCellLocation=" + mCellLocation);
pw.println("registrations: count=" + recordCount);
- for (int i = 0; i < recordCount; i++) {
- Record r = mRecords.get(i);
+ for (Record r : mRecords) {
pw.println(" " + r.pkgForDebug + " 0x" + Integer.toHexString(r.events));
}
}
@@ -543,7 +596,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
private void broadcastDataConnectionStateChanged(int state,
boolean isDataConnectivityPossible,
- String reason, String apn, String[] apnTypes, String interfaceName, String gateway) {
+ String reason, String apn, String apnType, LinkProperties linkProperties,
+ LinkCapabilities linkCapabilities) {
// Note: not reporting to the battery stats service here, because the
// status bar takes care of that after taking into account all of the
// required info.
@@ -556,29 +610,26 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (reason != null) {
intent.putExtra(Phone.STATE_CHANGE_REASON_KEY, reason);
}
- intent.putExtra(Phone.DATA_APN_KEY, apn);
- String types = new String("");
- if (apnTypes.length > 0) {
- types = apnTypes[0];
- for (int i = 1; i < apnTypes.length; i++) {
- types = types+","+apnTypes[i];
+ if (linkProperties != null) {
+ intent.putExtra(Phone.DATA_LINK_PROPERTIES_KEY, linkProperties);
+ String iface = linkProperties.getInterfaceName();
+ if (iface != null) {
+ intent.putExtra(Phone.DATA_IFACE_NAME_KEY, iface);
}
}
- intent.putExtra(Phone.DATA_APN_TYPES_KEY, types);
- intent.putExtra(Phone.DATA_IFACE_NAME_KEY, interfaceName);
- int gatewayAddr = 0;
- if (gateway != null) {
- gatewayAddr = NetworkUtils.v4StringToInt(gateway);
+ if (linkCapabilities != null) {
+ intent.putExtra(Phone.DATA_LINK_CAPABILITIES_KEY, linkCapabilities);
}
- intent.putExtra(Phone.DATA_GATEWAY_KEY, gatewayAddr);
-
+ intent.putExtra(Phone.DATA_APN_KEY, apn);
+ intent.putExtra(Phone.DATA_APN_TYPE_KEY, apnType);
mContext.sendStickyBroadcast(intent);
}
- private void broadcastDataConnectionFailed(String reason) {
+ private void broadcastDataConnectionFailed(String reason, String apnType) {
Intent intent = new Intent(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra(Phone.FAILURE_REASON_KEY, reason);
+ intent.putExtra(Phone.DATA_APN_TYPE_KEY, apnType);
mContext.sendStickyBroadcast(intent);
}
@@ -605,4 +656,13 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
android.Manifest.permission.READ_PHONE_STATE, null);
}
}
+
+ private void handleRemoveListLocked() {
+ if (mRemoveList.size() > 0) {
+ for (IBinder b: mRemoveList) {
+ remove(b);
+ }
+ mRemoveList.clear();
+ }
+ }
}
diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java
index a93a6ee..d841cb3 100644
--- a/services/java/com/android/server/ThrottleService.java
+++ b/services/java/com/android/server/ThrottleService.java
@@ -60,6 +60,8 @@ import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
import java.util.GregorianCalendar;
import java.util.Properties;
import java.util.Random;
@@ -83,8 +85,8 @@ public class ThrottleService extends IThrottleManager.Stub {
private static final long TESTING_THRESHOLD = 1 * 1024 * 1024;
private int mPolicyPollPeriodSec;
- private long mPolicyThreshold;
- private int mPolicyThrottleValue;
+ private AtomicLong mPolicyThreshold;
+ private AtomicInteger mPolicyThrottleValue;
private int mPolicyResetDay; // 1-28
private int mPolicyNotificationsAllowedMask;
@@ -114,7 +116,7 @@ public class ThrottleService extends IThrottleManager.Stub {
private InterfaceObserver mInterfaceObserver;
private SettingsObserver mSettingsObserver;
- private int mThrottleIndex; // 0 for none, 1 for first throttle val, 2 for next, etc
+ private AtomicInteger mThrottleIndex; // 0 for none, 1 for first throttle val, 2 for next, etc
private static final int THROTTLE_INDEX_UNINITIALIZED = -1;
private static final int THROTTLE_INDEX_UNTHROTTLED = 0;
@@ -126,6 +128,10 @@ public class ThrottleService extends IThrottleManager.Stub {
if (VDBG) Slog.v(TAG, "Starting ThrottleService");
mContext = context;
+ mPolicyThreshold = new AtomicLong();
+ mPolicyThrottleValue = new AtomicInteger();
+ mThrottleIndex = new AtomicInteger();
+
mNtpActive = false;
mIface = mContext.getResources().getString(R.string.config_datause_iface);
@@ -214,7 +220,7 @@ public class ThrottleService extends IThrottleManager.Stub {
}
private long ntpToWallTime(long ntpTime) {
- long bestNow = getBestTime();
+ long bestNow = getBestTime(true); // do it quickly
long localNow = System.currentTimeMillis();
return localNow + (ntpTime - bestNow);
}
@@ -222,40 +228,42 @@ public class ThrottleService extends IThrottleManager.Stub {
// TODO - fetch for the iface
// return time in the local, system wall time, correcting for the use of ntp
- public synchronized long getResetTime(String iface) {
+ public long getResetTime(String iface) {
enforceAccessPermission();
long resetTime = 0;
if (mRecorder != null) {
- resetTime = ntpToWallTime(mRecorder.getPeriodEnd());
+ resetTime = mRecorder.getPeriodEnd();
}
+ resetTime = ntpToWallTime(resetTime);
return resetTime;
}
// TODO - fetch for the iface
// return time in the local, system wall time, correcting for the use of ntp
- public synchronized long getPeriodStartTime(String iface) {
- enforceAccessPermission();
+ public long getPeriodStartTime(String iface) {
long startTime = 0;
+ enforceAccessPermission();
if (mRecorder != null) {
- startTime = ntpToWallTime(mRecorder.getPeriodStart());
+ startTime = mRecorder.getPeriodStart();
}
+ startTime = ntpToWallTime(startTime);
return startTime;
}
//TODO - a better name? getCliffByteCountThreshold?
// TODO - fetch for the iface
- public synchronized long getCliffThreshold(String iface, int cliff) {
+ public long getCliffThreshold(String iface, int cliff) {
enforceAccessPermission();
if (cliff == 1) {
- return mPolicyThreshold;
+ return mPolicyThreshold.get();
}
return 0;
}
// TODO - a better name? getThrottleRate?
// TODO - fetch for the iface
- public synchronized int getCliffLevel(String iface, int cliff) {
+ public int getCliffLevel(String iface, int cliff) {
enforceAccessPermission();
if (cliff == 1) {
- return mPolicyThrottleValue;
+ return mPolicyThrottleValue.get();
}
return 0;
}
@@ -267,10 +275,9 @@ public class ThrottleService extends IThrottleManager.Stub {
}
// TODO - fetch for the iface
- public synchronized long getByteCount(String iface, int dir, int period, int ago) {
+ public long getByteCount(String iface, int dir, int period, int ago) {
enforceAccessPermission();
- if ((period == ThrottleManager.PERIOD_CYCLE) &&
- (mRecorder != null)) {
+ if ((period == ThrottleManager.PERIOD_CYCLE) && (mRecorder != null)) {
if (dir == ThrottleManager.DIRECTION_TX) return mRecorder.getPeriodTx(ago);
if (dir == ThrottleManager.DIRECTION_RX) return mRecorder.getPeriodRx(ago);
}
@@ -279,10 +286,10 @@ public class ThrottleService extends IThrottleManager.Stub {
// TODO - a better name - getCurrentThrottleRate?
// TODO - fetch for the iface
- public synchronized int getThrottle(String iface) {
+ public int getThrottle(String iface) {
enforceAccessPermission();
- if (mThrottleIndex == 1) {
- return mPolicyThrottleValue;
+ if (mThrottleIndex.get() == 1) {
+ return mPolicyThrottleValue.get();
}
return 0;
}
@@ -305,22 +312,6 @@ public class ThrottleService extends IThrottleManager.Stub {
}
}, new IntentFilter(ACTION_RESET));
- // use a new thread as we don't want to stall the system for file writes
- mThread = new HandlerThread(TAG);
- mThread.start();
- mHandler = new MyHandler(mThread.getLooper());
- mHandler.obtainMessage(EVENT_REBOOT_RECOVERY).sendToTarget();
-
- mInterfaceObserver = new InterfaceObserver(mHandler, EVENT_IFACE_UP, mIface);
- try {
- mNMService.registerObserver(mInterfaceObserver);
- } catch (RemoteException e) {
- Slog.e(TAG, "Could not register InterfaceObserver " + e);
- }
-
- mSettingsObserver = new SettingsObserver(mHandler, EVENT_POLICY_CHANGED);
- mSettingsObserver.observe(mContext);
-
FileInputStream stream = null;
try {
Properties properties = new Properties();
@@ -337,6 +328,22 @@ public class ThrottleService extends IThrottleManager.Stub {
} catch (Exception e) {}
}
}
+
+ // use a new thread as we don't want to stall the system for file writes
+ mThread = new HandlerThread(TAG);
+ mThread.start();
+ mHandler = new MyHandler(mThread.getLooper());
+ mHandler.obtainMessage(EVENT_REBOOT_RECOVERY).sendToTarget();
+
+ mInterfaceObserver = new InterfaceObserver(mHandler, EVENT_IFACE_UP, mIface);
+ try {
+ mNMService.registerObserver(mInterfaceObserver);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not register InterfaceObserver " + e);
+ }
+
+ mSettingsObserver = new SettingsObserver(mHandler, EVENT_POLICY_CHANGED);
+ mSettingsObserver.observe(mContext);
}
@@ -375,7 +382,7 @@ public class ThrottleService extends IThrottleManager.Stub {
// check for sim change TODO
// reregister for notification of policy change
- mThrottleIndex = THROTTLE_INDEX_UNINITIALIZED;
+ mThrottleIndex.set(THROTTLE_INDEX_UNINITIALIZED);
mRecorder = new DataRecorder(mContext, ThrottleService.this);
@@ -403,15 +410,16 @@ public class ThrottleService extends IThrottleManager.Stub {
R.integer.config_datause_threshold_bytes);
int defaultValue = mContext.getResources().getInteger(
R.integer.config_datause_throttle_kbitsps);
- synchronized (ThrottleService.this) {
- mPolicyThreshold = Settings.Secure.getLong(mContext.getContentResolver(),
- Settings.Secure.THROTTLE_THRESHOLD_BYTES, defaultThreshold);
- mPolicyThrottleValue = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.THROTTLE_VALUE_KBITSPS, defaultValue);
- if (testing) {
- mPolicyPollPeriodSec = TESTING_POLLING_PERIOD_SEC;
- mPolicyThreshold = TESTING_THRESHOLD;
- }
+ long threshold = Settings.Secure.getLong(mContext.getContentResolver(),
+ Settings.Secure.THROTTLE_THRESHOLD_BYTES, defaultThreshold);
+ int value = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.THROTTLE_VALUE_KBITSPS, defaultValue);
+
+ mPolicyThreshold.set(threshold);
+ mPolicyThrottleValue.set(value);
+ if (testing) {
+ mPolicyPollPeriodSec = TESTING_POLLING_PERIOD_SEC;
+ mPolicyThreshold.set(TESTING_THRESHOLD);
}
mPolicyResetDay = Settings.Secure.getInt(mContext.getContentResolver(),
@@ -423,10 +431,8 @@ public class ThrottleService extends IThrottleManager.Stub {
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.THROTTLE_RESET_DAY, mPolicyResetDay);
}
- synchronized (ThrottleService.this) {
- if (mIface == null) {
- mPolicyThreshold = 0;
- }
+ if (mIface == null) {
+ mPolicyThreshold.set(0);
}
int defaultNotificationType = mContext.getResources().getInteger(
@@ -437,15 +443,16 @@ public class ThrottleService extends IThrottleManager.Stub {
mMaxNtpCacheAgeSec = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.THROTTLE_MAX_NTP_CACHE_AGE_SEC, MAX_NTP_CACHE_AGE_SEC);
- if (VDBG || (mPolicyThreshold != 0)) {
+ if (VDBG || (mPolicyThreshold.get() != 0)) {
Slog.d(TAG, "onPolicyChanged testing=" + testing +", period=" +
- mPolicyPollPeriodSec + ", threshold=" + mPolicyThreshold + ", value=" +
- mPolicyThrottleValue + ", resetDay=" + mPolicyResetDay + ", noteType=" +
- mPolicyNotificationsAllowedMask + ", maxNtpCacheAge=" + mMaxNtpCacheAgeSec);
+ mPolicyPollPeriodSec + ", threshold=" + mPolicyThreshold.get() +
+ ", value=" + mPolicyThrottleValue.get() + ", resetDay=" + mPolicyResetDay +
+ ", noteType=" + mPolicyNotificationsAllowedMask + ", maxNtpCacheAge=" +
+ mMaxNtpCacheAgeSec);
}
// force updates
- mThrottleIndex = THROTTLE_INDEX_UNINITIALIZED;
+ mThrottleIndex.set(THROTTLE_INDEX_UNINITIALIZED);
onResetAlarm();
@@ -487,7 +494,7 @@ public class ThrottleService extends IThrottleManager.Stub {
long periodRx = mRecorder.getPeriodRx(0);
long periodTx = mRecorder.getPeriodTx(0);
long total = periodRx + periodTx;
- if (VDBG || (mPolicyThreshold != 0)) {
+ if (VDBG || (mPolicyThreshold.get() != 0)) {
Slog.d(TAG, "onPollAlarm - roaming =" + roaming +
", read =" + incRead + ", written =" + incWrite + ", new total =" + total);
}
@@ -510,11 +517,11 @@ public class ThrottleService extends IThrottleManager.Stub {
private void onIfaceUp() {
// if we were throttled before, be sure and set it again - the iface went down
// (and may have disappeared all together) and these settings were lost
- if (mThrottleIndex == 1) {
+ if (mThrottleIndex.get() == 1) {
try {
mNMService.setInterfaceThrottle(mIface, -1, -1);
mNMService.setInterfaceThrottle(mIface,
- mPolicyThrottleValue, mPolicyThrottleValue);
+ mPolicyThrottleValue.get(), mPolicyThrottleValue.get());
} catch (Exception e) {
Slog.e(TAG, "error setting Throttle: " + e);
}
@@ -523,7 +530,8 @@ public class ThrottleService extends IThrottleManager.Stub {
private void checkThrottleAndPostNotification(long currentTotal) {
// is throttling enabled?
- if (mPolicyThreshold == 0) {
+ long threshold = mPolicyThreshold.get();
+ if (threshold == 0) {
clearThrottleAndNotification();
return;
}
@@ -535,15 +543,13 @@ public class ThrottleService extends IThrottleManager.Stub {
}
// check if we need to throttle
- if (currentTotal > mPolicyThreshold) {
- if (mThrottleIndex != 1) {
- synchronized (ThrottleService.this) {
- mThrottleIndex = 1;
- }
- if (DBG) Slog.d(TAG, "Threshold " + mPolicyThreshold + " exceeded!");
+ if (currentTotal > threshold) {
+ if (mThrottleIndex.get() != 1) {
+ mThrottleIndex.set(1);
+ if (DBG) Slog.d(TAG, "Threshold " + threshold + " exceeded!");
try {
mNMService.setInterfaceThrottle(mIface,
- mPolicyThrottleValue, mPolicyThrottleValue);
+ mPolicyThrottleValue.get(), mPolicyThrottleValue.get());
} catch (Exception e) {
Slog.e(TAG, "error setting Throttle: " + e);
}
@@ -556,7 +562,8 @@ public class ThrottleService extends IThrottleManager.Stub {
Notification.FLAG_ONGOING_EVENT);
Intent broadcast = new Intent(ThrottleManager.THROTTLE_ACTION);
- broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, mPolicyThrottleValue);
+ broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL,
+ mPolicyThrottleValue.get());
mContext.sendStickyBroadcast(broadcast);
} // else already up!
@@ -579,8 +586,8 @@ public class ThrottleService extends IThrottleManager.Stub {
long periodLength = end - start;
long now = System.currentTimeMillis();
long timeUsed = now - start;
- long warningThreshold = 2*mPolicyThreshold*timeUsed/(timeUsed+periodLength);
- if ((currentTotal > warningThreshold) && (currentTotal > mPolicyThreshold/4)) {
+ long warningThreshold = 2*threshold*timeUsed/(timeUsed+periodLength);
+ if ((currentTotal > warningThreshold) && (currentTotal > threshold/4)) {
if (mWarningNotificationSent == false) {
mWarningNotificationSent = true;
mNotificationManager.cancel(R.drawable.stat_sys_throttled);
@@ -625,11 +632,9 @@ public class ThrottleService extends IThrottleManager.Stub {
}
- private synchronized void clearThrottleAndNotification() {
- if (mThrottleIndex != THROTTLE_INDEX_UNTHROTTLED) {
- synchronized (ThrottleService.this) {
- mThrottleIndex = THROTTLE_INDEX_UNTHROTTLED;
- }
+ private void clearThrottleAndNotification() {
+ if (mThrottleIndex.get() != THROTTLE_INDEX_UNTHROTTLED) {
+ mThrottleIndex.set(THROTTLE_INDEX_UNTHROTTLED);
try {
mNMService.setInterfaceThrottle(mIface, -1, -1);
} catch (Exception e) {
@@ -687,12 +692,12 @@ public class ThrottleService extends IThrottleManager.Stub {
}
private void onResetAlarm() {
- if (VDBG || (mPolicyThreshold != 0)) {
+ if (VDBG || (mPolicyThreshold.get() != 0)) {
Slog.d(TAG, "onResetAlarm - last period had " + mRecorder.getPeriodRx(0) +
" bytes read and " + mRecorder.getPeriodTx(0) + " written");
}
- long now = getBestTime();
+ long now = getBestTime(false);
if (mNtpActive || (mNtpServer == null)) {
Calendar end = calculatePeriodEnd(now);
@@ -719,20 +724,23 @@ public class ThrottleService extends IThrottleManager.Stub {
// will try to get the ntp time and switch to it if found.
// will also cache the time so we don't fetch it repeatedly.
- getBestTime();
+ getBestTime(false);
}
private static final int MAX_NTP_CACHE_AGE_SEC = 60 * 60 * 24; // 1 day
- private static final int MAX_NTP_FETCH_WAIT = 10 * 1000;
+ private static final int MAX_NTP_FETCH_WAIT = 20 * 1000;
private int mMaxNtpCacheAgeSec = MAX_NTP_CACHE_AGE_SEC;
private long cachedNtp;
private long cachedNtpTimestamp;
- private long getBestTime() {
+ // if the request is tied to UI and ANR's are a danger, request a fast result
+ // the regular polling should have updated the cached time recently using the
+ // slower method (!fast)
+ private long getBestTime(boolean fast) {
if (mNtpServer != null) {
if (mNtpActive) {
long ntpAge = SystemClock.elapsedRealtime() - cachedNtpTimestamp;
- if (ntpAge < mMaxNtpCacheAgeSec * 1000) {
+ if (ntpAge < mMaxNtpCacheAgeSec * 1000 || fast) {
if (VDBG) Slog.v(TAG, "using cached time");
return cachedNtp + ntpAge;
}
@@ -1025,39 +1033,57 @@ public class ThrottleService extends IThrottleManager.Stub {
if (DBG) Slog.d(TAG, "data file empty");
return;
}
- synchronized (mParent) {
- String[] parsed = data.split(":");
- int parsedUsed = 0;
- if (parsed.length < 6) {
- Slog.e(TAG, "reading data file with insufficient length - ignoring");
- return;
- }
+ String[] parsed = data.split(":");
+ int parsedUsed = 0;
+ if (parsed.length < 6) {
+ Slog.e(TAG, "reading data file with insufficient length - ignoring");
+ return;
+ }
+ int periodCount;
+ long[] periodRxData;
+ long[] periodTxData;
+ int currentPeriod;
+ Calendar periodStart;
+ Calendar periodEnd;
+ try {
if (Integer.parseInt(parsed[parsedUsed++]) != DATA_FILE_VERSION) {
Slog.e(TAG, "reading data file with bad version - ignoring");
return;
}
- mPeriodCount = Integer.parseInt(parsed[parsedUsed++]);
- if (parsed.length != 5 + (2 * mPeriodCount)) {
+ periodCount = Integer.parseInt(parsed[parsedUsed++]);
+ if (parsed.length != 5 + (2 * periodCount)) {
Slog.e(TAG, "reading data file with bad length (" + parsed.length +
- " != " + (5+(2*mPeriodCount)) + ") - ignoring");
+ " != " + (5 + (2 * periodCount)) + ") - ignoring");
return;
}
-
- mPeriodRxData = new long[mPeriodCount];
- for(int i = 0; i < mPeriodCount; i++) {
- mPeriodRxData[i] = Long.parseLong(parsed[parsedUsed++]);
+ periodRxData = new long[periodCount];
+ for (int i = 0; i < periodCount; i++) {
+ periodRxData[i] = Long.parseLong(parsed[parsedUsed++]);
}
- mPeriodTxData = new long[mPeriodCount];
- for(int i = 0; i < mPeriodCount; i++) {
- mPeriodTxData[i] = Long.parseLong(parsed[parsedUsed++]);
+ periodTxData = new long[periodCount];
+ for (int i = 0; i < periodCount; i++) {
+ periodTxData[i] = Long.parseLong(parsed[parsedUsed++]);
}
- mCurrentPeriod = Integer.parseInt(parsed[parsedUsed++]);
- mPeriodStart = new GregorianCalendar();
- mPeriodStart.setTimeInMillis(Long.parseLong(parsed[parsedUsed++]));
- mPeriodEnd = new GregorianCalendar();
- mPeriodEnd.setTimeInMillis(Long.parseLong(parsed[parsedUsed++]));
+
+ currentPeriod = Integer.parseInt(parsed[parsedUsed++]);
+
+ periodStart = new GregorianCalendar();
+ periodStart.setTimeInMillis(Long.parseLong(parsed[parsedUsed++]));
+ periodEnd = new GregorianCalendar();
+ periodEnd.setTimeInMillis(Long.parseLong(parsed[parsedUsed++]));
+ } catch (Exception e) {
+ Slog.e(TAG, "Error parsing data file - ignoring");
+ return;
+ }
+ synchronized (mParent) {
+ mPeriodCount = periodCount;
+ mPeriodRxData = periodRxData;
+ mPeriodTxData = periodTxData;
+ mCurrentPeriod = currentPeriod;
+ mPeriodStart = periodStart;
+ mPeriodEnd = periodEnd;
}
}
@@ -1091,15 +1117,15 @@ public class ThrottleService extends IThrottleManager.Stub {
}
pw.println();
- pw.println("The threshold is " + mPolicyThreshold +
+ pw.println("The threshold is " + mPolicyThreshold.get() +
", after which you experince throttling to " +
- mPolicyThrottleValue + "kbps");
+ mPolicyThrottleValue.get() + "kbps");
pw.println("Current period is " +
(mRecorder.getPeriodEnd() - mRecorder.getPeriodStart())/1000 + " seconds long " +
"and ends in " + (getResetTime(mIface) - System.currentTimeMillis()) / 1000 +
" seconds.");
pw.println("Polling every " + mPolicyPollPeriodSec + " seconds");
- pw.println("Current Throttle Index is " + mThrottleIndex);
+ pw.println("Current Throttle Index is " + mThrottleIndex.get());
pw.println("Max NTP Cache Age is " + mMaxNtpCacheAgeSec);
for (int i = 0; i < mRecorder.getPeriodCount(); i++) {
diff --git a/services/java/com/android/server/UsbObserver.java b/services/java/com/android/server/UsbObserver.java
deleted file mode 100644
index d08fe9b..0000000
--- a/services/java/com/android/server/UsbObserver.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.hardware.Usb;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-import android.os.UEventObserver;
-import android.provider.Settings;
-import android.util.Log;
-import android.util.Slog;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.util.ArrayList;
-
-/**
- * <p>UsbObserver monitors for changes to USB state.
- */
-class UsbObserver extends UEventObserver {
- private static final String TAG = UsbObserver.class.getSimpleName();
- private static final boolean LOG = false;
-
- private static final String USB_CONFIGURATION_MATCH = "DEVPATH=/devices/virtual/switch/usb_configuration";
- private static final String USB_FUNCTIONS_MATCH = "DEVPATH=/devices/virtual/usb_composite/";
- private static final String USB_CONFIGURATION_PATH = "/sys/class/switch/usb_configuration/state";
- private static final String USB_COMPOSITE_CLASS_PATH = "/sys/class/usb_composite";
-
- private static final int MSG_UPDATE = 0;
-
- private int mUsbConfig = 0;
- private int mPreviousUsbConfig = 0;
-
- // lists of enabled and disabled USB functions
- private final ArrayList<String> mEnabledFunctions = new ArrayList<String>();
- private final ArrayList<String> mDisabledFunctions = new ArrayList<String>();
-
- private boolean mSystemReady;
-
- private final Context mContext;
-
- private PowerManagerService mPowerManager;
-
- public UsbObserver(Context context) {
- mContext = context;
- init(); // set initial status
-
- startObserving(USB_CONFIGURATION_MATCH);
- startObserving(USB_FUNCTIONS_MATCH);
- }
-
- @Override
- public void onUEvent(UEventObserver.UEvent event) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Slog.v(TAG, "USB UEVENT: " + event.toString());
- }
-
- synchronized (this) {
- String switchState = event.get("SWITCH_STATE");
- if (switchState != null) {
- try {
- int newConfig = Integer.parseInt(switchState);
- if (newConfig != mUsbConfig) {
- mPreviousUsbConfig = mUsbConfig;
- mUsbConfig = newConfig;
- // trigger an Intent broadcast
- if (mSystemReady) {
- update();
- }
- }
- } catch (NumberFormatException e) {
- Slog.e(TAG, "Could not parse switch state from event " + event);
- }
- } else {
- String function = event.get("FUNCTION");
- String enabledStr = event.get("ENABLED");
- if (function != null && enabledStr != null) {
- // Note: we do not broadcast a change when a function is enabled or disabled.
- // We just record the state change for the next broadcast.
- boolean enabled = "1".equals(enabledStr);
- if (enabled) {
- if (!mEnabledFunctions.contains(function)) {
- mEnabledFunctions.add(function);
- }
- mDisabledFunctions.remove(function);
- } else {
- if (!mDisabledFunctions.contains(function)) {
- mDisabledFunctions.add(function);
- }
- mEnabledFunctions.remove(function);
- }
- }
- }
- }
- }
- private final void init() {
- char[] buffer = new char[1024];
-
- try {
- FileReader file = new FileReader(USB_CONFIGURATION_PATH);
- int len = file.read(buffer, 0, 1024);
- mPreviousUsbConfig = mUsbConfig = Integer.valueOf((new String(buffer, 0, len)).trim());
-
- } catch (FileNotFoundException e) {
- Slog.w(TAG, "This kernel does not have USB configuration switch support");
- } catch (Exception e) {
- Slog.e(TAG, "" , e);
- }
-
- try {
- File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles();
- for (int i = 0; i < files.length; i++) {
- File file = new File(files[i], "enable");
- FileReader reader = new FileReader(file);
- int len = reader.read(buffer, 0, 1024);
- int value = Integer.valueOf((new String(buffer, 0, len)).trim());
- String functionName = files[i].getName();
- if (value == 1) {
- mEnabledFunctions.add(functionName);
- } else {
- mDisabledFunctions.add(functionName);
- }
- }
- } catch (FileNotFoundException e) {
- Slog.w(TAG, "This kernel does not have USB composite class support");
- } catch (Exception e) {
- Slog.e(TAG, "" , e);
- }
- }
-
- void systemReady() {
- synchronized (this) {
- update();
- mSystemReady = true;
- }
- }
-
- private final void update() {
- mHandler.sendEmptyMessage(MSG_UPDATE);
- }
-
- private final Handler mHandler = new Handler() {
- private void addEnabledFunctions(Intent intent) {
- // include state of all USB functions in our extras
- for (int i = 0; i < mEnabledFunctions.size(); i++) {
- intent.putExtra(mEnabledFunctions.get(i), Usb.USB_FUNCTION_ENABLED);
- }
- for (int i = 0; i < mDisabledFunctions.size(); i++) {
- intent.putExtra(mDisabledFunctions.get(i), Usb.USB_FUNCTION_DISABLED);
- }
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_UPDATE:
- synchronized (this) {
- final ContentResolver cr = mContext.getContentResolver();
-
- if (Settings.Secure.getInt(cr,
- Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
- Slog.i(TAG, "Device not provisioned, skipping USB broadcast");
- return;
- }
- // Send an Intent containing connected/disconnected state
- // and the enabled/disabled state of all USB functions
- Intent intent;
- boolean usbConnected = (mUsbConfig != 0);
- if (usbConnected) {
- intent = new Intent(Usb.ACTION_USB_CONNECTED);
- addEnabledFunctions(intent);
- } else {
- intent = new Intent(Usb.ACTION_USB_DISCONNECTED);
- }
- mContext.sendBroadcast(intent);
-
- // send a sticky broadcast for clients interested in both connect and disconnect
- intent = new Intent(Usb.ACTION_USB_STATE);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- intent.putExtra(Usb.USB_CONNECTED, usbConnected);
- addEnabledFunctions(intent);
- mContext.sendStickyBroadcast(intent);
- }
- break;
- }
- }
- };
-}
diff --git a/services/java/com/android/server/UsbService.java b/services/java/com/android/server/UsbService.java
new file mode 100644
index 0000000..8ef03d4
--- /dev/null
+++ b/services/java/com/android/server/UsbService.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.UsbManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UEventObserver;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.util.ArrayList;
+
+/**
+ * <p>UsbService monitors for changes to USB state.
+ */
+class UsbService {
+ private static final String TAG = UsbService.class.getSimpleName();
+ private static final boolean LOG = false;
+
+ private static final String USB_CONNECTED_MATCH =
+ "DEVPATH=/devices/virtual/switch/usb_connected";
+ private static final String USB_CONFIGURATION_MATCH =
+ "DEVPATH=/devices/virtual/switch/usb_configuration";
+ private static final String USB_FUNCTIONS_MATCH =
+ "DEVPATH=/devices/virtual/usb_composite/";
+ private static final String USB_CONNECTED_PATH =
+ "/sys/class/switch/usb_connected/state";
+ private static final String USB_CONFIGURATION_PATH =
+ "/sys/class/switch/usb_configuration/state";
+ private static final String USB_COMPOSITE_CLASS_PATH =
+ "/sys/class/usb_composite";
+
+ private static final int MSG_UPDATE = 0;
+
+ // Delay for debouncing USB disconnects.
+ // We often get rapid connect/disconnect events when enabling USB functions,
+ // which need debouncing.
+ private static final int UPDATE_DELAY = 1000;
+
+ // current connected and configuration state
+ private int mConnected;
+ private int mConfiguration;
+
+ // last broadcasted connected and configuration state
+ private int mLastConnected = -1;
+ private int mLastConfiguration = -1;
+
+ // lists of enabled and disabled USB functions
+ private final ArrayList<String> mEnabledFunctions = new ArrayList<String>();
+ private final ArrayList<String> mDisabledFunctions = new ArrayList<String>();
+
+ private boolean mSystemReady;
+
+ private final Context mContext;
+
+ private final UEventObserver mUEventObserver = new UEventObserver() {
+ @Override
+ public void onUEvent(UEventObserver.UEvent event) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Slog.v(TAG, "USB UEVENT: " + event.toString());
+ }
+
+ synchronized (this) {
+ String name = event.get("SWITCH_NAME");
+ String state = event.get("SWITCH_STATE");
+ if (name != null && state != null) {
+ try {
+ int intState = Integer.parseInt(state);
+ if ("usb_connected".equals(name)) {
+ mConnected = intState;
+ // trigger an Intent broadcast
+ if (mSystemReady) {
+ // debounce disconnects
+ update(mConnected == 0);
+ }
+ } else if ("usb_configuration".equals(name)) {
+ mConfiguration = intState;
+ // trigger an Intent broadcast
+ if (mSystemReady) {
+ update(mConnected == 0);
+ }
+ }
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Could not parse switch state from event " + event);
+ }
+ } else {
+ String function = event.get("FUNCTION");
+ String enabledStr = event.get("ENABLED");
+ if (function != null && enabledStr != null) {
+ // Note: we do not broadcast a change when a function is enabled or disabled.
+ // We just record the state change for the next broadcast.
+ boolean enabled = "1".equals(enabledStr);
+ if (enabled) {
+ if (!mEnabledFunctions.contains(function)) {
+ mEnabledFunctions.add(function);
+ }
+ mDisabledFunctions.remove(function);
+ } else {
+ if (!mDisabledFunctions.contains(function)) {
+ mDisabledFunctions.add(function);
+ }
+ mEnabledFunctions.remove(function);
+ }
+ }
+ }
+ }
+ }
+ };
+
+ public UsbService(Context context) {
+ mContext = context;
+ init(); // set initial status
+
+ if (mConfiguration >= 0) {
+ mUEventObserver.startObserving(USB_CONNECTED_MATCH);
+ mUEventObserver.startObserving(USB_CONFIGURATION_MATCH);
+ mUEventObserver.startObserving(USB_FUNCTIONS_MATCH);
+ }
+ }
+
+ private final void init() {
+ char[] buffer = new char[1024];
+
+ mConfiguration = -1;
+ try {
+ FileReader file = new FileReader(USB_CONNECTED_PATH);
+ int len = file.read(buffer, 0, 1024);
+ file.close();
+ mConnected = Integer.valueOf((new String(buffer, 0, len)).trim());
+
+ file = new FileReader(USB_CONFIGURATION_PATH);
+ len = file.read(buffer, 0, 1024);
+ file.close();
+ mConfiguration = Integer.valueOf((new String(buffer, 0, len)).trim());
+
+ } catch (FileNotFoundException e) {
+ Slog.i(TAG, "This kernel does not have USB configuration switch support");
+ } catch (Exception e) {
+ Slog.e(TAG, "" , e);
+ }
+ if (mConfiguration < 0)
+ return;
+
+ try {
+ File[] files = new File(USB_COMPOSITE_CLASS_PATH).listFiles();
+ for (int i = 0; i < files.length; i++) {
+ File file = new File(files[i], "enable");
+ FileReader reader = new FileReader(file);
+ int len = reader.read(buffer, 0, 1024);
+ reader.close();
+ int value = Integer.valueOf((new String(buffer, 0, len)).trim());
+ String functionName = files[i].getName();
+ if (value == 1) {
+ mEnabledFunctions.add(functionName);
+ } else {
+ mDisabledFunctions.add(functionName);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ Slog.w(TAG, "This kernel does not have USB composite class support");
+ } catch (Exception e) {
+ Slog.e(TAG, "" , e);
+ }
+ }
+
+ private void initHostSupport() {
+ // temporarily disabled
+ }
+
+ void systemReady() {
+ synchronized (this) {
+ if (mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_hasUsbHostSupport)) {
+ // start monitoring for connected USB devices
+ initHostSupport();
+ }
+
+ update(false);
+ mSystemReady = true;
+ }
+ }
+
+ private final void update(boolean delayed) {
+ mHandler.removeMessages(MSG_UPDATE);
+ mHandler.sendEmptyMessageDelayed(MSG_UPDATE, delayed ? UPDATE_DELAY : 0);
+ }
+
+ private final Handler mHandler = new Handler() {
+ private void addEnabledFunctions(Intent intent) {
+ // include state of all USB functions in our extras
+ for (int i = 0; i < mEnabledFunctions.size(); i++) {
+ intent.putExtra(mEnabledFunctions.get(i), UsbManager.USB_FUNCTION_ENABLED);
+ }
+ for (int i = 0; i < mDisabledFunctions.size(); i++) {
+ intent.putExtra(mDisabledFunctions.get(i), UsbManager.USB_FUNCTION_DISABLED);
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE:
+ synchronized (this) {
+ if (mConnected != mLastConnected || mConfiguration != mLastConfiguration) {
+
+ final ContentResolver cr = mContext.getContentResolver();
+ if (Settings.Secure.getInt(cr,
+ Settings.Secure.DEVICE_PROVISIONED, 0) == 0) {
+ Slog.i(TAG, "Device not provisioned, skipping USB broadcast");
+ return;
+ }
+
+ mLastConnected = mConnected;
+ mLastConfiguration = mConfiguration;
+
+ // send a sticky broadcast containing current USB state
+ Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra(UsbManager.USB_CONNECTED, mConnected != 0);
+ intent.putExtra(UsbManager.USB_CONFIGURATION, mConfiguration);
+ addEnabledFunctions(intent);
+ mContext.sendStickyBroadcast(intent);
+ }
+ }
+ break;
+ }
+ }
+ };
+}
diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java
index f0b5955..2fcdb5d 100755
--- a/services/java/com/android/server/VibratorService.java
+++ b/services/java/com/android/server/VibratorService.java
@@ -112,6 +112,10 @@ public class VibratorService extends IVibratorService.Stub {
context.registerReceiver(mIntentReceiver, filter);
}
+ public boolean hasVibrator() {
+ return vibratorExists();
+ }
+
public void vibrate(long milliseconds, IBinder token) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
!= PackageManager.PERMISSION_GRANTED) {
@@ -380,6 +384,7 @@ public class VibratorService extends IVibratorService.Stub {
volatile VibrateThread mThread;
+ native static boolean vibratorExists();
native static void vibratorOn(long milliseconds);
native static void vibratorOff();
}
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 5aa0111..22583f7 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,92 +16,76 @@
package com.android.server;
-import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
-import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING;
-import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
-import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
-import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
-
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
-
import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.database.ContentObserver;
import android.net.wifi.IWifiManager;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.net.wifi.WifiNative;
-import android.net.wifi.WifiStateTracker;
+import android.net.wifi.WifiStateMachine;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.SupplicantState;
import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.net.wifi.WpsConfiguration;
+import android.net.wifi.WpsResult;
import android.net.ConnectivityManager;
import android.net.InterfaceConfiguration;
-import android.net.NetworkStateTracker;
import android.net.DhcpInfo;
-import android.net.NetworkUtils;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.State;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.INetworkManagementService;
-import android.os.Looper;
import android.os.Message;
-import android.os.PowerManager;
-import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.WorkSource;
import android.provider.Settings;
-import android.util.Slog;
import android.text.TextUtils;
+import android.util.Slog;
+import java.net.InetAddress;
import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
import java.util.Set;
-import java.util.regex.Pattern;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.net.UnknownHostException;
import com.android.internal.app.IBatteryStats;
-import android.app.backup.IBackupManager;
+import com.android.internal.util.AsyncChannel;
import com.android.server.am.BatteryStatsService;
import com.android.internal.R;
/**
* WifiService handles remote WiFi operation requests by implementing
- * the IWifiManager interface. It also creates a WifiMonitor to listen
- * for Wifi-related events.
+ * the IWifiManager interface.
*
* @hide
*/
+//TODO: Clean up multiple locks and implement WifiService
+// as a SM to track soft AP/client/adhoc bring up based
+// on device idle state, airplane mode and boot.
+
public class WifiService extends IWifiManager.Stub {
private static final String TAG = "WifiService";
- private static final boolean DBG = false;
- private static final Pattern scanResultPattern = Pattern.compile("\t+");
- private final WifiStateTracker mWifiStateTracker;
- /* TODO: fetch a configurable interface */
- private static final String SOFTAP_IFACE = "wl0.1";
+ private static final boolean DBG = true;
+
+ private final WifiStateMachine mWifiStateMachine;
private Context mContext;
- private int mWifiApState;
private AlarmManager mAlarmManager;
private PendingIntent mIdleIntent;
@@ -110,10 +94,11 @@ public class WifiService extends IWifiManager.Stub {
private boolean mDeviceIdle;
private int mPluggedType;
- private enum DriverAction {DRIVER_UNLOAD, NO_DRIVER_UNLOAD};
+ /* Chipset supports background scan */
+ private final boolean mBackgroundScanSupported;
// true if the user enabled Wifi while in airplane mode
- private boolean mAirplaneModeOverwridden;
+ private AtomicBoolean mAirplaneModeOverwridden = new AtomicBoolean(false);
private final LockList mLocks = new LockList();
// some wifi lock statistics
@@ -131,9 +116,7 @@ public class WifiService extends IWifiManager.Stub {
private final IBatteryStats mBatteryStats;
- private INetworkManagementService nwService;
ConnectivityManager mCm;
- private WifiWatchdogService mWifiWatchdogService = null;
private String[] mWifiRegexs;
/**
@@ -143,122 +126,130 @@ public class WifiService extends IWifiManager.Stub {
* being enabled but not active exceeds the battery drain caused by
* re-establishing a connection to the mobile data network.
*/
- private static final long DEFAULT_IDLE_MILLIS = 15 * 60 * 1000; /* 15 minutes */
+ private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */
- private static final String WAKELOCK_TAG = "*wifi*";
+ private static final String ACTION_DEVICE_IDLE =
+ "com.android.server.WifiManager.action.DEVICE_IDLE";
+
+ private boolean mIsReceiverRegistered = false;
+
+ NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
+
+ // Variables relating to the 'available networks' notification
/**
- * The maximum amount of time to hold the wake lock after a disconnect
- * caused by stopping the driver. Establishing an EDGE connection has been
- * observed to take about 5 seconds under normal circumstances. This
- * provides a bit of extra margin.
- * <p>
- * See {@link android.provider.Settings.Secure#WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS}.
- * This is the default value if a Settings.Secure value is not present.
+ * The icon to show in the 'available networks' notification. This will also
+ * be the ID of the Notification given to the NotificationManager.
*/
- private static final int DEFAULT_WAKELOCK_TIMEOUT = 8000;
-
- // Wake lock used by driver-stop operation
- private static PowerManager.WakeLock sDriverStopWakeLock;
- // Wake lock used by other operations
- private static PowerManager.WakeLock sWakeLock;
-
- private static final int MESSAGE_ENABLE_WIFI = 0;
- private static final int MESSAGE_DISABLE_WIFI = 1;
- private static final int MESSAGE_STOP_WIFI = 2;
- private static final int MESSAGE_START_WIFI = 3;
- private static final int MESSAGE_RELEASE_WAKELOCK = 4;
- private static final int MESSAGE_UPDATE_STATE = 5;
- private static final int MESSAGE_START_ACCESS_POINT = 6;
- private static final int MESSAGE_STOP_ACCESS_POINT = 7;
- private static final int MESSAGE_SET_CHANNELS = 8;
- private static final int MESSAGE_ENABLE_NETWORKS = 9;
- private static final int MESSAGE_START_SCAN = 10;
- private static final int MESSAGE_REPORT_WORKSOURCE = 11;
- private static final int MESSAGE_ENABLE_RSSI_POLLING = 12;
-
-
- private final WifiHandler mWifiHandler;
-
- /*
- * Cache of scan results objects (size is somewhat arbitrary)
+ private static final int ICON_NETWORKS_AVAILABLE =
+ com.android.internal.R.drawable.stat_notify_wifi_in_range;
+ /**
+ * When a notification is shown, we wait this amount before possibly showing it again.
*/
- private static final int SCAN_RESULT_CACHE_SIZE = 80;
- private final LinkedHashMap<String, ScanResult> mScanResultCache;
-
- /*
- * Character buffer used to parse scan results (optimization)
+ private final long NOTIFICATION_REPEAT_DELAY_MS;
+ /**
+ * Whether the user has set the setting to show the 'available networks' notification.
*/
- private static final int SCAN_RESULT_BUFFER_SIZE = 512;
- private boolean mNeedReconfig;
-
+ private boolean mNotificationEnabled;
/**
- * Temporary for computing UIDS that are responsible for starting WIFI.
- * Protected by mWifiStateTracker lock.
+ * Observes the user setting to keep {@link #mNotificationEnabled} in sync.
*/
- private final WorkSource mTmpWorkSource = new WorkSource();
-
- /*
- * Last UID that asked to enable WIFI.
+ private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
+ /**
+ * The {@link System#currentTimeMillis()} must be at least this value for us
+ * to show the notification again.
*/
- private int mLastEnableUid = Process.myUid();
-
- /*
- * Last UID that asked to enable WIFI AP.
+ private long mNotificationRepeatTime;
+ /**
+ * The Notification object given to the NotificationManager.
*/
- private int mLastApEnableUid = Process.myUid();
-
-
+ private Notification mNotification;
+ /**
+ * Whether the notification is being shown, as set by us. That is, if the
+ * user cancels the notification, we will not receive the callback so this
+ * will still be true. We only guarantee if this is false, then the
+ * notification is not showing.
+ */
+ private boolean mNotificationShown;
/**
- * Number of allowed radio frequency channels in various regulatory domains.
- * This list is sufficient for 802.11b/g networks (2.4GHz range).
+ * The number of continuous scans that must occur before consider the
+ * supplicant in a scanning state. This allows supplicant to associate with
+ * remembered networks that are in the scan results.
*/
- private static int[] sValidRegulatoryChannelCounts = new int[] {11, 13, 14};
+ private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
+ /**
+ * The number of scans since the last network state change. When this
+ * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the
+ * supplicant to actually be scanning. When the network state changes to
+ * something other than scanning, we reset this to 0.
+ */
+ private int mNumScansSinceNetworkStateChange;
- private static final String ACTION_DEVICE_IDLE =
- "com.android.server.WifiManager.action.DEVICE_IDLE";
+ /**
+ * Asynchronous channel to WifiStateMachine
+ */
+ private AsyncChannel mChannel;
- WifiService(Context context, WifiStateTracker tracker) {
- mContext = context;
- mWifiStateTracker = tracker;
- mWifiStateTracker.enableRssiPolling(true);
- mBatteryStats = BatteryStatsService.getService();
+ /**
+ * TODO: Possibly change WifiService into an AsyncService.
+ */
+ private class WifiServiceHandler extends Handler {
+ private AsyncChannel mWshChannel;
- IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
- nwService = INetworkManagementService.Stub.asInterface(b);
+ WifiServiceHandler(android.os.Looper looper, Context context) {
+ super(looper);
+ mWshChannel = new AsyncChannel();
+ mWshChannel.connect(context, this, mWifiStateMachine.getHandler());
+ }
- mScanResultCache = new LinkedHashMap<String, ScanResult>(
- SCAN_RESULT_CACHE_SIZE, 0.75f, true) {
- /*
- * Limit the cache size by SCAN_RESULT_CACHE_SIZE
- * elements
- */
- public boolean removeEldestEntry(Map.Entry eldest) {
- return SCAN_RESULT_CACHE_SIZE < this.size();
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
+ if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ mChannel = mWshChannel;
+ } else {
+ Slog.d(TAG, "WifiServicehandler.handleMessage could not connect error=" +
+ msg.arg1);
+ mChannel = null;
+ }
+ break;
+ }
+ default: {
+ Slog.d(TAG, "WifiServicehandler.handleMessage ignoring msg=" + msg);
+ break;
}
- };
+ }
+ }
+ }
+ WifiServiceHandler mHandler;
- HandlerThread wifiThread = new HandlerThread("WifiService");
- wifiThread.start();
- mWifiHandler = new WifiHandler(wifiThread.getLooper());
+ /**
+ * Temporary for computing UIDS that are responsible for starting WIFI.
+ * Protected by mWifiStateTracker lock.
+ */
+ private final WorkSource mTmpWorkSource = new WorkSource();
- mWifiStateTracker.setWifiState(WIFI_STATE_DISABLED);
- mWifiApState = WIFI_AP_STATE_DISABLED;
+ WifiService(Context context) {
+ mContext = context;
+ mWifiStateMachine = new WifiStateMachine(mContext);
+ mWifiStateMachine.enableRssiPolling(true);
+ mBatteryStats = BatteryStatsService.getService();
mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0);
- PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
- sWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
- sDriverStopWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
+ HandlerThread wifiThread = new HandlerThread("WifiService");
+ wifiThread.start();
+ mHandler = new WifiServiceHandler(wifiThread.getLooper(), context);
mContext.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// clear our flag indicating the user has overwridden airplane mode
- mAirplaneModeOverwridden = false;
+ mAirplaneModeOverwridden.set(false);
// on airplane disable, restore Wifi if the saved state indicates so
if (!isAirplaneModeOn() && testAndClearWifiSavedState()) {
persistWifiEnabled(true);
@@ -273,14 +264,53 @@ public class WifiService extends IWifiManager.Stub {
@Override
public void onReceive(Context context, Intent intent) {
- ArrayList<String> available = intent.getStringArrayListExtra(
- ConnectivityManager.EXTRA_AVAILABLE_TETHER);
- ArrayList<String> active = intent.getStringArrayListExtra(
- ConnectivityManager.EXTRA_ACTIVE_TETHER);
- updateTetherState(available, active);
+ ArrayList<String> available = intent.getStringArrayListExtra(
+ ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+ ArrayList<String> active = intent.getStringArrayListExtra(
+ ConnectivityManager.EXTRA_ACTIVE_TETHER);
+ updateTetherState(available, active);
}
},new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+ // reset & clear notification on any wifi state change
+ resetNotification();
+ } else if (intent.getAction().equals(
+ WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
+ WifiManager.EXTRA_NETWORK_INFO);
+ // reset & clear notification on a network connect & disconnect
+ switch(mNetworkInfo.getDetailedState()) {
+ case CONNECTED:
+ case DISCONNECTED:
+ resetNotification();
+ break;
+ }
+ } else if (intent.getAction().equals(
+ WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+ checkAndSetNotification();
+ }
+ }
+ }, filter);
+
+ // Setting is in seconds
+ NOTIFICATION_REPEAT_DELAY_MS = Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l;
+ mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler());
+ mNotificationEnabledSettingObserver.register();
+
+ mBackgroundScanSupported = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_wifi_background_scan_support);
}
/**
@@ -289,7 +319,7 @@ public class WifiService extends IWifiManager.Stub {
*
* This function is used only at boot time
*/
- public void startWifi() {
+ public void checkAndStartWifi() {
/* Start if Wi-Fi is enabled or the saved state indicates Wi-Fi was on */
boolean wifiEnabled = !isAirplaneModeOn()
&& (getPersistedWifiEnabled() || testAndClearWifiSavedState());
@@ -306,7 +336,10 @@ public class WifiService extends IWifiManager.Stub {
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
- mCm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (mCm == null) {
+ mCm = (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+
mWifiRegexs = mCm.getTetherableWifiRegexs();
for (String intf : available) {
@@ -318,25 +351,22 @@ public class WifiService extends IWifiManager.Stub {
ifcg = service.getInterfaceConfig(intf);
if (ifcg != null) {
/* IP/netmask: 192.168.43.1/255.255.255.0 */
- ifcg.ipAddr = (192 << 24) + (168 << 16) + (43 << 8) + 1;
- ifcg.netmask = (255 << 24) + (255 << 16) + (255 << 8) + 0;
- ifcg.interfaceFlags = "up";
+ ifcg.addr = InetAddress.getByName("192.168.43.1");
+ ifcg.mask = InetAddress.getByName("255.255.255.0");
+ ifcg.interfaceFlags = "[up]";
service.setInterfaceConfig(intf, ifcg);
}
} catch (Exception e) {
Slog.e(TAG, "Error configuring interface " + intf + ", :" + e);
- try {
- nwService.stopAccessPoint();
- } catch (Exception ee) {
- Slog.e(TAG, "Could not stop AP, :" + ee);
- }
- setWifiApEnabledState(WIFI_AP_STATE_FAILED, 0, DriverAction.DRIVER_UNLOAD);
+ setWifiApEnabled(null, false);
return;
}
if(mCm.tether(intf) != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
- Slog.e(TAG, "Error tethering "+intf);
+ Slog.e(TAG, "Error tethering on " + intf);
+ setWifiApEnabled(null, false);
+ return;
}
break;
}
@@ -372,18 +402,18 @@ public class WifiService extends IWifiManager.Stub {
Settings.Secure.putInt(cr, Settings.Secure.WIFI_ON, enabled ? 1 : 0);
}
- NetworkStateTracker getNetworkStateTracker() {
- return mWifiStateTracker;
- }
-
/**
* see {@link android.net.wifi.WifiManager#pingSupplicant()}
- * @return {@code true} if the operation succeeds
+ * @return {@code true} if the operation succeeds, {@code false} otherwise
*/
public boolean pingSupplicant() {
- enforceChangePermission();
-
- return mWifiStateTracker.ping();
+ enforceAccessPermission();
+ if (mChannel != null) {
+ return mWifiStateMachine.syncPingSupplicant(mChannel);
+ } else {
+ Slog.e(TAG, "mChannel is not initialized");
+ return false;
+ }
}
/**
@@ -391,9 +421,24 @@ public class WifiService extends IWifiManager.Stub {
*/
public void startScan(boolean forceActive) {
enforceChangePermission();
- if (mWifiHandler == null) return;
+ mWifiStateMachine.startScan(forceActive);
+ }
+
+ private void enforceAccessPermission() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
+ "WifiService");
+ }
+
+ private void enforceChangePermission() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
+ "WifiService");
- Message.obtain(mWifiHandler, MESSAGE_START_SCAN, forceActive ? 1 : 0, 0).sendToTarget();
+ }
+
+ private void enforceMulticastChangePermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE,
+ "WifiService");
}
/**
@@ -402,168 +447,44 @@ public class WifiService extends IWifiManager.Stub {
* @return {@code true} if the enable/disable operation was
* started or is already in the queue.
*/
- public boolean setWifiEnabled(boolean enable) {
+ public synchronized boolean setWifiEnabled(boolean enable) {
enforceChangePermission();
- if (mWifiHandler == null) return false;
-
- synchronized (mWifiHandler) {
- // caller may not have WAKE_LOCK permission - it's not required here
- long ident = Binder.clearCallingIdentity();
- sWakeLock.acquire();
- Binder.restoreCallingIdentity(ident);
- mLastEnableUid = Binder.getCallingUid();
- // set a flag if the user is enabling Wifi while in airplane mode
- mAirplaneModeOverwridden = (enable && isAirplaneModeOn() && isAirplaneToggleable());
- sendEnableMessage(enable, true, Binder.getCallingUid());
+ if (DBG) {
+ Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n");
}
- return true;
- }
-
- /**
- * Enables/disables Wi-Fi synchronously.
- * @param enable {@code true} to turn Wi-Fi on, {@code false} to turn it off.
- * @param persist {@code true} if the setting should be persisted.
- * @param uid The UID of the process making the request.
- * @return {@code true} if the operation succeeds (or if the existing state
- * is the same as the requested state)
- */
- private boolean setWifiEnabledBlocking(boolean enable, boolean persist, int uid) {
- final int eventualWifiState = enable ? WIFI_STATE_ENABLED : WIFI_STATE_DISABLED;
- final int wifiState = mWifiStateTracker.getWifiState();
-
- if (wifiState == eventualWifiState) {
- return true;
- }
- if (enable && isAirplaneModeOn() && !mAirplaneModeOverwridden) {
- return false;
+ // set a flag if the user is enabling Wifi while in airplane mode
+ if (enable && isAirplaneModeOn() && isAirplaneToggleable()) {
+ mAirplaneModeOverwridden.set(true);
}
- /**
- * Multiple calls to unregisterReceiver() cause exception and a system crash.
- * This can happen if a supplicant is lost (or firmware crash occurs) and user indicates
- * disable wifi at the same time.
- * Avoid doing a disable when the current Wifi state is UNKNOWN
- * TODO: Handle driver load fail and supplicant lost as seperate states
- */
- if ((wifiState == WIFI_STATE_UNKNOWN) && !enable) {
- return false;
+ if (enable) {
+ reportStartWorkSource();
}
+ mWifiStateMachine.setWifiEnabled(enable);
- /**
- * Fail Wifi if AP is enabled
- * TODO: Deprecate WIFI_STATE_UNKNOWN and rename it
- * WIFI_STATE_FAILED
+ /*
+ * Caller might not have WRITE_SECURE_SETTINGS,
+ * only CHANGE_WIFI_STATE is enforced
*/
- if ((mWifiApState == WIFI_AP_STATE_ENABLED) && enable) {
- setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
- return false;
- }
-
- setWifiEnabledState(enable ? WIFI_STATE_ENABLING : WIFI_STATE_DISABLING, uid);
+ long ident = Binder.clearCallingIdentity();
+ persistWifiEnabled(enable);
+ Binder.restoreCallingIdentity(ident);
if (enable) {
- if (!mWifiStateTracker.loadDriver()) {
- Slog.e(TAG, "Failed to load Wi-Fi driver.");
- setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
- return false;
- }
- if (!mWifiStateTracker.startSupplicant()) {
- mWifiStateTracker.unloadDriver();
- Slog.e(TAG, "Failed to start supplicant daemon.");
- setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
- return false;
+ if (!mIsReceiverRegistered) {
+ registerForBroadcasts();
+ mIsReceiverRegistered = true;
}
-
- registerForBroadcasts();
- mWifiStateTracker.startEventLoop();
-
- } else {
-
+ } else if (mIsReceiverRegistered){
mContext.unregisterReceiver(mReceiver);
- // Remove notification (it will no-op if it isn't visible)
- mWifiStateTracker.setNotificationVisible(false, 0, false, 0);
-
- boolean failedToStopSupplicantOrUnloadDriver = false;
-
- if (!mWifiStateTracker.stopSupplicant()) {
- Slog.e(TAG, "Failed to stop supplicant daemon.");
- setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
- failedToStopSupplicantOrUnloadDriver = true;
- }
-
- /**
- * Reset connections and disable interface
- * before we unload the driver
- */
- mWifiStateTracker.resetConnections(true);
-
- if (!mWifiStateTracker.unloadDriver()) {
- Slog.e(TAG, "Failed to unload Wi-Fi driver.");
- if (!failedToStopSupplicantOrUnloadDriver) {
- setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
- failedToStopSupplicantOrUnloadDriver = true;
- }
- }
-
- if (failedToStopSupplicantOrUnloadDriver) {
- return false;
- }
+ mIsReceiverRegistered = false;
}
- // Success!
-
- if (persist) {
- persistWifiEnabled(enable);
- }
- setWifiEnabledState(eventualWifiState, uid);
return true;
}
- private void setWifiEnabledState(int wifiState, int uid) {
- final int previousWifiState = mWifiStateTracker.getWifiState();
-
- long ident = Binder.clearCallingIdentity();
- try {
- if (wifiState == WIFI_STATE_ENABLED) {
- mBatteryStats.noteWifiOn();
- } else if (wifiState == WIFI_STATE_DISABLED) {
- mBatteryStats.noteWifiOff();
- }
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
-
- // Update state
- mWifiStateTracker.setWifiState(wifiState);
-
- // Broadcast
- final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- intent.putExtra(WifiManager.EXTRA_WIFI_STATE, wifiState);
- intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, previousWifiState);
- mContext.sendStickyBroadcast(intent);
- }
-
- private void enforceAccessPermission() {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
- "WifiService");
- }
-
- private void enforceChangePermission() {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
- "WifiService");
-
- }
-
- private void enforceMulticastChangePermission() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE,
- "WifiService");
- }
-
/**
* see {@link WifiManager#getWifiState()}
* @return One of {@link WifiManager#WIFI_STATE_DISABLED},
@@ -574,66 +495,59 @@ public class WifiService extends IWifiManager.Stub {
*/
public int getWifiEnabledState() {
enforceAccessPermission();
- return mWifiStateTracker.getWifiState();
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#disconnect()}
- * @return {@code true} if the operation succeeds
- */
- public boolean disconnect() {
- enforceChangePermission();
-
- return mWifiStateTracker.disconnect();
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#reconnect()}
- * @return {@code true} if the operation succeeds
- */
- public boolean reconnect() {
- enforceChangePermission();
-
- return mWifiStateTracker.reconnectCommand();
- }
-
- /**
- * see {@link android.net.wifi.WifiManager#reassociate()}
- * @return {@code true} if the operation succeeds
- */
- public boolean reassociate() {
- enforceChangePermission();
-
- return mWifiStateTracker.reassociate();
+ return mWifiStateMachine.syncGetWifiState();
}
/**
* see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)}
* @param wifiConfig SSID, security and channel details as
* part of WifiConfiguration
- * @param enabled, true to enable and false to disable
+ * @param enabled true to enable and false to disable
* @return {@code true} if the start operation was
* started or is already in the queue.
*/
- public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
+ public synchronized boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
enforceChangePermission();
- if (mWifiHandler == null) return false;
-
- synchronized (mWifiHandler) {
+ if (enabled) {
+ /* Use default config if there is no existing config */
+ if (wifiConfig == null && ((wifiConfig = getWifiApConfiguration()) == null)) {
+ wifiConfig = new WifiConfiguration();
+ wifiConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default);
+ wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE);
+ }
+ /*
+ * Caller might not have WRITE_SECURE_SETTINGS,
+ * only CHANGE_WIFI_STATE is enforced
+ */
long ident = Binder.clearCallingIdentity();
- sWakeLock.acquire();
+ setWifiApConfiguration(wifiConfig);
Binder.restoreCallingIdentity(ident);
-
- mLastApEnableUid = Binder.getCallingUid();
- sendAccessPointMessage(enabled, wifiConfig, Binder.getCallingUid());
}
+ mWifiStateMachine.setWifiApEnabled(wifiConfig, enabled);
+
return true;
}
- public WifiConfiguration getWifiApConfiguration() {
+ /**
+ * see {@link WifiManager#getWifiApState()}
+ * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED},
+ * {@link WifiManager#WIFI_AP_STATE_DISABLING},
+ * {@link WifiManager#WIFI_AP_STATE_ENABLED},
+ * {@link WifiManager#WIFI_AP_STATE_ENABLING},
+ * {@link WifiManager#WIFI_AP_STATE_FAILED}
+ */
+ public int getWifiApEnabledState() {
enforceAccessPermission();
+ return mWifiStateMachine.syncGetWifiApState();
+ }
+
+ /**
+ * see {@link WifiManager#getWifiApConfiguration()}
+ * @return soft access point configuration
+ */
+ public synchronized WifiConfiguration getWifiApConfiguration() {
final ContentResolver cr = mContext.getContentResolver();
WifiConfiguration wifiConfig = new WifiConfiguration();
int authType;
@@ -651,7 +565,11 @@ public class WifiService extends IWifiManager.Stub {
}
}
- public void setWifiApConfiguration(WifiConfiguration wifiConfig) {
+ /**
+ * see {@link WifiManager#setWifiApConfiguration(WifiConfiguration)}
+ * @param wifiConfig WifiConfiguration details for soft access point
+ */
+ public synchronized void setWifiApConfiguration(WifiConfiguration wifiConfig) {
enforceChangePermission();
final ContentResolver cr = mContext.getContentResolver();
boolean isWpa;
@@ -667,143 +585,27 @@ public class WifiService extends IWifiManager.Stub {
}
/**
- * Enables/disables Wi-Fi AP synchronously. The driver is loaded
- * and soft access point configured as a single operation.
- * @param enable {@code true} to turn Wi-Fi on, {@code false} to turn it off.
- * @param uid The UID of the process making the request.
- * @param wifiConfig The WifiConfiguration for AP
- * @return {@code true} if the operation succeeds (or if the existing state
- * is the same as the requested state)
+ * see {@link android.net.wifi.WifiManager#disconnect()}
*/
- private boolean setWifiApEnabledBlocking(boolean enable,
- int uid, WifiConfiguration wifiConfig) {
- final int eventualWifiApState = enable ? WIFI_AP_STATE_ENABLED : WIFI_AP_STATE_DISABLED;
-
- if (mWifiApState == eventualWifiApState) {
- /* Configuration changed on a running access point */
- if(enable && (wifiConfig != null)) {
- try {
- nwService.setAccessPoint(wifiConfig, mWifiStateTracker.getInterfaceName(),
- SOFTAP_IFACE);
- setWifiApConfiguration(wifiConfig);
- return true;
- } catch(Exception e) {
- Slog.e(TAG, "Exception in nwService during AP restart");
- try {
- nwService.stopAccessPoint();
- } catch (Exception ee) {
- Slog.e(TAG, "Could not stop AP, :" + ee);
- }
- setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD);
- return false;
- }
- } else {
- return true;
- }
- }
-
- /**
- * Fail AP if Wifi is enabled
- */
- if ((mWifiStateTracker.getWifiState() == WIFI_STATE_ENABLED) && enable) {
- setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD);
- return false;
- }
-
- setWifiApEnabledState(enable ? WIFI_AP_STATE_ENABLING :
- WIFI_AP_STATE_DISABLING, uid, DriverAction.NO_DRIVER_UNLOAD);
-
- if (enable) {
-
- /* Use default config if there is no existing config */
- if (wifiConfig == null && ((wifiConfig = getWifiApConfiguration()) == null)) {
- wifiConfig = new WifiConfiguration();
- wifiConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default);
- wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE);
- }
-
- if (!mWifiStateTracker.loadDriver()) {
- Slog.e(TAG, "Failed to load Wi-Fi driver for AP mode");
- setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD);
- return false;
- }
-
- try {
- nwService.startAccessPoint(wifiConfig, mWifiStateTracker.getInterfaceName(),
- SOFTAP_IFACE);
- } catch(Exception e) {
- Slog.e(TAG, "Exception in startAccessPoint()");
- setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD);
- return false;
- }
-
- setWifiApConfiguration(wifiConfig);
-
- } else {
-
- try {
- nwService.stopAccessPoint();
- } catch(Exception e) {
- Slog.e(TAG, "Exception in stopAccessPoint()");
- setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD);
- return false;
- }
-
- if (!mWifiStateTracker.unloadDriver()) {
- Slog.e(TAG, "Failed to unload Wi-Fi driver for AP mode");
- setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD);
- return false;
- }
- }
-
- setWifiApEnabledState(eventualWifiApState, uid, DriverAction.NO_DRIVER_UNLOAD);
- return true;
+ public void disconnect() {
+ enforceChangePermission();
+ mWifiStateMachine.disconnectCommand();
}
/**
- * see {@link WifiManager#getWifiApState()}
- * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED},
- * {@link WifiManager#WIFI_AP_STATE_DISABLING},
- * {@link WifiManager#WIFI_AP_STATE_ENABLED},
- * {@link WifiManager#WIFI_AP_STATE_ENABLING},
- * {@link WifiManager#WIFI_AP_STATE_FAILED}
+ * see {@link android.net.wifi.WifiManager#reconnect()}
*/
- public int getWifiApEnabledState() {
- enforceAccessPermission();
- return mWifiApState;
+ public void reconnect() {
+ enforceChangePermission();
+ mWifiStateMachine.reconnectCommand();
}
- private void setWifiApEnabledState(int wifiAPState, int uid, DriverAction flag) {
- final int previousWifiApState = mWifiApState;
-
- /**
- * Unload the driver if going to a failed state
- */
- if ((mWifiApState == WIFI_AP_STATE_FAILED) && (flag == DriverAction.DRIVER_UNLOAD)) {
- mWifiStateTracker.unloadDriver();
- }
-
- long ident = Binder.clearCallingIdentity();
- try {
- if (wifiAPState == WIFI_AP_STATE_ENABLED) {
- mBatteryStats.noteWifiOn();
- } else if (wifiAPState == WIFI_AP_STATE_DISABLED) {
- mBatteryStats.noteWifiOff();
- }
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
-
- // Update state
- mWifiApState = wifiAPState;
-
- // Broadcast
- final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiAPState);
- intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState);
- mContext.sendStickyBroadcast(intent);
+ /**
+ * see {@link android.net.wifi.WifiManager#reassociate()}
+ */
+ public void reassociate() {
+ enforceChangePermission();
+ mWifiStateMachine.reassociateCommand();
}
/**
@@ -812,217 +614,7 @@ public class WifiService extends IWifiManager.Stub {
*/
public List<WifiConfiguration> getConfiguredNetworks() {
enforceAccessPermission();
- String listStr;
-
- /*
- * We don't cache the list, because we want to allow
- * for the possibility that the configuration file
- * has been modified through some external means,
- * such as the wpa_cli command line program.
- */
- listStr = mWifiStateTracker.listNetworks();
-
- List<WifiConfiguration> networks =
- new ArrayList<WifiConfiguration>();
- if (listStr == null)
- return networks;
-
- String[] lines = listStr.split("\n");
- // Skip the first line, which is a header
- for (int i = 1; i < lines.length; i++) {
- String[] result = lines[i].split("\t");
- // network-id | ssid | bssid | flags
- WifiConfiguration config = new WifiConfiguration();
- try {
- config.networkId = Integer.parseInt(result[0]);
- } catch(NumberFormatException e) {
- continue;
- }
- if (result.length > 3) {
- if (result[3].indexOf("[CURRENT]") != -1)
- config.status = WifiConfiguration.Status.CURRENT;
- else if (result[3].indexOf("[DISABLED]") != -1)
- config.status = WifiConfiguration.Status.DISABLED;
- else
- config.status = WifiConfiguration.Status.ENABLED;
- } else {
- config.status = WifiConfiguration.Status.ENABLED;
- }
- readNetworkVariables(config);
- networks.add(config);
- }
-
- return networks;
- }
-
- /**
- * Read the variables from the supplicant daemon that are needed to
- * fill in the WifiConfiguration object.
- * <p/>
- * The caller must hold the synchronization monitor.
- * @param config the {@link WifiConfiguration} object to be filled in.
- */
- private void readNetworkVariables(WifiConfiguration config) {
-
- int netId = config.networkId;
- if (netId < 0)
- return;
-
- /*
- * TODO: maybe should have a native method that takes an array of
- * variable names and returns an array of values. But we'd still
- * be doing a round trip to the supplicant daemon for each variable.
- */
- String value;
-
- value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.ssidVarName);
- if (!TextUtils.isEmpty(value)) {
- config.SSID = value;
- } else {
- config.SSID = null;
- }
-
- value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.bssidVarName);
- if (!TextUtils.isEmpty(value)) {
- config.BSSID = value;
- } else {
- config.BSSID = null;
- }
-
- value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.priorityVarName);
- config.priority = -1;
- if (!TextUtils.isEmpty(value)) {
- try {
- config.priority = Integer.parseInt(value);
- } catch (NumberFormatException ignore) {
- }
- }
-
- value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.hiddenSSIDVarName);
- config.hiddenSSID = false;
- if (!TextUtils.isEmpty(value)) {
- try {
- config.hiddenSSID = Integer.parseInt(value) != 0;
- } catch (NumberFormatException ignore) {
- }
- }
-
- value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.wepTxKeyIdxVarName);
- config.wepTxKeyIndex = -1;
- if (!TextUtils.isEmpty(value)) {
- try {
- config.wepTxKeyIndex = Integer.parseInt(value);
- } catch (NumberFormatException ignore) {
- }
- }
-
- /*
- * Get up to 4 WEP keys. Note that the actual keys are not passed back,
- * just a "*" if the key is set, or the null string otherwise.
- */
- for (int i = 0; i < 4; i++) {
- value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.wepKeyVarNames[i]);
- if (!TextUtils.isEmpty(value)) {
- config.wepKeys[i] = value;
- } else {
- config.wepKeys[i] = null;
- }
- }
-
- /*
- * Get the private shared key. Note that the actual keys are not passed back,
- * just a "*" if the key is set, or the null string otherwise.
- */
- value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.pskVarName);
- if (!TextUtils.isEmpty(value)) {
- config.preSharedKey = value;
- } else {
- config.preSharedKey = null;
- }
-
- value = mWifiStateTracker.getNetworkVariable(config.networkId,
- WifiConfiguration.Protocol.varName);
- if (!TextUtils.isEmpty(value)) {
- String vals[] = value.split(" ");
- for (String val : vals) {
- int index =
- lookupString(val, WifiConfiguration.Protocol.strings);
- if (0 <= index) {
- config.allowedProtocols.set(index);
- }
- }
- }
-
- value = mWifiStateTracker.getNetworkVariable(config.networkId,
- WifiConfiguration.KeyMgmt.varName);
- if (!TextUtils.isEmpty(value)) {
- String vals[] = value.split(" ");
- for (String val : vals) {
- int index =
- lookupString(val, WifiConfiguration.KeyMgmt.strings);
- if (0 <= index) {
- config.allowedKeyManagement.set(index);
- }
- }
- }
-
- value = mWifiStateTracker.getNetworkVariable(config.networkId,
- WifiConfiguration.AuthAlgorithm.varName);
- if (!TextUtils.isEmpty(value)) {
- String vals[] = value.split(" ");
- for (String val : vals) {
- int index =
- lookupString(val, WifiConfiguration.AuthAlgorithm.strings);
- if (0 <= index) {
- config.allowedAuthAlgorithms.set(index);
- }
- }
- }
-
- value = mWifiStateTracker.getNetworkVariable(config.networkId,
- WifiConfiguration.PairwiseCipher.varName);
- if (!TextUtils.isEmpty(value)) {
- String vals[] = value.split(" ");
- for (String val : vals) {
- int index =
- lookupString(val, WifiConfiguration.PairwiseCipher.strings);
- if (0 <= index) {
- config.allowedPairwiseCiphers.set(index);
- }
- }
- }
-
- value = mWifiStateTracker.getNetworkVariable(config.networkId,
- WifiConfiguration.GroupCipher.varName);
- if (!TextUtils.isEmpty(value)) {
- String vals[] = value.split(" ");
- for (String val : vals) {
- int index =
- lookupString(val, WifiConfiguration.GroupCipher.strings);
- if (0 <= index) {
- config.allowedGroupCiphers.set(index);
- }
- }
- }
-
- for (WifiConfiguration.EnterpriseField field :
- config.enterpriseFields) {
- value = mWifiStateTracker.getNetworkVariable(netId,
- field.varName());
- if (!TextUtils.isEmpty(value)) {
- if (field != config.eap) value = removeDoubleQuotes(value);
- field.setValue(value);
- }
- }
- }
-
- private static String removeDoubleQuotes(String string) {
- if (string.length() <= 2) return "";
- return string.substring(1, string.length() - 1);
- }
-
- private static String convertToQuotedString(String string) {
- return "\"" + string + "\"";
+ return mWifiStateMachine.syncGetConfiguredNetworks();
}
/**
@@ -1032,280 +624,15 @@ public class WifiService extends IWifiManager.Stub {
*/
public int addOrUpdateNetwork(WifiConfiguration config) {
enforceChangePermission();
-
- /*
- * If the supplied networkId is -1, we create a new empty
- * network configuration. Otherwise, the networkId should
- * refer to an existing configuration.
- */
- int netId = config.networkId;
- boolean newNetwork = netId == -1;
- boolean doReconfig = false;
- // networkId of -1 means we want to create a new network
- synchronized (mWifiStateTracker) {
- if (newNetwork) {
- netId = mWifiStateTracker.addNetwork();
- if (netId < 0) {
- if (DBG) {
- Slog.d(TAG, "Failed to add a network!");
- }
- return -1;
- }
- doReconfig = true;
- }
- mNeedReconfig = mNeedReconfig || doReconfig;
- }
-
- setVariables: {
- /*
- * Note that if a networkId for a non-existent network
- * was supplied, then the first setNetworkVariable()
- * will fail, so we don't bother to make a separate check
- * for the validity of the ID up front.
- */
- if (config.SSID != null &&
- !mWifiStateTracker.setNetworkVariable(
- netId,
- WifiConfiguration.ssidVarName,
- config.SSID)) {
- if (DBG) {
- Slog.d(TAG, "failed to set SSID: "+config.SSID);
- }
- break setVariables;
- }
-
- if (config.BSSID != null &&
- !mWifiStateTracker.setNetworkVariable(
- netId,
- WifiConfiguration.bssidVarName,
- config.BSSID)) {
- if (DBG) {
- Slog.d(TAG, "failed to set BSSID: "+config.BSSID);
- }
- break setVariables;
- }
-
- String allowedKeyManagementString =
- makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings);
- if (config.allowedKeyManagement.cardinality() != 0 &&
- !mWifiStateTracker.setNetworkVariable(
- netId,
- WifiConfiguration.KeyMgmt.varName,
- allowedKeyManagementString)) {
- if (DBG) {
- Slog.d(TAG, "failed to set key_mgmt: "+
- allowedKeyManagementString);
- }
- break setVariables;
- }
-
- String allowedProtocolsString =
- makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings);
- if (config.allowedProtocols.cardinality() != 0 &&
- !mWifiStateTracker.setNetworkVariable(
- netId,
- WifiConfiguration.Protocol.varName,
- allowedProtocolsString)) {
- if (DBG) {
- Slog.d(TAG, "failed to set proto: "+
- allowedProtocolsString);
- }
- break setVariables;
- }
-
- String allowedAuthAlgorithmsString =
- makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings);
- if (config.allowedAuthAlgorithms.cardinality() != 0 &&
- !mWifiStateTracker.setNetworkVariable(
- netId,
- WifiConfiguration.AuthAlgorithm.varName,
- allowedAuthAlgorithmsString)) {
- if (DBG) {
- Slog.d(TAG, "failed to set auth_alg: "+
- allowedAuthAlgorithmsString);
- }
- break setVariables;
- }
-
- String allowedPairwiseCiphersString =
- makeString(config.allowedPairwiseCiphers, WifiConfiguration.PairwiseCipher.strings);
- if (config.allowedPairwiseCiphers.cardinality() != 0 &&
- !mWifiStateTracker.setNetworkVariable(
- netId,
- WifiConfiguration.PairwiseCipher.varName,
- allowedPairwiseCiphersString)) {
- if (DBG) {
- Slog.d(TAG, "failed to set pairwise: "+
- allowedPairwiseCiphersString);
- }
- break setVariables;
- }
-
- String allowedGroupCiphersString =
- makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings);
- if (config.allowedGroupCiphers.cardinality() != 0 &&
- !mWifiStateTracker.setNetworkVariable(
- netId,
- WifiConfiguration.GroupCipher.varName,
- allowedGroupCiphersString)) {
- if (DBG) {
- Slog.d(TAG, "failed to set group: "+
- allowedGroupCiphersString);
- }
- break setVariables;
- }
-
- // Prevent client screw-up by passing in a WifiConfiguration we gave it
- // by preventing "*" as a key.
- if (config.preSharedKey != null && !config.preSharedKey.equals("*") &&
- !mWifiStateTracker.setNetworkVariable(
- netId,
- WifiConfiguration.pskVarName,
- config.preSharedKey)) {
- if (DBG) {
- Slog.d(TAG, "failed to set psk: "+config.preSharedKey);
- }
- break setVariables;
- }
-
- boolean hasSetKey = false;
- if (config.wepKeys != null) {
- for (int i = 0; i < config.wepKeys.length; i++) {
- // Prevent client screw-up by passing in a WifiConfiguration we gave it
- // by preventing "*" as a key.
- if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) {
- if (!mWifiStateTracker.setNetworkVariable(
- netId,
- WifiConfiguration.wepKeyVarNames[i],
- config.wepKeys[i])) {
- if (DBG) {
- Slog.d(TAG,
- "failed to set wep_key"+i+": " +
- config.wepKeys[i]);
- }
- break setVariables;
- }
- hasSetKey = true;
- }
- }
- }
-
- if (hasSetKey) {
- if (!mWifiStateTracker.setNetworkVariable(
- netId,
- WifiConfiguration.wepTxKeyIdxVarName,
- Integer.toString(config.wepTxKeyIndex))) {
- if (DBG) {
- Slog.d(TAG,
- "failed to set wep_tx_keyidx: "+
- config.wepTxKeyIndex);
- }
- break setVariables;
- }
- }
-
- if (!mWifiStateTracker.setNetworkVariable(
- netId,
- WifiConfiguration.priorityVarName,
- Integer.toString(config.priority))) {
- if (DBG) {
- Slog.d(TAG, config.SSID + ": failed to set priority: "
- +config.priority);
- }
- break setVariables;
- }
-
- if (config.hiddenSSID && !mWifiStateTracker.setNetworkVariable(
- netId,
- WifiConfiguration.hiddenSSIDVarName,
- Integer.toString(config.hiddenSSID ? 1 : 0))) {
- if (DBG) {
- Slog.d(TAG, config.SSID + ": failed to set hiddenSSID: "+
- config.hiddenSSID);
- }
- break setVariables;
- }
-
- for (WifiConfiguration.EnterpriseField field
- : config.enterpriseFields) {
- String varName = field.varName();
- String value = field.value();
- if (value != null) {
- if (field != config.eap) {
- value = (value.length() == 0) ? "NULL" : convertToQuotedString(value);
- }
- if (!mWifiStateTracker.setNetworkVariable(
- netId,
- varName,
- value)) {
- if (DBG) {
- Slog.d(TAG, config.SSID + ": failed to set " + varName +
- ": " + value);
- }
- break setVariables;
- }
- }
- }
- return netId;
- }
-
- /*
- * For an update, if one of the setNetworkVariable operations fails,
- * we might want to roll back all the changes already made. But the
- * chances are that if anything is going to go wrong, it'll happen
- * the first time we try to set one of the variables.
- */
- if (newNetwork) {
- removeNetwork(netId);
- if (DBG) {
- Slog.d(TAG,
- "Failed to set a network variable, removed network: "
- + netId);
- }
- }
- return -1;
- }
-
- private static String makeString(BitSet set, String[] strings) {
- StringBuffer buf = new StringBuffer();
- int nextSetBit = -1;
-
- /* Make sure all set bits are in [0, strings.length) to avoid
- * going out of bounds on strings. (Shouldn't happen, but...) */
- set = set.get(0, strings.length);
-
- while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
- buf.append(strings[nextSetBit].replace('_', '-')).append(' ');
- }
-
- // remove trailing space
- if (set.cardinality() > 0) {
- buf.setLength(buf.length() - 1);
- }
-
- return buf.toString();
- }
-
- private static int lookupString(String string, String[] strings) {
- int size = strings.length;
-
- string = string.replace('-', '_');
-
- for (int i = 0; i < size; i++)
- if (string.equals(strings[i]))
- return i;
-
- if (DBG) {
- // if we ever get here, we should probably add the
- // value to WifiConfiguration to reflect that it's
- // supported by the WPA supplicant
- Slog.w(TAG, "Failed to look-up a string: " + string);
+ if (mChannel != null) {
+ return mWifiStateMachine.syncAddOrUpdateNetwork(mChannel, config);
+ } else {
+ Slog.e(TAG, "mChannel is not initialized");
+ return -1;
}
-
- return -1;
}
- /**
+ /**
* See {@link android.net.wifi.WifiManager#removeNetwork(int)}
* @param netId the integer that identifies the network configuration
* to the supplicant
@@ -1313,8 +640,12 @@ public class WifiService extends IWifiManager.Stub {
*/
public boolean removeNetwork(int netId) {
enforceChangePermission();
-
- return mWifiStateTracker.removeNetwork(netId);
+ if (mChannel != null) {
+ return mWifiStateMachine.syncRemoveNetwork(mChannel, netId);
+ } else {
+ Slog.e(TAG, "mChannel is not initialized");
+ return false;
+ }
}
/**
@@ -1326,14 +657,12 @@ public class WifiService extends IWifiManager.Stub {
*/
public boolean enableNetwork(int netId, boolean disableOthers) {
enforceChangePermission();
-
- String ifname = mWifiStateTracker.getInterfaceName();
- NetworkUtils.enableInterface(ifname);
- boolean result = mWifiStateTracker.enableNetwork(netId, disableOthers);
- if (!result) {
- NetworkUtils.disableInterface(ifname);
+ if (mChannel != null) {
+ return mWifiStateMachine.syncEnableNetwork(mChannel, netId, disableOthers);
+ } else {
+ Slog.e(TAG, "mChannel is not initialized");
+ return false;
}
- return result;
}
/**
@@ -1344,8 +673,12 @@ public class WifiService extends IWifiManager.Stub {
*/
public boolean disableNetwork(int netId) {
enforceChangePermission();
-
- return mWifiStateTracker.disableNetwork(netId);
+ if (mChannel != null) {
+ return mWifiStateMachine.syncDisableNetwork(mChannel, netId);
+ } else {
+ Slog.e(TAG, "mChannel is not initialized");
+ return false;
+ }
}
/**
@@ -1358,7 +691,7 @@ public class WifiService extends IWifiManager.Stub {
* Make sure we have the latest information, by sending
* a status request to the supplicant.
*/
- return mWifiStateTracker.requestConnectionInfo();
+ return mWifiStateMachine.syncRequestConnectionInfo();
}
/**
@@ -1368,282 +701,161 @@ public class WifiService extends IWifiManager.Stub {
*/
public List<ScanResult> getScanResults() {
enforceAccessPermission();
- String reply;
+ return mWifiStateMachine.syncGetScanResultsList();
+ }
- reply = mWifiStateTracker.scanResults();
- if (reply == null) {
- return null;
+ /**
+ * Tell the supplicant to persist the current list of configured networks.
+ * @return {@code true} if the operation succeeded
+ *
+ * TODO: deprecate this
+ */
+ public boolean saveConfiguration() {
+ boolean result = true;
+ enforceChangePermission();
+ if (mChannel != null) {
+ return mWifiStateMachine.syncSaveConfig(mChannel);
+ } else {
+ Slog.e(TAG, "mChannel is not initialized");
+ return false;
}
+ }
- List<ScanResult> scanList = new ArrayList<ScanResult>();
-
- int lineCount = 0;
-
- int replyLen = reply.length();
- // Parse the result string, keeping in mind that the last line does
- // not end with a newline.
- for (int lineBeg = 0, lineEnd = 0; lineEnd <= replyLen; ++lineEnd) {
- if (lineEnd == replyLen || reply.charAt(lineEnd) == '\n') {
- ++lineCount;
- /*
- * Skip the first line, which is a header
- */
- if (lineCount == 1) {
- lineBeg = lineEnd + 1;
- continue;
- }
- if (lineEnd > lineBeg) {
- String line = reply.substring(lineBeg, lineEnd);
- ScanResult scanResult = parseScanResult(line);
- if (scanResult != null) {
- scanList.add(scanResult);
- } else if (DBG) {
- Slog.w(TAG, "misformatted scan result for: " + line);
- }
- }
- lineBeg = lineEnd + 1;
- }
- }
- mWifiStateTracker.setScanResultsList(scanList);
- return scanList;
+ /**
+ * Set the country code
+ * @param countryCode ISO 3166 country code.
+ * @param persist {@code true} if the setting should be remembered.
+ *
+ * The persist behavior exists so that wifi can fall back to the last
+ * persisted country code on a restart, when the locale information is
+ * not available from telephony.
+ */
+ public void setCountryCode(String countryCode, boolean persist) {
+ Slog.i(TAG, "WifiService trying to set country code to " + countryCode +
+ " with persist set to " + persist);
+ enforceChangePermission();
+ mWifiStateMachine.setCountryCode(countryCode, persist);
}
/**
- * Parse the scan result line passed to us by wpa_supplicant (helper).
- * @param line the line to parse
- * @return the {@link ScanResult} object
+ * Set the operational frequency band
+ * @param band One of
+ * {@link WifiManager#WIFI_FREQUENCY_BAND_AUTO},
+ * {@link WifiManager#WIFI_FREQUENCY_BAND_5GHZ},
+ * {@link WifiManager#WIFI_FREQUENCY_BAND_2GHZ},
+ * @param persist {@code true} if the setting should be remembered.
+ *
*/
- private ScanResult parseScanResult(String line) {
- ScanResult scanResult = null;
- if (line != null) {
- /*
- * Cache implementation (LinkedHashMap) is not synchronized, thus,
- * must synchronized here!
- */
- synchronized (mScanResultCache) {
- String[] result = scanResultPattern.split(line);
- if (3 <= result.length && result.length <= 5) {
- String bssid = result[0];
- // bssid | frequency | level | flags | ssid
- int frequency;
- int level;
- try {
- frequency = Integer.parseInt(result[1]);
- level = Integer.parseInt(result[2]);
- /* some implementations avoid negative values by adding 256
- * so we need to adjust for that here.
- */
- if (level > 0) level -= 256;
- } catch (NumberFormatException e) {
- frequency = 0;
- level = 0;
- }
+ public void setFrequencyBand(int band, boolean persist) {
+ enforceChangePermission();
+ if (!isDualBandSupported()) return;
+ Slog.i(TAG, "WifiService trying to set frequency band to " + band +
+ " with persist set to " + persist);
+ mWifiStateMachine.setFrequencyBand(band, persist);
+ }
- /*
- * The formatting of the results returned by
- * wpa_supplicant is intended to make the fields
- * line up nicely when printed,
- * not to make them easy to parse. So we have to
- * apply some heuristics to figure out which field
- * is the SSID and which field is the flags.
- */
- String ssid;
- String flags;
- if (result.length == 4) {
- if (result[3].charAt(0) == '[') {
- flags = result[3];
- ssid = "";
- } else {
- flags = "";
- ssid = result[3];
- }
- } else if (result.length == 5) {
- flags = result[3];
- ssid = result[4];
- } else {
- // Here, we must have 3 fields: no flags and ssid
- // set
- flags = "";
- ssid = "";
- }
- // bssid + ssid is the hash key
- String key = bssid + ssid;
- scanResult = mScanResultCache.get(key);
- if (scanResult != null) {
- scanResult.level = level;
- scanResult.SSID = ssid;
- scanResult.capabilities = flags;
- scanResult.frequency = frequency;
- } else {
- // Do not add scan results that have no SSID set
- if (0 < ssid.trim().length()) {
- scanResult =
- new ScanResult(
- ssid, bssid, flags, level, frequency);
- mScanResultCache.put(key, scanResult);
- }
- }
- } else {
- Slog.w(TAG, "Misformatted scan result text with " +
- result.length + " fields: " + line);
- }
- }
- }
+ /**
+ * Get the operational frequency band
+ */
+ public int getFrequencyBand() {
+ enforceAccessPermission();
+ return mWifiStateMachine.getFrequencyBand();
+ }
- return scanResult;
+ public boolean isDualBandSupported() {
+ //TODO: Should move towards adding a driver API that checks at runtime
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_wifi_dual_band_support);
}
/**
- * Parse the "flags" field passed back in a scan result by wpa_supplicant,
- * and construct a {@code WifiConfiguration} that describes the encryption,
- * key management, and authenticaion capabilities of the access point.
- * @param flags the string returned by wpa_supplicant
- * @return the {@link WifiConfiguration} object, filled in
+ * Return the DHCP-assigned addresses from the last successful DHCP request,
+ * if any.
+ * @return the DHCP information
*/
- WifiConfiguration parseScanFlags(String flags) {
- WifiConfiguration config = new WifiConfiguration();
-
- if (flags.length() == 0) {
- config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
- }
- // ... to be implemented
- return config;
+ public DhcpInfo getDhcpInfo() {
+ enforceAccessPermission();
+ return mWifiStateMachine.syncGetDhcpInfo();
}
/**
- * Tell the supplicant to persist the current list of configured networks.
- * @return {@code true} if the operation succeeded
+ * see {@link android.net.wifi.WifiManager#startWifi}
+ *
*/
- public boolean saveConfiguration() {
- boolean result;
+ public void startWifi() {
enforceChangePermission();
+ /* TODO: may be add permissions for access only to connectivity service
+ * TODO: if a start issued, keep wifi alive until a stop issued irrespective
+ * of WifiLock & device idle status unless wifi enabled status is toggled
+ */
- synchronized (mWifiStateTracker) {
- result = mWifiStateTracker.saveConfig();
- if (result && mNeedReconfig) {
- mNeedReconfig = false;
- result = mWifiStateTracker.reloadConfig();
-
- if (result) {
- Intent intent = new Intent(WifiManager.NETWORK_IDS_CHANGED_ACTION);
- mContext.sendBroadcast(intent);
- }
- }
- }
- // Inform the backup manager about a data change
- IBackupManager ibm = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
- if (ibm != null) {
- try {
- ibm.dataChanged("com.android.providers.settings");
- } catch (Exception e) {
- // Try again later
- }
- }
- return result;
+ mWifiStateMachine.setDriverStart(true);
+ mWifiStateMachine.reconnectCommand();
}
/**
- * Set the number of radio frequency channels that are allowed to be used
- * in the current regulatory domain. This method should be used only
- * if the correct number of channels cannot be determined automatically
- * for some reason. If the operation is successful, the new value may be
- * persisted as a Secure setting.
- * @param numChannels the number of allowed channels. Must be greater than 0
- * and less than or equal to 16.
- * @param persist {@code true} if the setting should be remembered.
- * @return {@code true} if the operation succeeds, {@code false} otherwise, e.g.,
- * {@code numChannels} is outside the valid range.
+ * see {@link android.net.wifi.WifiManager#stopWifi}
+ *
*/
- public boolean setNumAllowedChannels(int numChannels, boolean persist) {
- Slog.i(TAG, "WifiService trying to setNumAllowed to "+numChannels+
- " with persist set to "+persist);
+ public void stopWifi() {
enforceChangePermission();
-
- /*
- * Validate the argument. We'd like to let the Wi-Fi driver do this,
- * but if Wi-Fi isn't currently enabled, that's not possible, and
- * we want to persist the setting anyway,so that it will take
- * effect when Wi-Fi does become enabled.
+ /* TODO: may be add permissions for access only to connectivity service
+ * TODO: if a stop is issued, wifi is brought up only by startWifi
+ * unless wifi enabled status is toggled
*/
- boolean found = false;
- for (int validChan : sValidRegulatoryChannelCounts) {
- if (validChan == numChannels) {
- found = true;
- break;
- }
- }
- if (!found) {
- return false;
- }
+ mWifiStateMachine.setDriverStart(false);
+ }
- if (mWifiHandler == null) return false;
- Message.obtain(mWifiHandler,
- MESSAGE_SET_CHANNELS, numChannels, (persist ? 1 : 0)).sendToTarget();
+ /**
+ * see {@link android.net.wifi.WifiManager#addToBlacklist}
+ *
+ */
+ public void addToBlacklist(String bssid) {
+ enforceChangePermission();
- return true;
+ mWifiStateMachine.addToBlacklist(bssid);
}
/**
- * sets the number of allowed radio frequency channels synchronously
- * @param numChannels the number of allowed channels. Must be greater than 0
- * and less than or equal to 16.
- * @param persist {@code true} if the setting should be remembered.
- * @return {@code true} if the operation succeeds, {@code false} otherwise
+ * see {@link android.net.wifi.WifiManager#clearBlacklist}
+ *
*/
- private boolean setNumAllowedChannelsBlocking(int numChannels, boolean persist) {
- if (persist) {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS,
- numChannels);
- }
- return mWifiStateTracker.setNumAllowedChannels(numChannels);
+ public void clearBlacklist() {
+ enforceChangePermission();
+
+ mWifiStateMachine.clearBlacklist();
}
- /**
- * Return the number of frequency channels that are allowed
- * to be used in the current regulatory domain.
- * @return the number of allowed channels, or {@code -1} if an error occurs
- */
- public int getNumAllowedChannels() {
- int numChannels;
+ public void connectNetworkWithId(int networkId) {
+ enforceChangePermission();
+ mWifiStateMachine.connectNetwork(networkId);
+ }
- enforceAccessPermission();
+ public void connectNetworkWithConfig(WifiConfiguration config) {
+ enforceChangePermission();
+ mWifiStateMachine.connectNetwork(config);
+ }
- /*
- * If we can't get the value from the driver (e.g., because
- * Wi-Fi is not currently enabled), get the value from
- * Settings.
- */
- numChannels = mWifiStateTracker.getNumAllowedChannels();
- if (numChannels < 0) {
- numChannels = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS,
- -1);
- }
- return numChannels;
+ public void saveNetwork(WifiConfiguration config) {
+ enforceChangePermission();
+ mWifiStateMachine.saveNetwork(config);
}
- /**
- * Return the list of valid values for the number of allowed radio channels
- * for various regulatory domains.
- * @return the list of channel counts
- */
- public int[] getValidChannelCounts() {
- enforceAccessPermission();
- return sValidRegulatoryChannelCounts;
+ public void forgetNetwork(int netId) {
+ enforceChangePermission();
+ mWifiStateMachine.forgetNetwork(netId);
}
- /**
- * Return the DHCP-assigned addresses from the last successful DHCP request,
- * if any.
- * @return the DHCP information
- */
- public DhcpInfo getDhcpInfo() {
- enforceAccessPermission();
- return mWifiStateTracker.getDhcpInfo();
+ public WpsResult startWps(WpsConfiguration config) {
+ enforceChangePermission();
+ if (mChannel != null) {
+ return mWifiStateMachine.startWps(mChannel, config);
+ } else {
+ Slog.e(TAG, "mChannel is not initialized");
+ return new WpsResult(WpsResult.Status.FAILURE);
+ }
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -1653,7 +865,7 @@ public class WifiService extends IWifiManager.Stub {
long idleMillis =
Settings.Secure.getLong(mContext.getContentResolver(),
- Settings.Secure.WIFI_IDLE_MS, DEFAULT_IDLE_MILLIS);
+ Settings.Secure.WIFI_IDLE_MS, DEFAULT_IDLE_MS);
int stayAwakeConditions =
Settings.System.getInt(mContext.getContentResolver(),
Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0);
@@ -1666,20 +878,22 @@ public class WifiService extends IWifiManager.Stub {
mScreenOff = false;
// Once the screen is on, we are not keeping WIFI running
// because of any locks so clear that tracking immediately.
- sendReportWorkSourceMessage();
- sendEnableRssiPollingMessage(true);
- /* DHCP or other temporary failures in the past can prevent
- * a disabled network from being connected to, enable on screen on
- */
- if (mWifiStateTracker.isAnyNetworkDisabled()) {
- sendEnableNetworksMessage();
+ reportStartWorkSource();
+ mWifiStateMachine.enableRssiPolling(true);
+ if (mBackgroundScanSupported) {
+ mWifiStateMachine.enableBackgroundScan(false);
}
+ mWifiStateMachine.enableAllNetworks();
+ updateWifiState();
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
if (DBG) {
Slog.d(TAG, "ACTION_SCREEN_OFF");
}
mScreenOff = true;
- sendEnableRssiPollingMessage(false);
+ mWifiStateMachine.enableRssiPolling(false);
+ if (mBackgroundScanSupported) {
+ mWifiStateMachine.enableBackgroundScan(true);
+ }
/*
* Set a timer to put Wi-Fi to sleep, but only if the screen is off
* AND the "stay on while plugged in" setting doesn't match the
@@ -1687,11 +901,11 @@ public class WifiService extends IWifiManager.Stub {
* or plugged in to AC).
*/
if (!shouldWifiStayAwake(stayAwakeConditions, mPluggedType)) {
- WifiInfo info = mWifiStateTracker.requestConnectionInfo();
+ WifiInfo info = mWifiStateMachine.syncRequestConnectionInfo();
if (info.getSupplicantState() != SupplicantState.COMPLETED) {
// we used to go to sleep immediately, but this caused some race conditions
- // we don't have time to track down for this release. Delay instead, but not
- // as long as we would if connected (below)
+ // we don't have time to track down for this release. Delay instead,
+ // but not as long as we would if connected (below)
// TODO - fix the race conditions and switch back to the immediate turn-off
long triggerTime = System.currentTimeMillis() + (2*60*1000); // 2 min
if (DBG) {
@@ -1710,14 +924,13 @@ public class WifiService extends IWifiManager.Stub {
mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
}
}
- /* we can return now -- there's nothing to do until we get the idle intent back */
- return;
} else if (action.equals(ACTION_DEVICE_IDLE)) {
if (DBG) {
Slog.d(TAG, "got ACTION_DEVICE_IDLE");
}
mDeviceIdle = true;
- sendReportWorkSourceMessage();
+ reportStartWorkSource();
+ updateWifiState();
} else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
/*
* Set a timer to put Wi-Fi to sleep, but only if the screen is off
@@ -1737,26 +950,13 @@ public class WifiService extends IWifiManager.Stub {
Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for " + idleMillis + "ms");
}
mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
- mPluggedType = pluggedType;
- return;
}
mPluggedType = pluggedType;
- } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) {
- BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
- Set<BluetoothDevice> sinks = a2dp.getConnectedSinks();
- boolean isBluetoothPlaying = false;
- for (BluetoothDevice sink : sinks) {
- if (a2dp.getSinkState(sink) == BluetoothA2dp.STATE_PLAYING) {
- isBluetoothPlaying = true;
- }
- }
- mWifiStateTracker.setBluetoothScanMode(isBluetoothPlaying);
-
- } else {
- return;
+ } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
+ int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
+ BluetoothAdapter.STATE_DISCONNECTED);
+ mWifiStateMachine.sendBluetoothAdapterStateChange(state);
}
-
- updateWifiState();
}
/**
@@ -1767,8 +967,10 @@ public class WifiService extends IWifiManager.Stub {
* @see #shouldDeviceStayAwake(int, int)
*/
private boolean shouldWifiStayAwake(int stayAwakeConditions, int pluggedType) {
+ //Never sleep as long as the user has not changed the settings
int wifiSleepPolicy = Settings.System.getInt(mContext.getContentResolver(),
- Settings.System.WIFI_SLEEP_POLICY, Settings.System.WIFI_SLEEP_POLICY_DEFAULT);
+ Settings.System.WIFI_SLEEP_POLICY,
+ Settings.System.WIFI_SLEEP_POLICY_NEVER);
if (wifiSleepPolicy == Settings.System.WIFI_SLEEP_POLICY_NEVER) {
// Never sleep
@@ -1789,7 +991,7 @@ public class WifiService extends IWifiManager.Stub {
* of {@code 0} isn't really a plugged type, but rather an indication that the
* device isn't plugged in at all, there is no bit value corresponding to a
* {@code pluggedType} value of {@code 0}. That is why we shift by
- * {@code pluggedType&nbsp;&#8212;&nbsp;1} instead of by {@code pluggedType}.
+ * {@code pluggedType - 1} instead of by {@code pluggedType}.
* @param stayAwakeConditions a bit string specifying which "plugged types" should
* keep the device (and hence Wi-Fi) awake.
* @param pluggedType the type of plug (USB, AC, or none) for which the check is
@@ -1802,63 +1004,20 @@ public class WifiService extends IWifiManager.Stub {
}
};
- private void sendEnableMessage(boolean enable, boolean persist, int uid) {
- Message msg = Message.obtain(mWifiHandler,
- (enable ? MESSAGE_ENABLE_WIFI : MESSAGE_DISABLE_WIFI),
- (persist ? 1 : 0), uid);
- msg.sendToTarget();
- }
-
- private void sendStartMessage(int lockMode) {
- Message.obtain(mWifiHandler, MESSAGE_START_WIFI, lockMode, 0).sendToTarget();
- }
-
- private void sendAccessPointMessage(boolean enable, WifiConfiguration wifiConfig, int uid) {
- Message.obtain(mWifiHandler,
- (enable ? MESSAGE_START_ACCESS_POINT : MESSAGE_STOP_ACCESS_POINT),
- uid, 0, wifiConfig).sendToTarget();
- }
-
- private void sendEnableNetworksMessage() {
- Message.obtain(mWifiHandler, MESSAGE_ENABLE_NETWORKS).sendToTarget();
- }
-
- private void sendReportWorkSourceMessage() {
- Message.obtain(mWifiHandler, MESSAGE_REPORT_WORKSOURCE).sendToTarget();
- }
-
- private void sendEnableRssiPollingMessage(boolean enable) {
- Message.obtain(mWifiHandler, MESSAGE_ENABLE_RSSI_POLLING, enable ? 1 : 0, 0).sendToTarget();
- }
-
-
- private void reportStartWorkSource() {
- synchronized (mWifiStateTracker) {
- mTmpWorkSource.clear();
- if (mDeviceIdle) {
- for (int i=0; i<mLocks.mList.size(); i++) {
- mTmpWorkSource.add(mLocks.mList.get(i).mWorkSource);
- }
+ private synchronized void reportStartWorkSource() {
+ mTmpWorkSource.clear();
+ if (mDeviceIdle) {
+ for (int i=0; i<mLocks.mList.size(); i++) {
+ mTmpWorkSource.add(mLocks.mList.get(i).mWorkSource);
}
- mWifiStateTracker.updateBatteryWorkSourceLocked(mTmpWorkSource);
- sWakeLock.setWorkSource(mTmpWorkSource);
}
+ mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
}
private void updateWifiState() {
- // send a message so it's all serialized
- Message.obtain(mWifiHandler, MESSAGE_UPDATE_STATE, 0, 0).sendToTarget();
- }
-
- private void doUpdateWifiState() {
boolean wifiEnabled = getPersistedWifiEnabled();
- boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden;
-
- boolean lockHeld;
- synchronized (mLocks) {
- lockHeld = mLocks.hasLocks();
- }
-
+ boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden.get();
+ boolean lockHeld = mLocks.hasLocks();
int strongestLockMode = WifiManager.WIFI_MODE_FULL;
boolean wifiShouldBeEnabled = wifiEnabled && !airplaneMode;
boolean wifiShouldBeStarted = !mDeviceIdle || lockHeld;
@@ -1871,43 +1030,26 @@ public class WifiService extends IWifiManager.Stub {
strongestLockMode = WifiManager.WIFI_MODE_FULL;
}
- synchronized (mWifiHandler) {
- if ((mWifiStateTracker.getWifiState() == WIFI_STATE_ENABLING) && !airplaneMode) {
- return;
- }
-
- /* Disable tethering when airplane mode is enabled */
- if (airplaneMode &&
- (mWifiApState == WIFI_AP_STATE_ENABLING || mWifiApState == WIFI_AP_STATE_ENABLED)) {
- sWakeLock.acquire();
- sendAccessPointMessage(false, null, mLastApEnableUid);
- }
+ /* Disable tethering when airplane mode is enabled */
+ if (airplaneMode) {
+ mWifiStateMachine.setWifiApEnabled(null, false);
+ }
- if (wifiShouldBeEnabled) {
- if (wifiShouldBeStarted) {
- sWakeLock.acquire();
- sendEnableMessage(true, false, mLastEnableUid);
- sWakeLock.acquire();
- sendStartMessage(strongestLockMode);
- } else if (!mWifiStateTracker.isDriverStopped()) {
- int wakeLockTimeout =
- Settings.Secure.getInt(
- mContext.getContentResolver(),
- Settings.Secure.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS,
- DEFAULT_WAKELOCK_TIMEOUT);
- /*
- * We are assuming that ConnectivityService can make
- * a transition to cellular data within wakeLockTimeout time.
- * The wakelock is released by the delayed message.
- */
- sDriverStopWakeLock.acquire();
- mWifiHandler.sendEmptyMessage(MESSAGE_STOP_WIFI);
- mWifiHandler.sendEmptyMessageDelayed(MESSAGE_RELEASE_WAKELOCK, wakeLockTimeout);
- }
+ if (wifiShouldBeEnabled) {
+ if (wifiShouldBeStarted) {
+ reportStartWorkSource();
+ mWifiStateMachine.setWifiEnabled(true);
+ mWifiStateMachine.setScanOnlyMode(
+ strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY);
+ mWifiStateMachine.setDriverStart(true);
+ mWifiStateMachine.setHighPerfModeEnabled(strongestLockMode
+ == WifiManager.WIFI_MODE_FULL_HIGH_PERF);
} else {
- sWakeLock.acquire();
- sendEnableMessage(false, false, mLastEnableUid);
+ mWifiStateMachine.requestCmWakeLock();
+ mWifiStateMachine.setDriverStart(false);
}
+ } else {
+ mWifiStateMachine.setWifiEnabled(false);
}
}
@@ -1917,7 +1059,7 @@ public class WifiService extends IWifiManager.Stub {
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
intentFilter.addAction(ACTION_DEVICE_IDLE);
- intentFilter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
+ intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
mContext.registerReceiver(mReceiver, intentFilter);
}
@@ -1945,103 +1087,6 @@ public class WifiService extends IWifiManager.Stub {
Settings.System.AIRPLANE_MODE_ON, 0) == 1;
}
- /**
- * Handler that allows posting to the WifiThread.
- */
- private class WifiHandler extends Handler {
- public WifiHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
-
- case MESSAGE_ENABLE_WIFI:
- setWifiEnabledBlocking(true, msg.arg1 == 1, msg.arg2);
- if (mWifiWatchdogService == null) {
- mWifiWatchdogService = new WifiWatchdogService(mContext, mWifiStateTracker);
- }
- sWakeLock.release();
- break;
-
- case MESSAGE_START_WIFI:
- reportStartWorkSource();
- mWifiStateTracker.setScanOnlyMode(msg.arg1 == WifiManager.WIFI_MODE_SCAN_ONLY);
- mWifiStateTracker.restart();
- mWifiStateTracker.setHighPerfMode(msg.arg1 ==
- WifiManager.WIFI_MODE_FULL_HIGH_PERF);
- sWakeLock.release();
- break;
-
- case MESSAGE_UPDATE_STATE:
- doUpdateWifiState();
- break;
-
- case MESSAGE_DISABLE_WIFI:
- // a non-zero msg.arg1 value means the "enabled" setting
- // should be persisted
- setWifiEnabledBlocking(false, msg.arg1 == 1, msg.arg2);
- mWifiWatchdogService = null;
- sWakeLock.release();
- break;
-
- case MESSAGE_STOP_WIFI:
- mWifiStateTracker.disconnectAndStop();
- // don't release wakelock
- break;
-
- case MESSAGE_RELEASE_WAKELOCK:
- sDriverStopWakeLock.release();
- break;
-
- case MESSAGE_START_ACCESS_POINT:
- setWifiApEnabledBlocking(true,
- msg.arg1,
- (WifiConfiguration) msg.obj);
- sWakeLock.release();
- break;
-
- case MESSAGE_STOP_ACCESS_POINT:
- setWifiApEnabledBlocking(false,
- msg.arg1,
- (WifiConfiguration) msg.obj);
- sWakeLock.release();
- break;
-
- case MESSAGE_SET_CHANNELS:
- setNumAllowedChannelsBlocking(msg.arg1, msg.arg2 == 1);
- break;
-
- case MESSAGE_ENABLE_NETWORKS:
- mWifiStateTracker.enableAllNetworks(getConfiguredNetworks());
- break;
-
- case MESSAGE_START_SCAN:
- boolean forceActive = (msg.arg1 == 1);
- switch (mWifiStateTracker.getSupplicantState()) {
- case DISCONNECTED:
- case INACTIVE:
- case SCANNING:
- case DORMANT:
- break;
- default:
- mWifiStateTracker.setScanResultHandling(
- WifiStateTracker.SUPPL_SCAN_HANDLING_LIST_ONLY);
- break;
- }
- mWifiStateTracker.scan(forceActive);
- break;
- case MESSAGE_REPORT_WORKSOURCE:
- reportStartWorkSource();
- break;
- case MESSAGE_ENABLE_RSSI_POLLING:
- mWifiStateTracker.enableRssiPolling(msg.arg1 == 1);
- break;
- }
- }
- }
-
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -2051,17 +1096,17 @@ public class WifiService extends IWifiManager.Stub {
+ ", uid=" + Binder.getCallingUid());
return;
}
- pw.println("Wi-Fi is " + stateName(mWifiStateTracker.getWifiState()));
+ pw.println("Wi-Fi is " + mWifiStateMachine.syncGetWifiStateByName());
pw.println("Stay-awake conditions: " +
Settings.System.getInt(mContext.getContentResolver(),
Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0));
pw.println();
pw.println("Internal state:");
- pw.println(mWifiStateTracker);
+ pw.println(mWifiStateMachine);
pw.println();
pw.println("Latest scan results:");
- List<ScanResult> scanResults = mWifiStateTracker.getScanResultsList();
+ List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList();
if (scanResults != null && scanResults.size() != 0) {
pw.println(" BSSID Frequency RSSI Flags SSID");
for (ScanResult r : scanResults) {
@@ -2085,23 +1130,6 @@ public class WifiService extends IWifiManager.Stub {
mLocks.dump(pw);
}
- private static String stateName(int wifiState) {
- switch (wifiState) {
- case WIFI_STATE_DISABLING:
- return "disabling";
- case WIFI_STATE_DISABLED:
- return "disabled";
- case WIFI_STATE_ENABLING:
- return "enabling";
- case WIFI_STATE_ENABLED:
- return "enabled";
- case WIFI_STATE_UNKNOWN:
- return "unknown state";
- default:
- return "[invalid state]";
- }
- }
-
private class WifiLock extends DeathRecipient {
WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
super(lockMode, tag, binder, ws);
@@ -2179,7 +1207,7 @@ public class WifiService extends IWifiManager.Stub {
}
void enforceWakeSourcePermission(int uid, int pid) {
- if (uid == Process.myUid()) {
+ if (uid == android.os.Process.myUid()) {
return;
}
mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
@@ -2213,10 +1241,7 @@ public class WifiService extends IWifiManager.Stub {
private void noteAcquireWifiLock(WifiLock wifiLock) throws RemoteException {
switch(wifiLock.mMode) {
case WifiManager.WIFI_MODE_FULL:
- mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource);
- break;
case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
- /* Treat high power as a full lock for battery stats */
mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource);
break;
case WifiManager.WIFI_MODE_SCAN_ONLY:
@@ -2228,10 +1253,7 @@ public class WifiService extends IWifiManager.Stub {
private void noteReleaseWifiLock(WifiLock wifiLock) throws RemoteException {
switch(wifiLock.mMode) {
case WifiManager.WIFI_MODE_FULL:
- mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource);
- break;
case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
- /* Treat high power as a full lock for battery stats */
mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource);
break;
case WifiManager.WIFI_MODE_SCAN_ONLY:
@@ -2255,6 +1277,7 @@ public class WifiService extends IWifiManager.Stub {
case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
++mFullHighPerfLocksAcquired;
break;
+
case WifiManager.WIFI_MODE_SCAN_ONLY:
++mScanLocksAcquired;
break;
@@ -2262,7 +1285,7 @@ public class WifiService extends IWifiManager.Stub {
// Be aggressive about adding new locks into the accounted state...
// we want to over-report rather than under-report.
- sendReportWorkSourceMessage();
+ reportStartWorkSource();
updateWifiState();
return true;
@@ -2401,7 +1424,7 @@ public class WifiService extends IWifiManager.Stub {
if (mMulticasters.size() != 0) {
return;
} else {
- mWifiStateTracker.startPacketFiltering();
+ mWifiStateMachine.startPacketFiltering();
}
}
}
@@ -2416,7 +1439,7 @@ public class WifiService extends IWifiManager.Stub {
// our new size == 1 (first call), but this function won't
// be called often and by making the stopPacket call each
// time we're less fragile and self-healing.
- mWifiStateTracker.stopPacketFiltering();
+ mWifiStateMachine.stopPacketFiltering();
}
int uid = Binder.getCallingUid();
@@ -2453,7 +1476,7 @@ public class WifiService extends IWifiManager.Stub {
removed.unlinkDeathRecipient();
}
if (mMulticasters.size() == 0) {
- mWifiStateTracker.startPacketFiltering();
+ mWifiStateMachine.startPacketFiltering();
}
Long ident = Binder.clearCallingIdentity();
@@ -2472,4 +1495,144 @@ public class WifiService extends IWifiManager.Stub {
return (mMulticasters.size() > 0);
}
}
+
+ private void checkAndSetNotification() {
+ // If we shouldn't place a notification on available networks, then
+ // don't bother doing any of the following
+ if (!mNotificationEnabled) return;
+
+ State state = mNetworkInfo.getState();
+ if ((state == NetworkInfo.State.DISCONNECTED)
+ || (state == NetworkInfo.State.UNKNOWN)) {
+ // Look for an open network
+ List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList();
+ if (scanResults != null) {
+ int numOpenNetworks = 0;
+ for (int i = scanResults.size() - 1; i >= 0; i--) {
+ ScanResult scanResult = scanResults.get(i);
+
+ if (TextUtils.isEmpty(scanResult.capabilities)) {
+ numOpenNetworks++;
+ }
+ }
+
+ if (numOpenNetworks > 0) {
+ if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) {
+ /*
+ * We've scanned continuously at least
+ * NUM_SCANS_BEFORE_NOTIFICATION times. The user
+ * probably does not have a remembered network in range,
+ * since otherwise supplicant would have tried to
+ * associate and thus resetting this counter.
+ */
+ setNotificationVisible(true, numOpenNetworks, false, 0);
+ }
+ return;
+ }
+ }
+ }
+
+ // No open networks in range, remove the notification
+ setNotificationVisible(false, 0, false, 0);
+ }
+
+ /**
+ * Clears variables related to tracking whether a notification has been
+ * shown recently and clears the current notification.
+ */
+ private void resetNotification() {
+ mNotificationRepeatTime = 0;
+ mNumScansSinceNetworkStateChange = 0;
+ setNotificationVisible(false, 0, false, 0);
+ }
+
+ /**
+ * Display or don't display a notification that there are open Wi-Fi networks.
+ * @param visible {@code true} if notification should be visible, {@code false} otherwise
+ * @param numNetworks the number networks seen
+ * @param force {@code true} to force notification to be shown/not-shown,
+ * even if it is already shown/not-shown.
+ * @param delay time in milliseconds after which the notification should be made
+ * visible or invisible.
+ */
+ private void setNotificationVisible(boolean visible, int numNetworks, boolean force,
+ int delay) {
+
+ // Since we use auto cancel on the notification, when the
+ // mNetworksAvailableNotificationShown is true, the notification may
+ // have actually been canceled. However, when it is false we know
+ // for sure that it is not being shown (it will not be shown any other
+ // place than here)
+
+ // If it should be hidden and it is already hidden, then noop
+ if (!visible && !mNotificationShown && !force) {
+ return;
+ }
+
+ NotificationManager notificationManager = (NotificationManager) mContext
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+
+ Message message;
+ if (visible) {
+
+ // Not enough time has passed to show the notification again
+ if (System.currentTimeMillis() < mNotificationRepeatTime) {
+ return;
+ }
+
+ if (mNotification == null) {
+ // Cache the Notification object.
+ mNotification = new Notification();
+ mNotification.when = 0;
+ mNotification.icon = ICON_NETWORKS_AVAILABLE;
+ mNotification.flags = Notification.FLAG_AUTO_CANCEL;
+ mNotification.contentIntent = PendingIntent.getActivity(mContext, 0,
+ new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK), 0);
+ }
+
+ CharSequence title = mContext.getResources().getQuantityText(
+ com.android.internal.R.plurals.wifi_available, numNetworks);
+ CharSequence details = mContext.getResources().getQuantityText(
+ com.android.internal.R.plurals.wifi_available_detailed, numNetworks);
+ mNotification.tickerText = title;
+ mNotification.setLatestEventInfo(mContext, title, details, mNotification.contentIntent);
+
+ mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS;
+
+ notificationManager.notify(ICON_NETWORKS_AVAILABLE, mNotification);
+ } else {
+ notificationManager.cancel(ICON_NETWORKS_AVAILABLE);
+ }
+
+ mNotificationShown = visible;
+ }
+
+ private class NotificationEnabledSettingObserver extends ContentObserver {
+
+ public NotificationEnabledSettingObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void register() {
+ ContentResolver cr = mContext.getContentResolver();
+ cr.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
+ mNotificationEnabled = getValue();
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+
+ mNotificationEnabled = getValue();
+ resetNotification();
+ }
+
+ private boolean getValue() {
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
+ }
+ }
+
+
}
diff --git a/services/java/com/android/server/WifiStateTracker.java b/services/java/com/android/server/WifiStateTracker.java
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/services/java/com/android/server/WifiStateTracker.java
diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java
index 445dd03..46d6bef 100644
--- a/services/java/com/android/server/WifiWatchdogService.java
+++ b/services/java/com/android/server/WifiWatchdogService.java
@@ -27,7 +27,6 @@ import android.net.DhcpInfo;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.net.wifi.WifiStateTracker;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -77,7 +76,6 @@ public class WifiWatchdogService {
private Context mContext;
private ContentResolver mContentResolver;
- private WifiStateTracker mWifiStateTracker;
private WifiManager mWifiManager;
/**
@@ -108,10 +106,9 @@ public class WifiWatchdogService {
/** Whether the current AP check should be canceled. */
private boolean mShouldCancel;
- WifiWatchdogService(Context context, WifiStateTracker wifiStateTracker) {
+ WifiWatchdogService(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
- mWifiStateTracker = wifiStateTracker;
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
createThread();
@@ -275,12 +272,13 @@ public class WifiWatchdogService {
/**
* Unregister broadcasts and quit the watchdog thread
*/
- private void quit() {
- unregisterForWifiBroadcasts();
- mContext.getContentResolver().unregisterContentObserver(mContentObserver);
- mHandler.removeAllActions();
- mHandler.getLooper().quit();
- }
+ //TODO: Change back to running WWS when needed
+// private void quit() {
+// unregisterForWifiBroadcasts();
+// mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+// mHandler.removeAllActions();
+// mHandler.getLooper().quit();
+// }
/**
* Waits for the main watchdog thread to create the handler.
@@ -751,7 +749,7 @@ public class WifiWatchdogService {
// Black list this "bad" AP, this will cause an attempt to connect to another
blacklistAp(ap.bssid);
// Initiate an association to an alternate AP
- mWifiStateTracker.reassociate();
+ mWifiManager.reassociate();
}
private void blacklistAp(String bssid) {
@@ -762,10 +760,7 @@ public class WifiWatchdogService {
// Before taking action, make sure we should not cancel our processing
if (shouldCancel()) return;
- if (!mWifiStateTracker.addToBlacklist(bssid)) {
- // There's a known bug where this method returns failure on success
- //Slog.e(TAG, "Blacklisting " + bssid + " failed");
- }
+ mWifiManager.addToBlacklist(bssid);
if (D) {
myLogD("Blacklisting " + bssid);
@@ -860,10 +855,7 @@ public class WifiWatchdogService {
* (and blacklisted them). Clear the blacklist so the AP with best
* signal is chosen.
*/
- if (!mWifiStateTracker.clearBlacklist()) {
- // There's a known bug where this method returns failure on success
- //Slog.e(TAG, "Clearing blacklist failed");
- }
+ mWifiManager.clearBlacklist();
if (V) {
myLogV("handleSleep: Set state to SLEEP and cleared blacklist");
@@ -934,7 +926,7 @@ public class WifiWatchdogService {
* should revert anything done by the watchdog monitoring.
*/
private void handleReset() {
- mWifiStateTracker.clearBlacklist();
+ mWifiManager.clearBlacklist();
setIdleState(true);
}
@@ -1151,7 +1143,7 @@ public class WifiWatchdogService {
private void handleWifiStateChanged(int wifiState) {
if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
- quit();
+ onDisconnected();
} else if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
onEnabled();
}
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 667b544..2613988 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -23,13 +23,11 @@ import static android.view.WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
-import static android.view.WindowManager.LayoutParams.FLAG_SYSTEM_ERROR;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
-import static android.view.WindowManager.LayoutParams.MEMORY_TYPE_PUSH_BUFFERS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -39,6 +37,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import com.android.internal.app.IBatteryStats;
import com.android.internal.policy.PolicyManager;
import com.android.internal.policy.impl.PhoneWindowManager;
+import com.android.internal.view.BaseInputHandler;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
@@ -48,8 +47,11 @@ import com.android.server.am.BatteryStatsService;
import android.Manifest;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
+import android.app.StatusBarManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -57,10 +59,13 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Typeface;
@@ -81,6 +86,7 @@ import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.TokenWatcher;
@@ -92,8 +98,8 @@ import android.util.Slog;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.view.Display;
+import android.view.DragEvent;
import android.view.Gravity;
-import android.view.HapticFeedbackConstants;
import android.view.IApplicationToken;
import android.view.IOnKeyguardExitResult;
import android.view.IRotationWatcher;
@@ -103,6 +109,8 @@ import android.view.IWindowSession;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
+import android.view.InputHandler;
+import android.view.InputQueue;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
@@ -119,7 +127,6 @@ import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Transformation;
-import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
@@ -143,6 +150,7 @@ public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor {
static final String TAG = "WindowManager";
static final boolean DEBUG = false;
+ static final boolean DEBUG_ADD_REMOVE = false;
static final boolean DEBUG_FOCUS = false;
static final boolean DEBUG_ANIM = false;
static final boolean DEBUG_LAYOUT = false;
@@ -152,13 +160,14 @@ public class WindowManagerService extends IWindowManager.Stub
static final boolean DEBUG_INPUT_METHOD = false;
static final boolean DEBUG_VISIBILITY = false;
static final boolean DEBUG_WINDOW_MOVEMENT = false;
+ static final boolean DEBUG_TOKEN_MOVEMENT = false;
static final boolean DEBUG_ORIENTATION = false;
static final boolean DEBUG_CONFIGURATION = false;
static final boolean DEBUG_APP_TRANSITIONS = false;
static final boolean DEBUG_STARTING_WINDOW = false;
static final boolean DEBUG_REORDER = false;
static final boolean DEBUG_WALLPAPER = false;
- static final boolean DEBUG_FREEZE = false;
+ static final boolean DEBUG_DRAG = false;
static final boolean SHOW_TRANSACTIONS = false;
static final boolean HIDE_STACK_CRAWLS = true;
@@ -195,13 +204,19 @@ public class WindowManagerService extends IWindowManager.Stub
*/
static final int DEFAULT_FADE_IN_OUT_DURATION = 400;
- /** Adjustment to time to perform a dim, to make it more dramatic.
+ /**
+ * If true, the window manager will do its own custom freezing and general
+ * management of the screen during rotation.
*/
- static final int DIM_DURATION_MULTIPLIER = 6;
-
+ static final boolean CUSTOM_SCREEN_ROTATION = true;
+
// Maximum number of milliseconds to wait for input event injection.
// FIXME is this value reasonable?
private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
+
+ // Maximum number of milliseconds to wait for input devices to be enumerated before
+ // proceding with safe mode detection.
+ private static final int INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS = 1000;
// Default input dispatching timeout in nanoseconds.
private static final long DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS = 5000 * 1000000L;
@@ -288,12 +303,6 @@ public class WindowManagerService extends IWindowManager.Stub
new HashMap<IBinder, WindowToken>();
/**
- * The same tokens as mTokenMap, stored in a list for efficient iteration
- * over them.
- */
- final ArrayList<WindowToken> mTokenList = new ArrayList<WindowToken>();
-
- /**
* Window tokens that are in the process of exiting, but still
* on screen for animations.
*/
@@ -302,7 +311,7 @@ public class WindowManagerService extends IWindowManager.Stub
/**
* Z-ordered (bottom-most first) list of all application tokens, for
* controlling the ordering of windows in different applications. This
- * contains WindowToken objects.
+ * contains AppWindowToken objects.
*/
final ArrayList<AppWindowToken> mAppTokens = new ArrayList<AppWindowToken>();
@@ -319,18 +328,6 @@ public class WindowManagerService extends IWindowManager.Stub
final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<AppWindowToken>();
/**
- * This was the app token that was used to retrieve the last enter
- * animation. It will be used for the next exit animation.
- */
- AppWindowToken mLastEnterAnimToken;
-
- /**
- * These were the layout params used to retrieve the last enter animation.
- * They will be used for the next exit animation.
- */
- LayoutParams mLastEnterAnimParams;
-
- /**
* Z-ordered (bottom-most first) list of all Window objects.
*/
final ArrayList<WindowState> mWindows = new ArrayList<WindowState>();
@@ -348,6 +345,11 @@ public class WindowManagerService extends IWindowManager.Stub
final ArrayList<WindowState> mPendingRemove = new ArrayList<WindowState>();
/**
+ * Used when processing mPendingRemove to avoid working on the original array.
+ */
+ WindowState[] mPendingRemoveTmp = new WindowState[20];
+
+ /**
* Windows whose surface should be destroyed.
*/
final ArrayList<WindowState> mDestroySurface = new ArrayList<WindowState>();
@@ -364,6 +366,12 @@ public class WindowManagerService extends IWindowManager.Stub
*/
ArrayList<WindowState> mForceRemoves;
+ /**
+ * Used when rebuilding window list to keep track of windows that have
+ * been removed.
+ */
+ WindowState[] mRebuildTmp = new WindowState[20];
+
IInputMethodManager mInputMethodManager;
SurfaceSession mFxSession;
@@ -371,6 +379,8 @@ public class WindowManagerService extends IWindowManager.Stub
Surface mBlurSurface;
boolean mBlurShown;
Watermark mWatermark;
+ StrictModeFlash mStrictModeFlash;
+ ScreenRotationAnimation mScreenRotationAnimation;
int mTransactionSequence = 0;
@@ -387,6 +397,8 @@ public class WindowManagerService extends IWindowManager.Stub
int mLastRotationFlags;
ArrayList<IRotationWatcher> mRotationWatchers
= new ArrayList<IRotationWatcher>();
+ int mDeferredRotation;
+ int mDeferredRotationAnimFlags;
boolean mLayoutNeeded = true;
boolean mAnimationPending = false;
@@ -437,13 +449,16 @@ public class WindowManagerService extends IWindowManager.Stub
// This just indicates the window the input method is on top of, not
// necessarily the window its input is going to.
WindowState mInputMethodTarget = null;
- WindowState mUpcomingInputMethodTarget = null;
boolean mInputMethodTargetWaitingAnim;
int mInputMethodAnimLayerAdjustment;
WindowState mInputMethodWindow = null;
final ArrayList<WindowState> mInputMethodDialogs = new ArrayList<WindowState>();
+ boolean mHardKeyboardAvailable;
+ boolean mHardKeyboardEnabled;
+ OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
+
final ArrayList<WindowToken> mWallpaperTokens = new ArrayList<WindowToken>();
// If non-null, this is the currently visible window that is associated
@@ -455,6 +470,11 @@ public class WindowManagerService extends IWindowManager.Stub
// If non-null, we are in the middle of animating from one wallpaper target
// to another, and this is the higher one in Z-order.
WindowState mUpperWallpaperTarget = null;
+ // Window currently running an animation that has requested it be detached
+ // from the wallpaper. This means we need to ensure the wallpaper is
+ // visible behind it in case it animates in a way that would allow it to be
+ // seen.
+ WindowState mWindowDetachedWallpaper = null;
int mWallpaperAnimLayerAdjustment;
float mLastWallpaperX = -1;
float mLastWallpaperY = -1;
@@ -486,6 +506,392 @@ public class WindowManagerService extends IWindowManager.Stub
boolean mTurnOnScreen;
/**
+ * Drag/drop state
+ */
+ class DragState {
+ IBinder mToken;
+ Surface mSurface;
+ int mFlags;
+ IBinder mLocalWin;
+ ClipData mData;
+ ClipDescription mDataDescription;
+ boolean mDragResult;
+ float mCurrentX, mCurrentY;
+ float mThumbOffsetX, mThumbOffsetY;
+ InputChannel mServerChannel, mClientChannel;
+ WindowState mTargetWindow;
+ ArrayList<WindowState> mNotifiedWindows;
+ boolean mDragInProgress;
+
+ private final Region mTmpRegion = new Region();
+
+ DragState(IBinder token, Surface surface, int flags, IBinder localWin) {
+ mToken = token;
+ mSurface = surface;
+ mFlags = flags;
+ mLocalWin = localWin;
+ mNotifiedWindows = new ArrayList<WindowState>();
+ }
+
+ void reset() {
+ if (mSurface != null) {
+ mSurface.destroy();
+ }
+ mSurface = null;
+ mFlags = 0;
+ mLocalWin = null;
+ mToken = null;
+ mData = null;
+ mThumbOffsetX = mThumbOffsetY = 0;
+ mNotifiedWindows = null;
+ }
+
+ void register() {
+ if (DEBUG_DRAG) Slog.d(TAG, "registering drag input channel");
+ if (mClientChannel != null) {
+ Slog.e(TAG, "Duplicate register of drag input channel");
+ } else {
+ InputChannel[] channels = InputChannel.openInputChannelPair("drag");
+ mServerChannel = channels[0];
+ mClientChannel = channels[1];
+ mInputManager.registerInputChannel(mServerChannel, null);
+ InputQueue.registerInputChannel(mClientChannel, mDragInputHandler,
+ mH.getLooper().getQueue());
+ }
+ }
+
+ void unregister() {
+ if (DEBUG_DRAG) Slog.d(TAG, "unregistering drag input channel");
+ if (mClientChannel == null) {
+ Slog.e(TAG, "Unregister of nonexistent drag input channel");
+ } else {
+ mInputManager.unregisterInputChannel(mServerChannel);
+ InputQueue.unregisterInputChannel(mClientChannel);
+ mClientChannel.dispose();
+ mServerChannel.dispose();
+ mClientChannel = null;
+ mServerChannel = null;
+ }
+ }
+
+ int getDragLayerLw() {
+ return mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_DRAG)
+ * TYPE_LAYER_MULTIPLIER
+ + TYPE_LAYER_OFFSET;
+ }
+
+ /* call out to each visible window/session informing it about the drag
+ */
+ void broadcastDragStartedLw(final float touchX, final float touchY) {
+ // Cache a base-class instance of the clip metadata so that parceling
+ // works correctly in calling out to the apps.
+ mDataDescription = (mData != null) ? mData.getDescription() : null;
+ mNotifiedWindows.clear();
+ mDragInProgress = true;
+
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
+ }
+
+ final int N = mWindows.size();
+ for (int i = 0; i < N; i++) {
+ sendDragStartedLw(mWindows.get(i), touchX, touchY, mDataDescription);
+ }
+ }
+
+ /* helper - send a caller-provided event, presumed to be DRAG_STARTED, if the
+ * designated window is potentially a drop recipient. There are race situations
+ * around DRAG_ENDED broadcast, so we make sure that once we've declared that
+ * the drag has ended, we never send out another DRAG_STARTED for this drag action.
+ *
+ * This method clones the 'event' parameter if it's being delivered to the same
+ * process, so it's safe for the caller to call recycle() on the event afterwards.
+ */
+ private void sendDragStartedLw(WindowState newWin, float touchX, float touchY,
+ ClipDescription desc) {
+ // Don't actually send the event if the drag is supposed to be pinned
+ // to the originating window but 'newWin' is not that window.
+ if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) {
+ final IBinder winBinder = newWin.mClient.asBinder();
+ if (winBinder != mLocalWin) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "Not dispatching local DRAG_STARTED to " + newWin);
+ }
+ return;
+ }
+ }
+
+ if (mDragInProgress && newWin.isPotentialDragTarget()) {
+ DragEvent event = DragEvent.obtain(DragEvent.ACTION_DRAG_STARTED,
+ touchX - newWin.mFrame.left, touchY - newWin.mFrame.top,
+ null, desc, null, false);
+ try {
+ newWin.mClient.dispatchDragEvent(event);
+ // track each window that we've notified that the drag is starting
+ mNotifiedWindows.add(newWin);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to drag-start window " + newWin);
+ } finally {
+ // if the callee was local, the dispatch has already recycled the event
+ if (Process.myPid() != newWin.mSession.mPid) {
+ event.recycle();
+ }
+ }
+ }
+ }
+
+ /* helper - construct and send a DRAG_STARTED event only if the window has not
+ * previously been notified, i.e. it became visible after the drag operation
+ * was begun. This is a rare case.
+ */
+ private void sendDragStartedIfNeededLw(WindowState newWin) {
+ if (mDragInProgress) {
+ // If we have sent the drag-started, we needn't do so again
+ for (WindowState ws : mNotifiedWindows) {
+ if (ws == newWin) {
+ return;
+ }
+ }
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "need to send DRAG_STARTED to new window " + newWin);
+ }
+ sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription);
+ }
+ }
+
+ void broadcastDragEndedLw() {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "broadcasting DRAG_ENDED");
+ }
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
+ 0, 0, null, null, null, mDragResult);
+ for (WindowState ws: mNotifiedWindows) {
+ try {
+ ws.mClient.dispatchDragEvent(evt);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to drag-end window " + ws);
+ }
+ }
+ mNotifiedWindows.clear();
+ mDragInProgress = false;
+ evt.recycle();
+ }
+
+ void endDragLw() {
+ mDragState.broadcastDragEndedLw();
+
+ // stop intercepting input
+ mDragState.unregister();
+ mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+ // free our resources and drop all the object references
+ mDragState.reset();
+ mDragState = null;
+
+ if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-drag rotation");
+ boolean changed = setRotationUncheckedLocked(
+ WindowManagerPolicy.USE_LAST_ROTATION, 0, false);
+ if (changed) {
+ mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+ }
+ }
+
+ void notifyMoveLw(float x, float y) {
+ final int myPid = Process.myPid();
+
+ // Move the surface to the given touch
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION notifyMoveLw");
+ Surface.openTransaction();
+ try {
+ mSurface.setPosition((int)(x - mThumbOffsetX), (int)(y - mThumbOffsetY));
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, " DRAG "
+ + mSurface + ": pos=(" +
+ (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
+ } finally {
+ Surface.closeTransaction();
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION notifyMoveLw");
+ }
+
+ // Tell the affected window
+ WindowState touchedWin = getTouchedWinAtPointLw(x, y);
+ if (touchedWin == null) {
+ if (DEBUG_DRAG) Slog.d(TAG, "No touched win at x=" + x + " y=" + y);
+ return;
+ }
+ if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) {
+ final IBinder touchedBinder = touchedWin.mClient.asBinder();
+ if (touchedBinder != mLocalWin) {
+ // This drag is pinned only to the originating window, but the drag
+ // point is outside that window. Pretend it's over empty space.
+ touchedWin = null;
+ }
+ }
+ try {
+ // have we dragged over a new window?
+ if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "sending DRAG_EXITED to " + mTargetWindow);
+ }
+ // force DRAG_EXITED_EVENT if appropriate
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_EXITED,
+ x - mTargetWindow.mFrame.left, y - mTargetWindow.mFrame.top,
+ null, null, null, false);
+ mTargetWindow.mClient.dispatchDragEvent(evt);
+ if (myPid != mTargetWindow.mSession.mPid) {
+ evt.recycle();
+ }
+ }
+ if (touchedWin != null) {
+ if (false && DEBUG_DRAG) {
+ Slog.d(TAG, "sending DRAG_LOCATION to " + touchedWin);
+ }
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_LOCATION,
+ x - touchedWin.mFrame.left, y - touchedWin.mFrame.top,
+ null, null, null, false);
+ touchedWin.mClient.dispatchDragEvent(evt);
+ if (myPid != touchedWin.mSession.mPid) {
+ evt.recycle();
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "can't send drag notification to windows");
+ }
+ mTargetWindow = touchedWin;
+ }
+
+ // Tell the drop target about the data. Returns 'true' if we can immediately
+ // dispatch the global drag-ended message, 'false' if we need to wait for a
+ // result from the recipient.
+ boolean notifyDropLw(float x, float y) {
+ WindowState touchedWin = getTouchedWinAtPointLw(x, y);
+ if (touchedWin == null) {
+ // "drop" outside a valid window -- no recipient to apply a
+ // timeout to, and we can send the drag-ended message immediately.
+ mDragResult = false;
+ return true;
+ }
+
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "sending DROP to " + touchedWin);
+ }
+ final int myPid = Process.myPid();
+ final IBinder token = touchedWin.mClient.asBinder();
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DROP,
+ x - touchedWin.mFrame.left, y - touchedWin.mFrame.top,
+ null, null, mData, false);
+ try {
+ touchedWin.mClient.dispatchDragEvent(evt);
+
+ // 5 second timeout for this window to respond to the drop
+ mH.removeMessages(H.DRAG_END_TIMEOUT, token);
+ Message msg = mH.obtainMessage(H.DRAG_END_TIMEOUT, token);
+ mH.sendMessageDelayed(msg, 5000);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "can't send drop notification to win " + touchedWin);
+ return true;
+ } finally {
+ if (myPid != touchedWin.mSession.mPid) {
+ evt.recycle();
+ }
+ }
+ mToken = token;
+ return false;
+ }
+
+ // Find the visible, touch-deliverable window under the given point
+ private WindowState getTouchedWinAtPointLw(float xf, float yf) {
+ WindowState touchedWin = null;
+ final int x = (int) xf;
+ final int y = (int) yf;
+ final ArrayList<WindowState> windows = mWindows;
+ final int N = windows.size();
+ for (int i = N - 1; i >= 0; i--) {
+ WindowState child = windows.get(i);
+ final int flags = child.mAttrs.flags;
+ if (!child.isVisibleLw()) {
+ // not visible == don't tell about drags
+ continue;
+ }
+ if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
+ // not touchable == don't tell about drags
+ continue;
+ }
+
+ child.getTouchableRegion(mTmpRegion);
+
+ final int touchFlags = flags &
+ (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
+ if (mTmpRegion.contains(x, y) || touchFlags == 0) {
+ // Found it
+ touchedWin = child;
+ break;
+ }
+ }
+
+ return touchedWin;
+ }
+ }
+
+ DragState mDragState = null;
+ private final InputHandler mDragInputHandler = new BaseInputHandler() {
+ @Override
+ public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) {
+ boolean handled = false;
+ try {
+ if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0
+ && mDragState != null) {
+ boolean endDrag = false;
+ final float newX = event.getRawX();
+ final float newY = event.getRawY();
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN: {
+ if (DEBUG_DRAG) {
+ Slog.w(TAG, "Unexpected ACTION_DOWN in drag layer");
+ }
+ } break;
+
+ case MotionEvent.ACTION_MOVE: {
+ synchronized (mWindowMap) {
+ // move the surface and tell the involved window(s) where we are
+ mDragState.notifyMoveLw(newX, newY);
+ }
+ } break;
+
+ case MotionEvent.ACTION_UP: {
+ if (DEBUG_DRAG) Slog.d(TAG, "Got UP on move channel; dropping at "
+ + newX + "," + newY);
+ synchronized (mWindowMap) {
+ endDrag = mDragState.notifyDropLw(newX, newY);
+ }
+ } break;
+
+ case MotionEvent.ACTION_CANCEL: {
+ if (DEBUG_DRAG) Slog.d(TAG, "Drag cancelled!");
+ endDrag = true;
+ } break;
+ }
+
+ if (endDrag) {
+ if (DEBUG_DRAG) Slog.d(TAG, "Drag ended; tearing down state");
+ // tell all the windows that the drag has ended
+ synchronized (mWindowMap) {
+ mDragState.endDragLw();
+ }
+ }
+
+ handled = true;
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception caught by drag handleMotion", e);
+ } finally {
+ finishedCallback.finished(handled);
+ }
+ }
+ };
+
+ /**
* Whether the UI is currently running in touch mode (not showing
* navigational focus because the user is directly pressing the screen).
*/
@@ -508,7 +914,7 @@ public class WindowManagerService extends IWindowManager.Stub
Rect mCompatibleScreenFrame = new Rect();
// The surface used to fill the outer rim of the app running in compatibility mode.
Surface mBackgroundFillerSurface = null;
- boolean mBackgroundFillerShown = false;
+ WindowState mBackgroundFillerTarget = null;
public static WindowManagerService main(Context context,
PowerManagerService pm, boolean haveInputMethods) {
@@ -554,6 +960,11 @@ public class WindowManagerService extends IWindowManager.Stub
notifyAll();
}
+ // For debug builds, log event loop stalls to dropbox for analysis.
+ if (StrictMode.conditionallyEnableDebugLogging()) {
+ Slog.i(TAG, "Enabled StrictMode logging for WMThread's Looper");
+ }
+
Looper.loop();
}
}
@@ -591,6 +1002,11 @@ public class WindowManagerService extends IWindowManager.Stub
notifyAll();
}
+ // For debug builds, log event loop stalls to dropbox for analysis.
+ if (StrictMode.conditionallyEnableDebugLogging()) {
+ Slog.i(TAG, "Enabled StrictMode for PolicyThread's Looper");
+ }
+
Looper.loop();
}
}
@@ -664,7 +1080,7 @@ public class WindowManagerService extends IWindowManager.Stub
private void placeWindowAfter(WindowState pos, WindowState window) {
final int i = mWindows.indexOf(pos);
- if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v(
+ if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(
TAG, "Adding window " + window + " at "
+ (i+1) + " of " + mWindows.size() + " (after " + pos + ")");
mWindows.add(i+1, window);
@@ -673,7 +1089,7 @@ public class WindowManagerService extends IWindowManager.Stub
private void placeWindowBefore(WindowState pos, WindowState window) {
final int i = mWindows.indexOf(pos);
- if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v(
+ if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(
TAG, "Adding window " + window + " at "
+ i + " of " + mWindows.size() + " (before " + pos + ")");
mWindows.add(i, window);
@@ -731,9 +1147,10 @@ public class WindowManagerService extends IWindowManager.Stub
//apptoken note that the window could be a floating window
//that was created later or a window at the top of the list of
//windows associated with this token.
- if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v(
- TAG, "Adding window " + win + " at "
- + (newIdx+1) + " of " + N);
+ if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) {
+ Slog.v(TAG, "Adding window " + win + " at "
+ + (newIdx+1) + " of " + N);
+ }
localmWindows.add(newIdx+1, win);
mWindowsChanged = true;
}
@@ -812,9 +1229,10 @@ public class WindowManagerService extends IWindowManager.Stub
break;
}
}
- if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v(
- TAG, "Adding window " + win + " at "
- + i + " of " + N);
+ if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) {
+ Slog.v(TAG, "Adding window " + win + " at "
+ + i + " of " + N);
+ }
localmWindows.add(i, win);
mWindowsChanged = true;
}
@@ -830,13 +1248,14 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
if (i < 0) i = 0;
- if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Slog.v(
+ if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(
TAG, "Adding window " + win + " at "
+ i + " of " + N);
localmWindows.add(i, win);
mWindowsChanged = true;
}
if (addToToken) {
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token);
token.windows.add(tokenWindowsPos, win);
}
@@ -859,6 +1278,7 @@ public class WindowManagerService extends IWindowManager.Stub
// in the same sublayer.
if (wSublayer >= sublayer) {
if (addToToken) {
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token);
token.windows.add(i, win);
}
placeWindowBefore(
@@ -870,6 +1290,7 @@ public class WindowManagerService extends IWindowManager.Stub
// in the same sublayer.
if (wSublayer > sublayer) {
if (addToToken) {
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token);
token.windows.add(i, win);
}
placeWindowBefore(w, win);
@@ -879,6 +1300,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
if (i >= NA) {
if (addToToken) {
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token);
token.windows.add(win);
}
if (sublayer < 0) {
@@ -900,7 +1322,20 @@ public class WindowManagerService extends IWindowManager.Stub
static boolean canBeImeTarget(WindowState w) {
final int fl = w.mAttrs.flags
& (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM);
- if (fl == 0 || fl == (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM)) {
+ if (fl == 0 || fl == (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM)
+ || w.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
+ if (DEBUG_INPUT_METHOD) {
+ Slog.i(TAG, "isVisibleOrAdding " + w + ": " + w.isVisibleOrAdding());
+ if (!w.isVisibleOrAdding()) {
+ Slog.i(TAG, " mSurface=" + w.mSurface + " reportDestroy=" + w.mReportDestroySurface
+ + " relayoutCalled=" + w.mRelayoutCalled + " viewVis=" + w.mViewVisibility
+ + " policyVis=" + w.mPolicyVisibility + " attachHid=" + w.mAttachedHidden
+ + " exiting=" + w.mExiting + " destroying=" + w.mDestroying);
+ if (w.mAppToken != null) {
+ Slog.i(TAG, " mAppToken.hiddenRequested=" + w.mAppToken.hiddenRequested);
+ }
+ }
+ }
return w.isVisibleOrAdding();
}
return false;
@@ -915,8 +1350,8 @@ public class WindowManagerService extends IWindowManager.Stub
i--;
w = localmWindows.get(i);
- //Slog.i(TAG, "Checking window @" + i + " " + w + " fl=0x"
- // + Integer.toHexString(w.mAttrs.flags));
+ if (DEBUG_INPUT_METHOD && willMove) Slog.i(TAG, "Checking window @" + i
+ + " " + w + " fl=0x" + Integer.toHexString(w.mAttrs.flags));
if (canBeImeTarget(w)) {
//Slog.i(TAG, "Putting input method here!");
@@ -938,7 +1373,23 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- mUpcomingInputMethodTarget = w;
+ if (DEBUG_INPUT_METHOD && willMove) Slog.v(TAG, "Proposed new IME target: " + w);
+
+ // Now, a special case -- if the last target's window is in the
+ // process of exiting, and is above the new target, keep on the
+ // last target to avoid flicker. Consider for example a Dialog with
+ // the IME shown: when the Dialog is dismissed, we want to keep
+ // the IME above it until it is completely gone so it doesn't drop
+ // behind the dialog or its full-screen scrim.
+ if (mInputMethodTarget != null && w != null
+ && mInputMethodTarget.isDisplayedLw()
+ && mInputMethodTarget.mExiting) {
+ if (mInputMethodTarget.mAnimLayer > w.mAnimLayer) {
+ w = mInputMethodTarget;
+ i = localmWindows.indexOf(w);
+ if (DEBUG_INPUT_METHOD) Slog.v(TAG, "Current target higher, switching to: " + w);
+ }
+ }
if (DEBUG_INPUT_METHOD) Slog.v(TAG, "Desired input method target="
+ w + " willMove=" + willMove);
@@ -992,6 +1443,7 @@ public class WindowManagerService extends IWindowManager.Stub
// with an animation, and it is on top of the next target
// we will be over, then hold off on moving until
// that is done.
+ mInputMethodTargetWaitingAnim = true;
mInputMethodTarget = highestTarget;
return highestPos + 1;
}
@@ -1012,6 +1464,7 @@ public class WindowManagerService extends IWindowManager.Stub
+ mInputMethodTarget + " to " + w, e);
}
mInputMethodTarget = w;
+ mInputMethodTargetWaitingAnim = false;
if (w.mAppToken != null) {
setInputMethodAnimLayerAdjustment(w.mAppToken.animLayerAdjustment);
} else {
@@ -1040,7 +1493,7 @@ public class WindowManagerService extends IWindowManager.Stub
int pos = findDesiredInputMethodWindowIndexLocked(true);
if (pos >= 0) {
win.mTargetAppToken = mInputMethodTarget.mAppToken;
- if (DEBUG_WINDOW_MOVEMENT) Slog.v(
+ if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(
TAG, "Adding input method window " + win + " at " + pos);
mWindows.add(pos, win);
mWindowsChanged = true;
@@ -1227,7 +1680,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
imPos = tmpRemoveWindowLocked(imPos, imWin);
if (DEBUG_INPUT_METHOD) {
- Slog.v(TAG, "List after moving with new pos " + imPos + ":");
+ Slog.v(TAG, "List after removing with new pos " + imPos + ":");
logWindowList(" ");
}
imWin.mTargetAppToken = mInputMethodTarget.mAppToken;
@@ -1304,6 +1757,7 @@ public class WindowManagerService extends IWindowManager.Stub
int foundI = 0;
WindowState topCurW = null;
int topCurI = 0;
+ int windowDetachedI = -1;
int i = N;
while (i > 0) {
i--;
@@ -1316,13 +1770,12 @@ public class WindowManagerService extends IWindowManager.Stub
continue;
}
topCurW = null;
- if (w.mAppToken != null) {
+ if (w != mWindowDetachedWallpaper && w.mAppToken != null) {
// If this window's app token is hidden and not animating,
// it is of no interest to us.
if (w.mAppToken.hidden && w.mAppToken.animation == null) {
if (DEBUG_WALLPAPER) Slog.v(TAG,
- "Skipping hidden or animating token: " + w);
- topCurW = null;
+ "Skipping not hidden or animating token: " + w);
continue;
}
}
@@ -1347,9 +1800,18 @@ public class WindowManagerService extends IWindowManager.Stub
continue;
}
break;
+ } else if (w == mWindowDetachedWallpaper) {
+ windowDetachedI = i;
}
}
+ if (foundW == null && windowDetachedI >= 0) {
+ if (DEBUG_WALLPAPER) Slog.v(TAG,
+ "Found animating detached wallpaper activity: #" + i + "=" + w);
+ foundW = w;
+ foundI = windowDetachedI;
+ }
+
if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
// If we are currently waiting for an app transition, and either
// the current target or the next target are involved with it,
@@ -1481,9 +1943,10 @@ public class WindowManagerService extends IWindowManager.Stub
WindowState wb = localmWindows.get(foundI-1);
if (wb.mBaseLayer < maxLayer &&
wb.mAttachedWindow != foundW &&
- wb.mAttachedWindow != foundW.mAttachedWindow &&
+ (foundW.mAttachedWindow == null ||
+ wb.mAttachedWindow != foundW.mAttachedWindow) &&
(wb.mAttrs.type != TYPE_APPLICATION_STARTING ||
- wb.mToken != foundW.mToken)) {
+ foundW.mToken == null || wb.mToken != foundW.mToken)) {
// This window is not related to the previous one in any
// interesting way, so stop here.
break;
@@ -1581,9 +2044,10 @@ public class WindowManagerService extends IWindowManager.Stub
}
// Now stick it in.
- if (DEBUG_WALLPAPER || DEBUG_WINDOW_MOVEMENT) Slog.v(TAG,
- "Moving wallpaper " + wallpaper
- + " from " + oldIndex + " to " + foundI);
+ if (DEBUG_WALLPAPER || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) {
+ Slog.v(TAG, "Moving wallpaper " + wallpaper
+ + " from " + oldIndex + " to " + foundI);
+ }
localmWindows.add(foundI, wallpaper);
mWindowsChanged = true;
@@ -1788,18 +2252,11 @@ public class WindowManagerService extends IWindowManager.Stub
boolean reportNewConfig = false;
WindowState attachedWindow = null;
WindowState win = null;
+ long origId;
synchronized(mWindowMap) {
- // Instantiating a Display requires talking with the simulator,
- // so don't do it until we know the system is mostly up and
- // running.
if (mDisplay == null) {
- WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
- mDisplay = wm.getDefaultDisplay();
- mInitialDisplayWidth = mDisplay.getWidth();
- mInitialDisplayHeight = mDisplay.getHeight();
- mInputManager.setDisplaySize(0, mInitialDisplayWidth, mInitialDisplayHeight);
- reportNewConfig = true;
+ throw new IllegalStateException("Display has not been initialialized");
}
if (mWindowMap.containsKey(client.asBinder())) {
@@ -1898,18 +2355,17 @@ public class WindowManagerService extends IWindowManager.Stub
win.mInputChannel = inputChannels[0];
inputChannels[1].transferToBinderOutParameter(outInputChannel);
- mInputManager.registerInputChannel(win.mInputChannel);
+ mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
}
// From now on, no exceptions or errors allowed!
res = WindowManagerImpl.ADD_OKAY;
- final long origId = Binder.clearCallingIdentity();
+ origId = Binder.clearCallingIdentity();
if (addToken) {
mTokenMap.put(attrs.token, token);
- mTokenList.add(token);
}
win.attach();
mWindowMap.put(client.asBinder(), win);
@@ -1951,9 +2407,12 @@ public class WindowManagerService extends IWindowManager.Stub
res |= WindowManagerImpl.ADD_FLAG_APP_VISIBLE;
}
+ mInputMonitor.setUpdateInputWindowsNeededLw();
+
boolean focusChanged = false;
if (win.canReceiveKeys()) {
- focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS);
+ focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
+ false /*updateInputWindows*/);
if (focusChanged) {
imMayMove = false;
}
@@ -1970,26 +2429,23 @@ public class WindowManagerService extends IWindowManager.Stub
//dump();
if (focusChanged) {
- finishUpdateFocusedWindowAfterAssignLayersLocked();
+ finishUpdateFocusedWindowAfterAssignLayersLocked(false /*updateInputWindows*/);
}
-
+ mInputMonitor.updateInputWindowsLw(false /*force*/);
+
if (localLOGV) Slog.v(
TAG, "New client " + client.asBinder()
+ ": window=" + win);
- if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked()) {
+ if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) {
reportNewConfig = true;
}
}
- // sendNewConfiguration() checks caller permissions so we must call it with
- // privilege. updateOrientationFromAppTokens() clears and resets the caller
- // identity anyway, so it's safe to just clear & restore around this whole
- // block.
- final long origId = Binder.clearCallingIdentity();
if (reportNewConfig) {
sendNewConfiguration();
}
+
Binder.restoreCallingIdentity(origId);
return res;
@@ -2052,8 +2508,10 @@ public class WindowManagerService extends IWindowManager.Stub
win.mExiting = true;
win.mRemoveOnExit = true;
mLayoutNeeded = true;
- updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES);
+ updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+ false /*updateInputWindows*/);
performLayoutAndPlaceSurfacesLocked();
+ mInputMonitor.updateInputWindowsLw(false /*force*/);
if (win.mAppToken != null) {
win.mAppToken.updateReportedVisibilityLocked();
}
@@ -2068,14 +2526,26 @@ public class WindowManagerService extends IWindowManager.Stub
// So just update orientation if needed.
if (wasVisible && computeForcedAppOrientationLocked()
!= mForcedAppOrientation
- && updateOrientationFromAppTokensLocked()) {
+ && updateOrientationFromAppTokensLocked(false)) {
mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
}
- updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL);
+ updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
Binder.restoreCallingIdentity(origId);
}
private void removeWindowInnerLocked(Session session, WindowState win) {
+ if (win.mRemoved) {
+ // Nothing to do.
+ return;
+ }
+
+ for (int i=win.mChildWindows.size()-1; i>=0; i--) {
+ WindowState cwin = win.mChildWindows.get(i);
+ Slog.w(TAG, "Force-removing child win " + cwin + " from container "
+ + win);
+ removeWindowInnerLocked(cwin.mSession, cwin);
+ }
+
win.mRemoved = true;
if (mInputMethodTarget == win) {
@@ -2091,8 +2561,10 @@ public class WindowManagerService extends IWindowManager.Stub
mPolicy.removeWindowLw(win);
win.removeLocked();
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG, "removeWindowInnerLocked: " + win);
mWindowMap.remove(win.mClient.asBinder());
mWindows.remove(win);
+ mPendingRemove.remove(win);
mWindowsChanged = true;
if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Final remove of window: " + win);
@@ -2104,6 +2576,7 @@ public class WindowManagerService extends IWindowManager.Stub
final WindowToken token = win.mToken;
final AppWindowToken atoken = win.mAppToken;
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing " + win + " from " + token);
token.windows.remove(win);
if (atoken != null) {
atoken.allAppWindows.remove(win);
@@ -2114,7 +2587,6 @@ public class WindowManagerService extends IWindowManager.Stub
if (token.windows.size() == 0) {
if (!token.explicit) {
mTokenMap.remove(token.token);
- mTokenList.remove(token);
} else if (atoken != null) {
atoken.firstWindowDrawn = false;
}
@@ -2155,7 +2627,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- mInputMonitor.updateInputWindowsLw();
+ mInputMonitor.updateInputWindowsLw(true /*force*/);
}
private static void logSurface(WindowState w, String msg, RuntimeException where) {
@@ -2174,15 +2646,17 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized (mWindowMap) {
WindowState w = windowForClientLocked(session, client, false);
if ((w != null) && (w.mSurface != null)) {
- if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION");
+ if (SHOW_TRANSACTIONS) Slog.i(TAG,
+ ">>> OPEN TRANSACTION setTransparentRegion");
Surface.openTransaction();
try {
if (SHOW_TRANSACTIONS) logSurface(w,
"transparentRegionHint=" + region, null);
w.mSurface.setTransparentRegionHint(region);
} finally {
- if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION");
Surface.closeTransaction();
+ if (SHOW_TRANSACTIONS) Slog.i(TAG,
+ "<<< CLOSE TRANSACTION setTransparentRegion");
}
}
}
@@ -2193,7 +2667,7 @@ public class WindowManagerService extends IWindowManager.Stub
void setInsetsWindow(Session session, IWindow client,
int touchableInsets, Rect contentInsets,
- Rect visibleInsets) {
+ Rect visibleInsets, Region touchableRegion) {
long origId = Binder.clearCallingIdentity();
try {
synchronized (mWindowMap) {
@@ -2202,6 +2676,7 @@ public class WindowManagerService extends IWindowManager.Stub
w.mGivenInsetsPending = false;
w.mGivenContentInsets.set(contentInsets);
w.mGivenVisibleInsets.set(visibleInsets);
+ w.mGivenTouchableRegion.set(touchableRegion);
w.mTouchableInsets = touchableInsets;
mLayoutNeeded = true;
performLayoutAndPlaceSurfacesLocked();
@@ -2286,6 +2761,15 @@ public class WindowManagerService extends IWindowManager.Stub
boolean displayed = false;
boolean inTouchMode;
boolean configChanged;
+
+ // if they don't have this permission, mask out the status bar bits
+ if (attrs != null) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
+ != PackageManager.PERMISSION_GRANTED) {
+ attrs.systemUiVisibility &= ~StatusBarManager.DISABLE_MASK;
+ attrs.subtreeSystemUiVisibility &= ~StatusBarManager.DISABLE_MASK;
+ }
+ }
long origId = Binder.clearCallingIdentity();
synchronized(mWindowMap) {
@@ -2346,7 +2830,10 @@ public class WindowManagerService extends IWindowManager.Stub
displayed = !win.isVisibleLw();
if (win.mExiting) {
win.mExiting = false;
- win.mAnimation = null;
+ if (win.mAnimation != null) {
+ win.mAnimation.cancel();
+ win.mAnimation = null;
+ }
}
if (win.mDestroying) {
win.mDestroying = false;
@@ -2399,7 +2886,7 @@ public class WindowManagerService extends IWindowManager.Stub
outSurface.release();
}
} catch (Exception e) {
- mInputMonitor.updateInputWindowsLw();
+ mInputMonitor.updateInputWindowsLw(true /*force*/);
Slog.w(TAG, "Exception thrown when creating surface for client "
+ client + " (" + win.mAttrs.getTitle() + ")",
@@ -2486,7 +2973,8 @@ public class WindowManagerService extends IWindowManager.Stub
if (focusMayChange) {
//System.out.println("Focus may change: " + win.mAttrs.getTitle());
- if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES)) {
+ if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+ false /*updateInputWindows*/)) {
imMayMove = false;
}
//System.out.println("Relayout " + win + ": focus=" + mCurrentFocus);
@@ -2517,7 +3005,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (assignLayers) {
assignLayersLocked();
}
- configChanged = updateOrientationFromAppTokensLocked();
+ configChanged = updateOrientationFromAppTokensLocked(false);
performLayoutAndPlaceSurfacesLocked();
if (displayed && win.mIsWallpaper) {
updateWallpaperOffsetLocked(win, mDisplay.getWidth(),
@@ -2542,7 +3030,7 @@ public class WindowManagerService extends IWindowManager.Stub
inTouchMode = mInTouchMode;
- mInputMonitor.updateInputWindowsLw();
+ mInputMonitor.updateInputWindowsLw(true /*force*/);
}
if (configChanged) {
@@ -2571,7 +3059,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
private AttributeCache.Entry getCachedAnimations(WindowManager.LayoutParams lp) {
- if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: params package="
+ if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg="
+ (lp != null ? lp.packageName : null)
+ " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null));
if (lp != null && lp.windowAnimations != 0) {
@@ -2592,7 +3080,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
- if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: params package="
+ if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: package="
+ packageName + " resId=0x" + Integer.toHexString(resId));
if (packageName != null) {
if ((resId&0xFF000000) == 0x01000000) {
@@ -2874,7 +3362,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
wtoken = new WindowToken(token, type, true);
mTokenMap.put(token, wtoken);
- mTokenList.add(wtoken);
if (type == TYPE_WALLPAPER) {
mWallpaperTokens.add(wtoken);
}
@@ -2890,7 +3377,6 @@ public class WindowManagerService extends IWindowManager.Stub
final long origId = Binder.clearCallingIdentity();
synchronized(mWindowMap) {
WindowToken wtoken = mTokenMap.remove(token);
- mTokenList.remove(wtoken);
if (wtoken != null) {
boolean delayed = false;
if (!wtoken.hidden) {
@@ -2916,7 +3402,8 @@ public class WindowManagerService extends IWindowManager.Stub
if (changed) {
mLayoutNeeded = true;
performLayoutAndPlaceSurfacesLocked();
- updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL);
+ updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
+ false /*updateInputWindows*/);
}
if (delayed) {
@@ -2926,7 +3413,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- mInputMonitor.updateInputWindowsLw();
+ mInputMonitor.updateInputWindowsLw(true /*force*/);
} else {
Slog.w(TAG, "Attempted to remove non-existing token: " + token);
}
@@ -2966,10 +3453,9 @@ public class WindowManagerService extends IWindowManager.Stub
wtoken.groupId = groupId;
wtoken.appFullscreen = fullscreen;
wtoken.requestedOrientation = requestedOrientation;
+ if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "addAppToken: " + wtoken);
mAppTokens.add(addPos, wtoken);
- if (localLOGV) Slog.v(TAG, "Adding new app token: " + wtoken);
mTokenMap.put(token.asBinder(), wtoken);
- mTokenList.add(wtoken);
// Application tokens start out hidden.
wtoken.hidden = true;
@@ -3086,7 +3572,7 @@ public class WindowManagerService extends IWindowManager.Stub
long ident = Binder.clearCallingIdentity();
synchronized(mWindowMap) {
- if (updateOrientationFromAppTokensLocked()) {
+ if (updateOrientationFromAppTokensLocked(false)) {
if (freezeThisOneIfNeeded != null) {
AppWindowToken wtoken = findAppWindowToken(
freezeThisOneIfNeeded);
@@ -3108,7 +3594,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (currentConfig.diff(mTempConfiguration) != 0) {
mWaitingForConfig = true;
mLayoutNeeded = true;
- startFreezingDisplayLocked();
+ startFreezingDisplayLocked(false);
config = new Configuration(mTempConfiguration);
}
}
@@ -3133,8 +3619,8 @@ public class WindowManagerService extends IWindowManager.Stub
* @see android.view.IWindowManager#updateOrientationFromAppTokens(
* android.os.IBinder)
*/
- boolean updateOrientationFromAppTokensLocked() {
- if (mDisplayFrozen) {
+ boolean updateOrientationFromAppTokensLocked(boolean inTransaction) {
+ if (mDisplayFrozen || mOpeningApps.size() > 0 || mClosingApps.size() > 0) {
// If the display is frozen, some activities may be in the middle
// of restarting, and thus have removed their old window. If the
// window has the flag to hide the lock screen, then the lock screen
@@ -3154,7 +3640,8 @@ public class WindowManagerService extends IWindowManager.Stub
//action like disabling/enabling sensors etc.,
mPolicy.setCurrentOrientationLw(req);
if (setRotationUncheckedLocked(WindowManagerPolicy.USE_LAST_ROTATION,
- mLastRotationFlags | Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE)) {
+ mLastRotationFlags | Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE,
+ inTransaction)) {
changed = true;
}
}
@@ -3245,13 +3732,13 @@ public class WindowManagerService extends IWindowManager.Stub
if (moveFocusNow && changed) {
final long origId = Binder.clearCallingIdentity();
- updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL);
+ updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
Binder.restoreCallingIdentity(origId);
}
}
}
- public void prepareAppTransition(int transit) {
+ public void prepareAppTransition(int transit, boolean alwaysKeepCurrent) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"prepareAppTransition()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -3265,14 +3752,16 @@ public class WindowManagerService extends IWindowManager.Stub
if (mNextAppTransition == WindowManagerPolicy.TRANSIT_UNSET
|| mNextAppTransition == WindowManagerPolicy.TRANSIT_NONE) {
mNextAppTransition = transit;
- } else if (transit == WindowManagerPolicy.TRANSIT_TASK_OPEN
- && mNextAppTransition == WindowManagerPolicy.TRANSIT_TASK_CLOSE) {
- // Opening a new task always supersedes a close for the anim.
- mNextAppTransition = transit;
- } else if (transit == WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN
- && mNextAppTransition == WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE) {
- // Opening a new activity always supersedes a close for the anim.
- mNextAppTransition = transit;
+ } else if (!alwaysKeepCurrent) {
+ if (transit == WindowManagerPolicy.TRANSIT_TASK_OPEN
+ && mNextAppTransition == WindowManagerPolicy.TRANSIT_TASK_CLOSE) {
+ // Opening a new task always supersedes a close for the anim.
+ mNextAppTransition = transit;
+ } else if (transit == WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN
+ && mNextAppTransition == WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE) {
+ // Opening a new activity always supersedes a close for the anim.
+ mNextAppTransition = transit;
+ }
}
mAppTransitionReady = false;
mAppTransitionTimeout = false;
@@ -3322,7 +3811,7 @@ public class WindowManagerService extends IWindowManager.Stub
public void setAppStartingWindow(IBinder token, String pkg,
int theme, CharSequence nonLocalizedLabel, int labelRes, int icon,
- IBinder transferFrom, boolean createIfNeeded) {
+ int windowFlags, IBinder transferFrom, boolean createIfNeeded) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"setAppStartingIcon()")) {
throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
@@ -3378,10 +3867,12 @@ public class WindowManagerService extends IWindowManager.Stub
startingWindow.mToken = wtoken;
startingWindow.mRootToken = wtoken;
startingWindow.mAppToken = wtoken;
- if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG,
+ if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG,
"Removing starting window: " + startingWindow);
mWindows.remove(startingWindow);
mWindowsChanged = true;
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing starting " + startingWindow
+ + " from " + ttoken);
ttoken.windows.remove(startingWindow);
ttoken.allAppWindows.remove(startingWindow);
addWindowToListInOrderLocked(startingWindow, true);
@@ -3416,7 +3907,8 @@ public class WindowManagerService extends IWindowManager.Stub
ttoken.updateLayers();
}
- updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES);
+ updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+ true /*updateInputWindows*/);
mLayoutNeeded = true;
performLayoutAndPlaceSurfacesLocked();
Binder.restoreCallingIdentity(origId);
@@ -3470,7 +3962,7 @@ public class WindowManagerService extends IWindowManager.Stub
mStartingIconInTransition = true;
wtoken.startingData = new StartingData(
pkg, theme, nonLocalizedLabel,
- labelRes, icon);
+ labelRes, icon, windowFlags);
Message m = mH.obtainMessage(H.ADD_STARTING, wtoken);
// Note: we really want to do sendMessageAtFrontOfQueue() because we
// want to process the message ASAP, before any other queued
@@ -3576,12 +4068,13 @@ public class WindowManagerService extends IWindowManager.Stub
if (changed) {
mLayoutNeeded = true;
+ mInputMonitor.setUpdateInputWindowsNeededLw();
if (performLayout) {
- updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES);
+ updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+ false /*updateInputWindows*/);
performLayoutAndPlaceSurfacesLocked();
- } else {
- mInputMonitor.updateInputWindowsLw();
}
+ mInputMonitor.updateInputWindowsLw(false /*force*/);
}
}
@@ -3727,7 +4220,7 @@ public class WindowManagerService extends IWindowManager.Stub
wtoken.freezingScreen = true;
mAppsFreezingScreen++;
if (mAppsFreezingScreen == 1) {
- startFreezingDisplayLocked();
+ startFreezingDisplayLocked(false);
mH.removeMessages(H.APP_FREEZE_TIMEOUT);
mH.sendMessageDelayed(mH.obtainMessage(H.APP_FREEZE_TIMEOUT),
5000);
@@ -3796,7 +4289,6 @@ public class WindowManagerService extends IWindowManager.Stub
final long origId = Binder.clearCallingIdentity();
synchronized(mWindowMap) {
WindowToken basewtoken = mTokenMap.remove(token);
- mTokenList.remove(basewtoken);
if (basewtoken != null && (wtoken=basewtoken.appWindowToken) != null) {
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Removing app token: " + wtoken);
delayed = setTokenVisibilityLocked(wtoken, null, false, WindowManagerPolicy.TRANSIT_UNSET, true);
@@ -3816,6 +4308,8 @@ public class WindowManagerService extends IWindowManager.Stub
+ " animating=" + wtoken.animating);
if (delayed) {
// set the token aside because it has an active animation to be finished
+ if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
+ "removeAppToken make exiting: " + wtoken);
mExitingAppTokens.add(wtoken);
} else {
// Make sure there is no animation running on this token,
@@ -3824,11 +4318,9 @@ public class WindowManagerService extends IWindowManager.Stub
wtoken.animation = null;
wtoken.animating = false;
}
+ if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
+ "removeAppToken: " + wtoken);
mAppTokens.remove(wtoken);
- if (mLastEnterAnimToken == wtoken) {
- mLastEnterAnimToken = null;
- mLastEnterAnimParams = null;
- }
wtoken.removed = true;
if (wtoken.startingData != null) {
startingToken = wtoken;
@@ -3837,7 +4329,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (mFocusedApp == wtoken) {
if (DEBUG_FOCUS) Slog.v(TAG, "Removing focused app token:" + wtoken);
mFocusedApp = null;
- updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL);
+ updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
mInputMonitor.setFocusedAppLw(null);
}
} else {
@@ -3954,18 +4446,21 @@ public class WindowManagerService extends IWindowManager.Stub
if (!added && cwin.mSubLayer >= 0) {
if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding child window at "
+ index + ": " + cwin);
+ win.mRebuilding = false;
mWindows.add(index, win);
index++;
added = true;
}
if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding window at "
+ index + ": " + cwin);
+ cwin.mRebuilding = false;
mWindows.add(index, cwin);
index++;
}
if (!added) {
if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding window at "
+ index + ": " + win);
+ win.mRebuilding = false;
mWindows.add(index, win);
index++;
}
@@ -3991,6 +4486,9 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_REORDER) Slog.v(TAG, "Initial app tokens:");
if (DEBUG_REORDER) dumpAppTokensLocked();
final AppWindowToken wtoken = findAppWindowToken(token);
+ if (DEBUG_TOKEN_MOVEMENT || DEBUG_REORDER) Slog.v(TAG,
+ "Start moving token " + wtoken + " initially at "
+ + mAppTokens.indexOf(wtoken));
if (wtoken == null || !mAppTokens.remove(wtoken)) {
Slog.w(TAG, "Attempting to reorder token that doesn't exist: "
+ token + " (" + wtoken + ")");
@@ -3998,6 +4496,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
mAppTokens.add(index, wtoken);
if (DEBUG_REORDER) Slog.v(TAG, "Moved " + token + " to " + index + ":");
+ else if (DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "Moved " + token + " to " + index);
if (DEBUG_REORDER) dumpAppTokensLocked();
final long origId = Binder.clearCallingIdentity();
@@ -4009,9 +4508,12 @@ public class WindowManagerService extends IWindowManager.Stub
reAddAppWindowsLocked(findWindowOffsetLocked(index), wtoken);
if (DEBUG_REORDER) Slog.v(TAG, "Final window list:");
if (DEBUG_REORDER) dumpWindowsLocked();
- updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES);
+ updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+ false /*updateInputWindows*/);
mLayoutNeeded = true;
+ mInputMonitor.setUpdateInputWindowsNeededLw();
performLayoutAndPlaceSurfacesLocked();
+ mInputMonitor.updateInputWindowsLw(false /*force*/);
}
Binder.restoreCallingIdentity(origId);
}
@@ -4025,6 +4527,8 @@ public class WindowManagerService extends IWindowManager.Stub
for (int i=0; i<N; i++) {
IBinder token = tokens.get(i);
final AppWindowToken wtoken = findAppWindowToken(token);
+ if (DEBUG_REORDER || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
+ "Temporarily removing " + wtoken + " from " + mAppTokens.indexOf(wtoken));
if (!mAppTokens.remove(wtoken)) {
Slog.w(TAG, "Attempting to reorder token that doesn't exist: "
+ token + " (" + wtoken + ")");
@@ -4046,11 +4550,14 @@ public class WindowManagerService extends IWindowManager.Stub
pos = reAddAppWindowsLocked(pos, wtoken);
if (updateFocusAndLayout) {
- if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES)) {
+ mInputMonitor.setUpdateInputWindowsNeededLw();
+ if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+ false /*updateInputWindows*/)) {
assignLayersLocked();
}
mLayoutNeeded = true;
performLayoutAndPlaceSurfacesLocked();
+ mInputMonitor.updateInputWindowsLw(false /*force*/);
}
}
@@ -4076,11 +4583,14 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES)) {
+ mInputMonitor.setUpdateInputWindowsNeededLw();
+ if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+ false /*updateInputWindows*/)) {
assignLayersLocked();
}
mLayoutNeeded = true;
performLayoutAndPlaceSurfacesLocked();
+ mInputMonitor.updateInputWindowsLw(false /*force*/);
//dump();
}
@@ -4098,6 +4608,8 @@ public class WindowManagerService extends IWindowManager.Stub
for (int i=0; i<N; i++) {
AppWindowToken wt = findAppWindowToken(tokens.get(i));
if (wt != null) {
+ if (DEBUG_TOKEN_MOVEMENT || DEBUG_REORDER) Slog.v(TAG,
+ "Adding next to top: " + wt);
mAppTokens.add(wt);
if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
mToTopApps.remove(wt);
@@ -4130,6 +4642,8 @@ public class WindowManagerService extends IWindowManager.Stub
for (int i=0; i<N; i++) {
AppWindowToken wt = findAppWindowToken(tokens.get(i));
if (wt != null) {
+ if (DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
+ "Adding next to bottom: " + wt + " at " + pos);
mAppTokens.add(pos, wt);
if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
mToTopApps.remove(wt);
@@ -4231,6 +4745,14 @@ public class WindowManagerService extends IWindowManager.Stub
return mPolicy.inKeyguardRestrictedKeyInputMode();
}
+ public boolean isKeyguardLocked() {
+ return mPolicy.isKeyguardLocked();
+ }
+
+ public boolean isKeyguardSecure() {
+ return mPolicy.isKeyguardSecure();
+ }
+
public void closeSystemDialogs(String reason) {
synchronized(mWindowMap) {
for (int i=mWindows.size()-1; i>=0; i--) {
@@ -4435,8 +4957,7 @@ public class WindowManagerService extends IWindowManager.Stub
final int N = mWindows.size();
for (int i=0; i<N; i++) {
WindowState w = mWindows.get(i);
- if (w.isVisibleLw() && !w.mObscured
- && (w.mOrientationChanging || !w.isDrawnLw())) {
+ if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
return;
}
}
@@ -4477,6 +4998,191 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ // TODO: more accounting of which pid(s) turned it on, keep count,
+ // only allow disables from pids which have count on, etc.
+ public void showStrictModeViolation(boolean on) {
+ int pid = Binder.getCallingPid();
+ synchronized(mWindowMap) {
+ // Ignoring requests to enable the red border from clients
+ // which aren't on screen. (e.g. Broadcast Receivers in
+ // the background..)
+ if (on) {
+ boolean isVisible = false;
+ for (WindowState ws : mWindows) {
+ if (ws.mSession.mPid == pid && ws.isVisibleLw()) {
+ isVisible = true;
+ break;
+ }
+ }
+ if (!isVisible) {
+ return;
+ }
+ }
+
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION showStrictModeViolation");
+ Surface.openTransaction();
+ try {
+ if (mStrictModeFlash == null) {
+ mStrictModeFlash = new StrictModeFlash(mDisplay, mFxSession);
+ }
+ mStrictModeFlash.setVisibility(on);
+ } finally {
+ Surface.closeTransaction();
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION showStrictModeViolation");
+ }
+ }
+ }
+
+ public void setStrictModeVisualIndicatorPreference(String value) {
+ SystemProperties.set(StrictMode.VISUAL_PROPERTY, value);
+ }
+
+ public Bitmap screenshotApplications(IBinder appToken, int maxWidth, int maxHeight) {
+ if (!checkCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER,
+ "screenshotApplications()")) {
+ throw new SecurityException("Requires READ_FRAME_BUFFER permission");
+ }
+
+ Bitmap rawss;
+
+ int maxLayer = 0;
+ final Rect frame = new Rect();
+
+ float scale;
+ int sw, sh, dw, dh;
+ int rot;
+
+ synchronized(mWindowMap) {
+ long ident = Binder.clearCallingIdentity();
+
+ dw = mDisplay.getWidth();
+ dh = mDisplay.getHeight();
+
+ int aboveAppLayer = mPolicy.windowTypeToLayerLw(
+ WindowManager.LayoutParams.TYPE_APPLICATION) * TYPE_LAYER_MULTIPLIER
+ + TYPE_LAYER_OFFSET;
+ aboveAppLayer += TYPE_LAYER_MULTIPLIER;
+
+ boolean isImeTarget = mInputMethodTarget != null
+ && mInputMethodTarget.mAppToken != null
+ && mInputMethodTarget.mAppToken.appToken != null
+ && mInputMethodTarget.mAppToken.appToken.asBinder() == appToken;
+
+ // Figure out the part of the screen that is actually the app.
+ boolean including = false;
+ for (int i=mWindows.size()-1; i>=0; i--) {
+ WindowState ws = mWindows.get(i);
+ if (ws.mSurface == null) {
+ continue;
+ }
+ if (ws.mLayer >= aboveAppLayer) {
+ continue;
+ }
+ // When we will skip windows: when we are not including
+ // ones behind a window we didn't skip, and we are actually
+ // taking a screenshot of a specific app.
+ if (!including && appToken != null) {
+ // Also, we can possibly skip this window if it is not
+ // an IME target or the application for the screenshot
+ // is not the current IME target.
+ if (!ws.mIsImWindow || !isImeTarget) {
+ // And finally, this window is of no interest if it
+ // is not associated with the screenshot app.
+ if (ws.mAppToken == null || ws.mAppToken.token != appToken) {
+ continue;
+ }
+ }
+ }
+
+ // We keep on including windows until we go past a full-screen
+ // window.
+ including = !ws.mIsImWindow && !ws.isFullscreen(dw, dh);
+
+ if (maxLayer < ws.mAnimLayer) {
+ maxLayer = ws.mAnimLayer;
+ }
+ final Rect wf = ws.mFrame;
+ final Rect cr = ws.mContentInsets;
+ int left = wf.left + cr.left;
+ int top = wf.top + cr.top;
+ int right = wf.right - cr.right;
+ int bottom = wf.bottom - cr.bottom;
+ frame.union(left, top, right, bottom);
+ }
+ Binder.restoreCallingIdentity(ident);
+
+ // Constrain frame to the screen size.
+ frame.intersect(0, 0, dw, dh);
+
+ if (frame.isEmpty() || maxLayer == 0) {
+ return null;
+ }
+
+ // The screenshot API does not apply the current screen rotation.
+ rot = mDisplay.getRotation();
+ int fw = frame.width();
+ int fh = frame.height();
+
+ // First try reducing to fit in x dimension.
+ scale = maxWidth/(float)fw;
+ sw = maxWidth;
+ sh = (int)(fh*scale);
+ if (sh > maxHeight) {
+ // y dimension became too long; constrain by that.
+ scale = maxHeight/(float)fh;
+ sw = (int)(fw*scale);
+ sh = maxHeight;
+ }
+
+ // The screen shot will contain the entire screen.
+ dw = (int)(dw*scale);
+ dh = (int)(dh*scale);
+ if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
+ int tmp = dw;
+ dw = dh;
+ dh = tmp;
+ rot = (rot == Surface.ROTATION_90) ? Surface.ROTATION_270 : Surface.ROTATION_90;
+ }
+ rawss = Surface.screenshot(dw, dh, 0, maxLayer);
+ }
+
+ if (rawss == null) {
+ Log.w(TAG, "Failure taking screenshot for (" + dw + "x" + dh
+ + ") to layer " + maxLayer);
+ return null;
+ }
+
+ Bitmap bm = Bitmap.createBitmap(sw, sh, rawss.getConfig());
+ Matrix matrix = new Matrix();
+ ScreenRotationAnimation.createRotationMatrix(rot, dw, dh, matrix);
+ matrix.postTranslate(-(int)(frame.left*scale), -(int)(frame.top*scale));
+ Canvas canvas = new Canvas(bm);
+ canvas.drawBitmap(rawss, matrix, null);
+
+ rawss.recycle();
+ return bm;
+ }
+
+ public void freezeRotation() {
+ if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
+ "setRotation()")) {
+ throw new SecurityException("Requires SET_ORIENTATION permission");
+ }
+
+ mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_LOCKED, mRotation);
+ setRotationUnchecked(WindowManagerPolicy.USE_LAST_ROTATION, false, 0);
+ }
+
+ public void thawRotation() {
+ if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
+ "setRotation()")) {
+ throw new SecurityException("Requires SET_ORIENTATION permission");
+ }
+
+ mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_FREE, 0);
+ setRotationUnchecked(WindowManagerPolicy.USE_LAST_ROTATION, false, 0);
+ }
+
public void setRotation(int rotation,
boolean alwaysSendConfiguration, int animFlags) {
if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
@@ -4495,7 +5201,7 @@ public class WindowManagerService extends IWindowManager.Stub
long origId = Binder.clearCallingIdentity();
boolean changed;
synchronized(mWindowMap) {
- changed = setRotationUncheckedLocked(rotation, animFlags);
+ changed = setRotationUncheckedLocked(rotation, animFlags, false);
}
if (changed || alwaysSendConfiguration) {
@@ -4513,14 +5219,31 @@ public class WindowManagerService extends IWindowManager.Stub
* Returns null if the rotation has been changed. In this case YOU
* MUST CALL setNewConfiguration() TO UNFREEZE THE SCREEN.
*/
- public boolean setRotationUncheckedLocked(int rotation, int animFlags) {
+ public boolean setRotationUncheckedLocked(int rotation, int animFlags, boolean inTransaction) {
+ if (mDragState != null || mScreenRotationAnimation != null) {
+ // Potential rotation during a drag. Don't do the rotation now, but make
+ // a note to perform the rotation later.
+ if (DEBUG_ORIENTATION) Slog.v(TAG, "Deferring rotation.");
+ if (rotation != WindowManagerPolicy.USE_LAST_ROTATION) {
+ mDeferredRotation = rotation;
+ mDeferredRotationAnimFlags = animFlags;
+ }
+ return false;
+ }
+
boolean changed;
if (rotation == WindowManagerPolicy.USE_LAST_ROTATION) {
+ if (mDeferredRotation != WindowManagerPolicy.USE_LAST_ROTATION) {
+ rotation = mDeferredRotation;
+ mRequestedRotation = rotation;
+ mLastRotationFlags = mDeferredRotationAnimFlags;
+ }
rotation = mRequestedRotation;
} else {
mRequestedRotation = rotation;
mLastRotationFlags = animFlags;
}
+ mDeferredRotation = WindowManagerPolicy.USE_LAST_ROTATION;
if (DEBUG_ORIENTATION) Slog.v(TAG, "Overwriting rotation value from " + rotation);
rotation = mPolicy.rotationForOrientationLw(mForcedAppOrientation,
mRotation, mDisplayEnabled);
@@ -4540,11 +5263,33 @@ public class WindowManagerService extends IWindowManager.Stub
2000);
mWaitingForConfig = true;
mLayoutNeeded = true;
- startFreezingDisplayLocked();
+ startFreezingDisplayLocked(inTransaction);
Slog.i(TAG, "Setting rotation to " + rotation + ", animFlags=" + animFlags);
mInputManager.setDisplayOrientation(0, rotation);
if (mDisplayEnabled) {
- Surface.setOrientation(0, rotation, animFlags);
+ if (CUSTOM_SCREEN_ROTATION) {
+ Surface.freezeDisplay(0);
+ if (!inTransaction) {
+ if (SHOW_TRANSACTIONS) Slog.i(TAG,
+ ">>> OPEN TRANSACTION setRotationUnchecked");
+ Surface.openTransaction();
+ }
+ try {
+ if (mScreenRotationAnimation != null) {
+ mScreenRotationAnimation.setRotation(rotation);
+ }
+ } finally {
+ if (!inTransaction) {
+ Surface.closeTransaction();
+ if (SHOW_TRANSACTIONS) Slog.i(TAG,
+ "<<< CLOSE TRANSACTION setRotationUnchecked");
+ }
+ }
+ Surface.setOrientation(0, rotation, animFlags);
+ Surface.unfreezeDisplay(0);
+ } else {
+ Surface.setOrientation(0, rotation, animFlags);
+ }
}
for (int i=mWindows.size()-1; i>=0; i--) {
WindowState w = mWindows.get(i);
@@ -4958,7 +5703,13 @@ public class WindowManagerService extends IWindowManager.Stub
public Configuration computeNewConfiguration() {
synchronized (mWindowMap) {
- return computeNewConfigurationLocked();
+ Configuration config = computeNewConfigurationLocked();
+ if (config == null && mWaitingForConfig) {
+ // Nothing changed but we are waiting for something... stop that!
+ mWaitingForConfig = false;
+ performLayoutAndPlaceSurfacesLocked();
+ }
+ return config;
}
}
@@ -5049,12 +5800,124 @@ public class WindowManagerService extends IWindowManager.Stub
}
config.screenLayout = mScreenLayout;
+ // Determine whether a hard keyboard is available and enabled.
+ boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS;
+ if (hardKeyboardAvailable != mHardKeyboardAvailable) {
+ mHardKeyboardAvailable = hardKeyboardAvailable;
+ mHardKeyboardEnabled = hardKeyboardAvailable;
+
+ mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
+ mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
+ }
+ if (!mHardKeyboardEnabled) {
+ config.keyboard = Configuration.KEYBOARD_NOKEYS;
+ }
+
+ // Update value of keyboardHidden, hardKeyboardHidden and navigationHidden
+ // based on whether a hard or soft keyboard is present, whether navigation keys
+ // are present and the lid switch state.
config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
+ config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO;
mPolicy.adjustConfigurationLw(config);
return true;
}
-
+
+ public boolean isHardKeyboardAvailable() {
+ synchronized (mWindowMap) {
+ return mHardKeyboardAvailable;
+ }
+ }
+
+ public boolean isHardKeyboardEnabled() {
+ synchronized (mWindowMap) {
+ return mHardKeyboardEnabled;
+ }
+ }
+
+ public void setHardKeyboardEnabled(boolean enabled) {
+ synchronized (mWindowMap) {
+ if (mHardKeyboardEnabled != enabled) {
+ mHardKeyboardEnabled = enabled;
+ mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+ }
+ }
+ }
+
+ public void setOnHardKeyboardStatusChangeListener(
+ OnHardKeyboardStatusChangeListener listener) {
+ synchronized (mWindowMap) {
+ mHardKeyboardStatusChangeListener = listener;
+ }
+ }
+
+ void notifyHardKeyboardStatusChange() {
+ final boolean available, enabled;
+ final OnHardKeyboardStatusChangeListener listener;
+ synchronized (mWindowMap) {
+ listener = mHardKeyboardStatusChangeListener;
+ available = mHardKeyboardAvailable;
+ enabled = mHardKeyboardEnabled;
+ }
+ if (listener != null) {
+ listener.onHardKeyboardStatusChange(available, enabled);
+ }
+ }
+
+ // -------------------------------------------------------------
+ // Drag and drop
+ // -------------------------------------------------------------
+
+ IBinder prepareDragSurface(IWindow window, SurfaceSession session,
+ int flags, int width, int height, Surface outSurface) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "prepare drag surface: w=" + width + " h=" + height
+ + " flags=" + Integer.toHexString(flags) + " win=" + window
+ + " asbinder=" + window.asBinder());
+ }
+
+ final int callerPid = Binder.getCallingPid();
+ final long origId = Binder.clearCallingIdentity();
+ IBinder token = null;
+
+ try {
+ synchronized (mWindowMap) {
+ try {
+ if (mDragState == null) {
+ Surface surface = new Surface(session, callerPid, "drag surface", 0,
+ width, height, PixelFormat.TRANSLUCENT, Surface.HIDDEN);
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, " DRAG "
+ + surface + ": CREATE");
+ outSurface.copyFrom(surface);
+ final IBinder winBinder = window.asBinder();
+ token = new Binder();
+ // TODO: preserve flags param in DragState
+ mDragState = new DragState(token, surface, 0, winBinder);
+ mDragState.mSurface = surface;
+ token = mDragState.mToken = new Binder();
+
+ // 5 second timeout for this window to actually begin the drag
+ mH.removeMessages(H.DRAG_START_TIMEOUT, winBinder);
+ Message msg = mH.obtainMessage(H.DRAG_START_TIMEOUT, winBinder);
+ mH.sendMessageDelayed(msg, 5000);
+ } else {
+ Slog.w(TAG, "Drag already in progress");
+ }
+ } catch (Surface.OutOfResourcesException e) {
+ Slog.e(TAG, "Can't allocate drag surface w=" + width + " h=" + height, e);
+ if (mDragState != null) {
+ mDragState.reset();
+ mDragState = null;
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ return token;
+ }
+
// -------------------------------------------------------------
// Input Events and Focus Management
// -------------------------------------------------------------
@@ -5074,23 +5937,31 @@ public class WindowManagerService extends IWindowManager.Stub
// When true, input dispatch proceeds normally. Otherwise all events are dropped.
private boolean mInputDispatchEnabled = true;
+ // When true, need to call updateInputWindowsLw().
+ private boolean mUpdateInputWindowsNeeded = true;
+
// Temporary list of windows information to provide to the input dispatcher.
private InputWindowList mTempInputWindows = new InputWindowList();
// Temporary input application object to provide to the input dispatcher.
private InputApplication mTempInputApplication = new InputApplication();
+ // Set to true when the first input device configuration change notification
+ // is received to indicate that the input devices are ready.
+ private final Object mInputDevicesReadyMonitor = new Object();
+ private boolean mInputDevicesReady;
+
/* Notifies the window manager about a broken input channel.
*
* Called by the InputManager.
*/
- public void notifyInputChannelBroken(InputChannel inputChannel) {
+ public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) {
+ if (inputWindowHandle == null) {
+ return;
+ }
+
synchronized (mWindowMap) {
- WindowState windowState = getWindowStateForInputChannelLocked(inputChannel);
- if (windowState == null) {
- return; // irrelevant
- }
-
+ WindowState windowState = (WindowState) inputWindowHandle.windowState;
Slog.i(TAG, "WINDOW DIED " + windowState);
removeWindowLocked(windowState.mSession, windowState);
}
@@ -5101,11 +5972,12 @@ public class WindowManagerService extends IWindowManager.Stub
*
* Called by the InputManager.
*/
- public long notifyANR(Object token, InputChannel inputChannel) {
+ public long notifyANR(InputApplicationHandle inputApplicationHandle,
+ InputWindowHandle inputWindowHandle) {
AppWindowToken appWindowToken = null;
- if (inputChannel != null) {
+ if (inputWindowHandle != null) {
synchronized (mWindowMap) {
- WindowState windowState = getWindowStateForInputChannelLocked(inputChannel);
+ WindowState windowState = (WindowState) inputWindowHandle.windowState;
if (windowState != null) {
Slog.i(TAG, "Input event dispatching timed out sending to "
+ windowState.mAttrs.getTitle());
@@ -5114,8 +5986,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- if (appWindowToken == null && token != null) {
- appWindowToken = (AppWindowToken) token;
+ if (appWindowToken == null && inputApplicationHandle != null) {
+ appWindowToken = inputApplicationHandle.appWindowToken;
Slog.i(TAG, "Input event dispatching timed out sending to application "
+ appWindowToken.stringName);
}
@@ -5135,33 +6007,60 @@ public class WindowManagerService extends IWindowManager.Stub
}
return 0; // abort dispatching
}
-
- private WindowState getWindowStateForInputChannel(InputChannel inputChannel) {
- synchronized (mWindowMap) {
- return getWindowStateForInputChannelLocked(inputChannel);
- }
+
+ private void addDragInputWindowLw(InputWindowList windowList) {
+ final InputWindow inputWindow = windowList.add();
+ inputWindow.inputChannel = mDragState.mServerChannel;
+ inputWindow.name = "drag";
+ inputWindow.layoutParamsFlags = 0;
+ inputWindow.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
+ inputWindow.dispatchingTimeoutNanos = DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+ inputWindow.visible = true;
+ inputWindow.canReceiveKeys = false;
+ inputWindow.hasFocus = true;
+ inputWindow.hasWallpaper = false;
+ inputWindow.paused = false;
+ inputWindow.layer = mDragState.getDragLayerLw();
+ inputWindow.ownerPid = Process.myPid();
+ inputWindow.ownerUid = Process.myUid();
+
+ // The drag window covers the entire display
+ inputWindow.frameLeft = 0;
+ inputWindow.frameTop = 0;
+ inputWindow.frameRight = mDisplay.getWidth();
+ inputWindow.frameBottom = mDisplay.getHeight();
+
+ // The drag window cannot receive new touches.
+ inputWindow.touchableRegion.setEmpty();
}
-
- private WindowState getWindowStateForInputChannelLocked(InputChannel inputChannel) {
- int windowCount = mWindows.size();
- for (int i = 0; i < windowCount; i++) {
- WindowState windowState = mWindows.get(i);
- if (windowState.mInputChannel == inputChannel) {
- return windowState;
- }
- }
-
- return null;
+
+ public void setUpdateInputWindowsNeededLw() {
+ mUpdateInputWindowsNeeded = true;
}
-
+
/* Updates the cached window information provided to the input dispatcher. */
- public void updateInputWindowsLw() {
+ public void updateInputWindowsLw(boolean force) {
+ if (!force && !mUpdateInputWindowsNeeded) {
+ return;
+ }
+ mUpdateInputWindowsNeeded = false;
+
// Populate the input window list with information about all of the windows that
// could potentially receive input.
// As an optimization, we could try to prune the list of windows but this turns
// out to be difficult because only the native code knows for sure which window
// currently has touch focus.
final ArrayList<WindowState> windows = mWindows;
+
+ // If there's a drag in flight, provide a pseudowindow to catch drag input
+ final boolean inDrag = (mDragState != null);
+ if (inDrag) {
+ if (DEBUG_DRAG) {
+ Log.d(TAG, "Inserting drag window");
+ }
+ addDragInputWindowLw(mTempInputWindows);
+ }
+
final int N = windows.size();
for (int i = N - 1; i >= 0; i--) {
final WindowState child = windows.get(i);
@@ -5177,9 +6076,16 @@ public class WindowManagerService extends IWindowManager.Stub
final boolean isVisible = child.isVisibleLw();
final boolean hasWallpaper = (child == mWallpaperTarget)
&& (type != WindowManager.LayoutParams.TYPE_KEYGUARD);
-
+
+ // If there's a drag in progress and 'child' is a potential drop target,
+ // make sure it's been told about the drag
+ if (inDrag && isVisible) {
+ mDragState.sendDragStartedIfNeededLw(child);
+ }
+
// Add a window to our list of input windows.
final InputWindow inputWindow = mTempInputWindows.add();
+ inputWindow.inputWindowHandle = child.mInputWindowHandle;
inputWindow.inputChannel = child.mInputChannel;
inputWindow.name = child.toString();
inputWindow.layoutParamsFlags = flags;
@@ -5199,40 +6105,8 @@ public class WindowManagerService extends IWindowManager.Stub
inputWindow.frameTop = frame.top;
inputWindow.frameRight = frame.right;
inputWindow.frameBottom = frame.bottom;
-
- final Rect visibleFrame = child.mVisibleFrame;
- inputWindow.visibleFrameLeft = visibleFrame.left;
- inputWindow.visibleFrameTop = visibleFrame.top;
- inputWindow.visibleFrameRight = visibleFrame.right;
- inputWindow.visibleFrameBottom = visibleFrame.bottom;
-
- switch (child.mTouchableInsets) {
- default:
- case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
- inputWindow.touchableAreaLeft = frame.left;
- inputWindow.touchableAreaTop = frame.top;
- inputWindow.touchableAreaRight = frame.right;
- inputWindow.touchableAreaBottom = frame.bottom;
- break;
-
- case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT: {
- Rect inset = child.mGivenContentInsets;
- inputWindow.touchableAreaLeft = frame.left + inset.left;
- inputWindow.touchableAreaTop = frame.top + inset.top;
- inputWindow.touchableAreaRight = frame.right - inset.right;
- inputWindow.touchableAreaBottom = frame.bottom - inset.bottom;
- break;
- }
-
- case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE: {
- Rect inset = child.mGivenVisibleInsets;
- inputWindow.touchableAreaLeft = frame.left + inset.left;
- inputWindow.touchableAreaTop = frame.top + inset.top;
- inputWindow.touchableAreaRight = frame.right - inset.right;
- inputWindow.touchableAreaBottom = frame.bottom - inset.bottom;
- break;
- }
- }
+
+ child.getTouchableRegion(inputWindow.touchableRegion);
}
// Send windows to native code.
@@ -5242,7 +6116,32 @@ public class WindowManagerService extends IWindowManager.Stub
// Also avoids keeping InputChannel objects referenced unnecessarily.
mTempInputWindows.clear();
}
-
+
+ /* Notifies that the input device configuration has changed. */
+ public void notifyConfigurationChanged() {
+ sendNewConfiguration();
+
+ synchronized (mInputDevicesReadyMonitor) {
+ if (!mInputDevicesReady) {
+ mInputDevicesReady = true;
+ mInputDevicesReadyMonitor.notifyAll();
+ }
+ }
+ }
+
+ /* Waits until the built-in input devices have been configured. */
+ public boolean waitForInputDevicesReady(long timeoutMillis) {
+ synchronized (mInputDevicesReadyMonitor) {
+ if (!mInputDevicesReady) {
+ try {
+ mInputDevicesReadyMonitor.wait(timeoutMillis);
+ } catch (InterruptedException ex) {
+ }
+ }
+ return mInputDevicesReady;
+ }
+ }
+
/* Notifies that the lid switch changed state. */
public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
mPolicy.notifyLidSwitchChanged(whenNanos, lidOpen);
@@ -5250,26 +6149,31 @@ public class WindowManagerService extends IWindowManager.Stub
/* Provides an opportunity for the window manager policy to intercept early key
* processing as soon as the key has been read from the device. */
- public int interceptKeyBeforeQueueing(long whenNanos, int action, int flags,
- int keyCode, int scanCode, int policyFlags, boolean isScreenOn) {
- return mPolicy.interceptKeyBeforeQueueing(whenNanos, action, flags,
- keyCode, scanCode, policyFlags, isScreenOn);
+ public int interceptKeyBeforeQueueing(
+ KeyEvent event, int policyFlags, boolean isScreenOn) {
+ return mPolicy.interceptKeyBeforeQueueing(event, policyFlags, isScreenOn);
}
/* Provides an opportunity for the window manager policy to process a key before
* ordinary dispatch. */
- public boolean interceptKeyBeforeDispatching(InputChannel focus,
- int action, int flags, int keyCode, int scanCode, int metaState, int repeatCount,
- int policyFlags) {
- WindowState windowState = getWindowStateForInputChannel(focus);
- return mPolicy.interceptKeyBeforeDispatching(windowState, action, flags,
- keyCode, scanCode, metaState, repeatCount, policyFlags);
+ public boolean interceptKeyBeforeDispatching(
+ InputWindowHandle focus, KeyEvent event, int policyFlags) {
+ WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
+ return mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags);
+ }
+
+ /* Provides an opportunity for the window manager policy to process a key that
+ * the application did not handle. */
+ public KeyEvent dispatchUnhandledKey(
+ InputWindowHandle focus, KeyEvent event, int policyFlags) {
+ WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
+ return mPolicy.dispatchUnhandledKey(windowState, event, policyFlags);
}
/* Called when the current input focus changes.
* Layer assignment is assumed to be complete by the time this is called.
*/
- public void setInputFocusLw(WindowState newWindow) {
+ public void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) {
if (DEBUG_INPUT) {
Slog.d(TAG, "Input focus has changed to " + newWindow);
}
@@ -5281,9 +6185,13 @@ public class WindowManagerService extends IWindowManager.Stub
// forgets to resume.
newWindow.mToken.paused = false;
}
-
+
mInputFocus = newWindow;
- updateInputWindowsLw();
+ setUpdateInputWindowsNeededLw();
+
+ if (updateInputWindows) {
+ updateInputWindowsLw(false /*force*/);
+ }
}
}
@@ -5292,12 +6200,14 @@ public class WindowManagerService extends IWindowManager.Stub
if (newApp == null) {
mInputManager.setFocusedApplication(null);
} else {
+ mTempInputApplication.inputApplicationHandle = newApp.mInputApplicationHandle;
mTempInputApplication.name = newApp.toString();
mTempInputApplication.dispatchingTimeoutNanos =
newApp.inputDispatchingTimeoutNanos;
- mTempInputApplication.token = newApp;
-
+
mInputManager.setFocusedApplication(mTempInputApplication);
+
+ mTempInputApplication.recycle();
}
}
@@ -5308,7 +6218,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
window.paused = true;
- updateInputWindowsLw();
+ updateInputWindowsLw(true /*force*/);
}
}
@@ -5319,7 +6229,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
window.paused = false;
- updateInputWindowsLw();
+ updateInputWindowsLw(true /*force*/);
}
}
@@ -5557,11 +6467,35 @@ public class WindowManagerService extends IWindowManager.Stub
}
public boolean detectSafeMode() {
+ if (!mInputMonitor.waitForInputDevicesReady(
+ INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS)) {
+ Slog.w(TAG, "Devices still not ready after waiting "
+ + INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS
+ + " milliseconds before attempting to detect safe mode.");
+ }
+
mSafeMode = mPolicy.detectSafeMode();
return mSafeMode;
}
public void systemReady() {
+ synchronized(mWindowMap) {
+ if (mDisplay != null) {
+ throw new IllegalStateException("Display already initialized");
+ }
+ WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+ mDisplay = wm.getDefaultDisplay();
+ mInitialDisplayWidth = mDisplay.getWidth();
+ mInitialDisplayHeight = mDisplay.getHeight();
+ mInputManager.setDisplaySize(0, Display.unmapDisplaySize(mInitialDisplayWidth),
+ Display.unmapDisplaySize(mInitialDisplayHeight));
+ }
+
+ try {
+ mActivityManager.updateConfiguration(null);
+ } catch (RemoteException e) {
+ }
+
mPolicy.systemReady();
}
@@ -5686,9 +6620,9 @@ public class WindowManagerService extends IWindowManager.Stub
}
public void setInsets(IWindow window, int touchableInsets,
- Rect contentInsets, Rect visibleInsets) {
+ Rect contentInsets, Rect visibleInsets, Region touchableArea) {
setInsetsWindow(this, window, touchableInsets, contentInsets,
- visibleInsets);
+ visibleInsets, touchableArea);
}
public void getDisplayFrame(IWindow window, Rect outDisplayFrame) {
@@ -5727,6 +6661,132 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ /* Drag/drop */
+ public IBinder prepareDrag(IWindow window, int flags,
+ int width, int height, Surface outSurface) {
+ return prepareDragSurface(window, mSurfaceSession, flags,
+ width, height, outSurface);
+ }
+
+ public boolean performDrag(IWindow window, IBinder dragToken,
+ float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ ClipData data) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "perform drag: win=" + window + " data=" + data);
+ }
+
+ synchronized (mWindowMap) {
+ if (mDragState == null) {
+ Slog.w(TAG, "No drag prepared");
+ throw new IllegalStateException("performDrag() without prepareDrag()");
+ }
+
+ if (dragToken != mDragState.mToken) {
+ Slog.w(TAG, "Performing mismatched drag");
+ throw new IllegalStateException("performDrag() does not match prepareDrag()");
+ }
+
+ WindowState callingWin = windowForClientLocked(null, window, false);
+ if (callingWin == null) {
+ Slog.w(TAG, "Bad requesting window " + window);
+ return false; // !!! TODO: throw here?
+ }
+
+ // !!! TODO: if input is not still focused on the initiating window, fail
+ // the drag initiation (e.g. an alarm window popped up just as the application
+ // called performDrag()
+
+ mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder());
+
+ // !!! TODO: extract the current touch (x, y) in screen coordinates. That
+ // will let us eliminate the (touchX,touchY) parameters from the API.
+
+ // !!! FIXME: put all this heavy stuff onto the mH looper, as well as
+ // the actual drag event dispatch stuff in the dragstate
+
+ mDragState.register();
+ mInputMonitor.updateInputWindowsLw(true /*force*/);
+ if (!mInputManager.transferTouchFocus(callingWin.mInputChannel,
+ mDragState.mServerChannel)) {
+ Slog.e(TAG, "Unable to transfer touch focus");
+ mDragState.unregister();
+ mDragState = null;
+ mInputMonitor.updateInputWindowsLw(true /*force*/);
+ return false;
+ }
+
+ mDragState.mData = data;
+ mDragState.mCurrentX = touchX;
+ mDragState.mCurrentY = touchY;
+ mDragState.broadcastDragStartedLw(touchX, touchY);
+
+ // remember the thumb offsets for later
+ mDragState.mThumbOffsetX = thumbCenterX;
+ mDragState.mThumbOffsetY = thumbCenterY;
+
+ // Make the surface visible at the proper location
+ final Surface surface = mDragState.mSurface;
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION performDrag");
+ Surface.openTransaction();
+ try {
+ surface.setPosition((int)(touchX - thumbCenterX),
+ (int)(touchY - thumbCenterY));
+ surface.setAlpha(.7071f);
+ surface.setLayer(mDragState.getDragLayerLw());
+ surface.show();
+ } finally {
+ Surface.closeTransaction();
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION performDrag");
+ }
+ }
+
+ return true; // success!
+ }
+
+ public void reportDropResult(IWindow window, boolean consumed) {
+ IBinder token = window.asBinder();
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "Drop result=" + consumed + " reported by " + token);
+ }
+
+ synchronized (mWindowMap) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ if (mDragState == null || mDragState.mToken != token) {
+ Slog.w(TAG, "Invalid drop-result claim by " + window);
+ throw new IllegalStateException("reportDropResult() by non-recipient");
+ }
+
+ // The right window has responded, even if it's no longer around,
+ // so be sure to halt the timeout even if the later WindowState
+ // lookup fails.
+ mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder());
+ WindowState callingWin = windowForClientLocked(null, window, false);
+ if (callingWin == null) {
+ Slog.w(TAG, "Bad result-reporting window " + window);
+ return; // !!! TODO: throw here?
+ }
+
+ mDragState.mDragResult = consumed;
+ mDragState.endDragLw();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ public void dragRecipientEntered(IWindow window) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "Drag into new candidate view @ " + window.asBinder());
+ }
+ }
+
+ public void dragRecipientExited(IWindow window) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG, "Drag from old candidate view @ " + window.asBinder());
+ }
+ }
+
public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) {
synchronized(mWindowMap) {
long ident = Binder.clearCallingIdentity();
@@ -5901,6 +6961,11 @@ public class WindowManagerService extends IWindowManager.Stub
final Rect mGivenVisibleInsets = new Rect();
/**
+ * This is the given touchable area relative to the window frame, or null if none.
+ */
+ final Region mGivenTouchableRegion = new Region();
+
+ /**
* Flag indicating whether the touchable region should be adjusted by
* the visible insets; if false the area outside the visible insets is
* NOT touchable, so we must use those to adjust the frame during hit
@@ -5909,6 +6974,7 @@ public class WindowManagerService extends IWindowManager.Stub
int mTouchableInsets = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
// Current transformation being applied.
+ boolean mHaveMatrix;
float mDsDx=1, mDtDx=0, mDsDy=0, mDtDy=1;
float mLastDsDx=1, mLastDtDx=0, mLastDsDy=0, mLastDtDy=1;
float mHScale=1, mVScale=1;
@@ -5922,8 +6988,11 @@ public class WindowManagerService extends IWindowManager.Stub
final Rect mContainingFrame = new Rect();
final Rect mDisplayFrame = new Rect();
final Rect mContentFrame = new Rect();
+ final Rect mParentFrame = new Rect();
final Rect mVisibleFrame = new Rect();
+ boolean mContentChanged;
+
float mShownAlpha = 1;
float mAlpha = 1;
float mLastAlpha = 1;
@@ -5995,13 +7064,18 @@ public class WindowManagerService extends IWindowManager.Stub
// Is this window now (or just being) removed?
boolean mRemoved;
+ // Temp for keeping track of windows that have been removed when
+ // rebuilding window list.
+ boolean mRebuilding;
+
// For debugging, this is the last information given to the surface flinger.
boolean mSurfaceShown;
int mSurfaceX, mSurfaceY, mSurfaceW, mSurfaceH;
int mSurfaceLayer;
float mSurfaceAlpha;
- // Input channel
+ // Input channel and input window handle used by the input dispatcher.
+ InputWindowHandle mInputWindowHandle;
InputChannel mInputChannel;
// Used to improve performance of toString()
@@ -6046,6 +7120,7 @@ public class WindowManagerService extends IWindowManager.Stub
+ TYPE_LAYER_OFFSET;
mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);
mAttachedWindow = attachedWindow;
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + mAttachedWindow);
mAttachedWindow.mChildWindows.add(this);
mLayoutAttached = mAttrs.type !=
WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -6093,6 +7168,8 @@ public class WindowManagerService extends IWindowManager.Stub
mLayer = 0;
mAnimLayer = 0;
mLastLayer = 0;
+ mInputWindowHandle = new InputWindowHandle(
+ mAppToken != null ? mAppToken.mInputApplicationHandle : null, this);
}
void attach() {
@@ -6130,6 +7207,13 @@ public class WindowManagerService extends IWindowManager.Stub
h = mAttrs.height== mAttrs.MATCH_PARENT ? ph : mRequestedHeight;
}
+ if (!mParentFrame.equals(pf)) {
+ //Slog.i(TAG, "Window " + this + " content frame from " + mParentFrame
+ // + " to " + pf);
+ mParentFrame.set(pf);
+ mContentChanged = true;
+ }
+
final Rect content = mContentFrame;
content.set(cf);
@@ -6261,6 +7345,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (mAnimation != null) {
mAnimating = true;
mLocalAnimating = false;
+ mAnimation.cancel();
mAnimation = null;
}
}
@@ -6277,9 +7362,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
int flags = 0;
- if (mAttrs.memoryType == MEMORY_TYPE_PUSH_BUFFERS) {
- flags |= Surface.PUSH_BUFFERS;
- }
if ((mAttrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) {
flags |= Surface.SECURE;
@@ -6313,10 +7395,16 @@ public class WindowManagerService extends IWindowManager.Stub
mSurfaceW = w;
mSurfaceH = h;
try {
+ final boolean isHwAccelerated = (mAttrs.flags &
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
+ final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : mAttrs.format;
+ if (isHwAccelerated && mAttrs.format == PixelFormat.OPAQUE) {
+ flags |= Surface.OPAQUE;
+ }
mSurface = new Surface(
mSession.mSurfaceSession, mSession.mPid,
mAttrs.getTitle().toString(),
- 0, w, h, mAttrs.format, flags);
+ 0, w, h, format, flags);
if (SHOW_TRANSACTIONS) Slog.i(TAG, " CREATE SURFACE "
+ mSurface + " IN SESSION "
+ mSession.mSurfaceSession
@@ -6338,9 +7426,8 @@ public class WindowManagerService extends IWindowManager.Stub
+ ", set left=" + mFrame.left + " top=" + mFrame.top
+ ", animLayer=" + mAnimLayer);
if (SHOW_TRANSACTIONS) {
- Slog.i(TAG, ">>> OPEN TRANSACTION");
- if (SHOW_TRANSACTIONS) logSurface(this,
- "CREATE pos=(" + mFrame.left + "," + mFrame.top + ") (" +
+ Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked");
+ logSurface(this, "CREATE pos=(" + mFrame.left + "," + mFrame.top + ") (" +
mFrame.width() + "x" + mFrame.height() + "), layer=" +
mAnimLayer + " HIDE", null);
}
@@ -6365,8 +7452,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
mLastHidden = true;
} finally {
- if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION");
Surface.closeTransaction();
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION createSurfaceLocked");
}
if (localLOGV) Slog.v(
TAG, "Created surface " + this);
@@ -6526,6 +7613,7 @@ public class WindowManagerService extends IWindowManager.Stub
// starting window, so there is no need for it to also
// be doing its own stuff.
if (mAnimation != null) {
+ mAnimation.cancel();
mAnimation = null;
// Make sure we clean up the animation.
mAnimating = true;
@@ -6571,7 +7659,11 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_ANIM) Slog.v(
TAG, "Finished animation in " + this +
" @ " + currentTime);
- mAnimation = null;
+
+ if (mAnimation != null) {
+ mAnimation.cancel();
+ mAnimation = null;
+ }
//WindowManagerService.this.dump();
}
mHasLocalTransformation = false;
@@ -6600,6 +7692,7 @@ public class WindowManagerService extends IWindowManager.Stub
// clear it and make sure we run the cleanup code.
mAnimating = true;
mLocalAnimating = true;
+ mAnimation.cancel();
mAnimation = null;
}
@@ -6614,7 +7707,10 @@ public class WindowManagerService extends IWindowManager.Stub
mAnimating = false;
mLocalAnimating = false;
- mAnimation = null;
+ if (mAnimation != null) {
+ mAnimation.cancel();
+ mAnimation = null;
+ }
mAnimLayer = mLayer;
if (mIsImWindow) {
mAnimLayer += mInputMethodAnimLayerAdjustment;
@@ -6744,8 +7840,10 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ final boolean screenAnimation = mScreenRotationAnimation != null
+ && mScreenRotationAnimation.isAnimating();
if (selfTransformation || attachedTransformation != null
- || appTransformation != null) {
+ || appTransformation != null || screenAnimation) {
// cache often used attributes locally
final Rect frame = mFrame;
final float tmpFloats[] = mTmpFloats;
@@ -6756,13 +7854,17 @@ public class WindowManagerService extends IWindowManager.Stub
if (selfTransformation) {
tmpMatrix.postConcat(mTransformation.getMatrix());
}
- tmpMatrix.postTranslate(frame.left, frame.top);
+ tmpMatrix.postTranslate(frame.left + mXOffset, frame.top + mYOffset);
if (attachedTransformation != null) {
tmpMatrix.postConcat(attachedTransformation.getMatrix());
}
if (appTransformation != null) {
tmpMatrix.postConcat(appTransformation.getMatrix());
}
+ if (screenAnimation) {
+ tmpMatrix.postConcat(
+ mScreenRotationAnimation.getEnterTransformation().getMatrix());
+ }
// "convert" it into SurfaceFlinger's format
// (a 2x2 matrix + an offset)
@@ -6770,13 +7872,14 @@ public class WindowManagerService extends IWindowManager.Stub
// since it is already included in the transformation.
//Slog.i(TAG, "Transform: " + matrix);
+ mHaveMatrix = true;
tmpMatrix.getValues(tmpFloats);
mDsDx = tmpFloats[Matrix.MSCALE_X];
- mDtDx = tmpFloats[Matrix.MSKEW_X];
- mDsDy = tmpFloats[Matrix.MSKEW_Y];
+ mDtDx = tmpFloats[Matrix.MSKEW_Y];
+ mDsDy = tmpFloats[Matrix.MSKEW_X];
mDtDy = tmpFloats[Matrix.MSCALE_Y];
- int x = (int)tmpFloats[Matrix.MTRANS_X] + mXOffset;
- int y = (int)tmpFloats[Matrix.MTRANS_Y] + mYOffset;
+ int x = (int)tmpFloats[Matrix.MTRANS_X];
+ int y = (int)tmpFloats[Matrix.MTRANS_Y];
int w = frame.width();
int h = frame.height();
mShownFrame.set(x, y, x+w, y+h);
@@ -6801,6 +7904,10 @@ public class WindowManagerService extends IWindowManager.Stub
if (appTransformation != null) {
mShownAlpha *= appTransformation.getAlpha();
}
+ if (screenAnimation) {
+ mShownAlpha *=
+ mScreenRotationAnimation.getEnterTransformation().getAlpha();
+ }
} else {
//Slog.i(TAG, "Not applying alpha transform");
}
@@ -6817,6 +7924,7 @@ public class WindowManagerService extends IWindowManager.Stub
mShownFrame.offset(mXOffset, mYOffset);
}
mShownAlpha = mAlpha;
+ mHaveMatrix = false;
mDsDx = 1;
mDtDx = 0;
mDsDy = 0;
@@ -6847,7 +7955,7 @@ public class WindowManagerService extends IWindowManager.Stub
final AppWindowToken atoken = mAppToken;
return mSurface != null && !mAttachedHidden
&& (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested)
- && (mOrientationChanging || (!mDrawPending && !mCommitDrawPending))
+ && !mDrawPending && !mCommitDrawPending
&& !mExiting && !mDestroying;
}
@@ -6873,6 +7981,15 @@ public class WindowManagerService extends IWindowManager.Stub
}
/**
+ * Can this window possibly be a drag/drop target? The test here is
+ * a combination of the above "visible now" with the check that the
+ * Input Manager uses when discarding windows from input consideration.
+ */
+ boolean isPotentialDragTarget() {
+ return isVisibleNow() && (mInputChannel != null) && !mRemoved;
+ }
+
+ /**
* Same as isVisible(), but we also count it as visible between the
* call to IWindowSession.add() and the first relayout().
*/
@@ -6951,14 +8068,12 @@ public class WindowManagerService extends IWindowManager.Stub
/**
* Returns true if the window has a surface that it has drawn a
- * complete UI in to. Note that this returns true if the orientation
- * is changing even if the window hasn't redrawn because we don't want
- * to stop things from executing during that time.
+ * complete UI in to.
*/
public boolean isDrawnLw() {
final AppWindowToken atoken = mAppToken;
return mSurface != null && !mDestroying
- && (mOrientationChanging || (!mDrawPending && !mCommitDrawPending));
+ && !mDrawPending && !mCommitDrawPending;
}
/**
@@ -6973,6 +8088,19 @@ public class WindowManagerService extends IWindowManager.Stub
&& !mDrawPending && !mCommitDrawPending;
}
+ /**
+ * Return whether this window is wanting to have a translation
+ * animation applied to it for an in-progress move. (Only makes
+ * sense to call from performLayoutAndPlaceSurfacesLockedInner().)
+ */
+ boolean shouldAnimateMove() {
+ return mContentChanged && !mExiting && !mLastHidden && !mDisplayFrozen
+ && (mFrame.top != mLastFrame.top
+ || mFrame.left != mLastFrame.left)
+ && (mAttachedWindow == null || !mAttachedWindow.shouldAnimateMove())
+ && mPolicy.isScreenOn();
+ }
+
boolean needsBackgroundFiller(int screenWidth, int screenHeight) {
return
// only if the application is requesting compatible window
@@ -6983,9 +8111,7 @@ public class WindowManagerService extends IWindowManager.Stub
mFrame.left <= mCompatibleScreenFrame.left &&
mFrame.top <= mCompatibleScreenFrame.top &&
mFrame.right >= mCompatibleScreenFrame.right &&
- mFrame.bottom >= mCompatibleScreenFrame.bottom &&
- // and starting window do not need background filler
- mAttrs.type != mAttrs.TYPE_APPLICATION_STARTING;
+ mFrame.bottom >= mCompatibleScreenFrame.bottom;
}
boolean isFullscreen(int screenWidth, int screenHeight) {
@@ -6997,6 +8123,7 @@ public class WindowManagerService extends IWindowManager.Stub
disposeInputChannel();
if (mAttachedWindow != null) {
+ if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing " + this + " from " + mAttachedWindow);
mAttachedWindow.mChildWindows.remove(this);
}
destroySurfaceLocked();
@@ -7119,6 +8246,36 @@ public class WindowManagerService extends IWindowManager.Stub
return true;
}
+ public void getTouchableRegion(Region outRegion) {
+ final Rect frame = mFrame;
+ switch (mTouchableInsets) {
+ default:
+ case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
+ outRegion.set(frame);
+ break;
+ case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT: {
+ final Rect inset = mGivenContentInsets;
+ outRegion.set(
+ frame.left + inset.left, frame.top + inset.top,
+ frame.right - inset.right, frame.bottom - inset.bottom);
+ break;
+ }
+ case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE: {
+ final Rect inset = mGivenVisibleInsets;
+ outRegion.set(
+ frame.left + inset.left, frame.top + inset.top,
+ frame.right - inset.right, frame.bottom - inset.bottom);
+ break;
+ }
+ case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION: {
+ final Region givenTouchableRegion = mGivenTouchableRegion;
+ outRegion.set(givenTouchableRegion);
+ outRegion.translate(frame.left, frame.top);
+ break;
+ }
+ }
+ }
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("mSession="); pw.print(mSession);
pw.print(" mClient="); pw.println(mClient.asBinder());
@@ -7199,6 +8356,8 @@ public class WindowManagerService extends IWindowManager.Stub
pw.println();
pw.print(prefix); pw.print("mContainingFrame=");
mContainingFrame.printShortString(pw);
+ pw.print(" mParentFrame=");
+ mParentFrame.printShortString(pw);
pw.print(" mDisplayFrame=");
mDisplayFrame.printShortString(pw);
pw.println();
@@ -7210,11 +8369,6 @@ public class WindowManagerService extends IWindowManager.Stub
pw.print(" mVisibleInsets="); mVisibleInsets.printShortString(pw);
pw.print(" last="); mLastVisibleInsets.printShortString(pw);
pw.println();
- if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) {
- pw.print(prefix); pw.print("mShownAlpha="); pw.print(mShownAlpha);
- pw.print(" mAlpha="); pw.print(mAlpha);
- pw.print(" mLastAlpha="); pw.println(mLastAlpha);
- }
if (mAnimating || mLocalAnimating || mAnimationIsEntrance
|| mAnimation != null) {
pw.print(prefix); pw.print("mAnimating="); pw.print(mAnimating);
@@ -7229,6 +8383,17 @@ public class WindowManagerService extends IWindowManager.Stub
pw.print(" "); mTransformation.printShortString(pw);
pw.println();
}
+ if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) {
+ pw.print(prefix); pw.print("mShownAlpha="); pw.print(mShownAlpha);
+ pw.print(" mAlpha="); pw.print(mAlpha);
+ pw.print(" mLastAlpha="); pw.println(mLastAlpha);
+ }
+ if (mHaveMatrix) {
+ pw.print(prefix); pw.print("mDsDx="); pw.print(mDsDx);
+ pw.print(" mDtDx="); pw.print(mDtDx);
+ pw.print(" mDsDy="); pw.print(mDsDy);
+ pw.print(" mDtDy="); pw.println(mDtDy);
+ }
pw.print(prefix); pw.print("mDrawPending="); pw.print(mDrawPending);
pw.print(" mCommitDrawPending="); pw.print(mCommitDrawPending);
pw.print(" mReadyToShow="); pw.print(mReadyToShow);
@@ -7422,11 +8587,15 @@ public class WindowManagerService extends IWindowManager.Stub
boolean startingMoved;
boolean firstWindowDrawn;
+ // Input application handle used by the input dispatcher.
+ InputApplicationHandle mInputApplicationHandle;
+
AppWindowToken(IApplicationToken _token) {
super(_token.asBinder(),
WindowManager.LayoutParams.TYPE_APPLICATION, true);
appWindowToken = this;
appToken = _token;
+ mInputApplicationHandle = new InputApplicationHandle(this);
}
public void setAnimation(Animation anim) {
@@ -7473,7 +8642,7 @@ public class WindowManagerService extends IWindowManager.Stub
w.mAnimLayer = w.mLayer + adj;
if (DEBUG_LAYERS) Slog.v(TAG, "Updating layer " + w + ": "
+ w.mAnimLayer);
- if (w == mInputMethodTarget) {
+ if (w == mInputMethodTarget && !mInputMethodTargetWaitingAnim) {
setInputMethodAnimLayerAdjustment(adj);
}
if (w == mWallpaperTarget && mLowerWallpaperTarget == null) {
@@ -7564,6 +8733,10 @@ public class WindowManagerService extends IWindowManager.Stub
clearAnimation();
animating = false;
+ if (animLayerAdjustment != 0) {
+ animLayerAdjustment = 0;
+ updateLayers();
+ }
if (mInputMethodTarget != null && mInputMethodTarget.mAppToken == this) {
moveInputMethodWindowsIfNeededLocked(true);
}
@@ -7573,10 +8746,6 @@ public class WindowManagerService extends IWindowManager.Stub
+ ": reportedVisible=" + reportedVisible);
transformation.clear();
- if (animLayerAdjustment != 0) {
- animLayerAdjustment = 0;
- updateLayers();
- }
final int N = windows.size();
for (int i=0; i<N; i++) {
@@ -7694,14 +8863,14 @@ public class WindowManagerService extends IWindowManager.Stub
pw.print(prefix); pw.print("animating="); pw.print(animating);
pw.print(" animation="); pw.println(animation);
}
- if (animLayerAdjustment != 0) {
- pw.print(prefix); pw.print("animLayerAdjustment="); pw.println(animLayerAdjustment);
- }
if (hasTransformation) {
- pw.print(prefix); pw.print("hasTransformation="); pw.print(hasTransformation);
- pw.print(" transformation="); transformation.printShortString(pw);
+ pw.print(prefix); pw.print("XForm: ");
+ transformation.printShortString(pw);
pw.println();
}
+ if (animLayerAdjustment != 0) {
+ pw.print(prefix); pw.print("animLayerAdjustment="); pw.println(animLayerAdjustment);
+ }
if (startingData != null || removed || firstWindowDrawn) {
pw.print(prefix); pw.print("startingData="); pw.print(startingData);
pw.print(" removed="); pw.print(removed);
@@ -7753,14 +8922,16 @@ public class WindowManagerService extends IWindowManager.Stub
final CharSequence nonLocalizedLabel;
final int labelRes;
final int icon;
+ final int windowFlags;
StartingData(String _pkg, int _theme, CharSequence _nonLocalizedLabel,
- int _labelRes, int _icon) {
+ int _labelRes, int _icon, int _windowFlags) {
pkg = _pkg;
theme = _theme;
nonLocalizedLabel = _nonLocalizedLabel;
labelRes = _labelRes;
icon = _icon;
+ windowFlags = _windowFlags;
}
}
@@ -7781,6 +8952,9 @@ public class WindowManagerService extends IWindowManager.Stub
public static final int APP_FREEZE_TIMEOUT = 17;
public static final int SEND_NEW_CONFIGURATION = 18;
public static final int REPORT_WINDOWS_CHANGE = 19;
+ public static final int DRAG_START_TIMEOUT = 20;
+ public static final int DRAG_END_TIMEOUT = 21;
+ public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22;
private Session mLastReportedHold;
@@ -7833,6 +9007,8 @@ public class WindowManagerService extends IWindowManager.Stub
// Ignore if process has died.
}
}
+
+ mPolicy.focusChanged(lastFocus, newFocus);
}
} break;
@@ -7879,7 +9055,7 @@ public class WindowManagerService extends IWindowManager.Stub
view = mPolicy.addStartingWindow(
wtoken.token, sd.pkg,
sd.theme, sd.nonLocalizedLabel, sd.labelRes,
- sd.icon);
+ sd.icon, sd.windowFlags);
} catch (Exception e) {
Slog.w(TAG, "Exception when adding starting window", e);
}
@@ -8123,6 +9299,40 @@ public class WindowManagerService extends IWindowManager.Stub
break;
}
+ case DRAG_START_TIMEOUT: {
+ IBinder win = (IBinder)msg.obj;
+ if (DEBUG_DRAG) {
+ Slog.w(TAG, "Timeout starting drag by win " + win);
+ }
+ synchronized (mWindowMap) {
+ // !!! TODO: ANR the app that has failed to start the drag in time
+ if (mDragState != null) {
+ mDragState.unregister();
+ mInputMonitor.updateInputWindowsLw(true /*force*/);
+ mDragState.reset();
+ mDragState = null;
+ }
+ }
+ break;
+ }
+
+ case DRAG_END_TIMEOUT: {
+ IBinder win = (IBinder)msg.obj;
+ if (DEBUG_DRAG) {
+ Slog.w(TAG, "Timeout ending drag to win " + win);
+ }
+ synchronized (mWindowMap) {
+ // !!! TODO: ANR the drag-receiving app
+ mDragState.mDragResult = false;
+ mDragState.endDragLw();
+ }
+ break;
+ }
+
+ case REPORT_HARD_KEYBOARD_STATUS_CHANGE: {
+ notifyHardKeyboardStatusChange();
+ break;
+ }
}
}
}
@@ -8147,11 +9357,47 @@ public class WindowManagerService extends IWindowManager.Stub
WindowState imFocus;
if (idx > 0) {
imFocus = mWindows.get(idx-1);
+ //Log.i(TAG, "Desired input method target: " + imFocus);
+ //Log.i(TAG, "Current focus: " + this.mCurrentFocus);
+ //Log.i(TAG, "Last focus: " + this.mLastFocus);
if (imFocus != null) {
+ // This may be a starting window, in which case we still want
+ // to count it as okay.
+ if (imFocus.mAttrs.type == LayoutParams.TYPE_APPLICATION_STARTING
+ && imFocus.mAppToken != null) {
+ // The client has definitely started, so it really should
+ // have a window in this app token. Let's look for it.
+ for (int i=0; i<imFocus.mAppToken.windows.size(); i++) {
+ WindowState w = imFocus.mAppToken.windows.get(i);
+ if (w != imFocus) {
+ //Log.i(TAG, "Switching to real app window: " + w);
+ imFocus = w;
+ break;
+ }
+ }
+ }
+ //Log.i(TAG, "IM target client: " + imFocus.mSession.mClient);
+ //if (imFocus.mSession.mClient != null) {
+ // Log.i(TAG, "IM target client binder: " + imFocus.mSession.mClient.asBinder());
+ // Log.i(TAG, "Requesting client binder: " + client.asBinder());
+ //}
if (imFocus.mSession.mClient != null &&
imFocus.mSession.mClient.asBinder() == client.asBinder()) {
return true;
}
+
+ // Okay, how about this... what is the current focus?
+ // It seems in some cases we may not have moved the IM
+ // target window, such as when it was in a pop-up window,
+ // so let's also look at the current focus. (An example:
+ // go to Gmail, start searching so the keyboard goes up,
+ // press home. Sometimes the IME won't go down.)
+ // Would be nice to fix this more correctly, but it's
+ // way at the end of a release, and this should be good enough.
+ if (mCurrentFocus != null && mCurrentFocus.mSession.mClient != null &&
+ mCurrentFocus.mSession.mClient.asBinder() == client.asBinder()) {
+ return true;
+ }
}
}
}
@@ -8201,12 +9447,18 @@ public class WindowManagerService extends IWindowManager.Stub
int lastWallpaper = -1;
int numRemoved = 0;
+ if (mRebuildTmp.length < NW) {
+ mRebuildTmp = new WindowState[NW+10];
+ }
+
// First remove all existing app windows.
i=0;
while (i < NW) {
WindowState w = mWindows.get(i);
if (w.mAppToken != null) {
WindowState win = mWindows.remove(i);
+ win.mRebuilding = true;
+ mRebuildTmp[numRemoved] = win;
mWindowsChanged = true;
if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG,
"Rebuild removing window: " + win);
@@ -8244,6 +9496,21 @@ public class WindowManagerService extends IWindowManager.Stub
if (i != numRemoved) {
Slog.w(TAG, "Rebuild removed " + numRemoved
+ " windows but added " + i);
+ for (i=0; i<numRemoved; i++) {
+ WindowState ws = mRebuildTmp[i];
+ if (ws.mRebuilding) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ ws.dump(pw, "");
+ pw.flush();
+ Slog.w(TAG, "This window was lost: " + ws);
+ Slog.w(TAG, sw.toString());
+ }
+ }
+ Slog.w(TAG, "Current app token list:");
+ dumpAppTokensLocked();
+ Slog.w(TAG, "Final window list:");
+ dumpWindowsLocked();
}
}
@@ -8253,6 +9520,12 @@ public class WindowManagerService extends IWindowManager.Stub
int curLayer = 0;
int i;
+ if (DEBUG_LAYERS) {
+ RuntimeException here = new RuntimeException("here");
+ here.fillInStackTrace();
+ Log.v(TAG, "Assigning layers", here);
+ }
+
for (i=0; i<N; i++) {
WindowState w = mWindows.get(i);
if (w.mBaseLayer == curBaseLayer || w.mIsImWindow
@@ -8304,38 +9577,46 @@ public class WindowManagerService extends IWindowManager.Stub
return;
}
+ mInLayout = true;
boolean recoveringMemory = false;
- if (mForceRemoves != null) {
- recoveringMemory = true;
- // Wait a little it for things to settle down, and off we go.
- for (int i=0; i<mForceRemoves.size(); i++) {
- WindowState ws = mForceRemoves.get(i);
- Slog.i(TAG, "Force removing: " + ws);
- removeWindowInnerLocked(ws.mSession, ws);
- }
- mForceRemoves = null;
- Slog.w(TAG, "Due to memory failure, waiting a bit for next layout");
- Object tmp = new Object();
- synchronized (tmp) {
- try {
- tmp.wait(250);
- } catch (InterruptedException e) {
+
+ try {
+ if (mForceRemoves != null) {
+ recoveringMemory = true;
+ // Wait a little bit for things to settle down, and off we go.
+ for (int i=0; i<mForceRemoves.size(); i++) {
+ WindowState ws = mForceRemoves.get(i);
+ Slog.i(TAG, "Force removing: " + ws);
+ removeWindowInnerLocked(ws.mSession, ws);
+ }
+ mForceRemoves = null;
+ Slog.w(TAG, "Due to memory failure, waiting a bit for next layout");
+ Object tmp = new Object();
+ synchronized (tmp) {
+ try {
+ tmp.wait(250);
+ } catch (InterruptedException e) {
+ }
}
}
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "Unhandled exception while force removing for memory", e);
}
-
- mInLayout = true;
+
try {
performLayoutAndPlaceSurfacesLockedInner(recoveringMemory);
- int i = mPendingRemove.size()-1;
- if (i >= 0) {
- while (i >= 0) {
- WindowState w = mPendingRemove.get(i);
- removeWindowInnerLocked(w.mSession, w);
- i--;
+ int N = mPendingRemove.size();
+ if (N > 0) {
+ if (mPendingRemoveTmp.length < N) {
+ mPendingRemoveTmp = new WindowState[N+10];
}
+ mPendingRemove.toArray(mPendingRemoveTmp);
mPendingRemove.clear();
+ for (int i=0; i<N; i++) {
+ WindowState w = mPendingRemoveTmp[i];
+ removeWindowInnerLocked(w.mSession, w);
+ }
mInLayout = false;
assignLayersLocked();
@@ -8358,7 +9639,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- private final int performLayoutLockedInner() {
+ private final int performLayoutLockedInner(boolean initial, boolean updateInputWindows) {
if (!mLayoutNeeded) {
return 0;
}
@@ -8392,16 +9673,16 @@ public class WindowManagerService extends IWindowManager.Stub
final AppWindowToken atoken = win.mAppToken;
final boolean gone = win.mViewVisibility == View.GONE
|| !win.mRelayoutCalled
- || win.mRootToken.hidden
+ || (atoken == null && win.mRootToken.hidden)
|| (atoken != null && atoken.hiddenRequested)
|| win.mAttachedHidden
|| win.mExiting || win.mDestroying;
- if (!win.mLayoutAttached) {
- if (DEBUG_LAYOUT) Slog.v(TAG, "First pass " + win
+ if (DEBUG_LAYOUT && !win.mLayoutAttached) {
+ Slog.v(TAG, "First pass " + win
+ ": gone=" + gone + " mHaveFrame=" + win.mHaveFrame
+ " mLayoutAttached=" + win.mLayoutAttached);
- if (DEBUG_LAYOUT && gone) Slog.v(TAG, " (mViewVisibility="
+ if (gone) Slog.v(TAG, " (mViewVisibility="
+ win.mViewVisibility + " mRelayoutCalled="
+ win.mRelayoutCalled + " hidden="
+ win.mRootToken.hidden + " hiddenRequested="
@@ -8416,6 +9697,10 @@ public class WindowManagerService extends IWindowManager.Stub
// just don't display").
if (!gone || !win.mHaveFrame) {
if (!win.mLayoutAttached) {
+ if (initial) {
+ //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
+ win.mContentChanged = false;
+ }
mPolicy.layoutWindowLw(win, win.mAttrs, null);
win.mLayoutSeq = seq;
if (DEBUG_LAYOUT) Slog.v(TAG, "-> mFrame="
@@ -8435,18 +9720,22 @@ public class WindowManagerService extends IWindowManager.Stub
for (i = topAttached; i >= 0; i--) {
WindowState win = mWindows.get(i);
- // If this view is GONE, then skip it -- keep the current
- // frame, and let the caller know so they can ignore it
- // if they want. (We do the normal layout for INVISIBLE
- // windows, since that means "perform layout as normal,
- // just don't display").
if (win.mLayoutAttached) {
if (DEBUG_LAYOUT) Slog.v(TAG, "Second pass " + win
+ " mHaveFrame=" + win.mHaveFrame
+ " mViewVisibility=" + win.mViewVisibility
+ " mRelayoutCalled=" + win.mRelayoutCalled);
+ // If this view is GONE, then skip it -- keep the current
+ // frame, and let the caller know so they can ignore it
+ // if they want. (We do the normal layout for INVISIBLE
+ // windows, since that means "perform layout as normal,
+ // just don't display").
if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled)
|| !win.mHaveFrame) {
+ if (initial) {
+ //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
+ win.mContentChanged = false;
+ }
mPolicy.layoutWindowLw(win, win.mAttrs, win.mAttachedWindow);
win.mLayoutSeq = seq;
if (DEBUG_LAYOUT) Slog.v(TAG, "-> mFrame="
@@ -8458,13 +9747,22 @@ public class WindowManagerService extends IWindowManager.Stub
}
// Window frames may have changed. Tell the input dispatcher about it.
- mInputMonitor.updateInputWindowsLw();
+ mInputMonitor.setUpdateInputWindowsNeededLw();
+ if (updateInputWindows) {
+ mInputMonitor.updateInputWindowsLw(false /*force*/);
+ }
return mPolicy.finishLayoutLw();
}
+ // "Something has changed! Let's make it correct now."
private final void performLayoutAndPlaceSurfacesLockedInner(
boolean recoveringMemory) {
+ if (mDisplay == null) {
+ Slog.i(TAG, "skipping performLayoutAndPlaceSurfacesLockedInner with no mDisplay");
+ return;
+ }
+
final long currentTime = SystemClock.uptimeMillis();
final int dw = mDisplay.getWidth();
final int dh = mDisplay.getHeight();
@@ -8473,7 +9771,8 @@ public class WindowManagerService extends IWindowManager.Stub
if (mFocusMayChange) {
mFocusMayChange = false;
- updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES);
+ updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+ false /*updateInputWindows*/);
}
// Initialize state of exiting tokens.
@@ -8493,13 +9792,15 @@ public class WindowManagerService extends IWindowManager.Stub
boolean focusDisplayed = false;
boolean animating = false;
boolean createWatermark = false;
+ boolean updateRotation = false;
+ boolean screenRotationFinished = false;
if (mFxSession == null) {
mFxSession = new SurfaceSession();
createWatermark = true;
}
- if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION");
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
Surface.openTransaction();
@@ -8509,6 +9810,9 @@ public class WindowManagerService extends IWindowManager.Stub
if (mWatermark != null) {
mWatermark.positionSurface(dw, dh);
}
+ if (mStrictModeFlash != null) {
+ mStrictModeFlash.positionSurface(dw, dh);
+ }
try {
boolean wallpaperForceHidingChanged = false;
@@ -8534,7 +9838,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
if ((changes&WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) {
if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout");
- if (updateOrientationFromAppTokensLocked()) {
+ if (updateOrientationFromAppTokensLocked(true)) {
mLayoutNeeded = true;
mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
}
@@ -8546,7 +9850,7 @@ public class WindowManagerService extends IWindowManager.Stub
// FIRST LOOP: Perform a layout, if needed.
if (repeats < 4) {
- changes = performLayoutLockedInner();
+ changes = performLayoutLockedInner(repeats == 0, false /*updateInputWindows*/);
if (changes != 0) {
continue;
}
@@ -8581,9 +9885,21 @@ public class WindowManagerService extends IWindowManager.Stub
animating = tokensAnimating;
+ if (mScreenRotationAnimation != null) {
+ if (mScreenRotationAnimation.isAnimating()) {
+ if (mScreenRotationAnimation.stepAnimation(currentTime)) {
+ animating = true;
+ } else {
+ screenRotationFinished = true;
+ updateRotation = true;
+ }
+ }
+ }
+
boolean tokenMayBeDrawn = false;
boolean wallpaperMayChange = false;
boolean forceHiding = false;
+ WindowState windowDetachedWallpaper = null;
mPolicy.beginAnimationLw(dw, dh);
@@ -8595,7 +9911,7 @@ public class WindowManagerService extends IWindowManager.Stub
final WindowManager.LayoutParams attrs = w.mAttrs;
if (w.mSurface != null) {
- // Execute animation.
+ // Take care of the window being ready to display.
if (w.commitFinishDrawingLocked(currentTime)) {
if ((w.mAttrs.flags
& WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) {
@@ -8605,19 +9921,56 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- boolean wasAnimating = w.mAnimating;
- if (w.stepAnimationLocked(currentTime, dw, dh)) {
+ final boolean wasAnimating = w.mAnimating;
+
+ int animDw = dw;
+ int animDh = dh;
+
+ // If the window has moved due to its containing
+ // content frame changing, then we'd like to animate
+ // it. The checks here are ordered by what is least
+ // likely to be true first.
+ if (w.shouldAnimateMove()) {
+ // Frame has moved, containing content frame
+ // has also moved, and we're not currently animating...
+ // let's do something.
+ Animation a = AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.window_move_from_decor);
+ w.setAnimation(a);
+ animDw = w.mLastFrame.left - w.mFrame.left;
+ animDh = w.mLastFrame.top - w.mFrame.top;
+ }
+
+ // Execute animation.
+ final boolean nowAnimating = w.stepAnimationLocked(currentTime,
+ animDw, animDh);
+
+ // If this window is animating, make a note that we have
+ // an animating window and take care of a request to run
+ // a detached wallpaper animation.
+ if (nowAnimating) {
+ if (w.mAnimation != null && w.mAnimation.getDetachWallpaper()) {
+ windowDetachedWallpaper = w;
+ }
animating = true;
- //w.dump(" ");
}
+
+ // If this window's app token is running a detached wallpaper
+ // animation, make a note so we can ensure the wallpaper is
+ // displayed behind it.
+ if (w.mAppToken != null && w.mAppToken.animation != null
+ && w.mAppToken.animation.getDetachWallpaper()) {
+ windowDetachedWallpaper = w;
+ }
+
if (wasAnimating && !w.mAnimating && mWallpaperTarget == w) {
wallpaperMayChange = true;
}
if (mPolicy.doesForceHide(w, attrs)) {
- if (!wasAnimating && animating) {
+ if (!wasAnimating && nowAnimating) {
if (DEBUG_VISIBILITY) Slog.v(TAG,
- "Animation done that could impact force hide: "
+ "Animation started that could impact force hide: "
+ w);
wallpaperForceHidingChanged = true;
mFocusMayChange = true;
@@ -8714,12 +10067,9 @@ public class WindowManagerService extends IWindowManager.Stub
if (tokenMayBeDrawn) {
// See if any windows have been drawn, so they (and others
// associated with them) can now be shown.
- final int NT = mTokenList.size();
+ final int NT = mAppTokens.size();
for (i=0; i<NT; i++) {
- AppWindowToken wtoken = mTokenList.get(i).appWindowToken;
- if (wtoken == null) {
- continue;
- }
+ AppWindowToken wtoken = mAppTokens.get(i);
if (wtoken.freezingScreen) {
int numInteresting = wtoken.numInterestingWindows;
if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) {
@@ -8816,8 +10166,8 @@ public class WindowManagerService extends IWindowManager.Stub
// The top-most window will supply the layout params,
// and we will determine it below.
LayoutParams animLp = null;
- AppWindowToken animToken = null;
int bestAnimLayer = -1;
+ boolean fullscreenAnim = false;
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"New wallpaper target=" + mWallpaperTarget
@@ -8859,11 +10209,18 @@ public class WindowManagerService extends IWindowManager.Stub
// window, we will always use its anim.
if ((ws.mAttrs.flags&FLAG_COMPATIBLE_WINDOW) != 0) {
animLp = ws.mAttrs;
- animToken = ws.mAppToken;
bestAnimLayer = Integer.MAX_VALUE;
- } else if (ws.mLayer > bestAnimLayer) {
+ } else if (!fullscreenAnim || ws.mLayer > bestAnimLayer) {
+ animLp = ws.mAttrs;
+ bestAnimLayer = ws.mLayer;
+ }
+ fullscreenAnim = true;
+ }
+ } else if (!fullscreenAnim) {
+ WindowState ws = wtoken.findMainWindow();
+ if (ws != null) {
+ if (ws.mLayer > bestAnimLayer) {
animLp = ws.mAttrs;
- animToken = ws.mAppToken;
bestAnimLayer = ws.mLayer;
}
}
@@ -8901,15 +10258,6 @@ public class WindowManagerService extends IWindowManager.Stub
"New transit into wallpaper: " + transit);
}
- if ((transit&WindowManagerPolicy.TRANSIT_ENTER_MASK) != 0) {
- mLastEnterAnimToken = animToken;
- mLastEnterAnimParams = animLp;
- } else if (mLastEnterAnimParams != null) {
- animLp = mLastEnterAnimParams;
- mLastEnterAnimToken = null;
- mLastEnterAnimParams = null;
- }
-
// If all closing windows are obscured, then there is
// no need to do an animation. This is the case, for
// example, when this transition is being done behind
@@ -8954,12 +10302,14 @@ public class WindowManagerService extends IWindowManager.Stub
// This has changed the visibility of windows, so perform
// a new layout to get them all up-to-date.
- changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT;
+ changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT
+ | WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
mLayoutNeeded = true;
if (!moveInputMethodWindowsIfNeededLocked(true)) {
assignLayersLocked();
}
- updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES);
+ updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,
+ false /*updateInputWindows*/);
mFocusMayChange = false;
}
}
@@ -9041,6 +10391,14 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ if (mWindowDetachedWallpaper != windowDetachedWallpaper) {
+ if (DEBUG_WALLPAPER) Slog.v(TAG,
+ "Detached wallpaper changed from " + mWindowDetachedWallpaper
+ + windowDetachedWallpaper);
+ mWindowDetachedWallpaper = windowDetachedWallpaper;
+ wallpaperMayChange = true;
+ }
+
if (wallpaperMayChange) {
if (DEBUG_WALLPAPER) Slog.v(TAG,
"Wallpaper may change! Adjusting");
@@ -9060,7 +10418,8 @@ public class WindowManagerService extends IWindowManager.Stub
if (mFocusMayChange) {
mFocusMayChange = false;
- if (updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES)) {
+ if (updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,
+ false /*updateInputWindows*/)) {
changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM;
adjResult = 0;
}
@@ -9072,8 +10431,6 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: changes=0x"
+ Integer.toHexString(changes));
-
- mInputMonitor.updateInputWindowsLw();
} while (changes != 0);
// THIRD LOOP: Update the surfaces of all windows.
@@ -9085,7 +10442,8 @@ public class WindowManagerService extends IWindowManager.Stub
boolean dimming = false;
boolean covered = false;
boolean syswin = false;
- boolean backgroundFillerShown = false;
+ boolean backgroundFillerWasShown = mBackgroundFillerTarget != null;
+ mBackgroundFillerTarget = null;
final int N = mWindows.size();
@@ -9117,11 +10475,8 @@ public class WindowManagerService extends IWindowManager.Stub
+ ": new=" + w.mShownFrame + ", old="
+ w.mLastShownFrame);
- boolean resize;
int width, height;
if ((w.mAttrs.flags & w.mAttrs.FLAG_SCALED) != 0) {
- resize = w.mLastRequestedWidth != w.mRequestedWidth ||
- w.mLastRequestedHeight != w.mRequestedHeight;
// for a scaled surface, we just want to use
// the requested size.
width = w.mRequestedWidth;
@@ -9129,58 +10484,61 @@ public class WindowManagerService extends IWindowManager.Stub
w.mLastRequestedWidth = width;
w.mLastRequestedHeight = height;
w.mLastShownFrame.set(w.mShownFrame);
- try {
- if (SHOW_TRANSACTIONS) logSurface(w,
- "POS " + w.mShownFrame.left
- + ", " + w.mShownFrame.top, null);
- w.mSurfaceX = w.mShownFrame.left;
- w.mSurfaceY = w.mShownFrame.top;
- w.mSurface.setPosition(w.mShownFrame.left, w.mShownFrame.top);
- } catch (RuntimeException e) {
- Slog.w(TAG, "Error positioning surface in " + w, e);
- if (!recoveringMemory) {
- reclaimSomeSurfaceMemoryLocked(w, "position");
- }
- }
} else {
- resize = !w.mLastShownFrame.equals(w.mShownFrame);
width = w.mShownFrame.width();
height = w.mShownFrame.height();
w.mLastShownFrame.set(w.mShownFrame);
}
- if (resize) {
- if (width < 1) width = 1;
- if (height < 1) height = 1;
- if (w.mSurface != null) {
+ if (w.mSurface != null) {
+ if (w.mSurfaceX != w.mShownFrame.left
+ || w.mSurfaceY != w.mShownFrame.top) {
try {
if (SHOW_TRANSACTIONS) logSurface(w,
- "POS " + w.mShownFrame.left + ","
- + w.mShownFrame.top + " SIZE "
- + w.mShownFrame.width() + "x"
+ "POS " + w.mShownFrame.left
+ + ", " + w.mShownFrame.top, null);
+ w.mSurfaceX = w.mShownFrame.left;
+ w.mSurfaceY = w.mShownFrame.top;
+ w.mSurface.setPosition(w.mShownFrame.left, w.mShownFrame.top);
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Error positioning surface of " + w
+ + " pos=(" + w.mShownFrame.left
+ + "," + w.mShownFrame.top + ")", e);
+ if (!recoveringMemory) {
+ reclaimSomeSurfaceMemoryLocked(w, "position");
+ }
+ }
+ }
+
+ if (width < 1) {
+ width = 1;
+ }
+ if (height < 1) {
+ height = 1;
+ }
+
+ if (w.mSurfaceW != width || w.mSurfaceH != height) {
+ try {
+ if (SHOW_TRANSACTIONS) logSurface(w,
+ "SIZE " + w.mShownFrame.width() + "x"
+ w.mShownFrame.height(), null);
w.mSurfaceResized = true;
w.mSurfaceW = width;
w.mSurfaceH = height;
w.mSurface.setSize(width, height);
- w.mSurfaceX = w.mShownFrame.left;
- w.mSurfaceY = w.mShownFrame.top;
- w.mSurface.setPosition(w.mShownFrame.left,
- w.mShownFrame.top);
} catch (RuntimeException e) {
// If something goes wrong with the surface (such
// as running out of memory), don't take down the
// entire system.
- Slog.e(TAG, "Failure updating surface of " + w
- + "size=(" + width + "x" + height
- + "), pos=(" + w.mShownFrame.left
- + "," + w.mShownFrame.top + ")", e);
+ Slog.e(TAG, "Error resizing surface of " + w
+ + " size=(" + width + "x" + height + ")", e);
if (!recoveringMemory) {
reclaimSomeSurfaceMemoryLocked(w, "size");
}
}
}
}
+
if (!w.mAppFreezing && w.mLayoutSeq == mLayoutSeq) {
w.mContentInsetsChanged =
!w.mLastContentInsets.equals(w.mContentInsets);
@@ -9197,11 +10555,21 @@ public class WindowManagerService extends IWindowManager.Stub
if (localLOGV) Slog.v(TAG, "Resizing " + w
+ ": configChanged=" + configChanged
+ " last=" + w.mLastFrame + " frame=" + w.mFrame);
- if (!w.mLastFrame.equals(w.mFrame)
+ boolean frameChanged = !w.mLastFrame.equals(w.mFrame);
+ if (frameChanged
|| w.mContentInsetsChanged
|| w.mVisibleInsetsChanged
|| w.mSurfaceResized
|| configChanged) {
+ if (DEBUG_RESIZE || DEBUG_ORIENTATION) {
+ Slog.v(TAG, "Resize reasons: "
+ + "frameChanged=" + frameChanged
+ + " contentInsetsChanged=" + w.mContentInsetsChanged
+ + " visibleInsetsChanged=" + w.mVisibleInsetsChanged
+ + " surfaceResized=" + w.mSurfaceResized
+ + " configChanged=" + configChanged);
+ }
+
w.mLastFrame.set(w.mFrame);
w.mLastContentInsets.set(w.mContentInsets);
w.mLastVisibleInsets.set(w.mVisibleInsets);
@@ -9253,12 +10621,6 @@ public class WindowManagerService extends IWindowManager.Stub
if (w.mAttachedHidden || !w.isReadyForDisplay()) {
if (!w.mLastHidden) {
//dump();
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Window hiding: waitingToShow="
- + w.mRootToken.waitingToShow + " polvis="
- + w.mPolicyVisibility + " atthid="
- + w.mAttachedHidden + " tokhid="
- + w.mRootToken.hidden + " vis="
- + w.mViewVisibility);
w.mLastHidden = true;
if (SHOW_TRANSACTIONS) logSurface(w,
"HIDE (performLayout)", null);
@@ -9370,6 +10732,11 @@ public class WindowManagerService extends IWindowManager.Stub
w.mOrientationChanging = false;
}
+ if (w.mContentChanged) {
+ //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing");
+ w.mContentChanged = false;
+ }
+
final boolean canBeSeen = w.isDisplayedLw();
if (someoneLosingFocus && w == mCurrentFocus && canBeSeen) {
@@ -9378,6 +10745,16 @@ public class WindowManagerService extends IWindowManager.Stub
final boolean obscuredChanged = w.mObscured != obscured;
+ if (mBackgroundFillerTarget != null) {
+ if (w.isAnimating()) {
+ // Background filler is below all other windows that
+ // are animating.
+ mBackgroundFillerTarget = w;
+ } else if (w.mIsWallpaper) {
+ mBackgroundFillerTarget = w;
+ }
+ }
+
// Update effect.
if (!(w.mObscured=obscured)) {
if (w.mSurface != null) {
@@ -9406,33 +10783,10 @@ public class WindowManagerService extends IWindowManager.Stub
// so we want to leave all of them as unblurred (for
// performance reasons).
obscured = true;
- } else if (opaqueDrawn && w.needsBackgroundFiller(dw, dh)) {
- if (SHOW_TRANSACTIONS) Slog.d(TAG, "showing background filler");
+ } else if (w.needsBackgroundFiller(dw, dh) && (canBeSeen || w.isAnimating())) {
// This window is in compatibility mode, and needs background filler.
obscured = true;
- if (mBackgroundFillerSurface == null) {
- try {
- mBackgroundFillerSurface = new Surface(mFxSession, 0,
- "BackGroundFiller",
- 0, dw, dh,
- PixelFormat.OPAQUE,
- Surface.FX_SURFACE_NORMAL);
- } catch (Exception e) {
- Slog.e(TAG, "Exception creating filler surface", e);
- }
- }
- try {
- mBackgroundFillerSurface.setPosition(0, 0);
- mBackgroundFillerSurface.setSize(dw, dh);
- // Using the same layer as Dim because they will never be shown at the
- // same time.
- mBackgroundFillerSurface.setLayer(w.mAnimLayer - 1);
- mBackgroundFillerSurface.show();
- } catch (RuntimeException e) {
- Slog.e(TAG, "Exception showing filler surface");
- }
- backgroundFillerShown = true;
- mBackgroundFillerShown = true;
+ mBackgroundFillerTarget = w;
} else if (canBeSeen && !obscured &&
(attrFlags&FLAG_BLUR_BEHIND|FLAG_DIM_BEHIND) != 0) {
if (localLOGV) Slog.v(TAG, "Win " + w
@@ -9447,7 +10801,8 @@ public class WindowManagerService extends IWindowManager.Stub
mDimAnimator = new DimAnimator(mFxSession);
}
mDimAnimator.show(dw, dh);
- mDimAnimator.updateParameters(w, currentTime);
+ mDimAnimator.updateParameters(mContext.getResources(),
+ w, currentTime);
}
}
if ((attrFlags&FLAG_BLUR_BEHIND) != 0) {
@@ -9455,8 +10810,6 @@ public class WindowManagerService extends IWindowManager.Stub
//Slog.i(TAG, "BLUR BEHIND: " + w);
blurring = true;
if (mBlurSurface == null) {
- if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR "
- + mBlurSurface + ": CREATE");
try {
mBlurSurface = new Surface(mFxSession, 0,
"BlurSurface",
@@ -9466,6 +10819,8 @@ public class WindowManagerService extends IWindowManager.Stub
} catch (Exception e) {
Slog.e(TAG, "Exception creating Blur surface", e);
}
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR "
+ + mBlurSurface + ": CREATE");
}
if (mBlurSurface != null) {
if (SHOW_TRANSACTIONS) Slog.i(TAG, " BLUR "
@@ -9498,9 +10853,39 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- if (backgroundFillerShown == false && mBackgroundFillerShown) {
- mBackgroundFillerShown = false;
- if (SHOW_TRANSACTIONS) Slog.d(TAG, "hiding background filler");
+ if (mBackgroundFillerTarget != null) {
+ if (mBackgroundFillerSurface == null) {
+ try {
+ mBackgroundFillerSurface = new Surface(mFxSession, 0,
+ "BackGroundFiller",
+ 0, dw, dh,
+ PixelFormat.OPAQUE,
+ Surface.FX_SURFACE_NORMAL);
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception creating filler surface", e);
+ }
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, " BG FILLER "
+ + mBackgroundFillerSurface + ": CREATE");
+ }
+ try {
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, " BG FILLER "
+ + mBackgroundFillerSurface + " SHOW: pos=(0,0) ("
+ + dw + "x" + dh + ") layer="
+ + (mBackgroundFillerTarget.mLayer - 1));
+ mBackgroundFillerSurface.setPosition(0, 0);
+ mBackgroundFillerSurface.setSize(dw, dh);
+ // Using the same layer as Dim because they will never be shown at the
+ // same time. NOTE: we do NOT use mAnimLayer, because we don't
+ // want this surface dragged up in front of stuff that is animating.
+ mBackgroundFillerSurface.setLayer(mBackgroundFillerTarget.mLayer - 1);
+ mBackgroundFillerSurface.show();
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "Exception showing filler surface");
+ }
+ } else if (backgroundFillerWasShown) {
+ mBackgroundFillerTarget = null;
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, " BG FILLER "
+ + mBackgroundFillerSurface + " HIDE");
try {
mBackgroundFillerSurface.hide();
} catch (RuntimeException e) {
@@ -9523,16 +10908,14 @@ public class WindowManagerService extends IWindowManager.Stub
}
mBlurShown = false;
}
-
- if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION");
} catch (RuntimeException e) {
Slog.e(TAG, "Unhandled exception in Window Manager", e);
}
- mInputMonitor.updateInputWindowsLw();
-
Surface.closeTransaction();
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
+
if (mWatermark != null) {
mWatermark.drawIfNeeded();
}
@@ -9622,12 +11005,10 @@ public class WindowManagerService extends IWindowManager.Stub
// soon as their animations are complete
token.animation = null;
token.animating = false;
+ if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
+ "performLayout: App token exiting now removed" + token);
mAppTokens.remove(token);
mExitingAppTokens.remove(i);
- if (mLastEnterAnimToken == token) {
- mLastEnterAnimToken = null;
- mLastEnterAnimParams = null;
- }
}
}
@@ -9659,13 +11040,12 @@ public class WindowManagerService extends IWindowManager.Stub
} else if (animating) {
requestAnimationLocked(currentTime+(1000/60)-SystemClock.uptimeMillis());
}
-
- mInputMonitor.updateInputWindowsLw();
-
- if (DEBUG_FREEZE) Slog.v(TAG, "Layout: mDisplayFrozen=" + mDisplayFrozen
- + " holdScreen=" + holdScreen);
+
+ // Finally update all input windows now that the window changes have stabilized.
+ mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+ setHoldScreenLocked(holdScreen != null);
if (!mDisplayFrozen) {
- setHoldScreenLocked(holdScreen != null);
if (screenBrightness < 0 || screenBrightness > 1.0f) {
mPowerManager.setScreenBrightnessOverride(-1);
} else {
@@ -9678,11 +11058,11 @@ public class WindowManagerService extends IWindowManager.Stub
mPowerManager.setButtonBrightnessOverride((int)
(buttonBrightness * Power.BRIGHTNESS_ON));
}
- if (holdScreen != mHoldingScreenOn) {
- mHoldingScreenOn = holdScreen;
- Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, holdScreen);
- mH.sendMessage(m);
- }
+ }
+ if (holdScreen != mHoldingScreenOn) {
+ mHoldingScreenOn = holdScreen;
+ Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, holdScreen);
+ mH.sendMessage(m);
}
if (mTurnOnScreen) {
@@ -9692,6 +11072,20 @@ public class WindowManagerService extends IWindowManager.Stub
mTurnOnScreen = false;
}
+ if (screenRotationFinished && mScreenRotationAnimation != null) {
+ mScreenRotationAnimation.kill();
+ mScreenRotationAnimation = null;
+ }
+
+ if (updateRotation) {
+ if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
+ boolean changed = setRotationUncheckedLocked(
+ WindowManagerPolicy.USE_LAST_ROTATION, 0, false);
+ if (changed) {
+ mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+ }
+ }
+
// Check to see if we are now in a state where the screen should
// be enabled, because the window obscured flags have changed.
enableScreenIfNeededLocked();
@@ -9741,7 +11135,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
return true;
} catch (RuntimeException e) {
- Slog.w(TAG, "Failure showing surface " + win.mSurface + " in " + win);
+ Slog.w(TAG, "Failure showing surface " + win.mSurface + " in " + win, e);
}
reclaimSomeSurfaceMemoryLocked(win, "show");
@@ -9776,6 +11170,7 @@ public class WindowManagerService extends IWindowManager.Stub
+ " token=" + win.mToken
+ " pid=" + ws.mSession.mPid
+ " uid=" + ws.mSession.mUid);
+ if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", null);
ws.mSurface.destroy();
ws.mSurfaceShown = false;
ws.mSurface = null;
@@ -9783,10 +11178,11 @@ public class WindowManagerService extends IWindowManager.Stub
i--;
N--;
leakedSurface = true;
- } else if (win.mAppToken != null && win.mAppToken.clientHidden) {
+ } else if (ws.mAppToken != null && ws.mAppToken.clientHidden) {
Slog.w(TAG, "LEAKED SURFACE (app token hidden): "
+ ws + " surface=" + ws.mSurface
+ " token=" + win.mAppToken);
+ if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", null);
ws.mSurface.destroy();
ws.mSurfaceShown = false;
ws.mSurface = null;
@@ -9824,6 +11220,7 @@ public class WindowManagerService extends IWindowManager.Stub
// surface and ask the app to request another one.
Slog.w(TAG, "Looks like we have reclaimed some memory, clearing surface for retry.");
if (surface != null) {
+ if (SHOW_TRANSACTIONS) logSurface(win, "RECOVER DESTROY", null);
surface.destroy();
win.mSurfaceShown = false;
win.mSurface = null;
@@ -9839,7 +11236,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- private boolean updateFocusedWindowLocked(int mode) {
+ private boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
WindowState newFocus = computeFocusedWindowLocked();
if (mCurrentFocus != newFocus) {
// This check makes sure that we don't already have the focus
@@ -9860,7 +11257,7 @@ public class WindowManagerService extends IWindowManager.Stub
mLayoutNeeded = true;
}
if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
- performLayoutLockedInner();
+ performLayoutLockedInner(true /*initial*/, updateInputWindows);
} else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) {
// Client will do the layout, but we need to assign layers
// for handleNewWindowLocked() below.
@@ -9871,15 +11268,15 @@ public class WindowManagerService extends IWindowManager.Stub
if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
// If we defer assigning layers, then the caller is responsible for
// doing this part.
- finishUpdateFocusedWindowAfterAssignLayersLocked();
+ finishUpdateFocusedWindowAfterAssignLayersLocked(updateInputWindows);
}
return true;
}
return false;
}
- private void finishUpdateFocusedWindowAfterAssignLayersLocked() {
- mInputMonitor.setInputFocusLw(mCurrentFocus);
+ private void finishUpdateFocusedWindowAfterAssignLayersLocked(boolean updateInputWindows) {
+ mInputMonitor.setInputFocusLw(mCurrentFocus, updateInputWindows);
}
private WindowState computeFocusedWindowLocked() {
@@ -9951,7 +11348,7 @@ public class WindowManagerService extends IWindowManager.Stub
return result;
}
- private void startFreezingDisplayLocked() {
+ private void startFreezingDisplayLocked(boolean inTransaction) {
if (mDisplayFrozen) {
return;
}
@@ -9971,8 +11368,6 @@ public class WindowManagerService extends IWindowManager.Stub
mFreezeGcPending = now;
}
- if (DEBUG_FREEZE) Slog.v(TAG, "*** FREEZING DISPLAY", new RuntimeException());
-
mDisplayFrozen = true;
mInputMonitor.freezeInputDispatchingLw();
@@ -9987,7 +11382,19 @@ public class WindowManagerService extends IWindowManager.Stub
File file = new File("/data/system/frozen");
Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
}
- Surface.freezeDisplay(0);
+
+ if (CUSTOM_SCREEN_ROTATION) {
+ if (mScreenRotationAnimation != null && mScreenRotationAnimation.isAnimating()) {
+ mScreenRotationAnimation.kill();
+ mScreenRotationAnimation = null;
+ }
+ if (mScreenRotationAnimation == null) {
+ mScreenRotationAnimation = new ScreenRotationAnimation(mContext,
+ mDisplay, mFxSession, inTransaction);
+ }
+ } else {
+ Surface.freezeDisplay(0);
+ }
}
private void stopFreezingDisplayLocked() {
@@ -9999,24 +11406,37 @@ public class WindowManagerService extends IWindowManager.Stub
return;
}
- if (DEBUG_FREEZE) Slog.v(TAG, "*** UNFREEZING DISPLAY", new RuntimeException());
-
mDisplayFrozen = false;
mH.removeMessages(H.APP_FREEZE_TIMEOUT);
if (PROFILE_ORIENTATION) {
Debug.stopMethodTracing();
}
- Surface.unfreezeDisplay(0);
+
+ boolean updateRotation = false;
+
+ if (CUSTOM_SCREEN_ROTATION) {
+ if (mScreenRotationAnimation != null) {
+ if (mScreenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION,
+ mTransitionAnimationScale)) {
+ requestAnimationLocked(0);
+ } else {
+ mScreenRotationAnimation = null;
+ updateRotation = true;
+ }
+ }
+ } else {
+ Surface.unfreezeDisplay(0);
+ }
mInputMonitor.thawInputDispatchingLw();
+ boolean configChanged;
+
// While the display is frozen we don't re-compute the orientation
// to avoid inconsistent states. However, something interesting
// could have actually changed during that time so re-evaluate it
// now to catch that.
- if (updateOrientationFromAppTokensLocked()) {
- mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
- }
+ configChanged = updateOrientationFromAppTokensLocked(false);
// A little kludge: a lot could have happened while the
// display was frozen, so now that we are coming back we
@@ -10028,6 +11448,16 @@ public class WindowManagerService extends IWindowManager.Stub
2000);
mScreenFrozenLock.release();
+
+ if (updateRotation) {
+ if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
+ configChanged |= setRotationUncheckedLocked(
+ WindowManagerPolicy.USE_LAST_ROTATION, 0, false);
+ }
+
+ if (configChanged) {
+ mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+ }
}
static int getPropertyInt(String[] tokens, int index, int defUnits, int defDps,
@@ -10049,7 +11479,7 @@ public class WindowManagerService extends IWindowManager.Stub
return val;
}
- class Watermark {
+ static class Watermark {
final String[] mTokens;
final String mText;
final Paint mTextPaint;
@@ -10065,9 +11495,9 @@ public class WindowManagerService extends IWindowManager.Stub
int mLastDH;
boolean mDrawNeeded;
- Watermark(SurfaceSession session, String[] tokens) {
+ Watermark(Display display, SurfaceSession session, String[] tokens) {
final DisplayMetrics dm = new DisplayMetrics();
- mDisplay.getMetrics(dm);
+ display.getMetrics(dm);
if (false) {
Log.i(TAG, "*********************** WATERMARK");
@@ -10161,6 +11591,8 @@ public class WindowManagerService extends IWindowManager.Stub
} catch (OutOfResourcesException e) {
}
if (c != null) {
+ c.drawColor(0, PorterDuff.Mode.CLEAR);
+
int deltaX = mDeltaX;
int deltaY = mDeltaY;
@@ -10203,7 +11635,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (line != null) {
String[] toks = line.split("%");
if (toks != null && toks.length > 0) {
- mWatermark = new Watermark(mFxSession, toks);
+ mWatermark = new Watermark(mDisplay, mFxSession, toks);
}
}
} catch (FileNotFoundException e) {
@@ -10219,6 +11651,23 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
+ public void statusBarVisibilityChanged(int visibility) {
+ synchronized (mWindowMap) {
+ final int N = mWindows.size();
+ for (int i = 0; i < N; i++) {
+ WindowState ws = mWindows.get(i);
+ try {
+ if (ws.getAttrs().hasSystemUiListeners) {
+ ws.mClient.dispatchSystemUiVisibilityChanged(visibility);
+ }
+ } catch (RemoteException e) {
+ // so sorry
+ }
+ }
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission("android.permission.DUMP")
!= PackageManager.PERMISSION_GRANTED) {
@@ -10317,14 +11766,6 @@ public class WindowManagerService extends IWindowManager.Stub
token.dump(pw, " ");
}
}
- if (mTokenList.size() > 0) {
- pw.println(" ");
- pw.println(" Window token list:");
- for (int i=0; i<mTokenList.size(); i++) {
- pw.print(" #"); pw.print(i); pw.print(": ");
- pw.println(mTokenList.get(i));
- }
- }
if (mWallpaperTokens.size() > 0) {
pw.println(" ");
pw.println(" Wallpaper tokens:");
@@ -10384,6 +11825,9 @@ public class WindowManagerService extends IWindowManager.Stub
pw.print(" mLowerWallpaperTarget="); pw.println(mLowerWallpaperTarget);
pw.print(" mUpperWallpaperTarget="); pw.println(mUpperWallpaperTarget);
}
+ if (mWindowDetachedWallpaper != null) {
+ pw.print(" mWindowDetachedWallpaper="); pw.println(mWindowDetachedWallpaper);
+ }
pw.print(" mCurConfiguration="); pw.println(this.mCurConfiguration);
pw.print(" mInTouchMode="); pw.print(mInTouchMode);
pw.print(" mLayoutSeq="); pw.println(mLayoutSeq);
@@ -10409,6 +11853,8 @@ public class WindowManagerService extends IWindowManager.Stub
pw.print(" mRotation="); pw.print(mRotation);
pw.print(", mForcedAppOrientation="); pw.print(mForcedAppOrientation);
pw.print(", mRequestedRotation="); pw.println(mRequestedRotation);
+ pw.print(" mDeferredRotation="); pw.print(mDeferredRotation);
+ pw.print(", mDeferredRotationAnimFlags="); pw.print(mDeferredRotationAnimFlags);
pw.print(" mAnimationPending="); pw.print(mAnimationPending);
pw.print(" mWindowAnimationScale="); pw.print(mWindowAnimationScale);
pw.print(" mTransitionWindowAnimationScale="); pw.println(mTransitionAnimationScale);
@@ -10427,10 +11873,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
pw.print(" mStartingIconInTransition="); pw.print(mStartingIconInTransition);
pw.print(", mSkipAppTransitionAnimation="); pw.println(mSkipAppTransitionAnimation);
- if (mLastEnterAnimToken != null || mLastEnterAnimToken != null) {
- pw.print(" mLastEnterAnimToken="); pw.print(mLastEnterAnimToken);
- pw.print(", mLastEnterAnimParams="); pw.println(mLastEnterAnimParams);
- }
if (mOpeningApps.size() > 0) {
pw.print(" mOpeningApps="); pw.println(mOpeningApps);
}
@@ -10443,8 +11885,14 @@ public class WindowManagerService extends IWindowManager.Stub
if (mToBottomApps.size() > 0) {
pw.print(" mToBottomApps="); pw.println(mToBottomApps);
}
- pw.print(" DisplayWidth="); pw.print(mDisplay.getWidth());
- pw.print(" DisplayHeight="); pw.println(mDisplay.getHeight());
+ if (mDisplay != null) {
+ pw.print(" DisplayWidth="); pw.print(mDisplay.getWidth());
+ pw.print(" DisplayHeight="); pw.println(mDisplay.getHeight());
+ } else {
+ pw.println(" NO DISPLAY");
+ }
+ pw.println(" Policy:");
+ mPolicy.dump(" ", fd, pw, args);
}
}
@@ -10512,7 +11960,7 @@ public class WindowManagerService extends IWindowManager.Stub
* Set's the dim surface's layer and update dim parameters that will be used in
* {@link updateSurface} after all windows are examined.
*/
- void updateParameters(WindowState w, long currentTime) {
+ void updateParameters(Resources res, WindowState w, long currentTime) {
mDimSurface.setLayer(w.mAnimLayer-1);
final float target = w.mExiting ? 0 : w.mAttrs.dimAmount;
@@ -10526,11 +11974,15 @@ public class WindowManagerService extends IWindowManager.Stub
? w.mAnimation.computeDurationHint()
: DEFAULT_DIM_DURATION;
if (target > mDimTargetAlpha) {
- // This is happening behind the activity UI,
- // so we can make it run a little longer to
- // give a stronger impression without disrupting
- // the user.
- duration *= DIM_DURATION_MULTIPLIER;
+ TypedValue tv = new TypedValue();
+ res.getValue(com.android.internal.R.fraction.config_dimBehindFadeDuration,
+ tv, true);
+ if (tv.type == TypedValue.TYPE_FRACTION) {
+ duration = (long)tv.getFraction((float)duration, (float)duration);
+ } else if (tv.type >= TypedValue.TYPE_FIRST_INT
+ && tv.type <= TypedValue.TYPE_LAST_INT) {
+ duration = tv.data;
+ }
}
if (duration < 1) {
// Don't divide by zero
@@ -10616,7 +12068,6 @@ public class WindowManagerService extends IWindowManager.Stub
* This is used for opening/closing transition for apps in compatible mode.
*/
private static class FadeInOutAnimation extends Animation {
- int mWidth;
boolean mFadeIn;
public FadeInOutAnimation(boolean fadeIn) {
@@ -10631,24 +12082,11 @@ public class WindowManagerService extends IWindowManager.Stub
if (!mFadeIn) {
x = 1.0f - x; // reverse the interpolation for fade out
}
- if (x < 0.5) {
- // move the window out of the screen.
- t.getMatrix().setTranslate(mWidth, 0);
- } else {
- t.getMatrix().setTranslate(0, 0);// show
- t.setAlpha((x - 0.5f) * 2);
- }
- }
-
- @Override
- public void initialize(int width, int height, int parentWidth, int parentHeight) {
- // width is the screen width {@see AppWindowToken#stepAnimatinoLocked}
- mWidth = width;
+ t.setAlpha(x);
}
+ }
- @Override
- public int getZAdjustment() {
- return Animation.ZORDER_TOP;
- }
+ public interface OnHardKeyboardStatusChangeListener {
+ public void onHardKeyboardStatusChange(boolean available, boolean enabled);
}
}
diff --git a/services/java/com/android/server/WiredAccessoryObserver.java b/services/java/com/android/server/WiredAccessoryObserver.java
new file mode 100644
index 0000000..e45c368
--- /dev/null
+++ b/services/java/com/android/server/WiredAccessoryObserver.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.ActivityManagerNative;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.UEventObserver;
+import android.util.Slog;
+import android.media.AudioManager;
+import android.util.Log;
+
+import java.io.FileReader;
+import java.io.FileNotFoundException;
+
+/**
+ * <p>WiredAccessoryObserver monitors for a wired headset on the main board or dock.
+ */
+class WiredAccessoryObserver extends UEventObserver {
+ private static final String TAG = WiredAccessoryObserver.class.getSimpleName();
+ private static final boolean LOG = true;
+ private static final int MAX_AUDIO_PORTS = 3; /* h2w, USB Audio & hdmi */
+ private static final String uEventInfo[][] = { {"DEVPATH=/devices/virtual/switch/h2w",
+ "/sys/class/switch/h2w/state",
+ "/sys/class/switch/h2w/name"},
+ {"DEVPATH=/devices/virtual/switch/usb_audio",
+ "/sys/class/switch/usb_audio/state",
+ "/sys/class/switch/usb_audio/name"},
+ {"DEVPATH=/devices/virtual/switch/hdmi",
+ "/sys/class/switch/hdmi/state",
+ "/sys/class/switch/hdmi/name"} };
+
+ private static final int BIT_HEADSET = (1 << 0);
+ private static final int BIT_HEADSET_NO_MIC = (1 << 1);
+ private static final int BIT_USB_HEADSET_ANLG = (1 << 2);
+ private static final int BIT_USB_HEADSET_DGTL = (1 << 3);
+ private static final int BIT_HDMI_AUDIO = (1 << 4);
+ private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC|
+ BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL|
+ BIT_HDMI_AUDIO);
+ private static final int HEADSETS_WITH_MIC = BIT_HEADSET;
+
+ private int mHeadsetState;
+ private int mPrevHeadsetState;
+ private String mHeadsetName;
+ private int switchState;
+
+ private final Context mContext;
+ private final WakeLock mWakeLock; // held while there is a pending route change
+
+ public WiredAccessoryObserver(Context context) {
+ mContext = context;
+ PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryObserver");
+ mWakeLock.setReferenceCounted(false);
+
+ context.registerReceiver(new BootCompletedReceiver(),
+ new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
+ }
+
+ private final class BootCompletedReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // At any given time accessories could be inserted
+ // one on the board, one on the dock and one on HDMI:
+ // observe three UEVENTs
+ init(); // set initial status
+ for (int i = 0; i < MAX_AUDIO_PORTS; i++) {
+ startObserving(uEventInfo[i][0]);
+ }
+ }
+ }
+
+ @Override
+ public void onUEvent(UEventObserver.UEvent event) {
+ if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());
+
+ try {
+ String name = event.get("SWITCH_NAME");
+ int state = Integer.parseInt(event.get("SWITCH_STATE"));
+ updateState(name, state);
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Could not parse switch state from event " + event);
+ }
+ }
+
+ private synchronized final void updateState(String name, int state)
+ {
+ if (name.equals("usb_audio")) {
+ if (state == 1) {
+ switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|
+ BIT_USB_HEADSET_DGTL|BIT_HDMI_AUDIO)) |
+ (state << 2));
+ } else if (state == 2) {
+ switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|
+ BIT_USB_HEADSET_ANLG|BIT_HDMI_AUDIO)) |
+ (state << 3));
+ } else switchState = (mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|BIT_HDMI_AUDIO));
+ }
+ else if (name.equals("hdmi")) {
+ switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|
+ BIT_USB_HEADSET_DGTL|BIT_USB_HEADSET_ANLG)) |
+ (state << 4));
+ }
+ else {
+ switchState = ((mHeadsetState & (BIT_HDMI_AUDIO|BIT_USB_HEADSET_ANLG|
+ BIT_USB_HEADSET_DGTL)) |
+ state);
+ }
+ update(name, switchState);
+ }
+
+ private synchronized final void init() {
+ char[] buffer = new char[1024];
+
+ String newName = mHeadsetName;
+ int newState = mHeadsetState;
+ mPrevHeadsetState = mHeadsetState;
+
+ if (LOG) Slog.v(TAG, "init()");
+
+ for (int i = 0; i < MAX_AUDIO_PORTS; i++) {
+ try {
+ FileReader file = new FileReader(uEventInfo[i][1]);
+ int len = file.read(buffer, 0, 1024);
+ file.close();
+ newState = Integer.valueOf((new String(buffer, 0, len)).trim());
+
+ file = new FileReader(uEventInfo[i][2]);
+ len = file.read(buffer, 0, 1024);
+ file.close();
+ newName = new String(buffer, 0, len).trim();
+
+ if (newState > 0) {
+ updateState(newName, newState);
+ }
+
+ } catch (FileNotFoundException e) {
+ Slog.w(TAG, "This kernel does not have wired headset support");
+ } catch (Exception e) {
+ Slog.e(TAG, "" , e);
+ }
+ }
+ }
+
+ private synchronized final void update(String newName, int newState) {
+ // Retain only relevant bits
+ int headsetState = newState & SUPPORTED_HEADSETS;
+ int newOrOld = headsetState | mHeadsetState;
+ int delay = 0;
+ int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG;
+ int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL;
+ int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC);
+ boolean h2wStateChange = true;
+ boolean usbStateChange = true;
+ // reject all suspect transitions: only accept state changes from:
+ // - a: 0 heaset to 1 headset
+ // - b: 1 headset to 0 headset
+ if (LOG) Slog.v(TAG, "newState = "+newState+", headsetState = "+headsetState+","
+ + "mHeadsetState = "+mHeadsetState);
+ if (mHeadsetState == headsetState || ((h2w_headset & (h2w_headset - 1)) != 0)) {
+ Log.e(TAG, "unsetting h2w flag");
+ h2wStateChange = false;
+ }
+ // - c: 0 usb headset to 1 usb headset
+ // - d: 1 usb headset to 0 usb headset
+ if ((usb_headset_anlg >> 2) == 1 && (usb_headset_dgtl >> 3) == 1) {
+ Log.e(TAG, "unsetting usb flag");
+ usbStateChange = false;
+ }
+ if (!h2wStateChange && !usbStateChange) {
+ Log.e(TAG, "invalid transition, returning ...");
+ return;
+ }
+
+ mHeadsetName = newName;
+ mPrevHeadsetState = mHeadsetState;
+ mHeadsetState = headsetState;
+
+ if (headsetState == 0) {
+ Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+ mContext.sendBroadcast(intent);
+ // It can take hundreds of ms flush the audio pipeline after
+ // apps pause audio playback, but audio route changes are
+ // immediate, so delay the route change by 1000ms.
+ // This could be improved once the audio sub-system provides an
+ // interface to clear the audio pipeline.
+ delay = 1000;
+ } else {
+ // Insert the same delay for headset connection so that the connection event is not
+ // broadcast before the disconnection event in case of fast removal/insertion
+ if (mHandler.hasMessages(0)) {
+ delay = 1000;
+ }
+ }
+ mWakeLock.acquire();
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(0,
+ mHeadsetState,
+ mPrevHeadsetState,
+ mHeadsetName),
+ delay);
+ }
+
+ private synchronized final void sendIntents(int headsetState, int prevHeadsetState, String headsetName) {
+ int allHeadsets = SUPPORTED_HEADSETS;
+ for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) {
+ if ((curHeadset & allHeadsets) != 0) {
+ sendIntent(curHeadset, headsetState, prevHeadsetState, headsetName);
+ allHeadsets &= ~curHeadset;
+ }
+ }
+ }
+
+ private final void sendIntent(int headset, int headsetState, int prevHeadsetState, String headsetName) {
+ if ((headsetState & headset) != (prevHeadsetState & headset)) {
+
+ int state = 0;
+ if ((headsetState & headset) != 0) {
+ state = 1;
+ }
+ if((headset == BIT_USB_HEADSET_ANLG) || (headset == BIT_USB_HEADSET_DGTL) ||
+ (headset == BIT_HDMI_AUDIO)) {
+ Intent intent;
+
+ // Pack up the values and broadcast them to everyone
+ if (headset == BIT_USB_HEADSET_ANLG) {
+ intent = new Intent(Intent.ACTION_USB_ANLG_HEADSET_PLUG);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ intent.putExtra("state", state);
+ intent.putExtra("name", headsetName);
+ ActivityManagerNative.broadcastStickyIntent(intent, null);
+ } else if (headset == BIT_USB_HEADSET_DGTL) {
+ intent = new Intent(Intent.ACTION_USB_DGTL_HEADSET_PLUG);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ intent.putExtra("state", state);
+ intent.putExtra("name", headsetName);
+ ActivityManagerNative.broadcastStickyIntent(intent, null);
+ } else if (headset == BIT_HDMI_AUDIO) {
+ intent = new Intent(Intent.ACTION_HDMI_AUDIO_PLUG);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ intent.putExtra("state", state);
+ intent.putExtra("name", headsetName);
+ ActivityManagerNative.broadcastStickyIntent(intent, null);
+ }
+
+ if (LOG) Slog.v(TAG, "Intent.ACTION_USB_HEADSET_PLUG: state: "+state+" name: "+headsetName);
+ // TODO: Should we require a permission?
+ }
+ if((headset == BIT_HEADSET) || (headset == BIT_HEADSET_NO_MIC)) {
+
+ // Pack up the values and broadcast them to everyone
+ Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ //int state = 0;
+ int microphone = 0;
+
+ if ((headset & HEADSETS_WITH_MIC) != 0) {
+ microphone = 1;
+ }
+
+ intent.putExtra("state", state);
+ intent.putExtra("name", headsetName);
+ intent.putExtra("microphone", microphone);
+
+ if (LOG) Slog.v(TAG, "Intent.ACTION_HEADSET_PLUG: state: "+state+" name: "+headsetName+" mic: "+microphone);
+ // TODO: Should we require a permission?
+ ActivityManagerNative.broadcastStickyIntent(intent, null);
+ }
+ }
+ }
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ sendIntents(msg.arg1, msg.arg2, (String)msg.obj);
+ mWakeLock.release();
+ }
+ };
+}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 0e38e10..c24fe8e 100755
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -76,6 +76,8 @@ import android.content.pm.ServiceInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.net.Proxy;
+import android.net.ProxyProperties;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -554,7 +556,7 @@ public final class ActivityManagerService extends ActivityManagerNative
= new HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>>();
/**
- * Fingerprints (String.hashCode()) of stack traces that we've
+ * Fingerprints (hashCode()) of stack traces that we've
* already logged DropBox entries for. Guarded by itself. If
* something (rogue user app) forces this over
* MAX_DUP_SUPPRESSED_STACKS entries, the contents are cleared.
@@ -959,6 +961,8 @@ public final class ActivityManagerService extends ActivityManagerNative
static final int CANCEL_HEAVY_NOTIFICATION_MSG = 25;
static final int SHOW_STRICT_MODE_VIOLATION_MSG = 26;
static final int CHECK_EXCESSIVE_WAKE_LOCKS_MSG = 27;
+ static final int CLEAR_DNS_CACHE = 28;
+ static final int UPDATE_HTTP_PROXY = 29;
AlertDialog mUidAlert;
@@ -1110,6 +1114,44 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
} break;
+ case CLEAR_DNS_CACHE: {
+ synchronized (ActivityManagerService.this) {
+ for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
+ ProcessRecord r = mLruProcesses.get(i);
+ if (r.thread != null) {
+ try {
+ r.thread.clearDnsCache();
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to clear dns cache for: " + r.info.processName);
+ }
+ }
+ }
+ }
+ } break;
+ case UPDATE_HTTP_PROXY: {
+ ProxyProperties proxy = (ProxyProperties)msg.obj;
+ String host = "";
+ String port = "";
+ String exclList = "";
+ if (proxy != null) {
+ host = proxy.getHost();
+ port = Integer.toString(proxy.getPort());
+ exclList = proxy.getExclusionList();
+ }
+ synchronized (ActivityManagerService.this) {
+ for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
+ ProcessRecord r = mLruProcesses.get(i);
+ if (r.thread != null) {
+ try {
+ r.thread.setHttpProxy(host, port, exclList);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to update http proxy for: " +
+ r.info.processName);
+ }
+ }
+ }
+ }
+ } break;
case SHOW_UID_ERROR_MSG: {
// XXX This is a temporary dialog, no need to localize.
AlertDialog d = new BaseErrorDialog(mContext);
@@ -1282,6 +1324,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ActivityThread at = ActivityThread.systemMain();
mSystemThread = at;
Context context = at.getSystemContext();
+ context.setTheme(android.R.style.Theme_Holo);
m.mContext = context;
m.mFactoryTest = factoryTest;
m.mMainStack = new ActivityStack(m, context, true);
@@ -1334,6 +1377,11 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ // For debug builds, log event loop stalls to dropbox for analysis.
+ if (StrictMode.conditionallyEnableDebugLogging()) {
+ Slog.i(TAG, "Enabled StrictMode logging for AThread's Looper");
+ }
+
Looper.loop();
}
}
@@ -1978,7 +2026,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (app == null || app.instrumentationClass == null) {
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
mMainStack.startActivityLocked(null, intent, null, null, 0, aInfo,
- null, null, 0, 0, 0, false, false);
+ null, null, 0, 0, 0, false, false, null);
}
}
@@ -2034,7 +2082,7 @@ public final class ActivityManagerService extends ActivityManagerNative
intent.setComponent(new ComponentName(
ri.activityInfo.packageName, ri.activityInfo.name));
mMainStack.startActivityLocked(null, intent, null, null, 0, ri.activityInfo,
- null, null, 0, 0, 0, false, false);
+ null, null, 0, 0, 0, false, false, null);
}
}
}
@@ -2073,13 +2121,13 @@ public final class ActivityManagerService extends ActivityManagerNative
}
mPendingActivityLaunches.clear();
}
-
+
public final int startActivity(IApplicationThread caller,
Intent intent, String resolvedType, Uri[] grantedUriPermissions,
int grantedMode, IBinder resultTo,
String resultWho, int requestCode, boolean onlyIfNeeded,
boolean debug) {
- return mMainStack.startActivityMayWait(caller, intent, resolvedType,
+ return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,
grantedUriPermissions, grantedMode, resultTo, resultWho,
requestCode, onlyIfNeeded, debug, null, null);
}
@@ -2090,7 +2138,7 @@ public final class ActivityManagerService extends ActivityManagerNative
String resultWho, int requestCode, boolean onlyIfNeeded,
boolean debug) {
WaitResult res = new WaitResult();
- mMainStack.startActivityMayWait(caller, intent, resolvedType,
+ mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,
grantedUriPermissions, grantedMode, resultTo, resultWho,
requestCode, onlyIfNeeded, debug, res, null);
return res;
@@ -2101,12 +2149,12 @@ public final class ActivityManagerService extends ActivityManagerNative
int grantedMode, IBinder resultTo,
String resultWho, int requestCode, boolean onlyIfNeeded,
boolean debug, Configuration config) {
- return mMainStack.startActivityMayWait(caller, intent, resolvedType,
+ return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,
grantedUriPermissions, grantedMode, resultTo, resultWho,
requestCode, onlyIfNeeded, debug, null, config);
}
- public int startActivityIntentSender(IApplicationThread caller,
+ public int startActivityIntentSender(IApplicationThread caller,
IntentSender intent, Intent fillInIntent, String resolvedType,
IBinder resultTo, String resultWho, int requestCode,
int flagsMask, int flagsValues) {
@@ -2219,7 +2267,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// those are not yet exposed to user code, so there is no need.
int res = mMainStack.startActivityLocked(r.app.thread, intent,
r.resolvedType, null, 0, aInfo, resultTo, resultWho,
- requestCode, -1, r.launchedFromUid, false, false);
+ requestCode, -1, r.launchedFromUid, false, false, null);
Binder.restoreCallingIdentity(origId);
r.finishing = wasFinishing;
@@ -2241,43 +2289,37 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new SecurityException(
"startActivityInPackage only available to the system");
}
-
- final boolean componentSpecified = intent.getComponent() != null;
-
- // Don't modify the client's object!
- intent = new Intent(intent);
- // Collect information about the target of the Intent.
- ActivityInfo aInfo;
- try {
- ResolveInfo rInfo =
- AppGlobals.getPackageManager().resolveIntent(
- intent, resolvedType,
- PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS);
- aInfo = rInfo != null ? rInfo.activityInfo : null;
- } catch (RemoteException e) {
- aInfo = null;
- }
+ return mMainStack.startActivityMayWait(null, uid, intent, resolvedType,
+ null, 0, resultTo, resultWho, requestCode, onlyIfNeeded, false, null, null);
+ }
- if (aInfo != null) {
- // Store the found target back into the intent, because now that
- // we have it we never want to do this again. For example, if the
- // user navigates back to this point in the history, we should
- // always restart the exact same activity.
- intent.setComponent(new ComponentName(
- aInfo.applicationInfo.packageName, aInfo.name));
- }
+ public final int startActivities(IApplicationThread caller,
+ Intent[] intents, String[] resolvedTypes, IBinder resultTo) {
+ return mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo);
+ }
- synchronized(this) {
- return mMainStack.startActivityLocked(null, intent, resolvedType,
- null, 0, aInfo, resultTo, resultWho, requestCode, -1, uid,
- onlyIfNeeded, componentSpecified);
+ public final int startActivitiesInPackage(int uid,
+ Intent[] intents, String[] resolvedTypes, IBinder resultTo) {
+
+ // This is so super not safe, that only the system (or okay root)
+ // can do it.
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != 0 && callingUid != Process.myUid()) {
+ throw new SecurityException(
+ "startActivityInPackage only available to the system");
}
+
+ return mMainStack.startActivities(null, uid, intents, resolvedTypes, resultTo);
}
final void addRecentTaskLocked(TaskRecord task) {
- // Remove any existing entries that are the same kind of task.
int N = mRecentTasks.size();
+ // Quick case: check if the top-most recent task is the same.
+ if (N > 0 && mRecentTasks.get(0) == task) {
+ return;
+ }
+ // Remove any existing entries that are the same kind of task.
for (int i=0; i<N; i++) {
TaskRecord tr = mRecentTasks.get(i);
if ((task.affinity != null && task.affinity.equals(tr.affinity))
@@ -2451,6 +2493,10 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (proc.thread != null) {
+ if (proc.pid == Process.myPid()) {
+ Log.w(TAG, "crashApplication: trying to crash self!");
+ return;
+ }
long ident = Binder.clearCallingIdentity();
try {
proc.thread.scheduleCrash(message);
@@ -2566,6 +2612,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (localLOGV) Slog.v(
TAG, "Removing this entry! frozen=" + r.haveState
+ " finishing=" + r.finishing);
+ r.makeFinishing();
mMainStack.mHistory.remove(i);
r.inHistory = false;
@@ -3013,7 +3060,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (uid == pkgUid || checkComponentPermission(
android.Manifest.permission.CLEAR_APP_USER_DATA,
- pid, uid, -1)
+ pid, uid, -1, true)
== PackageManager.PERMISSION_GRANTED) {
forceStopPackageLocked(packageName, pkgUid);
} else {
@@ -3738,22 +3785,22 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- public final void activityPaused(IBinder token, Bundle icicle) {
- // Refuse possible leaked file descriptors
- if (icicle != null && icicle.hasFileDescriptors()) {
- throw new IllegalArgumentException("File descriptors passed in Bundle");
- }
-
+ public final void activityPaused(IBinder token) {
final long origId = Binder.clearCallingIdentity();
- mMainStack.activityPaused(token, icicle, false);
+ mMainStack.activityPaused(token, false);
Binder.restoreCallingIdentity(origId);
}
- public final void activityStopped(IBinder token, Bitmap thumbnail,
+ public final void activityStopped(IBinder token, Bundle icicle, Bitmap thumbnail,
CharSequence description) {
if (localLOGV) Slog.v(
TAG, "Activity stopped: token=" + token);
+ // Refuse possible leaked file descriptors
+ if (icicle != null && icicle.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Bundle");
+ }
+
ActivityRecord r = null;
final long origId = Binder.clearCallingIdentity();
@@ -3762,8 +3809,18 @@ public final class ActivityManagerService extends ActivityManagerNative
int index = mMainStack.indexOfTokenLocked(token);
if (index >= 0) {
r = (ActivityRecord)mMainStack.mHistory.get(index);
- r.thumbnail = thumbnail;
+ r.icicle = icicle;
+ r.haveState = true;
+ if (thumbnail != null) {
+ r.thumbnail = thumbnail;
+ if (r.task != null) {
+ r.task.lastThumbnail = r.thumbnail;
+ }
+ }
r.description = description;
+ if (r.task != null) {
+ r.task.lastDescription = r.description;
+ }
r.stopped = true;
r.state = ActivityState.STOPPED;
if (!r.finishing) {
@@ -3838,16 +3895,30 @@ public final class ActivityManagerService extends ActivityManagerNative
public IIntentSender getIntentSender(int type,
String packageName, IBinder token, String resultWho,
- int requestCode, Intent intent, String resolvedType, int flags) {
+ int requestCode, Intent[] intents, String[] resolvedTypes, int flags) {
// Refuse possible leaked file descriptors
- if (intent != null && intent.hasFileDescriptors() == true) {
- throw new IllegalArgumentException("File descriptors passed in Intent");
- }
-
- if (type == INTENT_SENDER_BROADCAST) {
- if ((intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
+ if (intents != null) {
+ if (intents.length < 1) {
+ throw new IllegalArgumentException("Intents array length must be >= 1");
+ }
+ for (int i=0; i<intents.length; i++) {
+ Intent intent = intents[i];
+ if (intent == null) {
+ throw new IllegalArgumentException("Null intent at index " + i);
+ }
+ if (intent.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+ if (type == INTENT_SENDER_BROADCAST &&
+ (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
+ throw new IllegalArgumentException(
+ "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
+ }
+ intents[i] = new Intent(intent);
+ }
+ if (resolvedTypes != null && resolvedTypes.length != intents.length) {
throw new IllegalArgumentException(
- "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
+ "Intent array length does not match resolvedTypes length");
}
}
@@ -3870,7 +3941,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
return getIntentSenderLocked(type, packageName, callingUid,
- token, resultWho, requestCode, intent, resolvedType, flags);
+ token, resultWho, requestCode, intents, resolvedTypes, flags);
} catch (RemoteException e) {
throw new SecurityException(e);
@@ -3880,7 +3951,7 @@ public final class ActivityManagerService extends ActivityManagerNative
IIntentSender getIntentSenderLocked(int type,
String packageName, int callingUid, IBinder token, String resultWho,
- int requestCode, Intent intent, String resolvedType, int flags) {
+ int requestCode, Intent[] intents, String[] resolvedTypes, int flags) {
ActivityRecord activity = null;
if (type == INTENT_SENDER_ACTIVITY_RESULT) {
int index = mMainStack.indexOfTokenLocked(token);
@@ -3901,14 +3972,24 @@ public final class ActivityManagerService extends ActivityManagerNative
PendingIntentRecord.Key key = new PendingIntentRecord.Key(
type, packageName, activity, resultWho,
- requestCode, intent, resolvedType, flags);
+ requestCode, intents, resolvedTypes, flags);
WeakReference<PendingIntentRecord> ref;
ref = mIntentSenderRecords.get(key);
PendingIntentRecord rec = ref != null ? ref.get() : null;
if (rec != null) {
if (!cancelCurrent) {
if (updateCurrent) {
- rec.key.requestIntent.replaceExtras(intent);
+ if (rec.key.requestIntent != null) {
+ rec.key.requestIntent.replaceExtras(intents != null ? intents[0] : null);
+ }
+ if (intents != null) {
+ intents[intents.length-1] = rec.key.requestIntent;
+ rec.key.allIntents = intents;
+ rec.key.allResolvedTypes = resolvedTypes;
+ } else {
+ rec.key.allIntents = null;
+ rec.key.allResolvedTypes = null;
+ }
}
return rec;
}
@@ -4070,7 +4151,7 @@ public final class ActivityManagerService extends ActivityManagerNative
* This can be called with or without the global lock held.
*/
int checkComponentPermission(String permission, int pid, int uid,
- int reqUid) {
+ int owningUid, boolean exported) {
// We might be performing an operation on behalf of an indirect binder
// invocation, e.g. via {@link #openContentUri}. Check and adjust the
// client identity accordingly before proceeding.
@@ -4087,9 +4168,14 @@ public final class ActivityManagerService extends ActivityManagerNative
!Process.supportsProcesses()) {
return PackageManager.PERMISSION_GRANTED;
}
- // If the target requires a specific UID, always fail for others.
- if (reqUid >= 0 && uid != reqUid) {
- Slog.w(TAG, "Permission denied: checkComponentPermission() reqUid=" + reqUid);
+ // If there is a uid that owns whatever is being accessed, it has
+ // blanket access to it regardless of the permissions it requires.
+ if (owningUid >= 0 && uid == owningUid) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ // If the target is not exported, then nobody else can get to it.
+ if (!exported) {
+ Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid);
return PackageManager.PERMISSION_DENIED;
}
if (permission == null) {
@@ -4118,7 +4204,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (permission == null) {
return PackageManager.PERMISSION_DENIED;
}
- return checkComponentPermission(permission, pid, uid, -1);
+ return checkComponentPermission(permission, pid, uid, -1, true);
}
/**
@@ -4268,8 +4354,10 @@ public final class ActivityManagerService extends ActivityManagerNative
return -1;
}
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Checking grant " + targetPkg + " permission to " + uri);
+ if (targetPkg != null) {
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ "Checking grant " + targetPkg + " permission to " + uri);
+ }
final IPackageManager pm = AppGlobals.getPackageManager();
@@ -4298,23 +4386,45 @@ public final class ActivityManagerService extends ActivityManagerNative
}
int targetUid;
- try {
- targetUid = pm.getPackageUid(targetPkg);
- if (targetUid < 0) {
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Can't grant URI permission no uid for: " + targetPkg);
+ if (targetPkg != null) {
+ try {
+ targetUid = pm.getPackageUid(targetPkg);
+ if (targetUid < 0) {
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ "Can't grant URI permission no uid for: " + targetPkg);
+ return -1;
+ }
+ } catch (RemoteException ex) {
return -1;
}
- } catch (RemoteException ex) {
- return -1;
+ } else {
+ targetUid = -1;
}
- // First... does the target actually need this permission?
- if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) {
- // No need to grant the target this permission.
- if (DEBUG_URI_PERMISSION) Slog.v(TAG,
- "Target " + targetPkg + " already has full permission to " + uri);
- return -1;
+ if (targetUid >= 0) {
+ // First... does the target actually need this permission?
+ if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) {
+ // No need to grant the target this permission.
+ if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+ "Target " + targetPkg + " already has full permission to " + uri);
+ return -1;
+ }
+ } else {
+ // First... there is no target package, so can anyone access it?
+ boolean allowed = pi.exported;
+ if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+ if (pi.readPermission != null) {
+ allowed = false;
+ }
+ }
+ if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+ if (pi.writePermission != null) {
+ allowed = false;
+ }
+ }
+ if (allowed) {
+ return -1;
+ }
}
// Second... is the provider allowing granting of URI permissions?
@@ -4344,16 +4454,25 @@ public final class ActivityManagerService extends ActivityManagerNative
// Third... does the caller itself have permission to access
// this uri?
- if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
- if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
- throw new SecurityException("Uid " + callingUid
- + " does not have permission to uri " + uri);
+ if (callingUid != Process.myUid()) {
+ if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
+ if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
+ throw new SecurityException("Uid " + callingUid
+ + " does not have permission to uri " + uri);
+ }
}
}
return targetUid;
}
+ public int checkGrantUriPermission(int callingUid, String targetPkg,
+ Uri uri, int modeFlags) {
+ synchronized(this) {
+ return checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags);
+ }
+ }
+
void grantUriPermissionUncheckedLocked(int targetUid, String targetPkg,
Uri uri, int modeFlags, UriPermissionOwner owner) {
modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -4399,6 +4518,10 @@ public final class ActivityManagerService extends ActivityManagerNative
void grantUriPermissionLocked(int callingUid,
String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) {
+ if (targetPkg == null) {
+ throw new NullPointerException("targetPkg");
+ }
+
int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags);
if (targetUid < 0) {
return;
@@ -4417,6 +4540,10 @@ public final class ActivityManagerService extends ActivityManagerNative
+ " from " + intent + "; flags=0x"
+ Integer.toHexString(intent != null ? intent.getFlags() : 0));
+ if (targetPkg == null) {
+ throw new NullPointerException("targetPkg");
+ }
+
if (intent == null) {
return -1;
}
@@ -4718,6 +4845,11 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new SecurityException(msg);
}
+ final boolean canReadFb = (flags&ActivityManager.TASKS_GET_THUMBNAILS) != 0
+ && checkCallingPermission(
+ android.Manifest.permission.READ_FRAME_BUFFER)
+ == PackageManager.PERMISSION_GRANTED;
+
int pos = mMainStack.mHistory.size()-1;
ActivityRecord next =
pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null;
@@ -4762,7 +4894,13 @@ public final class ActivityManagerService extends ActivityManagerNative
ci.id = curTask.taskId;
ci.baseActivity = r.intent.getComponent();
ci.topActivity = top.intent.getComponent();
- ci.thumbnail = top.thumbnail;
+ if (canReadFb) {
+ if (top.thumbnail != null) {
+ ci.thumbnail = top.thumbnail;
+ } else if (top.state == ActivityState.RESUMED) {
+ ci.thumbnail = top.stack.screenshotActivities(top);
+ }
+ }
ci.description = topDescription;
ci.numActivities = numActivities;
ci.numRunning = numRunning;
@@ -4831,8 +4969,15 @@ public final class ActivityManagerService extends ActivityManagerNative
enforceCallingPermission(android.Manifest.permission.GET_TASKS,
"getRecentTasks()");
+ final boolean canReadFb = (flags&ActivityManager.TASKS_GET_THUMBNAILS) != 0
+ && checkCallingPermission(
+ android.Manifest.permission.READ_FRAME_BUFFER)
+ == PackageManager.PERMISSION_GRANTED;
+
IPackageManager pm = AppGlobals.getPackageManager();
+ ActivityRecord resumed = mMainStack.mResumedActivity;
+
final int N = mRecentTasks.size();
ArrayList<ActivityManager.RecentTaskInfo> res
= new ArrayList<ActivityManager.RecentTaskInfo>(
@@ -4850,6 +4995,15 @@ public final class ActivityManagerService extends ActivityManagerNative
tr.intent != null ? tr.intent : tr.affinityIntent);
rti.origActivity = tr.origActivity;
+ if (canReadFb) {
+ if (resumed != null && resumed.task == tr) {
+ rti.thumbnail = resumed.stack.screenshotActivities(resumed);
+ } else {
+ rti.thumbnail = tr.lastThumbnail;
+ }
+ }
+ rti.description = tr.lastDescription;
+
if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0) {
// Check whether this activity is currently available.
try {
@@ -4916,7 +5070,7 @@ public final class ActivityManagerService extends ActivityManagerNative
/**
* TODO: Add mController hook
*/
- public void moveTaskToFront(int task) {
+ public void moveTaskToFront(int task, int flags) {
enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,
"moveTaskToFront()");
@@ -4931,6 +5085,11 @@ public final class ActivityManagerService extends ActivityManagerNative
for (int i=0; i<N; i++) {
TaskRecord tr = mRecentTasks.get(i);
if (tr.taskId == task) {
+ if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) {
+ // Caller wants the home activity moved with it. To accomplish this,
+ // we'll just move the home task to the top first.
+ mMainStack.moveHomeToFrontLocked();
+ }
mMainStack.moveTaskToFrontLocked(tr, null);
return;
}
@@ -4938,6 +5097,11 @@ public final class ActivityManagerService extends ActivityManagerNative
for (int i=mMainStack.mHistory.size()-1; i>=0; i--) {
ActivityRecord hr = (ActivityRecord)mMainStack.mHistory.get(i);
if (hr.task.taskId == task) {
+ if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) {
+ // Caller wants the home activity moved with it. To accomplish this,
+ // we'll just move the home task to the top first.
+ mMainStack.moveHomeToFrontLocked();
+ }
mMainStack.moveTaskToFrontLocked(hr.task, null);
return;
}
@@ -5166,12 +5330,12 @@ public final class ActivityManagerService extends ActivityManagerNative
final int callingPid = (r != null) ? r.pid : Binder.getCallingPid();
final int callingUid = (r != null) ? r.info.uid : Binder.getCallingUid();
if (checkComponentPermission(cpi.readPermission, callingPid, callingUid,
- cpi.exported ? -1 : cpi.applicationInfo.uid)
+ cpi.applicationInfo.uid, cpi.exported)
== PackageManager.PERMISSION_GRANTED) {
return null;
}
if (checkComponentPermission(cpi.writePermission, callingPid, callingUid,
- cpi.exported ? -1 : cpi.applicationInfo.uid)
+ cpi.applicationInfo.uid, cpi.exported)
== PackageManager.PERMISSION_GRANTED) {
return null;
}
@@ -5183,12 +5347,12 @@ public final class ActivityManagerService extends ActivityManagerNative
i--;
PathPermission pp = pps[i];
if (checkComponentPermission(pp.getReadPermission(), callingPid, callingUid,
- cpi.exported ? -1 : cpi.applicationInfo.uid)
+ cpi.applicationInfo.uid, cpi.exported)
== PackageManager.PERMISSION_GRANTED) {
return null;
}
if (checkComponentPermission(pp.getWritePermission(), callingPid, callingUid,
- cpi.exported ? -1 : cpi.applicationInfo.uid)
+ cpi.applicationInfo.uid, cpi.exported)
== PackageManager.PERMISSION_GRANTED) {
return null;
}
@@ -5204,10 +5368,18 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- String msg = "Permission Denial: opening provider " + cpi.name
- + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid
- + ", uid=" + callingUid + ") requires "
- + cpi.readPermission + " or " + cpi.writePermission;
+ String msg;
+ if (!cpi.exported) {
+ msg = "Permission Denial: opening provider " + cpi.name
+ + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid
+ + ", uid=" + callingUid + ") that is not exported from uid "
+ + cpi.applicationInfo.uid;
+ } else {
+ msg = "Permission Denial: opening provider " + cpi.name
+ + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid
+ + ", uid=" + callingUid + ") requires "
+ + cpi.readPermission + " or " + cpi.writePermission;
+ }
Slog.w(TAG, msg);
return msg;
}
@@ -5689,16 +5861,18 @@ public final class ActivityManagerService extends ActivityManagerNative
return pfd;
}
+ // Actually is sleeping or shutting down or whatever else in the future
+ // is an inactive state.
+ public boolean isSleeping() {
+ return mSleeping || mShuttingDown;
+ }
+
public void goingToSleep() {
synchronized(this) {
mSleeping = true;
mWindowManager.setEventDispatching(false);
- if (mMainStack.mResumedActivity != null) {
- mMainStack.pauseIfSleepingLocked();
- } else {
- Slog.w(TAG, "goingToSleep with no resumed activity!");
- }
+ mMainStack.stopIfSleepingLocked();
// Initialize the wake times of all processes.
checkExcessivePowerUsageLocked(false);
@@ -5722,7 +5896,7 @@ public final class ActivityManagerService extends ActivityManagerNative
mWindowManager.setEventDispatching(false);
if (mMainStack.mResumedActivity != null) {
- mMainStack.pauseIfSleepingLocked();
+ mMainStack.stopIfSleepingLocked();
final long endTime = System.currentTimeMillis() + timeout;
while (mMainStack.mResumedActivity != null
|| mMainStack.mPausingActivity != null) {
@@ -5746,13 +5920,30 @@ public final class ActivityManagerService extends ActivityManagerNative
return timedout;
}
+ public final void activitySlept(IBinder token) {
+ if (localLOGV) Slog.v(
+ TAG, "Activity slept: token=" + token);
+
+ ActivityRecord r = null;
+
+ final long origId = Binder.clearCallingIdentity();
+
+ synchronized (this) {
+ int index = mMainStack.indexOfTokenLocked(token);
+ if (index >= 0) {
+ r = (ActivityRecord)mMainStack.mHistory.get(index);
+ mMainStack.activitySleptLocked(r);
+ }
+ }
+
+ Binder.restoreCallingIdentity(origId);
+ }
+
public void wakingUp() {
synchronized(this) {
- if (mMainStack.mGoingToSleep.isHeld()) {
- mMainStack.mGoingToSleep.release();
- }
mWindowManager.setEventDispatching(true);
mSleeping = false;
+ mMainStack.awakeFromSleepingLocked();
mMainStack.resumeTopActivityLocked(null);
}
}
@@ -5797,7 +5988,7 @@ public final class ActivityManagerService extends ActivityManagerNative
final int perm = checkComponentPermission(
android.Manifest.permission.STOP_APP_SWITCHES, callingPid,
- callingUid, -1);
+ callingUid, -1, true);
if (perm == PackageManager.PERMISSION_GRANTED) {
return true;
}
@@ -5881,6 +6072,35 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ public void setImmersive(IBinder token, boolean immersive) {
+ synchronized(this) {
+ int index = (token != null) ? mMainStack.indexOfTokenLocked(token) : -1;
+ if (index < 0) {
+ throw new IllegalArgumentException();
+ }
+ ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index);
+ r.immersive = immersive;
+ }
+ }
+
+ public boolean isImmersive(IBinder token) {
+ synchronized (this) {
+ int index = (token != null) ? mMainStack.indexOfTokenLocked(token) : -1;
+ if (index < 0) {
+ throw new IllegalArgumentException();
+ }
+ ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index);
+ return r.immersive;
+ }
+ }
+
+ public boolean isTopActivityImmersive() {
+ synchronized (this) {
+ ActivityRecord r = mMainStack.topRunningActivityLocked(null);
+ return (r != null) ? r.immersive : false;
+ }
+ }
+
public final void enterSafeMode() {
synchronized(this) {
// It only makes sense to do this before the system is ready
@@ -5890,23 +6110,25 @@ public final class ActivityManagerService extends ActivityManagerNative
AppGlobals.getPackageManager().enterSafeMode();
} catch (RemoteException e) {
}
-
- View v = LayoutInflater.from(mContext).inflate(
- com.android.internal.R.layout.safe_mode, null);
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
- lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
- lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
- lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
- lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
- lp.format = v.getBackground().getOpacity();
- lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
- ((WindowManager)mContext.getSystemService(
- Context.WINDOW_SERVICE)).addView(v, lp);
}
}
}
+ public final void showSafeModeOverlay() {
+ View v = LayoutInflater.from(mContext).inflate(
+ com.android.internal.R.layout.safe_mode, null);
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
+ lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+ lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ lp.gravity = Gravity.BOTTOM | Gravity.LEFT;
+ lp.format = v.getBackground().getOpacity();
+ lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ ((WindowManager)mContext.getSystemService(
+ Context.WINDOW_SERVICE)).addView(v, lp);
+ }
+
public void noteWakeupAlarm(IIntentSender sender) {
if (!(sender instanceof PendingIntentRecord)) {
return;
@@ -6378,7 +6600,6 @@ public final class ActivityManagerService extends ActivityManagerNative
+ " has crashed too many times: killing!");
EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH,
app.info.processName, app.info.uid);
- killServicesLocked(app, false);
for (int i=mMainStack.mHistory.size()-1; i>=0; i--) {
ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i);
if (r.app == app) {
@@ -6388,6 +6609,10 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
if (!app.persistent) {
+ // Don't let services in this process be restarted and potentially
+ // annoy the user repeatedly. Unless it is persistent, since those
+ // processes run critical code.
+ killServicesLocked(app, false);
// We don't want to start this process again until the user
// explicitly does so... but for persistent process, we really
// need to keep it running. If a persistent process is actually
@@ -6411,7 +6636,7 @@ public final class ActivityManagerService extends ActivityManagerNative
int index = mMainStack.indexOfTokenLocked(r);
r.stack.finishActivityLocked(r, index,
Activity.RESULT_CANCELED, null, "crashed");
- // Also terminate an activities below it that aren't yet
+ // Also terminate any activities below it that aren't yet
// stopped, to avoid a situation where one will get
// re-start our crashing activity once it gets resumed again.
index--;
@@ -6420,7 +6645,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (r.state == ActivityState.RESUMED
|| r.state == ActivityState.PAUSING
|| r.state == ActivityState.PAUSED) {
- if (!r.isHomeActivity) {
+ if (!r.isHomeActivity || mHomeProcess != r.app) {
Slog.w(TAG, " Force finishing activity "
+ r.intent.getComponent().flattenToShortString());
r.stack.finishActivityLocked(r, index,
@@ -6528,7 +6753,7 @@ public final class ActivityManagerService extends ActivityManagerNative
ProcessRecord r = findAppProcess(app);
if ((violationMask & StrictMode.PENALTY_DROPBOX) != 0) {
- Integer stackFingerprint = info.crashInfo.stackTrace.hashCode();
+ Integer stackFingerprint = info.hashCode();
boolean logIt = true;
synchronized (mAlreadyLoggedViolatedStacks) {
if (mAlreadyLoggedViolatedStacks.contains(stackFingerprint)) {
@@ -6605,9 +6830,23 @@ public final class ActivityManagerService extends ActivityManagerNative
if (info.violationNumThisLoop != 0) {
sb.append("Loop-Violation-Number: ").append(info.violationNumThisLoop).append("\n");
}
- if (info != null && info.durationMillis != -1) {
+ if (info.numAnimationsRunning != 0) {
+ sb.append("Animations-Running: ").append(info.numAnimationsRunning).append("\n");
+ }
+ if (info.broadcastIntentAction != null) {
+ sb.append("Broadcast-Intent-Action: ").append(info.broadcastIntentAction).append("\n");
+ }
+ if (info.durationMillis != -1) {
sb.append("Duration-Millis: ").append(info.durationMillis).append("\n");
}
+ if (info.numInstances != -1) {
+ sb.append("Instance-Count: ").append(info.numInstances).append("\n");
+ }
+ if (info.tags != null) {
+ for (String tag : info.tags) {
+ sb.append("Span-Tag: ").append(tag).append("\n");
+ }
+ }
sb.append("\n");
if (info.crashInfo != null && info.crashInfo.stackTrace != null) {
sb.append(info.crashInfo.stackTrace);
@@ -7172,6 +7411,9 @@ public final class ActivityManagerService extends ActivityManagerNative
pw.println(" prov[iders]: content provider state");
pw.println(" s[ervices]: service state");
pw.println(" service [name]: service client-side state");
+ pw.println(" cmd may also be a component name (com.foo/.myApp),");
+ pw.println(" a partial substring in a component name, or an");
+ pw.println(" ActivityRecord hex object identifier.");
return;
} else {
pw.println("Unknown argument: " + opt + "; use -h for help");
@@ -7213,13 +7455,21 @@ public final class ActivityManagerService extends ActivityManagerNative
}
return;
} else if ("service".equals(cmd)) {
- dumpService(fd, pw, args, opti, dumpAll);
+ dumpService(fd, pw, args, opti);
return;
} else if ("services".equals(cmd) || "s".equals(cmd)) {
synchronized (this) {
dumpServicesLocked(fd, pw, args, opti, true);
}
return;
+ } else {
+ // Dumping a single activity?
+ if (dumpActivity(fd, pw, cmd, args, opti, dumpAll)) {
+ return;
+ }
+ pw.println("Bad activity command, or no activities match: " + cmd);
+ pw.println("Use -h for help.");
+ return;
}
}
@@ -7292,6 +7542,11 @@ public final class ActivityManagerService extends ActivityManagerNative
pw.println(" Activities waiting to stop:");
dumpHistoryList(pw, mMainStack.mStoppingActivities, " ", "Stop", false);
}
+ if (mMainStack.mGoingToSleepActivities.size() > 0) {
+ pw.println(" ");
+ pw.println(" Activities waiting to sleep:");
+ dumpHistoryList(pw, mMainStack.mGoingToSleepActivities, " ", "Sleep", false);
+ }
if (mMainStack.mFinishingActivities.size() > 0) {
pw.println(" ");
pw.println(" Activities waiting to finish:");
@@ -7303,6 +7558,7 @@ public final class ActivityManagerService extends ActivityManagerNative
pw.println(" mResumedActivity: " + mMainStack.mResumedActivity);
pw.println(" mFocusedActivity: " + mFocusedActivity);
pw.println(" mLastPausedActivity: " + mMainStack.mLastPausedActivity);
+ pw.println(" mSleepTimeout: " + mMainStack.mSleepTimeout);
if (dumpAll && mRecentTasks.size() > 0) {
pw.println(" ");
@@ -7566,8 +7822,7 @@ public final class ActivityManagerService extends ActivityManagerNative
* - the first arg isn't the flattened component name of an existing service:
* dump all services whose component contains the first arg as a substring
*/
- protected void dumpService(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean dumpAll) {
+ protected void dumpService(FileDescriptor fd, PrintWriter pw, String[] args, int opti) {
String[] newArgs;
String componentNameString;
ServiceRecord r;
@@ -7587,7 +7842,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (r != null) {
- dumpService(fd, pw, r, newArgs, dumpAll);
+ dumpService(fd, pw, r, newArgs);
} else {
ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
synchronized (this) {
@@ -7599,7 +7854,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
for (int i=0; i<services.size(); i++) {
- dumpService(fd, pw, services.get(i), newArgs, dumpAll);
+ dumpService(fd, pw, services.get(i), newArgs);
}
}
}
@@ -7608,16 +7863,10 @@ public final class ActivityManagerService extends ActivityManagerNative
* Invokes IApplicationThread.dumpService() on the thread of the specified service if
* there is a thread associated with the service.
*/
- private void dumpService(FileDescriptor fd, PrintWriter pw, ServiceRecord r, String[] args,
- boolean dumpAll) {
- pw.println(" Service " + r.name.flattenToString());
- if (dumpAll) {
- synchronized (this) {
- pw.print(" * "); pw.println(r);
- r.dump(pw, " ");
- }
- pw.println("");
- }
+ private void dumpService(FileDescriptor fd, PrintWriter pw, ServiceRecord r, String[] args) {
+ pw.println("------------------------------------------------------------"
+ + "-------------------");
+ pw.println("APP SERVICE: " + r.name.flattenToString());
if (r.app != null && r.app.thread != null) {
try {
// flush anything that is already in the PrintWriter since the thread is going
@@ -7632,6 +7881,96 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ /**
+ * There are three things that cmd can be:
+ * - a flattened component name that matched an existing activity
+ * - the cmd arg isn't the flattened component name of an existing activity:
+ * dump all activity whose component contains the cmd as a substring
+ * - A hex number of the ActivityRecord object instance.
+ */
+ protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args,
+ int opti, boolean dumpAll) {
+ String[] newArgs;
+ ComponentName componentName = ComponentName.unflattenFromString(name);
+ int objectId = 0;
+ if (componentName == null) {
+ // Not a '/' separated full component name; maybe an object ID?
+ try {
+ objectId = Integer.parseInt(name, 16);
+ name = null;
+ componentName = null;
+ } catch (RuntimeException e) {
+ }
+ }
+ newArgs = new String[args.length - opti];
+ if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti);
+
+ ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>();
+ synchronized (this) {
+ for (ActivityRecord r1 : (ArrayList<ActivityRecord>)mMainStack.mHistory) {
+ if (componentName != null) {
+ if (r1.intent.getComponent().equals(componentName)) {
+ activities.add(r1);
+ }
+ } else if (name != null) {
+ if (r1.intent.getComponent().flattenToString().contains(name)) {
+ activities.add(r1);
+ }
+ } else if (System.identityHashCode(r1) == objectId) {
+ activities.add(r1);
+ }
+ }
+ }
+
+ if (activities.size() <= 0) {
+ return false;
+ }
+
+ TaskRecord lastTask = null;
+ for (int i=activities.size()-1; i>=0; i--) {
+ ActivityRecord r = (ActivityRecord)activities.get(i);
+ if (lastTask != r.task) {
+ lastTask = r.task;
+ pw.print("* Task "); pw.print(lastTask.affinity);
+ pw.print(" id="); pw.println(lastTask.taskId);
+ if (dumpAll) {
+ lastTask.dump(pw, " ");
+ }
+ }
+ dumpActivity(" ", fd, pw, activities.get(i), newArgs, dumpAll);
+ }
+ return true;
+ }
+
+ /**
+ * Invokes IApplicationThread.dumpActivity() on the thread of the specified activity if
+ * there is a thread associated with the activity.
+ */
+ private void dumpActivity(String prefix, FileDescriptor fd, PrintWriter pw,
+ ActivityRecord r, String[] args, boolean dumpAll) {
+ synchronized (this) {
+ pw.print(prefix); pw.print("* Activity ");
+ pw.print(Integer.toHexString(System.identityHashCode(r)));
+ pw.print(" "); pw.print(r.shortComponentName); pw.print(" pid=");
+ if (r.app != null) pw.println(r.app.pid);
+ else pw.println("(not running)");
+ if (dumpAll) {
+ r.dump(pw, prefix + " ");
+ }
+ }
+ if (r.app != null && r.app.thread != null) {
+ try {
+ // flush anything that is already in the PrintWriter since the thread is going
+ // to write to the file descriptor directly
+ pw.flush();
+ r.app.thread.dumpActivity(fd, r, prefix + " ", args);
+ pw.flush();
+ } catch (RemoteException e) {
+ pw.println("got a RemoteException while dumping the activity");
+ }
+ }
+ }
+
boolean dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll) {
boolean needSep = false;
@@ -8188,7 +8527,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- if (sr.crashCount >= 2) {
+ if (sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags
+ &ApplicationInfo.FLAG_PERSISTENT) == 0) {
Slog.w(TAG, "Service crashed " + sr.crashCount
+ " times, stopping: " + sr);
EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH,
@@ -8596,8 +8936,16 @@ public final class ActivityManagerService extends ActivityManagerNative
int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
if (checkComponentPermission(r.permission,
- callingPid, callingUid, r.exported ? -1 : r.appInfo.uid)
+ callingPid, callingUid, r.appInfo.uid, r.exported)
!= PackageManager.PERMISSION_GRANTED) {
+ if (!r.exported) {
+ Slog.w(TAG, "Permission Denial: Accessing service " + r.name
+ + " from pid=" + callingPid
+ + ", uid=" + callingUid
+ + " that is not exported from uid " + r.appInfo.uid);
+ return new ServiceLookupResult(null, "not exported from uid "
+ + r.appInfo.uid);
+ }
Slog.w(TAG, "Permission Denial: Accessing service " + r.name
+ " from pid=" + callingPid
+ ", uid=" + callingUid
@@ -8679,11 +9027,19 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (r != null) {
if (checkComponentPermission(r.permission,
- callingPid, callingUid, r.exported ? -1 : r.appInfo.uid)
+ callingPid, callingUid, r.appInfo.uid, r.exported)
!= PackageManager.PERMISSION_GRANTED) {
+ if (!r.exported) {
+ Slog.w(TAG, "Permission Denial: Accessing service " + r.name
+ + " from pid=" + callingPid
+ + ", uid=" + callingUid
+ + " that is not exported from uid " + r.appInfo.uid);
+ return new ServiceLookupResult(null, "not exported from uid "
+ + r.appInfo.uid);
+ }
Slog.w(TAG, "Permission Denial: Accessing service " + r.name
- + " from pid=" + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
+ + " from pid=" + callingPid
+ + ", uid=" + callingUid
+ " requires " + r.permission);
return new ServiceLookupResult(null, r.permission);
}
@@ -8850,6 +9206,11 @@ public final class ActivityManagerService extends ActivityManagerNative
long minDuration = SERVICE_RESTART_DURATION;
long resetTime = SERVICE_RESET_RUN_DURATION;
+ if ((r.serviceInfo.applicationInfo.flags
+ &ApplicationInfo.FLAG_PERSISTENT) != 0) {
+ minDuration /= 4;
+ }
+
// Any delivered but not yet finished starts should be put back
// on the pending list.
final int N = r.deliveredStarts.size();
@@ -8889,9 +9250,16 @@ public final class ActivityManagerService extends ActivityManagerNative
r.restartCount = 1;
r.restartDelay = minDuration;
} else {
- r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR;
- if (r.restartDelay < minDuration) {
- r.restartDelay = minDuration;
+ if ((r.serviceInfo.applicationInfo.flags
+ &ApplicationInfo.FLAG_PERSISTENT) != 0) {
+ // Services in peristent processes will restart much more
+ // quickly, since they are pretty important. (Think SystemUI).
+ r.restartDelay += minDuration/2;
+ } else {
+ r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR;
+ if (r.restartDelay < minDuration) {
+ r.restartDelay = minDuration;
+ }
}
}
}
@@ -10171,7 +10539,7 @@ public final class ActivityManagerService extends ActivityManagerNative
|| uidRemoved) {
if (checkComponentPermission(
android.Manifest.permission.BROADCAST_PACKAGE_REMOVED,
- callingPid, callingUid, -1)
+ callingPid, callingUid, -1, true)
== PackageManager.PERMISSION_GRANTED) {
if (uidRemoved) {
final Bundle intentExtras = intent.getExtras();
@@ -10230,6 +10598,15 @@ public final class ActivityManagerService extends ActivityManagerNative
mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
}
+ if (intent.ACTION_CLEAR_DNS_CACHE.equals(intent.getAction())) {
+ mHandler.sendEmptyMessage(CLEAR_DNS_CACHE);
+ }
+
+ if (Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())) {
+ ProxyProperties proxy = intent.getParcelableExtra("proxy");
+ mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY, proxy));
+ }
+
/*
* Prevent non-system code (defined here to be non-persistent
* processes) from sending protected broadcasts.
@@ -10830,7 +11207,7 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean skip = false;
if (filter.requiredPermission != null) {
int perm = checkComponentPermission(filter.requiredPermission,
- r.callingPid, r.callingUid, -1);
+ r.callingPid, r.callingUid, -1, true);
if (perm != PackageManager.PERMISSION_GRANTED) {
Slog.w(TAG, "Permission Denial: broadcasting "
+ r.intent.toString()
@@ -10843,7 +11220,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (r.requiredPermission != null) {
int perm = checkComponentPermission(r.requiredPermission,
- filter.receiverList.pid, filter.receiverList.uid, -1);
+ filter.receiverList.pid, filter.receiverList.uid, -1, true);
if (perm != PackageManager.PERMISSION_GRANTED) {
Slog.w(TAG, "Permission Denial: receiving "
+ r.intent.toString()
@@ -11109,17 +11486,26 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean skip = false;
int perm = checkComponentPermission(info.activityInfo.permission,
- r.callingPid, r.callingUid,
- info.activityInfo.exported
- ? -1 : info.activityInfo.applicationInfo.uid);
+ r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
+ info.activityInfo.exported);
if (perm != PackageManager.PERMISSION_GRANTED) {
- Slog.w(TAG, "Permission Denial: broadcasting "
- + r.intent.toString()
- + " from " + r.callerPackage + " (pid=" + r.callingPid
- + ", uid=" + r.callingUid + ")"
- + " requires " + info.activityInfo.permission
- + " due to receiver " + info.activityInfo.packageName
- + "/" + info.activityInfo.name);
+ if (!info.activityInfo.exported) {
+ Slog.w(TAG, "Permission Denial: broadcasting "
+ + r.intent.toString()
+ + " from " + r.callerPackage + " (pid=" + r.callingPid
+ + ", uid=" + r.callingUid + ")"
+ + " is not exported from uid " + info.activityInfo.applicationInfo.uid
+ + " due to receiver " + info.activityInfo.packageName
+ + "/" + info.activityInfo.name);
+ } else {
+ Slog.w(TAG, "Permission Denial: broadcasting "
+ + r.intent.toString()
+ + " from " + r.callerPackage + " (pid=" + r.callingPid
+ + ", uid=" + r.callingUid + ")"
+ + " requires " + info.activityInfo.permission
+ + " due to receiver " + info.activityInfo.packageName
+ + "/" + info.activityInfo.name);
+ }
skip = true;
}
if (r.callingUid != Process.SYSTEM_UID &&
@@ -11464,14 +11850,9 @@ public final class ActivityManagerService extends ActivityManagerNative
if (starting != null) {
kept = mMainStack.ensureActivityConfigurationLocked(starting, changes);
- if (kept) {
- // If this didn't result in the starting activity being
- // destroyed, then we need to make sure at this point that all
- // other activities are made visible.
- if (DEBUG_SWITCH) Slog.i(TAG, "Config didn't destroy " + starting
- + ", ensuring others are correct.");
- mMainStack.ensureActivitiesVisibleLocked(starting, changes);
- }
+ // And we need to make sure at this point that all other activities
+ // are made visible with the correct configuration.
+ mMainStack.ensureActivitiesVisibleLocked(starting, changes);
}
if (values != null && mWindowManager != null) {
@@ -11566,28 +11947,6 @@ public final class ActivityManagerService extends ActivityManagerNative
adj = FOREGROUND_APP_ADJ;
schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "exec-service";
- } else if (app.foregroundServices) {
- // The user is aware of this app, so make it visible.
- adj = PERCEPTIBLE_APP_ADJ;
- schedGroup = Process.THREAD_GROUP_DEFAULT;
- app.adjType = "foreground-service";
- } else if (app.forcingToForeground != null) {
- // The user is aware of this app, so make it visible.
- adj = PERCEPTIBLE_APP_ADJ;
- schedGroup = Process.THREAD_GROUP_DEFAULT;
- app.adjType = "force-foreground";
- app.adjSource = app.forcingToForeground;
- } else if (app == mHeavyWeightProcess) {
- // We don't want to kill the current heavy-weight process.
- adj = HEAVY_WEIGHT_APP_ADJ;
- schedGroup = Process.THREAD_GROUP_DEFAULT;
- app.adjType = "heavy";
- } else if (app == mHomeProcess) {
- // This process is hosting what we currently consider to be the
- // home app, so we don't want to let it go into the background.
- adj = HOME_APP_ADJ;
- schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
- app.adjType = "home";
} else if ((N=app.activities.size()) != 0) {
// This app is in the background with paused activities.
app.hidden = true;
@@ -11596,13 +11955,19 @@ public final class ActivityManagerService extends ActivityManagerNative
app.adjType = "bg-activities";
N = app.activities.size();
for (int j=0; j<N; j++) {
- if (app.activities.get(j).visible) {
+ ActivityRecord r = app.activities.get(j);
+ if (r.visible) {
// This app has a visible activity!
app.hidden = false;
adj = VISIBLE_APP_ADJ;
schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "visible";
break;
+ } else if (r.state == ActivityState.PAUSING
+ || r.state == ActivityState.PAUSED
+ || r.state == ActivityState.STOPPING) {
+ adj = PERCEPTIBLE_APP_ADJ;
+ app.adjType = "stopping";
}
}
} else {
@@ -11614,7 +11979,37 @@ public final class ActivityManagerService extends ActivityManagerNative
adj = hiddenAdj;
app.adjType = "bg-empty";
}
+
+ if (adj > PERCEPTIBLE_APP_ADJ) {
+ if (app.foregroundServices) {
+ // The user is aware of this app, so make it visible.
+ adj = PERCEPTIBLE_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_DEFAULT;
+ app.adjType = "foreground-service";
+ } else if (app.forcingToForeground != null) {
+ // The user is aware of this app, so make it visible.
+ adj = PERCEPTIBLE_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_DEFAULT;
+ app.adjType = "force-foreground";
+ app.adjSource = app.forcingToForeground;
+ }
+ }
+
+ if (adj > HEAVY_WEIGHT_APP_ADJ && app == mHeavyWeightProcess) {
+ // We don't want to kill the current heavy-weight process.
+ adj = HEAVY_WEIGHT_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
+ app.adjType = "heavy";
+ }
+ if (adj > HOME_APP_ADJ && app == mHomeProcess) {
+ // This process is hosting what we currently consider to be the
+ // home app, so we don't want to let it go into the background.
+ adj = HOME_APP_ADJ;
+ schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
+ app.adjType = "home";
+ }
+
//Slog.i(TAG, "OOM " + app + ": initial adj=" + adj);
// By default, we use the computed adjustment. It may be changed if
@@ -12505,7 +12900,69 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
-
+
+ public boolean dumpHeap(String process, boolean managed,
+ String path, ParcelFileDescriptor fd) throws RemoteException {
+
+ try {
+ synchronized (this) {
+ // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
+ // its own permission (same as profileControl).
+ if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires permission "
+ + android.Manifest.permission.SET_ACTIVITY_WATCHER);
+ }
+
+ if (fd == null) {
+ throw new IllegalArgumentException("null fd");
+ }
+
+ ProcessRecord proc = null;
+ try {
+ int pid = Integer.parseInt(process);
+ synchronized (mPidsSelfLocked) {
+ proc = mPidsSelfLocked.get(pid);
+ }
+ } catch (NumberFormatException e) {
+ }
+
+ if (proc == null) {
+ HashMap<String, SparseArray<ProcessRecord>> all
+ = mProcessNames.getMap();
+ SparseArray<ProcessRecord> procs = all.get(process);
+ if (procs != null && procs.size() > 0) {
+ proc = procs.valueAt(0);
+ }
+ }
+
+ if (proc == null || proc.thread == null) {
+ throw new IllegalArgumentException("Unknown process: " + process);
+ }
+
+ boolean isSecure = "1".equals(SystemProperties.get(SYSTEM_SECURE, "0"));
+ if (isSecure) {
+ if ((proc.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
+ throw new SecurityException("Process not debuggable: " + proc);
+ }
+ }
+
+ proc.thread.dumpHeap(managed, path, fd);
+ fd = null;
+ return true;
+ }
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Process disappeared");
+ } finally {
+ if (fd != null) {
+ try {
+ fd.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
/** In this method we try to acquire our lock to make sure that we have not deadlocked */
public void monitor() {
synchronized (this) { }
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index 4d773e4..0fb30ff 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -26,6 +26,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.os.Process;
@@ -36,6 +37,7 @@ import android.util.Log;
import android.util.Slog;
import android.util.TimeUtils;
import android.view.IApplicationToken;
+import android.view.WindowManager;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -58,7 +60,8 @@ class ActivityRecord extends IApplicationToken.Stub {
final String processName; // process where this component wants to run
final String taskAffinity; // as per ActivityInfo.taskAffinity
final boolean stateNotNeeded; // As per ActivityInfo.flags
- final boolean fullscreen; // covers the full screen?
+ final boolean fullscreen; // covers the full screen?
+ final boolean noDisplay; // activity is not displayed?
final boolean componentSpecified; // did caller specifiy an explicit component?
final boolean isHomeActivity; // do we consider this to be a home activity?
final String baseDir; // where activity source (resources etc) located
@@ -68,6 +71,8 @@ class ActivityRecord extends IApplicationToken.Stub {
int labelRes; // the label information from the package mgr.
int icon; // resource identifier of activity's icon.
int theme; // resource identifier of activity's theme.
+ int realTheme; // actual theme resource we will use, never 0.
+ int windowFlags; // custom window flags for preview window.
TaskRecord task; // the task this is in.
long launchTime; // when we starting launching this activity
long startTime; // last time this activity was started
@@ -98,12 +103,14 @@ class ActivityRecord extends IApplicationToken.Stub {
boolean inHistory; // are we in the history stack?
int launchMode; // the launch mode activity attribute.
boolean visible; // does this activity's window need to be shown?
+ boolean sleeping; // have we told the activity to sleep?
boolean waitingVisible; // true if waiting for a new act to become vis
boolean nowVisible; // is this activity's window visible?
boolean thumbnailNeeded;// has someone requested a thumbnail?
boolean idle; // has the activity gone idle?
boolean hasBeenLaunched;// has this activity ever been launched?
boolean frozenBeforeDestroy;// has been frozen but not yet destroyed.
+ boolean immersive; // immersive mode (don't interrupt if possible)
String stringName; // for caching of toString().
@@ -159,12 +166,15 @@ class ActivityRecord extends IApplicationToken.Stub {
pw.print(" finishing="); pw.println(finishing);
pw.print(prefix); pw.print("keysPaused="); pw.print(keysPaused);
pw.print(" inHistory="); pw.print(inHistory);
- pw.print(" launchMode="); pw.println(launchMode);
- pw.print(prefix); pw.print("fullscreen="); pw.print(fullscreen);
pw.print(" visible="); pw.print(visible);
- pw.print(" frozenBeforeDestroy="); pw.print(frozenBeforeDestroy);
- pw.print(" thumbnailNeeded="); pw.print(thumbnailNeeded);
+ pw.print(" sleeping="); pw.print(sleeping);
pw.print(" idle="); pw.println(idle);
+ pw.print(prefix); pw.print("fullscreen="); pw.print(fullscreen);
+ pw.print(" noDisplay="); pw.print(noDisplay);
+ pw.print(" immersive="); pw.print(immersive);
+ pw.print(" launchMode="); pw.println(launchMode);
+ pw.print(prefix); pw.print("frozenBeforeDestroy="); pw.print(frozenBeforeDestroy);
+ pw.print(" thumbnailNeeded="); pw.println(thumbnailNeeded);
if (launchTime != 0 || startTime != 0) {
pw.print(prefix); pw.print("launchTime=");
TimeUtils.formatDuration(launchTime, pw); pw.print(" startTime=");
@@ -242,6 +252,16 @@ class ActivityRecord extends IApplicationToken.Stub {
}
icon = aInfo.getIconResource();
theme = aInfo.getThemeResource();
+ realTheme = theme;
+ if (realTheme == 0) {
+ realTheme = aInfo.applicationInfo.targetSdkVersion
+ < Build.VERSION_CODES.HONEYCOMB
+ ? android.R.style.Theme
+ : android.R.style.Theme_Holo;
+ }
+ if ((aInfo.flags&ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
+ windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ }
if ((aInfo.flags&ActivityInfo.FLAG_MULTIPROCESS) != 0
&& _caller != null
&& (aInfo.applicationInfo.uid == Process.SYSTEM_UID
@@ -259,12 +279,13 @@ class ActivityRecord extends IApplicationToken.Stub {
launchMode = aInfo.launchMode;
AttributeCache.Entry ent = AttributeCache.instance().get(packageName,
- theme != 0 ? theme : android.R.style.Theme,
- com.android.internal.R.styleable.Window);
+ realTheme, com.android.internal.R.styleable.Window);
fullscreen = ent != null && !ent.array.getBoolean(
com.android.internal.R.styleable.Window_windowIsFloating, false)
&& !ent.array.getBoolean(
com.android.internal.R.styleable.Window_windowIsTranslucent, false);
+ noDisplay = ent != null && ent.array.getBoolean(
+ com.android.internal.R.styleable.Window_windowNoDisplay, false);
if (!_componentSpecified || _launchedFromUid == Process.myUid()
|| _launchedFromUid == 0) {
@@ -289,6 +310,8 @@ class ActivityRecord extends IApplicationToken.Stub {
} else {
isHomeActivity = false;
}
+
+ immersive = (aInfo.flags & ActivityInfo.FLAG_IMMERSIVE) != 0;
} else {
realActivity = null;
taskAffinity = null;
@@ -299,7 +322,18 @@ class ActivityRecord extends IApplicationToken.Stub {
processName = null;
packageName = null;
fullscreen = true;
+ noDisplay = false;
isHomeActivity = false;
+ immersive = false;
+ }
+ }
+
+ void makeFinishing() {
+ if (!finishing) {
+ finishing = true;
+ if (task != null) {
+ task.numActivities--;
+ }
}
}
@@ -403,7 +437,7 @@ class ActivityRecord extends IApplicationToken.Stub {
// an application, and that application is not blocked or unresponding.
// In any other case, we can't count on getting the screen unfrozen,
// so it is best to leave as-is.
- return app == null || (!app.crashing && !app.notResponding);
+ return app != null && !app.crashing && !app.notResponding;
}
public void startFreezingScreenLocked(ProcessRecord app, int configChanges) {
@@ -570,14 +604,32 @@ class ActivityRecord extends IApplicationToken.Stub {
public boolean isInterestingToUserLocked() {
return visible || nowVisible || state == ActivityState.PAUSING ||
state == ActivityState.RESUMED;
- }
+ }
+
+ public void setSleeping(boolean _sleeping) {
+ if (sleeping == _sleeping) {
+ return;
+ }
+ if (app != null && app.thread != null) {
+ try {
+ app.thread.scheduleSleeping(this, _sleeping);
+ if (sleeping && !stack.mGoingToSleepActivities.contains(this)) {
+ stack.mGoingToSleepActivities.add(this);
+ }
+ sleeping = _sleeping;
+ } catch (RemoteException e) {
+ Slog.w(ActivityStack.TAG, "Exception thrown when sleeping: "
+ + intent.getComponent(), e);
+ }
+ }
+ }
public String toString() {
if (stringName != null) {
return stringName;
}
StringBuilder sb = new StringBuilder(128);
- sb.append("HistoryRecord{");
+ sb.append("ActivityRecord{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(' ');
sb.append(intent.getComponent().flattenToShortString());
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 463493b..5b44d39 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -46,9 +46,10 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -91,6 +92,9 @@ public class ActivityStack {
// next activity.
static final int PAUSE_TIMEOUT = 500;
+ // How long we can hold the sleep wake lock before giving up.
+ static final int SLEEP_TIMEOUT = 5*1000;
+
// How long we can hold the launch wake lock before giving up.
static final int LAUNCH_TIMEOUT = 10*1000;
@@ -99,8 +103,8 @@ public class ActivityStack {
static final int DESTROY_TIMEOUT = 10*1000;
// How long until we reset a task when the user returns to it. Currently
- // 30 minutes.
- static final long ACTIVITY_INACTIVE_RESET_TIME = 1000*60*30;
+ // disabled.
+ static final long ACTIVITY_INACTIVE_RESET_TIME = 0;
// How long between activity launches that we consider safe to not warn
// the user about an unexpected activity being launched on top.
@@ -157,6 +161,12 @@ public class ActivityStack {
= new ArrayList<ActivityRecord>();
/**
+ * List of activities that are in the process of going to sleep.
+ */
+ final ArrayList<ActivityRecord> mGoingToSleepActivities
+ = new ArrayList<ActivityRecord>();
+
+ /**
* Animations that for the current transition have requested not to
* be considered for the transition animation.
*/
@@ -237,6 +247,15 @@ public class ActivityStack {
long mInitialStartTime = 0;
+ /**
+ * Set when we have taken too long waiting to go to sleep.
+ */
+ boolean mSleepTimeout = false;
+
+ int mThumbnailWidth = -1;
+ int mThumbnailHeight = -1;
+
+ static final int SLEEP_TIMEOUT_MSG = 8;
static final int PAUSE_TIMEOUT_MSG = 9;
static final int IDLE_TIMEOUT_MSG = 10;
static final int IDLE_NOW_MSG = 11;
@@ -251,12 +270,19 @@ public class ActivityStack {
public void handleMessage(Message msg) {
switch (msg.what) {
+ case SLEEP_TIMEOUT_MSG: {
+ if (mService.isSleeping()) {
+ Slog.w(TAG, "Sleep timeout! Sleeping now.");
+ mSleepTimeout = true;
+ checkReadyForSleepLocked();
+ }
+ } break;
case PAUSE_TIMEOUT_MSG: {
IBinder token = (IBinder)msg.obj;
// We don't at this point know if the activity is fullscreen,
// so we need to be conservative and assume it isn't.
Slog.w(TAG, "Activity pause timeout for " + token);
- activityPaused(token, null, true);
+ activityPaused(token, true);
} break;
case IDLE_TIMEOUT_MSG: {
if (mService.mDidDexOpt) {
@@ -510,6 +536,7 @@ public class ActivityStack {
mService.mHomeProcess = app;
}
mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName());
+ r.sleeping = false;
app.thread.scheduleLaunchActivity(new Intent(r.intent), r,
System.identityHashCode(r),
r.info, r.icicle, results, newIntents, !andResume,
@@ -564,13 +591,14 @@ public class ActivityStack {
// As part of the process of launching, ActivityThread also performs
// a resume.
r.state = ActivityState.RESUMED;
- r.icicle = null;
- r.haveState = false;
r.stopped = false;
mResumedActivity = r;
r.task.touchActiveTime();
+ if (mMainStack) {
+ mService.addRecentTaskLocked(r.task);
+ }
completeResumeLocked(r);
- pauseIfSleepingLocked();
+ checkReadyForSleepLocked();
} else {
// This activity is not starting in the resumed state... which
// should look like we asked it to pause+stop (but remain visible),
@@ -580,6 +608,9 @@ public class ActivityStack {
r.stopped = true;
}
+ r.icicle = null;
+ r.haveState = false;
+
// Launch the new version setup screen if needed. We do this -after-
// launching the initial activity (that is, home), so that it can have
// a chance to initialize itself while in the background, making the
@@ -623,8 +654,8 @@ public class ActivityStack {
"activity", r.intent.getComponent(), false);
}
- void pauseIfSleepingLocked() {
- if (mService.mSleeping || mService.mShuttingDown) {
+ void stopIfSleepingLocked() {
+ if (mService.isSleeping()) {
if (!mGoingToSleep.isHeld()) {
mGoingToSleep.acquire();
if (mLaunchingActivity.isHeld()) {
@@ -632,18 +663,113 @@ public class ActivityStack {
mService.mHandler.removeMessages(LAUNCH_TIMEOUT_MSG);
}
}
+ mHandler.removeMessages(SLEEP_TIMEOUT_MSG);
+ Message msg = mHandler.obtainMessage(SLEEP_TIMEOUT_MSG);
+ mHandler.sendMessageDelayed(msg, SLEEP_TIMEOUT);
+ checkReadyForSleepLocked();
+ }
+ }
- // If we are not currently pausing an activity, get the current
- // one to pause. If we are pausing one, we will just let that stuff
- // run and release the wake lock when all done.
- if (mPausingActivity == null) {
- if (DEBUG_PAUSE) Slog.v(TAG, "Sleep needs to pause...");
+ void awakeFromSleepingLocked() {
+ mHandler.removeMessages(SLEEP_TIMEOUT_MSG);
+ mSleepTimeout = false;
+ if (mGoingToSleep.isHeld()) {
+ mGoingToSleep.release();
+ }
+ // Ensure activities are no longer sleeping.
+ for (int i=mHistory.size()-1; i>=0; i--) {
+ ActivityRecord r = (ActivityRecord)mHistory.get(i);
+ r.setSleeping(false);
+ }
+ mGoingToSleepActivities.clear();
+ }
+
+ void activitySleptLocked(ActivityRecord r) {
+ mGoingToSleepActivities.remove(r);
+ checkReadyForSleepLocked();
+ }
+
+ void checkReadyForSleepLocked() {
+ if (!mService.isSleeping()) {
+ // Do not care.
+ return;
+ }
+
+ if (!mSleepTimeout) {
+ if (mResumedActivity != null) {
+ // Still have something resumed; can't sleep until it is paused.
+ if (DEBUG_PAUSE) Slog.v(TAG, "Sleep needs to pause " + mResumedActivity);
if (DEBUG_USER_LEAVING) Slog.v(TAG, "Sleep => pause with userLeaving=false");
startPausingLocked(false, true);
+ return;
+ }
+ if (mPausingActivity != null) {
+ // Still waiting for something to pause; can't sleep yet.
+ if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still waiting to pause " + mPausingActivity);
+ return;
+ }
+
+ if (mStoppingActivities.size() > 0) {
+ // Still need to tell some activities to stop; can't sleep yet.
+ if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still need to stop "
+ + mStoppingActivities.size() + " activities");
+ Message msg = Message.obtain();
+ msg.what = IDLE_NOW_MSG;
+ mHandler.sendMessage(msg);
+ return;
}
+
+ ensureActivitiesVisibleLocked(null, 0);
+
+ // Make sure any stopped but visible activities are now sleeping.
+ // This ensures that the activity's onStop() is called.
+ for (int i=mHistory.size()-1; i>=0; i--) {
+ ActivityRecord r = (ActivityRecord)mHistory.get(i);
+ if (r.state == ActivityState.STOPPING || r.state == ActivityState.STOPPED) {
+ r.setSleeping(true);
+ }
+ }
+
+ if (mGoingToSleepActivities.size() > 0) {
+ // Still need to tell some activities to sleep; can't sleep yet.
+ if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still need to sleep "
+ + mGoingToSleepActivities.size() + " activities");
+ return;
+ }
+ }
+
+ mHandler.removeMessages(SLEEP_TIMEOUT_MSG);
+
+ if (mGoingToSleep.isHeld()) {
+ mGoingToSleep.release();
+ }
+ if (mService.mShuttingDown) {
+ mService.notifyAll();
}
+
}
+ public final Bitmap screenshotActivities(ActivityRecord who) {
+ if (who.noDisplay) {
+ return null;
+ }
+
+ Resources res = mService.mContext.getResources();
+ int w = mThumbnailWidth;
+ int h = mThumbnailHeight;
+ if (w < 0) {
+ mThumbnailWidth = w =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
+ mThumbnailHeight = h =
+ res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
+ }
+
+ if (w > 0) {
+ return mService.mWindowManager.screenshotApplications(who, w, h);
+ }
+ return null;
+ }
+
private final void startPausingLocked(boolean userLeaving, boolean uiSleeping) {
if (mPausingActivity != null) {
RuntimeException e = new RuntimeException();
@@ -663,6 +789,10 @@ public class ActivityStack {
mLastPausedActivity = prev;
prev.state = ActivityState.PAUSING;
prev.task.touchActiveTime();
+ prev.thumbnail = screenshotActivities(prev);
+ if (prev.task != null) {
+ prev.task.lastThumbnail = prev.thumbnail;
+ }
mService.updateCpuStats();
@@ -726,10 +856,9 @@ public class ActivityStack {
}
}
- final void activityPaused(IBinder token, Bundle icicle, boolean timeout) {
+ final void activityPaused(IBinder token, boolean timeout) {
if (DEBUG_PAUSE) Slog.v(
- TAG, "Activity paused: token=" + token + ", icicle=" + icicle
- + ", timeout=" + timeout);
+ TAG, "Activity paused: token=" + token + ", timeout=" + timeout);
ActivityRecord r = null;
@@ -737,10 +866,6 @@ public class ActivityStack {
int index = indexOfTokenLocked(token);
if (index >= 0) {
r = (ActivityRecord)mHistory.get(index);
- if (!timeout) {
- r.icicle = icicle;
- r.haveState = true;
- }
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
if (mPausingActivity == r) {
r.state = ActivityState.PAUSED;
@@ -789,6 +914,8 @@ public class ActivityStack {
Message msg = Message.obtain();
msg.what = IDLE_NOW_MSG;
mHandler.sendMessage(msg);
+ } else {
+ checkReadyForSleepLocked();
}
}
} else {
@@ -798,15 +925,10 @@ public class ActivityStack {
mPausingActivity = null;
}
- if (!mService.mSleeping && !mService.mShuttingDown) {
+ if (!mService.isSleeping()) {
resumeTopActivityLocked(prev);
} else {
- if (mGoingToSleep.isHeld()) {
- mGoingToSleep.release();
- }
- if (mService.mShuttingDown) {
- mService.notifyAll();
- }
+ checkReadyForSleepLocked();
}
if (prev != null) {
@@ -961,6 +1083,7 @@ public class ActivityStack {
TAG, "Making visible and scheduling visibility: " + r);
try {
mService.mWindowManager.setAppVisibility(r, true);
+ r.sleeping = false;
r.app.thread.scheduleWindowVisibility(r, true);
r.stopFreezingScreenLocked(false);
} catch (Exception e) {
@@ -1090,6 +1213,8 @@ public class ActivityStack {
// The activity may be waiting for stop, but that is no longer
// appropriate for it.
mStoppingActivities.remove(next);
+ mGoingToSleepActivities.remove(next);
+ next.sleeping = false;
mWaitingVisibleActivities.remove(next);
if (DEBUG_SWITCH) Slog.v(TAG, "Resuming " + next);
@@ -1176,11 +1301,12 @@ public class ActivityStack {
if (DEBUG_TRANSITION) Slog.v(TAG,
"Prepare close transition: prev=" + prev);
if (mNoAnimActivities.contains(prev)) {
- mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE);
+ mService.mWindowManager.prepareAppTransition(
+ WindowManagerPolicy.TRANSIT_NONE, false);
} else {
mService.mWindowManager.prepareAppTransition(prev.task == next.task
? WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE
- : WindowManagerPolicy.TRANSIT_TASK_CLOSE);
+ : WindowManagerPolicy.TRANSIT_TASK_CLOSE, false);
}
mService.mWindowManager.setAppWillBeHidden(prev);
mService.mWindowManager.setAppVisibility(prev, false);
@@ -1188,11 +1314,12 @@ public class ActivityStack {
if (DEBUG_TRANSITION) Slog.v(TAG,
"Prepare open transition: prev=" + prev);
if (mNoAnimActivities.contains(next)) {
- mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE);
+ mService.mWindowManager.prepareAppTransition(
+ WindowManagerPolicy.TRANSIT_NONE, false);
} else {
mService.mWindowManager.prepareAppTransition(prev.task == next.task
? WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN
- : WindowManagerPolicy.TRANSIT_TASK_OPEN);
+ : WindowManagerPolicy.TRANSIT_TASK_OPEN, false);
}
}
if (false) {
@@ -1203,9 +1330,11 @@ public class ActivityStack {
if (DEBUG_TRANSITION) Slog.v(TAG,
"Prepare open transition: no previous");
if (mNoAnimActivities.contains(next)) {
- mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE);
+ mService.mWindowManager.prepareAppTransition(
+ WindowManagerPolicy.TRANSIT_NONE, false);
} else {
- mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN);
+ mService.mWindowManager.prepareAppTransition(
+ WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN, false);
}
}
@@ -1223,6 +1352,9 @@ public class ActivityStack {
next.state = ActivityState.RESUMED;
mResumedActivity = next;
next.task.touchActiveTime();
+ if (mMainStack) {
+ mService.addRecentTaskLocked(next.task);
+ }
mService.updateLruProcessLocked(next.app, true, true);
updateLRUListLocked(next);
@@ -1284,10 +1416,11 @@ public class ActivityStack {
System.identityHashCode(next),
next.task.taskId, next.shortComponentName);
+ next.sleeping = false;
next.app.thread.scheduleResumeActivity(next,
mService.isNextTransitionForward());
- pauseIfSleepingLocked();
+ checkReadyForSleepLocked();
} catch (Exception e) {
// Whoops, need to restart this activity!
@@ -1301,7 +1434,8 @@ public class ActivityStack {
mService.mWindowManager.setAppStartingWindow(
next, next.packageName, next.theme,
next.nonLocalizedLabel,
- next.labelRes, next.icon, null, true);
+ next.labelRes, next.icon, next.windowFlags,
+ null, true);
}
}
startSpecificActivityLocked(next, true, false);
@@ -1336,7 +1470,8 @@ public class ActivityStack {
mService.mWindowManager.setAppStartingWindow(
next, next.packageName, next.theme,
next.nonLocalizedLabel,
- next.labelRes, next.icon, null, true);
+ next.labelRes, next.icon, next.windowFlags,
+ null, true);
}
if (DEBUG_SWITCH) Slog.v(TAG, "Restarting: " + next);
}
@@ -1347,7 +1482,7 @@ public class ActivityStack {
}
private final void startActivityLocked(ActivityRecord r, boolean newTask,
- boolean doResume) {
+ boolean doResume, boolean keepCurTransition) {
final int NH = mHistory.size();
int addPos = -1;
@@ -1418,16 +1553,17 @@ public class ActivityStack {
if (DEBUG_TRANSITION) Slog.v(TAG,
"Prepare open transition: starting " + r);
if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
- mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE);
+ mService.mWindowManager.prepareAppTransition(
+ WindowManagerPolicy.TRANSIT_NONE, keepCurTransition);
mNoAnimActivities.add(r);
} else if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
mService.mWindowManager.prepareAppTransition(
- WindowManagerPolicy.TRANSIT_TASK_OPEN);
+ WindowManagerPolicy.TRANSIT_TASK_OPEN, keepCurTransition);
mNoAnimActivities.remove(r);
} else {
mService.mWindowManager.prepareAppTransition(newTask
? WindowManagerPolicy.TRANSIT_TASK_OPEN
- : WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN);
+ : WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN, keepCurTransition);
mNoAnimActivities.remove(r);
}
mService.mWindowManager.addAppToken(
@@ -1460,7 +1596,7 @@ public class ActivityStack {
}
mService.mWindowManager.setAppStartingWindow(
r, r.packageName, r.theme, r.nonLocalizedLabel,
- r.labelRes, r.icon, prev, showStartingIcon);
+ r.labelRes, r.icon, r.windowFlags, prev, showStartingIcon);
}
} else {
// If this is the first activity, don't do any fancy animations,
@@ -1485,7 +1621,8 @@ public class ActivityStack {
ActivityRecord newActivity) {
boolean forceReset = (newActivity.info.flags
&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0;
- if (taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) {
+ if (ACTIVITY_INACTIVE_RESET_TIME > 0
+ && taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) {
if ((newActivity.info.flags
&ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE) == 0) {
forceReset = true;
@@ -1571,8 +1708,7 @@ public class ActivityStack {
if (mService.mCurTask <= 0) {
mService.mCurTask = 1;
}
- target.task = new TaskRecord(mService.mCurTask, target.info, null,
- (target.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0);
+ target.task = new TaskRecord(mService.mCurTask, target.info, null);
target.task.affinityIntent = target.intent;
if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target
+ " out to new task " + target.task);
@@ -1609,9 +1745,6 @@ public class ActivityStack {
taskTopI = -1;
}
replyChainEnd = -1;
- if (mMainStack) {
- mService.addRecentTaskLocked(target.task);
- }
} else if (forceReset || finishOnTaskLaunch
|| clearWhenTaskReset) {
// If the activity should just be removed -- either
@@ -1774,11 +1907,11 @@ public class ActivityStack {
* activities on top of it and return the instance.
*
* @param newR Description of the new activity being started.
- * @return Returns the old activity that should be continue to be used,
+ * @return Returns the old activity that should be continued to be used,
* or null if none was found.
*/
private final ActivityRecord performClearTaskLocked(int taskId,
- ActivityRecord newR, int launchFlags, boolean doClear) {
+ ActivityRecord newR, int launchFlags) {
int i = mHistory.size();
// First find the requested task.
@@ -1804,17 +1937,18 @@ public class ActivityStack {
if (r.realActivity.equals(newR.realActivity)) {
// Here it is! Now finish everything in front...
ActivityRecord ret = r;
- if (doClear) {
- while (i < (mHistory.size()-1)) {
- i++;
- r = (ActivityRecord)mHistory.get(i);
- if (r.finishing) {
- continue;
- }
- if (finishActivityLocked(r, i, Activity.RESULT_CANCELED,
- null, "clear")) {
- i--;
- }
+ while (i < (mHistory.size()-1)) {
+ i++;
+ r = (ActivityRecord)mHistory.get(i);
+ if (r.task.taskId != taskId) {
+ break;
+ }
+ if (r.finishing) {
+ continue;
+ }
+ if (finishActivityLocked(r, i, Activity.RESULT_CANCELED,
+ null, "clear")) {
+ i--;
}
}
@@ -1841,6 +1975,51 @@ public class ActivityStack {
}
/**
+ * Completely remove all activities associated with an existing task.
+ */
+ private final void performClearTaskLocked(int taskId) {
+ int i = mHistory.size();
+
+ // First find the requested task.
+ while (i > 0) {
+ i--;
+ ActivityRecord r = (ActivityRecord)mHistory.get(i);
+ if (r.task.taskId == taskId) {
+ i++;
+ break;
+ }
+ }
+
+ // Now clear it.
+ while (i > 0) {
+ i--;
+ ActivityRecord r = (ActivityRecord)mHistory.get(i);
+ if (r.finishing) {
+ continue;
+ }
+ if (r.task.taskId != taskId) {
+ // We hit the bottom. Now finish it all...
+ while (i < (mHistory.size()-1)) {
+ i++;
+ r = (ActivityRecord)mHistory.get(i);
+ if (r.task.taskId != taskId) {
+ // Whoops hit the end.
+ return;
+ }
+ if (r.finishing) {
+ continue;
+ }
+ if (finishActivityLocked(r, i, Activity.RESULT_CANCELED,
+ null, "clear")) {
+ i--;
+ }
+ }
+ return;
+ }
+ }
+ }
+
+ /**
* Find the activity in the history stack within the given task. Returns
* the index within the history at which it's found, or < 0 if not found.
*/
@@ -1880,7 +2059,7 @@ public class ActivityStack {
int grantedMode, ActivityInfo aInfo, IBinder resultTo,
String resultWho, int requestCode,
int callingPid, int callingUid, boolean onlyIfNeeded,
- boolean componentSpecified) {
+ boolean componentSpecified, ActivityRecord[] outActivity) {
int err = START_SUCCESS;
@@ -1958,17 +2137,25 @@ public class ActivityStack {
}
final int perm = mService.checkComponentPermission(aInfo.permission, callingPid,
- callingUid, aInfo.exported ? -1 : aInfo.applicationInfo.uid);
+ callingUid, aInfo.applicationInfo.uid, aInfo.exported);
if (perm != PackageManager.PERMISSION_GRANTED) {
if (resultRecord != null) {
sendActivityResultLocked(-1,
resultRecord, resultWho, requestCode,
Activity.RESULT_CANCELED, null);
}
- String msg = "Permission Denial: starting " + intent.toString()
- + " from " + callerApp + " (pid=" + callingPid
- + ", uid=" + callingUid + ")"
- + " requires " + aInfo.permission;
+ String msg;
+ if (!aInfo.exported) {
+ msg = "Permission Denial: starting " + intent.toString()
+ + " from " + callerApp + " (pid=" + callingPid
+ + ", uid=" + callingUid + ")"
+ + " not exported from uid " + aInfo.applicationInfo.uid;
+ } else {
+ msg = "Permission Denial: starting " + intent.toString()
+ + " from " + callerApp + " (pid=" + callingPid
+ + ", uid=" + callingUid + ")"
+ + " requires " + aInfo.permission;
+ }
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
@@ -2002,6 +2189,9 @@ public class ActivityStack {
ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid,
intent, resolvedType, aInfo, mService.mConfiguration,
resultRecord, resultWho, requestCode, componentSpecified);
+ if (outActivity != null) {
+ outActivity[0] = r;
+ }
if (mMainStack) {
if (mResumedActivity == null
@@ -2036,6 +2226,16 @@ public class ActivityStack {
grantedUriPermissions, grantedMode, onlyIfNeeded, true);
}
+ final void moveHomeToFrontFromLaunchLocked(int launchFlags) {
+ if ((launchFlags &
+ (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME))
+ == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) {
+ // Caller wants to appear on home activity, so before starting
+ // their own activity we will bring home to the front.
+ moveHomeToFrontLocked();
+ }
+ }
+
final int startActivityUncheckedLocked(ActivityRecord r,
ActivityRecord sourceRecord, Uri[] grantedUriPermissions,
int grantedMode, boolean onlyIfNeeded, boolean doResume) {
@@ -2109,6 +2309,7 @@ public class ActivityStack {
}
boolean addingToTask = false;
+ TaskRecord reuseTask = null;
if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
(launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
@@ -2146,6 +2347,7 @@ public class ActivityStack {
if (callerAtFront) {
// We really do want to push this one into the
// user's face, right now.
+ moveHomeToFrontFromLaunchLocked(launchFlags);
moveTaskToFrontLocked(taskTop.task, r);
}
}
@@ -2164,7 +2366,16 @@ public class ActivityStack {
}
return START_RETURN_INTENT_TO_CALLER;
}
- if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0
+ if ((launchFlags &
+ (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK))
+ == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) {
+ // The caller has requested to completely replace any
+ // existing task with its new activity. Well that should
+ // not be too hard...
+ reuseTask = taskTop.task;
+ performClearTaskLocked(taskTop.task.taskId);
+ reuseTask.setIntent(r.intent, r.info);
+ } else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
|| r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
// In this situation we want to remove all activities
@@ -2172,7 +2383,7 @@ public class ActivityStack {
// cases this means we are resetting the task to its
// initial state.
ActivityRecord top = performClearTaskLocked(
- taskTop.task.taskId, r, launchFlags, true);
+ taskTop.task.taskId, r, launchFlags);
if (top != null) {
if (top.frontOfTask) {
// Activity aliases may mean we use different
@@ -2233,7 +2444,7 @@ public class ActivityStack {
// for now we'll just drop it.
taskTop.task.setIntent(r.intent, r.info);
}
- if (!addingToTask) {
+ if (!addingToTask && reuseTask == null) {
// We didn't do anything... but it was needed (a.k.a., client
// don't use that intent!) And for paranoia, make
// sure we have correctly resumed the top activity.
@@ -2292,23 +2503,25 @@ public class ActivityStack {
}
boolean newTask = false;
+ boolean keepCurTransition = false;
// Should this be considered a new task?
if (r.resultTo == null && !addingToTask
&& (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
- // todo: should do better management of integers.
- mService.mCurTask++;
- if (mService.mCurTask <= 0) {
- mService.mCurTask = 1;
+ if (reuseTask == null) {
+ // todo: should do better management of integers.
+ mService.mCurTask++;
+ if (mService.mCurTask <= 0) {
+ mService.mCurTask = 1;
+ }
+ r.task = new TaskRecord(mService.mCurTask, r.info, intent);
+ if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
+ + " in new task " + r.task);
+ } else {
+ r.task = reuseTask;
}
- r.task = new TaskRecord(mService.mCurTask, r.info, intent,
- (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0);
- if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
- + " in new task " + r.task);
newTask = true;
- if (mMainStack) {
- mService.addRecentTaskLocked(r.task);
- }
+ moveHomeToFrontFromLaunchLocked(launchFlags);
} else if (sourceRecord != null) {
if (!addingToTask &&
@@ -2317,7 +2530,8 @@ public class ActivityStack {
// task, but the caller has asked to clear that task if the
// activity is already running.
ActivityRecord top = performClearTaskLocked(
- sourceRecord.task.taskId, r, launchFlags, true);
+ sourceRecord.task.taskId, r, launchFlags);
+ keepCurTransition = true;
if (top != null) {
logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task);
top.deliverNewIntentLocked(callingUid, r.intent);
@@ -2359,9 +2573,8 @@ public class ActivityStack {
ActivityRecord prev =
N > 0 ? (ActivityRecord)mHistory.get(N-1) : null;
r.task = prev != null
- ? prev.task
- : new TaskRecord(mService.mCurTask, r.info, intent,
- (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0);
+ ? prev.task
+ : new TaskRecord(mService.mCurTask, r.info, intent);
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
+ " in new guessed " + r.task);
}
@@ -2380,25 +2593,11 @@ public class ActivityStack {
EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.task.taskId);
}
logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task);
- startActivityLocked(r, newTask, doResume);
+ startActivityLocked(r, newTask, doResume, keepCurTransition);
return START_SUCCESS;
}
- final int startActivityMayWait(IApplicationThread caller,
- Intent intent, String resolvedType, Uri[] grantedUriPermissions,
- int grantedMode, IBinder resultTo,
- String resultWho, int requestCode, boolean onlyIfNeeded,
- boolean debug, WaitResult outResult, Configuration config) {
- // Refuse possible leaked file descriptors
- if (intent != null && intent.hasFileDescriptors()) {
- throw new IllegalArgumentException("File descriptors passed in Intent");
- }
-
- boolean componentSpecified = intent.getComponent() != null;
-
- // Don't modify the client's object!
- intent = new Intent(intent);
-
+ ActivityInfo resolveActivity(Intent intent, String resolvedType, boolean debug) {
// Collect information about the target of the Intent.
ActivityInfo aInfo;
try {
@@ -2427,11 +2626,32 @@ public class ActivityStack {
}
}
}
+ return aInfo;
+ }
+
+ final int startActivityMayWait(IApplicationThread caller, int callingUid,
+ Intent intent, String resolvedType, Uri[] grantedUriPermissions,
+ int grantedMode, IBinder resultTo,
+ String resultWho, int requestCode, boolean onlyIfNeeded,
+ boolean debug, WaitResult outResult, Configuration config) {
+ // Refuse possible leaked file descriptors
+ if (intent != null && intent.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+
+ boolean componentSpecified = intent.getComponent() != null;
+
+ // Don't modify the client's object!
+ intent = new Intent(intent);
+
+ // Collect information about the target of the Intent.
+ ActivityInfo aInfo = resolveActivity(intent, resolvedType, debug);
synchronized (mService) {
int callingPid;
- int callingUid;
- if (caller == null) {
+ if (callingUid >= 0) {
+ callingPid = -1;
+ } else if (caller == null) {
callingPid = Binder.getCallingPid();
callingUid = Binder.getCallingUid();
} else {
@@ -2470,8 +2690,8 @@ public class ActivityStack {
IIntentSender target = mService.getIntentSenderLocked(
IActivityManager.INTENT_SENDER_ACTIVITY, "android",
- realCallingUid, null, null, 0, intent,
- resolvedType, PendingIntent.FLAG_CANCEL_CURRENT
+ realCallingUid, null, null, 0, new Intent[] { intent },
+ new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT
| PendingIntent.FLAG_ONE_SHOT);
Intent newIntent = new Intent();
@@ -2516,7 +2736,7 @@ public class ActivityStack {
int res = startActivityLocked(caller, intent, resolvedType,
grantedUriPermissions, grantedMode, aInfo,
resultTo, resultWho, requestCode, callingPid, callingUid,
- onlyIfNeeded, componentSpecified);
+ onlyIfNeeded, componentSpecified, null);
if (mConfigWillChange && mMainStack) {
// If the caller also wants to switch to a new configuration,
@@ -2567,6 +2787,75 @@ public class ActivityStack {
}
}
+ final int startActivities(IApplicationThread caller, int callingUid,
+ Intent[] intents, String[] resolvedTypes, IBinder resultTo) {
+ if (intents == null) {
+ throw new NullPointerException("intents is null");
+ }
+ if (resolvedTypes == null) {
+ throw new NullPointerException("resolvedTypes is null");
+ }
+ if (intents.length != resolvedTypes.length) {
+ throw new IllegalArgumentException("intents are length different than resolvedTypes");
+ }
+
+ ActivityRecord[] outActivity = new ActivityRecord[1];
+
+ int callingPid;
+ if (callingUid >= 0) {
+ callingPid = -1;
+ } else if (caller == null) {
+ callingPid = Binder.getCallingPid();
+ callingUid = Binder.getCallingUid();
+ } else {
+ callingPid = callingUid = -1;
+ }
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mService) {
+
+ for (int i=0; i<intents.length; i++) {
+ Intent intent = intents[i];
+ if (intent == null) {
+ continue;
+ }
+
+ // Refuse possible leaked file descriptors
+ if (intent != null && intent.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+
+ boolean componentSpecified = intent.getComponent() != null;
+
+ // Don't modify the client's object!
+ intent = new Intent(intent);
+
+ // Collect information about the target of the Intent.
+ ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], false);
+
+ if (mMainStack && aInfo != null && (aInfo.applicationInfo.flags
+ & ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
+ throw new IllegalArgumentException(
+ "FLAG_CANT_SAVE_STATE not supported here");
+ }
+
+ int res = startActivityLocked(caller, intent, resolvedTypes[i],
+ null, 0, aInfo, resultTo, null, -1, callingPid, callingUid,
+ false, componentSpecified, outActivity);
+ if (res < 0) {
+ return res;
+ }
+
+ resultTo = outActivity[0];
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ return IActivityManager.START_SUCCESS;
+ }
+
void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r,
long thisTime, long totalTime) {
for (int i=mWaitingActivityLaunched.size()-1; i>=0; i--) {
@@ -2644,6 +2933,9 @@ public class ActivityStack {
mService.mWindowManager.setAppVisibility(r, false);
}
r.app.thread.scheduleStopActivity(r, r.visible, r.configChangeFlags);
+ if (mService.isSleeping()) {
+ r.setSleeping(true);
+ }
} catch (Exception e) {
// Maybe just ignore exceptions here... if the process
// has crashed, our death notification will clean things
@@ -2687,7 +2979,7 @@ public class ActivityStack {
mService.mWindowManager.setAppVisibility(s, false);
}
}
- if (!s.waitingVisible && remove) {
+ if ((!s.waitingVisible || mService.isSleeping()) && remove) {
if (localLOGV) Slog.v(TAG, "Ready to stop: " + s);
if (stops == null) {
stops = new ArrayList<ActivityRecord>();
@@ -2891,11 +3183,10 @@ public class ActivityStack {
return false;
}
- r.finishing = true;
+ r.makeFinishing();
EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY,
System.identityHashCode(r),
r.task.taskId, r.shortComponentName, reason);
- r.task.numActivities--;
if (index < (mHistory.size()-1)) {
ActivityRecord next = (ActivityRecord)mHistory.get(index+1);
if (next.task == r.task) {
@@ -2958,7 +3249,7 @@ public class ActivityStack {
"Prepare close transition: finishing " + r);
mService.mWindowManager.prepareAppTransition(endTask
? WindowManagerPolicy.TRANSIT_TASK_CLOSE
- : WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE);
+ : WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE, false);
// Tell window manager to prepare for this one to be removed.
mService.mWindowManager.setAppVisibility(r, false);
@@ -3011,6 +3302,8 @@ public class ActivityStack {
Message msg = Message.obtain();
msg.what = IDLE_NOW_MSG;
mHandler.sendMessage(msg);
+ } else {
+ checkReadyForSleepLocked();
}
}
r.state = ActivityState.STOPPING;
@@ -3020,6 +3313,7 @@ public class ActivityStack {
// make sure the record is cleaned out of other places.
mStoppingActivities.remove(r);
+ mGoingToSleepActivities.remove(r);
mWaitingVisibleActivities.remove(r);
if (mResumedActivity == r) {
mResumedActivity = null;
@@ -3096,6 +3390,7 @@ public class ActivityStack {
private final void removeActivityFromHistoryLocked(ActivityRecord r) {
if (r.state != ActivityState.DESTROYED) {
+ r.makeFinishing();
mHistory.remove(r);
r.inHistory = false;
r.state = ActivityState.DESTROYED;
@@ -3246,10 +3541,30 @@ public class ActivityStack {
void removeHistoryRecordsForAppLocked(ProcessRecord app) {
removeHistoryRecordsForAppLocked(mLRUActivities, app);
removeHistoryRecordsForAppLocked(mStoppingActivities, app);
+ removeHistoryRecordsForAppLocked(mGoingToSleepActivities, app);
removeHistoryRecordsForAppLocked(mWaitingVisibleActivities, app);
removeHistoryRecordsForAppLocked(mFinishingActivities, app);
}
+ /**
+ * Move the current home activity's task (if one exists) to the front
+ * of the stack.
+ */
+ final void moveHomeToFrontLocked() {
+ TaskRecord homeTask = null;
+ for (int i=mHistory.size()-1; i>=0; i--) {
+ ActivityRecord hr = (ActivityRecord)mHistory.get(i);
+ if (hr.isHomeActivity) {
+ homeTask = hr.task;
+ break;
+ }
+ }
+ if (homeTask != null) {
+ moveTaskToFrontLocked(homeTask, null);
+ }
+ }
+
+
final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason) {
if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr);
@@ -3274,17 +3589,12 @@ public class ActivityStack {
ActivityRecord r = (ActivityRecord)mHistory.get(pos);
if (localLOGV) Slog.v(
TAG, "At " + pos + " ckp " + r.task + ": " + r);
- boolean first = true;
if (r.task.taskId == task) {
if (localLOGV) Slog.v(TAG, "Removing and adding at " + top);
mHistory.remove(pos);
mHistory.add(top, r);
moved.add(0, r);
top--;
- if (first && mMainStack) {
- mService.addRecentTaskLocked(r.task);
- first = false;
- }
}
pos--;
}
@@ -3293,13 +3603,15 @@ public class ActivityStack {
"Prepare to front transition: task=" + tr);
if (reason != null &&
(reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
- mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE);
+ mService.mWindowManager.prepareAppTransition(
+ WindowManagerPolicy.TRANSIT_NONE, false);
ActivityRecord r = topRunningActivityLocked(null);
if (r != null) {
mNoAnimActivities.add(r);
}
} else {
- mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_FRONT);
+ mService.mWindowManager.prepareAppTransition(
+ WindowManagerPolicy.TRANSIT_TASK_TO_FRONT, false);
}
mService.mWindowManager.moveAppTokensToTop(moved);
@@ -3378,13 +3690,15 @@ public class ActivityStack {
if (reason != null &&
(reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
- mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE);
+ mService.mWindowManager.prepareAppTransition(
+ WindowManagerPolicy.TRANSIT_NONE, false);
ActivityRecord r = topRunningActivityLocked(null);
if (r != null) {
mNoAnimActivities.add(r);
}
} else {
- mService.mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_BACK);
+ mService.mWindowManager.prepareAppTransition(
+ WindowManagerPolicy.TRANSIT_TASK_TO_BACK, false);
}
mService.mWindowManager.moveAppTokensToBottom(moved);
if (VALIDATE_TOKENS) {
diff --git a/services/java/com/android/server/am/BaseErrorDialog.java b/services/java/com/android/server/am/BaseErrorDialog.java
index 03e3272..d1e89bc 100644
--- a/services/java/com/android/server/am/BaseErrorDialog.java
+++ b/services/java/com/android/server/am/BaseErrorDialog.java
@@ -34,7 +34,7 @@ class BaseErrorDialog extends AlertDialog {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
getWindow().setTitle("Error Dialog");
- setIcon(R.drawable.ic_dialog_alert);
+ setIconAttribute(R.attr.alertDialogIcon);
}
public void onStart() {
diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java
index 367c4cf..963a691 100644
--- a/services/java/com/android/server/am/BatteryStatsService.java
+++ b/services/java/com/android/server/am/BatteryStatsService.java
@@ -16,7 +16,9 @@
package com.android.server.am;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Binder;
@@ -46,6 +48,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
final BatteryStatsImpl mStats;
Context mContext;
+ private boolean mBluetoothPendingStats;
+ private BluetoothHeadset mBluetoothHeadset;
BatteryStatsService(String filename) {
mStats = new BatteryStatsImpl(filename);
@@ -287,16 +291,43 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
public void noteBluetoothOn() {
enforceCallingPermission();
- BluetoothHeadset headset = new BluetoothHeadset(mContext, null);
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null) {
+ adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
+ BluetoothProfile.HEADSET);
+ }
synchronized (mStats) {
- mStats.noteBluetoothOnLocked();
- mStats.setBtHeadset(headset);
+ if (mBluetoothHeadset != null) {
+ mStats.noteBluetoothOnLocked();
+ mStats.setBtHeadset(mBluetoothHeadset);
+ } else {
+ mBluetoothPendingStats = true;
+ }
}
}
-
+
+ private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+ new BluetoothProfile.ServiceListener() {
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ mBluetoothHeadset = (BluetoothHeadset) proxy;
+ synchronized (mStats) {
+ if (mBluetoothPendingStats) {
+ mStats.noteBluetoothOnLocked();
+ mStats.setBtHeadset(mBluetoothHeadset);
+ mBluetoothPendingStats = false;
+ }
+ }
+ }
+
+ public void onServiceDisconnected(int profile) {
+ mBluetoothHeadset = null;
+ }
+ };
+
public void noteBluetoothOff() {
enforceCallingPermission();
synchronized (mStats) {
+ mBluetoothPendingStats = false;
mStats.noteBluetoothOffLocked();
}
}
diff --git a/services/java/com/android/server/am/LaunchWarningWindow.java b/services/java/com/android/server/am/LaunchWarningWindow.java
index 4130e33..1114a31 100644
--- a/services/java/com/android/server/am/LaunchWarningWindow.java
+++ b/services/java/com/android/server/am/LaunchWarningWindow.java
@@ -4,6 +4,7 @@ import com.android.internal.R;
import android.app.Dialog;
import android.content.Context;
+import android.util.TypedValue;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
@@ -20,8 +21,11 @@ public class LaunchWarningWindow extends Dialog {
setContentView(R.layout.launch_warning);
setTitle(context.getText(R.string.launch_warning_title));
- getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,
- R.drawable.ic_dialog_alert);
+
+ TypedValue out = new TypedValue();
+ getContext().getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true);
+ getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, out.resourceId);
+
ImageView icon = (ImageView)findViewById(R.id.replace_app_icon);
icon.setImageDrawable(next.info.applicationInfo.loadIcon(context.getPackageManager()));
TextView text = (TextView)findViewById(R.id.replace_message);
diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java
index 7a85eb8..ee6e420 100644
--- a/services/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/java/com/android/server/am/PendingIntentRecord.java
@@ -47,20 +47,24 @@ class PendingIntentRecord extends IIntentSender.Stub {
final int requestCode;
final Intent requestIntent;
final String requestResolvedType;
+ Intent[] allIntents;
+ String[] allResolvedTypes;
final int flags;
final int hashCode;
private static final int ODD_PRIME_NUMBER = 37;
Key(int _t, String _p, ActivityRecord _a, String _w,
- int _r, Intent _i, String _it, int _f) {
+ int _r, Intent[] _i, String[] _it, int _f) {
type = _t;
packageName = _p;
activity = _a;
who = _w;
requestCode = _r;
- requestIntent = _i;
- requestResolvedType = _it;
+ requestIntent = _i != null ? _i[_i.length-1] : null;
+ requestResolvedType = _it != null ? _it[_it.length-1] : null;
+ allIntents = _i;
+ allResolvedTypes = _it;
flags = _f;
int hash = 23;
@@ -72,11 +76,11 @@ class PendingIntentRecord extends IIntentSender.Stub {
if (_a != null) {
hash = (ODD_PRIME_NUMBER*hash) + _a.hashCode();
}
- if (_i != null) {
- hash = (ODD_PRIME_NUMBER*hash) + _i.filterHashCode();
+ if (requestIntent != null) {
+ hash = (ODD_PRIME_NUMBER*hash) + requestIntent.filterHashCode();
}
- if (_it != null) {
- hash = (ODD_PRIME_NUMBER*hash) + _it.hashCode();
+ if (requestResolvedType != null) {
+ hash = (ODD_PRIME_NUMBER*hash) + requestResolvedType.hashCode();
}
hash = (ODD_PRIME_NUMBER*hash) + _p.hashCode();
hash = (ODD_PRIME_NUMBER*hash) + _t;
@@ -209,9 +213,24 @@ class PendingIntentRecord extends IIntentSender.Stub {
switch (key.type) {
case IActivityManager.INTENT_SENDER_ACTIVITY:
try {
- owner.startActivityInPackage(uid,
- finalIntent, resolvedType,
- resultTo, resultWho, requestCode, false);
+ if (key.allIntents != null && key.allIntents.length > 1) {
+ Intent[] allIntents = new Intent[key.allIntents.length];
+ String[] allResolvedTypes = new String[key.allIntents.length];
+ System.arraycopy(key.allIntents, 0, allIntents, 0,
+ key.allIntents.length);
+ if (key.allResolvedTypes != null) {
+ System.arraycopy(key.allResolvedTypes, 0, allResolvedTypes, 0,
+ key.allResolvedTypes.length);
+ }
+ allIntents[allIntents.length-1] = finalIntent;
+ allResolvedTypes[allResolvedTypes.length-1] = resolvedType;
+ owner.startActivitiesInPackage(uid, allIntents,
+ allResolvedTypes, resultTo);
+ } else {
+ owner.startActivityInPackage(uid,
+ finalIntent, resolvedType,
+ resultTo, resultWho, requestCode, false);
+ }
} catch (RuntimeException e) {
Slog.w(ActivityManagerService.TAG,
"Unable to send startActivity intent", e);
diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java
index bcb8f54..86cec42 100644
--- a/services/java/com/android/server/am/TaskRecord.java
+++ b/services/java/com/android/server/am/TaskRecord.java
@@ -19,14 +19,13 @@ package com.android.server.am;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.os.SystemClock;
+import android.graphics.Bitmap;
import java.io.PrintWriter;
class TaskRecord {
final int taskId; // Unique identifier for this task.
final String affinity; // The affinity name for this task, or null.
- final boolean clearOnBackground; // As per the original activity.
Intent intent; // The original intent that started the task.
Intent affinityIntent; // Intent of affinity-moved activity that started this task.
ComponentName origActivity; // The non-alias activity component of the intent.
@@ -35,14 +34,14 @@ class TaskRecord {
long lastActiveTime; // Last time this task was active, including sleep.
boolean rootWasReset; // True if the intent at the root of the task had
// the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag.
+ Bitmap lastThumbnail; // Last thumbnail captured for this task.
+ CharSequence lastDescription; // Last description captured for this task.
String stringName; // caching of toString() result.
- TaskRecord(int _taskId, ActivityInfo info, Intent _intent,
- boolean _clearOnBackground) {
+ TaskRecord(int _taskId, ActivityInfo info, Intent _intent) {
taskId = _taskId;
affinity = info.taskAffinity;
- clearOnBackground = _clearOnBackground;
setIntent(_intent, info);
}
@@ -86,9 +85,8 @@ class TaskRecord {
}
void dump(PrintWriter pw, String prefix) {
- if (clearOnBackground || numActivities != 0 || rootWasReset) {
- pw.print(prefix); pw.print("clearOnBackground="); pw.print(clearOnBackground);
- pw.print(" numActivities="); pw.print(numActivities);
+ if (numActivities != 0 || rootWasReset) {
+ pw.print(prefix); pw.print("numActivities="); pw.print(numActivities);
pw.print(" rootWasReset="); pw.println(rootWasReset);
}
if (affinity != null) {
diff --git a/services/java/com/android/server/am/UriPermissionOwner.java b/services/java/com/android/server/am/UriPermissionOwner.java
index 99c82e6..68a2e0f 100644
--- a/services/java/com/android/server/am/UriPermissionOwner.java
+++ b/services/java/com/android/server/am/UriPermissionOwner.java
@@ -45,7 +45,7 @@ class UriPermissionOwner {
}
Binder getExternalTokenLocked() {
- if (externalToken != null) {
+ if (externalToken == null) {
externalToken = new ExternalToken();
}
return externalToken;
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index a73a4ce..26c7e71 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -19,20 +19,20 @@ package com.android.server.connectivity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.bluetooth.BluetoothPan;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.hardware.Usb;
+import android.hardware.UsbManager;
import android.net.ConnectivityManager;
import android.net.InterfaceConfiguration;
import android.net.IConnectivityManager;
import android.net.INetworkManagementEventObserver;
+import android.net.LinkProperties;
import android.net.NetworkInfo;
-import android.net.NetworkUtils;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
@@ -52,8 +52,10 @@ import com.android.internal.util.HierarchicalStateMachine;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.Set;
/**
* @hide
@@ -66,7 +68,8 @@ import java.util.Set;
public class Tethering extends INetworkManagementEventObserver.Stub {
private Context mContext;
- private final String TAG = "Tethering";
+ private final static String TAG = "Tethering";
+ private final static boolean DEBUG = true;
private boolean mBooted = false;
//used to remember if we got connected before boot finished
@@ -75,6 +78,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
// TODO - remove both of these - should be part of interface inspection/selection stuff
private String[] mTetherableUsbRegexs;
private String[] mTetherableWifiRegexs;
+ private String[] mTetherableBluetoothRegexs;
private String[] mUpstreamIfaceRegexs;
private Looper mLooper;
@@ -87,17 +91,31 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129";
private static final String USB_NETMASK = "255.255.255.0";
- // FYI - the default wifi is 192.168.43.1 and 255.255.255.0
+ // USB is 192.168.42.1 and 255.255.255.0
+ // Wifi is 192.168.43.1 and 255.255.255.0
+ // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
+ // with 255.255.255.0
private String[] mDhcpRange;
private static final String DHCP_DEFAULT_RANGE1_START = "192.168.42.2";
private static final String DHCP_DEFAULT_RANGE1_STOP = "192.168.42.254";
private static final String DHCP_DEFAULT_RANGE2_START = "192.168.43.2";
private static final String DHCP_DEFAULT_RANGE2_STOP = "192.168.43.254";
+ private static final String DHCP_DEFAULT_RANGE3_START = "192.168.44.2";
+ private static final String DHCP_DEFAULT_RANGE3_STOP = "192.168.44.254";
+ private static final String DHCP_DEFAULT_RANGE4_START = "192.168.45.2";
+ private static final String DHCP_DEFAULT_RANGE4_STOP = "192.168.45.254";
+ private static final String DHCP_DEFAULT_RANGE5_START = "192.168.46.2";
+ private static final String DHCP_DEFAULT_RANGE5_STOP = "192.168.46.254";
+ private static final String DHCP_DEFAULT_RANGE6_START = "192.168.47.2";
+ private static final String DHCP_DEFAULT_RANGE6_STOP = "192.168.47.254";
+ private static final String DHCP_DEFAULT_RANGE7_START = "192.168.48.2";
+ private static final String DHCP_DEFAULT_RANGE7_STOP = "192.168.48.254";
+
private String[] mDnsServers;
private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8";
- private static final String DNS_DEFAULT_SERVER2 = "4.2.2.2";
+ private static final String DNS_DEFAULT_SERVER2 = "8.8.4.4";
// resampled each time we turn on tethering - used as cache for settings/config-val
private boolean mDunRequired; // configuration info - must use DUN apn on 3g
@@ -111,14 +129,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
private boolean mUsbMassStorageOff; // track the status of USB Mass Storage
private boolean mUsbConnected; // track the status of USB connection
- // mUsbHandler message
- static final int USB_STATE_CHANGE = 1;
- static final int USB_DISCONNECTED = 0;
- static final int USB_CONNECTED = 1;
-
- // Time to delay before processing USB disconnect events
- static final long USB_DISCONNECT_DELAY = 1000;
-
public Tethering(Context context, Looper looper) {
mContext = context;
mLooper = looper;
@@ -143,7 +153,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
mStateReceiver = new StateReceiver();
IntentFilter filter = new IntentFilter();
- filter.addAction(Usb.ACTION_USB_STATE);
+ filter.addAction(UsbManager.ACTION_USB_STATE);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(Intent.ACTION_BOOT_COMPLETED);
mContext.registerReceiver(mStateReceiver, filter);
@@ -160,11 +170,21 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
mDhcpRange = context.getResources().getStringArray(
com.android.internal.R.array.config_tether_dhcp_range);
if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) {
- mDhcpRange = new String[4];
+ mDhcpRange = new String[14];
mDhcpRange[0] = DHCP_DEFAULT_RANGE1_START;
mDhcpRange[1] = DHCP_DEFAULT_RANGE1_STOP;
mDhcpRange[2] = DHCP_DEFAULT_RANGE2_START;
mDhcpRange[3] = DHCP_DEFAULT_RANGE2_STOP;
+ mDhcpRange[4] = DHCP_DEFAULT_RANGE3_START;
+ mDhcpRange[5] = DHCP_DEFAULT_RANGE3_STOP;
+ mDhcpRange[6] = DHCP_DEFAULT_RANGE4_START;
+ mDhcpRange[7] = DHCP_DEFAULT_RANGE4_STOP;
+ mDhcpRange[8] = DHCP_DEFAULT_RANGE5_START;
+ mDhcpRange[9] = DHCP_DEFAULT_RANGE5_STOP;
+ mDhcpRange[10] = DHCP_DEFAULT_RANGE6_START;
+ mDhcpRange[11] = DHCP_DEFAULT_RANGE6_STOP;
+ mDhcpRange[12] = DHCP_DEFAULT_RANGE7_START;
+ mDhcpRange[13] = DHCP_DEFAULT_RANGE7_STOP;
}
mDunRequired = false; // resample when we turn on
@@ -172,6 +192,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
com.android.internal.R.array.config_tether_usb_regexs);
mTetherableWifiRegexs = context.getResources().getStringArray(
com.android.internal.R.array.config_tether_wifi_regexs);
+ mTetherableBluetoothRegexs = context.getResources().getStringArray(
+ com.android.internal.R.array.config_tether_bluetooth_regexs);
mUpstreamIfaceRegexs = context.getResources().getStringArray(
com.android.internal.R.array.config_tether_upstream_regexs);
@@ -182,7 +204,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
}
public void interfaceLinkStatusChanged(String iface, boolean link) {
- Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link);
+ if (DEBUG) Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link);
boolean found = false;
boolean usb = false;
if (isWifi(iface)) {
@@ -190,6 +212,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
} else if (isUsb(iface)) {
found = true;
usb = true;
+ } else if (isBluetooth(iface)) {
+ found = true;
}
if (found == false) return;
@@ -224,6 +248,12 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
return false;
}
+ public boolean isBluetooth(String iface) {
+ for (String regex : mTetherableBluetoothRegexs) {
+ if (iface.matches(regex)) return true;
+ }
+ return false;
+ }
public void interfaceAdded(String iface) {
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
@@ -236,29 +266,34 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
found = true;
usb = true;
}
+ if (isBluetooth(iface)) {
+ found = true;
+ }
if (found == false) {
- Log.d(TAG, iface + " is not a tetherable iface, ignoring");
+ if (DEBUG) Log.d(TAG, iface + " is not a tetherable iface, ignoring");
return;
}
synchronized (mIfaces) {
TetherInterfaceSM sm = mIfaces.get(iface);
if (sm != null) {
- Log.e(TAG, "active iface (" + iface + ") reported as added, ignoring");
+ if (DEBUG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring");
return;
}
sm = new TetherInterfaceSM(iface, mLooper, usb);
mIfaces.put(iface, sm);
sm.start();
}
- Log.d(TAG, "interfaceAdded :" + iface);
+ if (DEBUG) Log.d(TAG, "interfaceAdded :" + iface);
}
public void interfaceRemoved(String iface) {
synchronized (mIfaces) {
TetherInterfaceSM sm = mIfaces.get(iface);
if (sm == null) {
- Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring");
+ if (DEBUG) {
+ Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring");
+ }
return;
}
sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN);
@@ -329,13 +364,14 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
boolean wifiTethered = false;
boolean usbTethered = false;
+ boolean bluetoothTethered = false;
synchronized (mIfaces) {
Set ifaces = mIfaces.keySet();
for (Object iface : ifaces) {
TetherInterfaceSM sm = mIfaces.get(iface);
if (sm != null) {
- if(sm.isErrored()) {
+ if (sm.isErrored()) {
erroredList.add((String)iface);
} else if (sm.isAvailable()) {
availableList.add((String)iface);
@@ -344,6 +380,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
usbTethered = true;
} else if (isWifi((String)iface)) {
wifiTethered = true;
+ } else if (isBluetooth((String)iface)) {
+ bluetoothTethered = true;
}
activeList.add((String)iface);
}
@@ -358,17 +396,25 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ERRORED_TETHER,
erroredList);
mContext.sendStickyBroadcast(broadcast);
- Log.d(TAG, "sendTetherStateChangedBroadcast " + availableList.size() + ", " +
- activeList.size() + ", " + erroredList.size());
+ if (DEBUG) {
+ Log.d(TAG, "sendTetherStateChangedBroadcast " + availableList.size() + ", " +
+ activeList.size() + ", " + erroredList.size());
+ }
if (usbTethered) {
- if (wifiTethered) {
+ if (wifiTethered || bluetoothTethered) {
showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_general);
} else {
showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_usb);
}
} else if (wifiTethered) {
- showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_wifi);
+ if (bluetoothTethered) {
+ showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_general);
+ } else {
+ showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_wifi);
+ }
+ } else if (bluetoothTethered) {
+ showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_bluetooth);
} else {
clearTetheredNotification();
}
@@ -399,7 +445,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
CharSequence message = r.getText(com.android.internal.R.string.
tethered_notification_message);
- if(mTetheredNotification == null) {
+ if (mTetheredNotification == null) {
mTetheredNotification = new Notification();
mTetheredNotification.when = 0;
}
@@ -429,25 +475,12 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
}
}
- private Handler mUsbHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- mUsbConnected = (msg.arg1 == USB_CONNECTED);
- updateUsbStatus();
- }
- };
-
private class StateReceiver extends BroadcastReceiver {
public void onReceive(Context content, Intent intent) {
String action = intent.getAction();
- if (action.equals(Usb.ACTION_USB_STATE)) {
- // process connect events immediately, but delay handling disconnects
- // to debounce USB configuration changes
- boolean connected = intent.getExtras().getBoolean(Usb.USB_CONNECTED);
- Message msg = Message.obtain(mUsbHandler, USB_STATE_CHANGE,
- (connected ? USB_CONNECTED : USB_DISCONNECTED), 0);
- mUsbHandler.removeMessages(USB_STATE_CHANGE);
- mUsbHandler.sendMessageDelayed(msg, connected ? 0 : USB_DISCONNECT_DELAY);
+ if (action.equals(UsbManager.ACTION_USB_STATE)) {
+ mUsbConnected = intent.getExtras().getBoolean(UsbManager.USB_CONNECTED);
+ updateUsbStatus();
} else if (action.equals(Intent.ACTION_MEDIA_SHARED)) {
mUsbMassStorageOff = false;
updateUsbStatus();
@@ -456,6 +489,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
mUsbMassStorageOff = true;
updateUsbStatus();
} else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ if (DEBUG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION");
mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED);
} else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
mBooted = true;
@@ -486,9 +520,9 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
}
}
- // toggled when we enter/leave the fully teathered state
+ // toggled when we enter/leave the fully tethered state
private boolean enableUsbRndis(boolean enabled) {
- Log.d(TAG, "enableUsbRndis(" + enabled + ")");
+ if (DEBUG) Log.d(TAG, "enableUsbRndis(" + enabled + ")");
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
@@ -513,7 +547,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
// configured when we start tethering and unconfig'd on error or conclusion
private boolean configureUsbIface(boolean enabled) {
- Log.d(TAG, "configureUsbIface(" + enabled + ")");
+ if (DEBUG) Log.d(TAG, "configureUsbIface(" + enabled + ")");
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
@@ -532,16 +566,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
try {
ifcg = service.getInterfaceConfig(iface);
if (ifcg != null) {
- String[] addr = USB_NEAR_IFACE_ADDR.split("\\.");
- ifcg.ipAddr = (Integer.parseInt(addr[0]) << 24) +
- (Integer.parseInt(addr[1]) << 16) +
- (Integer.parseInt(addr[2]) << 8) +
- (Integer.parseInt(addr[3]));
- addr = USB_NETMASK.split("\\.");
- ifcg.netmask = (Integer.parseInt(addr[0]) << 24) +
- (Integer.parseInt(addr[1]) << 16) +
- (Integer.parseInt(addr[2]) << 8) +
- (Integer.parseInt(addr[3]));
+ ifcg.addr = InetAddress.getByName(USB_NEAR_IFACE_ADDR);
+ ifcg.mask = InetAddress.getByName(USB_NETMASK);
if (enabled) {
ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up");
} else {
@@ -569,6 +595,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
return mTetherableWifiRegexs;
}
+ public String[] getTetherableBluetoothRegexs() {
+ return mTetherableBluetoothRegexs;
+ }
+
public String[] getUpstreamIfaceRegexs() {
return mUpstreamIfaceRegexs;
}
@@ -760,7 +790,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
@Override
public boolean processMessage(Message message) {
- Log.d(TAG, "InitialState.processMessage what=" + message.what);
+ if (DEBUG) Log.d(TAG, "InitialState.processMessage what=" + message.what);
boolean retValue = true;
switch (message.what) {
case CMD_TETHER_REQUESTED:
@@ -801,7 +831,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
}
@Override
public boolean processMessage(Message message) {
- Log.d(TAG, "StartingState.processMessage what=" + message.what);
+ if (DEBUG) Log.d(TAG, "StartingState.processMessage what=" + message.what);
boolean retValue = true;
switch (message.what) {
// maybe a parent class?
@@ -853,7 +883,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
return;
}
if (mUsb) Tethering.this.enableUsbRndis(true);
- Log.d(TAG, "Tethered " + mIfaceName);
+ if (DEBUG) Log.d(TAG, "Tethered " + mIfaceName);
setAvailable(false);
setTethered(true);
sendTetherStateChangedBroadcast();
@@ -864,7 +894,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
}
@Override
public boolean processMessage(Message message) {
- Log.d(TAG, "TetheredState.processMessage what=" + message.what);
+ if (DEBUG) Log.d(TAG, "TetheredState.processMessage what=" + message.what);
boolean retValue = true;
boolean error = false;
switch (message.what) {
@@ -907,13 +937,18 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
} else if (message.what == CMD_INTERFACE_DOWN) {
transitionTo(mUnavailableState);
}
- Log.d(TAG, "Untethered " + mIfaceName);
+ if (DEBUG) Log.d(TAG, "Untethered " + mIfaceName);
break;
case CMD_TETHER_CONNECTION_CHANGED:
String newUpstreamIfaceName = (String)(message.obj);
b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
service = INetworkManagementService.Stub.asInterface(b);
-
+ if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) ||
+ (mMyUpstreamIfaceName != null &&
+ mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) {
+ if (DEBUG) Log.d(TAG, "Connection changed noop - dropping");
+ break;
+ }
if (mMyUpstreamIfaceName != null) {
try {
service.disableNat(mIfaceName, mMyUpstreamIfaceName);
@@ -980,7 +1015,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
break;
}
- Log.d(TAG, "Tether lost upstream connection " + mIfaceName);
+ if (DEBUG) Log.d(TAG, "Tether lost upstream connection " + mIfaceName);
sendTetherStateChangedBroadcast();
if (mUsb) {
if (!Tethering.this.configureUsbIface(false)) {
@@ -1056,7 +1091,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
private ArrayList mNotifyList;
- private boolean mConnectionRequested = false;
+ private int mCurrentConnectionSequence;
+ private boolean mMobileReserved = false;
private String mUpstreamIfaceName = null;
@@ -1095,43 +1131,47 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
public boolean processMessage(Message m) {
return false;
}
- protected int turnOnMobileConnection() {
+ protected boolean turnOnMobileConnection() {
+ boolean retValue = true;
+ if (mMobileReserved) return retValue;
IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
- int retValue = Phone.APN_REQUEST_FAILED;
+ int result = Phone.APN_REQUEST_FAILED;
try {
- retValue = service.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
- (mDunRequired ? Phone.FEATURE_ENABLE_DUN : Phone.FEATURE_ENABLE_HIPRI),
- new Binder());
+ result = service.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+ (mDunRequired ? Phone.FEATURE_ENABLE_DUN_ALWAYS :
+ Phone.FEATURE_ENABLE_HIPRI), new Binder());
} catch (Exception e) {
}
- switch (retValue) {
+ switch (result) {
case Phone.APN_ALREADY_ACTIVE:
case Phone.APN_REQUEST_STARTED:
- sendMessageDelayed(CMD_CELL_CONNECTION_RENEW, CELL_CONNECTION_RENEW_MS);
- mConnectionRequested = true;
+ mMobileReserved = true;
+ Message m = obtainMessage(CMD_CELL_CONNECTION_RENEW);
+ m.arg1 = ++mCurrentConnectionSequence;
+ sendMessageDelayed(m, CELL_CONNECTION_RENEW_MS);
break;
case Phone.APN_REQUEST_FAILED:
default:
- mConnectionRequested = false;
+ retValue = false;
break;
}
return retValue;
}
protected boolean turnOffMobileConnection() {
- if (mConnectionRequested) {
+ if (mMobileReserved) {
IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
IConnectivityManager service =
IConnectivityManager.Stub.asInterface(b);
try {
service.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
- (mDunRequired? Phone.FEATURE_ENABLE_DUN :
+ (mDunRequired? Phone.FEATURE_ENABLE_DUN_ALWAYS :
Phone.FEATURE_ENABLE_HIPRI));
} catch (Exception e) {
return false;
}
- mConnectionRequested = false;
+ mMobileReserved = false;
}
return true;
}
@@ -1180,7 +1220,20 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
}
protected String findActiveUpstreamIface() {
// check for what iface we can use - if none found switch to error.
- IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
+ IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b);
+
+ try {
+ LinkProperties defaultProp = cm.getActiveLinkProperties();
+ if (defaultProp != null) {
+ String iface = defaultProp.getInterfaceName();
+ for(String regex : mUpstreamIfaceRegexs) {
+ if (iface.matches(regex)) return iface;
+ }
+ }
+ } catch (RemoteException e) { }
+
+ b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
String[] ifaces = new String[0];
@@ -1194,32 +1247,35 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
for (String iface : ifaces) {
for (String regex : mUpstreamIfaceRegexs) {
if (iface.matches(regex)) {
- // verify it is up!
+ // verify it is active
InterfaceConfiguration ifcg = null;
try {
ifcg = service.getInterfaceConfig(iface);
+ if (ifcg.isActive()) {
+ return iface;
+ }
} catch (Exception e) {
Log.e(TAG, "Error getting iface config :" + e);
// ignore - try next
continue;
}
- if (ifcg.interfaceFlags.contains("up")) {
- return iface;
- }
}
}
}
return null;
}
+
protected void chooseUpstreamType(boolean tryCell) {
// decide if the current upstream is good or not and if not
// do something about it (start up DUN if required or HiPri if not)
String iface = findActiveUpstreamIface();
IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b);
- mConnectionRequested = false;
- Log.d(TAG, "chooseUpstreamType(" + tryCell + "), dunRequired ="
- + mDunRequired + ", iface=" + iface);
+ mMobileReserved = false;
+ if (DEBUG) {
+ Log.d(TAG, "chooseUpstreamType(" + tryCell + "), dunRequired ="
+ + mDunRequired + ", iface=" + iface);
+ }
if (iface != null) {
try {
if (mDunRequired) {
@@ -1227,7 +1283,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
NetworkInfo info = cm.getNetworkInfo(
ConnectivityManager.TYPE_MOBILE_DUN);
if (info.isConnected()) {
- Log.d(TAG, "setting dun ifacename =" + iface);
+ if (DEBUG) Log.d(TAG, "setting dun ifacename =" + iface);
// even if we're already connected - it may be somebody else's
// refcount, so add our own
turnOnMobileConnection();
@@ -1239,11 +1295,11 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
}
}
} else {
- Log.d(TAG, "checking if hipri brought us this connection");
+ if (DEBUG) Log.d(TAG, "checking if hipri brought us this connection");
NetworkInfo info = cm.getNetworkInfo(
ConnectivityManager.TYPE_MOBILE_HIPRI);
if (info.isConnected()) {
- Log.d(TAG, "yes - hipri in use");
+ if (DEBUG) Log.d(TAG, "yes - hipri in use");
// even if we're already connected - it may be sombody else's
// refcount, so add our own
turnOnMobileConnection();
@@ -1256,16 +1312,19 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
}
// may have been set to null in the if above
if (iface == null ) {
+ boolean success = false;
if (tryCell == TRY_TO_SETUP_MOBILE_CONNECTION) {
- turnOnMobileConnection();
+ success = turnOnMobileConnection();
+ }
+ if (!success) {
+ // wait for things to settle and retry
+ sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
}
- // wait for things to settle and retry
- sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
}
notifyTetheredOfNewUpstreamIface(iface);
}
protected void notifyTetheredOfNewUpstreamIface(String ifaceName) {
- Log.d(TAG, "notifying tethered with iface =" + ifaceName);
+ if (DEBUG) Log.d(TAG, "notifying tethered with iface =" + ifaceName);
mUpstreamIfaceName = ifaceName;
for (Object o : mNotifyList) {
TetherInterfaceSM sm = (TetherInterfaceSM)o;
@@ -1278,23 +1337,23 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
class InitialState extends TetherMasterUtilState {
@Override
public void enter() {
- mConnectionRequested = false;
+ mMobileReserved = false;
}
@Override
public boolean processMessage(Message message) {
- Log.d(TAG, "MasterInitialState.processMessage what=" + message.what);
+ if (DEBUG) Log.d(TAG, "MasterInitialState.processMessage what=" + message.what);
boolean retValue = true;
switch (message.what) {
case CMD_TETHER_MODE_REQUESTED:
mDunRequired = isDunRequired();
TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
- Log.d(TAG, "Tether Mode requested by " + who.toString());
+ if (DEBUG) Log.d(TAG, "Tether Mode requested by " + who.toString());
mNotifyList.add(who);
transitionTo(mTetherModeAliveState);
break;
case CMD_TETHER_MODE_UNREQUESTED:
who = (TetherInterfaceSM)message.obj;
- Log.d(TAG, "Tether Mode unrequested by " + who.toString());
+ if (DEBUG) Log.d(TAG, "Tether Mode unrequested by " + who.toString());
int index = mNotifyList.indexOf(who);
if (index != -1) {
mNotifyList.remove(who);
@@ -1309,10 +1368,11 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
}
class TetherModeAliveState extends TetherMasterUtilState {
- boolean mTryCell = WAIT_FOR_NETWORK_TO_SETTLE;
+ boolean mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE;
@Override
public void enter() {
- mTryCell = WAIT_FOR_NETWORK_TO_SETTLE; // first pass lets just see what we have.
+ mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE; // better try something first pass
+ // or crazy tests cases will fail
chooseUpstreamType(mTryCell);
mTryCell = !mTryCell;
turnOnMasterTetherSettings(); // may transition us out
@@ -1324,7 +1384,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
}
@Override
public boolean processMessage(Message message) {
- Log.d(TAG, "TetherModeAliveState.processMessage what=" + message.what);
+ if (DEBUG) Log.d(TAG, "TetherModeAliveState.processMessage what=" + message.what);
boolean retValue = true;
switch (message.what) {
case CMD_TETHER_MODE_REQUESTED:
@@ -1344,16 +1404,20 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
}
break;
case CMD_UPSTREAM_CHANGED:
- mTryCell = WAIT_FOR_NETWORK_TO_SETTLE;
+ // need to try DUN immediately if Wifi goes down
+ mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE;
chooseUpstreamType(mTryCell);
mTryCell = !mTryCell;
break;
case CMD_CELL_CONNECTION_RENEW:
// make sure we're still using a requested connection - may have found
// wifi or something since then.
- if (mConnectionRequested) {
- Log.d(TAG, "renewing mobile connection - requeuing for another " +
- CELL_CONNECTION_RENEW_MS + "ms");
+ if (mCurrentConnectionSequence == message.arg1) {
+ if (DEBUG) {
+ Log.d(TAG, "renewing mobile connection - requeuing for another " +
+ CELL_CONNECTION_RENEW_MS + "ms");
+ }
+ mMobileReserved = false; // need to renew it
turnOnMobileConnection();
}
break;
diff --git a/services/java/com/android/server/location/ComprehensiveCountryDetector.java b/services/java/com/android/server/location/ComprehensiveCountryDetector.java
new file mode 100755
index 0000000..e9ce3ce
--- /dev/null
+++ b/services/java/com/android/server/location/ComprehensiveCountryDetector.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.location;
+
+import android.content.Context;
+import android.location.Country;
+import android.location.CountryListener;
+import android.location.Geocoder;
+import android.provider.Settings;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import java.util.Locale;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * This class is used to detect the country where the user is. The sources of
+ * country are queried in order of reliability, like
+ * <ul>
+ * <li>Mobile network</li>
+ * <li>Location</li>
+ * <li>SIM's country</li>
+ * <li>Phone's locale</li>
+ * </ul>
+ * <p>
+ * Call the {@link #detectCountry()} to get the available country immediately.
+ * <p>
+ * To be notified of the future country change, using the
+ * {@link #setCountryListener(CountryListener)}
+ * <p>
+ * Using the {@link #stop()} to stop listening to the country change.
+ * <p>
+ * The country information will be refreshed every
+ * {@link #LOCATION_REFRESH_INTERVAL} once the location based country is used.
+ *
+ * @hide
+ */
+public class ComprehensiveCountryDetector extends CountryDetectorBase {
+
+ private final static String TAG = "ComprehensiveCountryDetector";
+
+ /**
+ * The refresh interval when the location based country was used
+ */
+ private final static long LOCATION_REFRESH_INTERVAL = 1000 * 60 * 60 * 24; // 1 day
+
+ protected CountryDetectorBase mLocationBasedCountryDetector;
+ protected Timer mLocationRefreshTimer;
+
+ private final int mPhoneType;
+ private Country mCountry;
+ private TelephonyManager mTelephonyManager;
+ private Country mCountryFromLocation;
+ private boolean mStopped = false;
+ private ServiceState mLastState;
+
+ private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+ @Override
+ public void onServiceStateChanged(ServiceState serviceState) {
+ // TODO: Find out how often we will be notified, if this method is called too
+ // many times, let's consider querying the network.
+ Slog.d(TAG, "onServiceStateChanged");
+ // We only care the state change
+ if (mLastState == null || mLastState.getState() != serviceState.getState()) {
+ detectCountry(true, true);
+ mLastState = new ServiceState(serviceState);
+ }
+ }
+ };
+
+ /**
+ * The listener for receiving the notification from LocationBasedCountryDetector.
+ */
+ private CountryListener mLocationBasedCountryDetectionListener = new CountryListener() {
+ public void onCountryDetected(Country country) {
+ mCountryFromLocation = country;
+ // Don't start the LocationBasedCountryDetector.
+ detectCountry(true, false);
+ stopLocationBasedDetector();
+ }
+ };
+
+ public ComprehensiveCountryDetector(Context context) {
+ super(context);
+ mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ mPhoneType = mTelephonyManager.getPhoneType();
+ }
+
+ @Override
+ public Country detectCountry() {
+ // Don't start the LocationBasedCountryDetector if we have been stopped.
+ return detectCountry(false, !mStopped);
+ }
+
+ @Override
+ public void stop() {
+ Slog.i(TAG, "Stop the detector.");
+ cancelLocationRefresh();
+ removePhoneStateListener();
+ stopLocationBasedDetector();
+ mListener = null;
+ mStopped = true;
+ }
+
+ /**
+ * Get the country from different sources in order of the reliability.
+ */
+ private Country getCountry() {
+ Country result = null;
+ result = getNetworkBasedCountry();
+ if (result == null) {
+ result = getLastKnownLocationBasedCountry();
+ }
+ if (result == null) {
+ result = getSimBasedCountry();
+ }
+ if (result == null) {
+ result = getLocaleCountry();
+ }
+ return result;
+ }
+
+ /**
+ * @return the country from the mobile network.
+ */
+ protected Country getNetworkBasedCountry() {
+ String countryIso = null;
+ // TODO: The document says the result may be unreliable on CDMA networks. Shall we use
+ // it on CDMA phone? We may test the Android primarily used countries.
+ if (mPhoneType == TelephonyManager.PHONE_TYPE_GSM) {
+ countryIso = mTelephonyManager.getNetworkCountryIso();
+ if (!TextUtils.isEmpty(countryIso)) {
+ return new Country(countryIso, Country.COUNTRY_SOURCE_NETWORK);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return the cached location based country.
+ */
+ protected Country getLastKnownLocationBasedCountry() {
+ return mCountryFromLocation;
+ }
+
+ /**
+ * @return the country from SIM card
+ */
+ protected Country getSimBasedCountry() {
+ String countryIso = null;
+ countryIso = mTelephonyManager.getSimCountryIso();
+ if (!TextUtils.isEmpty(countryIso)) {
+ return new Country(countryIso, Country.COUNTRY_SOURCE_SIM);
+ }
+ return null;
+ }
+
+ /**
+ * @return the country from the system's locale.
+ */
+ protected Country getLocaleCountry() {
+ Locale defaultLocale = Locale.getDefault();
+ if (defaultLocale != null) {
+ return new Country(defaultLocale.getCountry(), Country.COUNTRY_SOURCE_LOCALE);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @param notifyChange indicates whether the listener should be notified the change of the
+ * country
+ * @param startLocationBasedDetection indicates whether the LocationBasedCountryDetector could
+ * be started if the current country source is less reliable than the location.
+ * @return the current available UserCountry
+ */
+ private Country detectCountry(boolean notifyChange, boolean startLocationBasedDetection) {
+ Country country = getCountry();
+ runAfterDetectionAsync(mCountry != null ? new Country(mCountry) : mCountry, country,
+ notifyChange, startLocationBasedDetection);
+ mCountry = country;
+ return mCountry;
+ }
+
+ /**
+ * Run the tasks in the service's thread.
+ */
+ protected void runAfterDetectionAsync(final Country country, final Country detectedCountry,
+ final boolean notifyChange, final boolean startLocationBasedDetection) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ runAfterDetection(
+ country, detectedCountry, notifyChange, startLocationBasedDetection);
+ }
+ });
+ }
+
+ @Override
+ public void setCountryListener(CountryListener listener) {
+ CountryListener prevListener = mListener;
+ mListener = listener;
+ if (mListener == null) {
+ // Stop listening all services
+ removePhoneStateListener();
+ stopLocationBasedDetector();
+ cancelLocationRefresh();
+ } else if (prevListener == null) {
+ addPhoneStateListener();
+ detectCountry(false, true);
+ }
+ }
+
+ void runAfterDetection(final Country country, final Country detectedCountry,
+ final boolean notifyChange, final boolean startLocationBasedDetection) {
+ if (notifyChange) {
+ notifyIfCountryChanged(country, detectedCountry);
+ }
+ if (startLocationBasedDetection && (detectedCountry == null
+ || detectedCountry.getSource() > Country.COUNTRY_SOURCE_LOCATION)
+ && isAirplaneModeOff() && mListener != null && isGeoCoderImplemented()) {
+ // Start finding location when the source is less reliable than the
+ // location and the airplane mode is off (as geocoder will not
+ // work).
+ // TODO : Shall we give up starting the detector within a
+ // period of time?
+ startLocationBasedDetector(mLocationBasedCountryDetectionListener);
+ }
+ if (detectedCountry == null
+ || detectedCountry.getSource() >= Country.COUNTRY_SOURCE_LOCATION) {
+ // Schedule the location refresh if the country source is
+ // not more reliable than the location or no country is
+ // found.
+ // TODO: Listen to the preference change of GPS, Wifi etc,
+ // and start detecting the country.
+ scheduleLocationRefresh();
+ } else {
+ // Cancel the location refresh once the current source is
+ // more reliable than the location.
+ cancelLocationRefresh();
+ stopLocationBasedDetector();
+ }
+ }
+
+ /**
+ * Find the country from LocationProvider.
+ */
+ private synchronized void startLocationBasedDetector(CountryListener listener) {
+ if (mLocationBasedCountryDetector != null) {
+ return;
+ }
+ mLocationBasedCountryDetector = createLocationBasedCountryDetector();
+ mLocationBasedCountryDetector.setCountryListener(listener);
+ mLocationBasedCountryDetector.detectCountry();
+ }
+
+ private synchronized void stopLocationBasedDetector() {
+ if (mLocationBasedCountryDetector != null) {
+ mLocationBasedCountryDetector.stop();
+ mLocationBasedCountryDetector = null;
+ }
+ }
+
+ protected CountryDetectorBase createLocationBasedCountryDetector() {
+ return new LocationBasedCountryDetector(mContext);
+ }
+
+ protected boolean isAirplaneModeOff() {
+ return Settings.System.getInt(
+ mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) == 0;
+ }
+
+ /**
+ * Notify the country change.
+ */
+ private void notifyIfCountryChanged(final Country country, final Country detectedCountry) {
+ if (detectedCountry != null && mListener != null
+ && (country == null || !country.equals(detectedCountry))) {
+ Slog.d(TAG,
+ "The country was changed from " + country != null ? country.getCountryIso() :
+ country + " to " + detectedCountry.getCountryIso());
+ notifyListener(detectedCountry);
+ }
+ }
+
+ /**
+ * Schedule the next location refresh. We will do nothing if the scheduled task exists.
+ */
+ private synchronized void scheduleLocationRefresh() {
+ if (mLocationRefreshTimer != null) return;
+ mLocationRefreshTimer = new Timer();
+ mLocationRefreshTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ mLocationRefreshTimer = null;
+ detectCountry(false, true);
+ }
+ }, LOCATION_REFRESH_INTERVAL);
+ }
+
+ /**
+ * Cancel the scheduled refresh task if it exists
+ */
+ private synchronized void cancelLocationRefresh() {
+ if (mLocationRefreshTimer != null) {
+ mLocationRefreshTimer.cancel();
+ mLocationRefreshTimer = null;
+ }
+ }
+
+ protected synchronized void addPhoneStateListener() {
+ if (mPhoneStateListener == null && mPhoneType == TelephonyManager.PHONE_TYPE_GSM) {
+ mLastState = null;
+ mPhoneStateListener = new PhoneStateListener() {
+ @Override
+ public void onServiceStateChanged(ServiceState serviceState) {
+ // TODO: Find out how often we will be notified, if this
+ // method is called too
+ // many times, let's consider querying the network.
+ Slog.d(TAG, "onServiceStateChanged");
+ // We only care the state change
+ if (mLastState == null || mLastState.getState() != serviceState.getState()) {
+ detectCountry(true, true);
+ mLastState = new ServiceState(serviceState);
+ }
+ }
+ };
+ mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
+ }
+ }
+
+ protected synchronized void removePhoneStateListener() {
+ if (mPhoneStateListener != null) {
+ mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+ mPhoneStateListener = null;
+ }
+ }
+
+ protected boolean isGeoCoderImplemented() {
+ return Geocoder.isPresent();
+ }
+}
diff --git a/services/java/com/android/server/location/CountryDetectorBase.java b/services/java/com/android/server/location/CountryDetectorBase.java
new file mode 100644
index 0000000..8326ef9
--- /dev/null
+++ b/services/java/com/android/server/location/CountryDetectorBase.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.location;
+
+import android.content.Context;
+import android.location.Country;
+import android.location.CountryListener;
+import android.os.Handler;
+
+/**
+ * This class defines the methods need to be implemented by the country
+ * detector.
+ * <p>
+ * Calling {@link #detectCountry} to start detecting the country. The country
+ * could be returned immediately if it is available.
+ *
+ * @hide
+ */
+public abstract class CountryDetectorBase {
+ protected final Handler mHandler;
+ protected final Context mContext;
+ protected CountryListener mListener;
+ protected Country mDetectedCountry;
+
+ public CountryDetectorBase(Context ctx) {
+ mContext = ctx;
+ mHandler = new Handler();
+ }
+
+ /**
+ * Start detecting the country that the user is in.
+ *
+ * @return the country if it is available immediately, otherwise null should
+ * be returned.
+ */
+ public abstract Country detectCountry();
+
+ /**
+ * Register a listener to receive the notification when the country is detected or changed.
+ * <p>
+ * The previous listener will be replaced if it exists.
+ */
+ public void setCountryListener(CountryListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Stop detecting the country. The detector should release all system services and be ready to
+ * be freed
+ */
+ public abstract void stop();
+
+ protected void notifyListener(Country country) {
+ if (mListener != null) {
+ mListener.onCountryDetected(country);
+ }
+ }
+}
diff --git a/services/java/com/android/server/location/LocationBasedCountryDetector.java b/services/java/com/android/server/location/LocationBasedCountryDetector.java
new file mode 100755
index 0000000..139f05d
--- /dev/null
+++ b/services/java/com/android/server/location/LocationBasedCountryDetector.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.location;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import android.content.Context;
+import android.location.Address;
+import android.location.Country;
+import android.location.Geocoder;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.util.Slog;
+
+/**
+ * This class detects which country the user currently is in through the enabled
+ * location providers and the GeoCoder
+ * <p>
+ * Use {@link #detectCountry} to start querying. If the location can not be
+ * resolved within the given time, the last known location will be used to get
+ * the user country through the GeoCoder. The IllegalStateException will be
+ * thrown if there is a ongoing query.
+ * <p>
+ * The current query can be stopped by {@link #stop()}
+ *
+ * @hide
+ */
+public class LocationBasedCountryDetector extends CountryDetectorBase {
+ private final static String TAG = "LocationBasedCountryDetector";
+ private final static long QUERY_LOCATION_TIMEOUT = 1000 * 60 * 5; // 5 mins
+
+ /**
+ * Used for canceling location query
+ */
+ protected Timer mTimer;
+
+ /**
+ * The thread to query the country from the GeoCoder.
+ */
+ protected Thread mQueryThread;
+ protected List<LocationListener> mLocationListeners;
+
+ private LocationManager mLocationManager;
+ private List<String> mEnabledProviders;
+
+ public LocationBasedCountryDetector(Context ctx) {
+ super(ctx);
+ mLocationManager = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE);
+ }
+
+ /**
+ * @return the ISO 3166-1 two letters country code from the location
+ */
+ protected String getCountryFromLocation(Location location) {
+ String country = null;
+ Geocoder geoCoder = new Geocoder(mContext);
+ try {
+ List<Address> addresses = geoCoder.getFromLocation(
+ location.getLatitude(), location.getLongitude(), 1);
+ if (addresses != null && addresses.size() > 0) {
+ country = addresses.get(0).getCountryCode();
+ }
+ } catch (IOException e) {
+ Slog.w(TAG, "Exception occurs when getting country from location");
+ }
+ return country;
+ }
+
+ /**
+ * Register the listeners with the location providers
+ */
+ protected void registerEnabledProviders(List<LocationListener> listeners) {
+ int total = listeners.size();
+ for (int i = 0; i< total; i++) {
+ mLocationManager.requestLocationUpdates(
+ mEnabledProviders.get(i), 0, 0, listeners.get(i));
+ }
+ }
+
+ /**
+ * Unregister the listeners with the location providers
+ */
+ protected void unregisterProviders(List<LocationListener> listeners) {
+ for (LocationListener listener : listeners) {
+ mLocationManager.removeUpdates(listener);
+ }
+ }
+
+ /**
+ * @return the last known location from all providers
+ */
+ protected Location getLastKnownLocation() {
+ List<String> providers = mLocationManager.getAllProviders();
+ Location bestLocation = null;
+ for (String provider : providers) {
+ Location lastKnownLocation = mLocationManager.getLastKnownLocation(provider);
+ if (lastKnownLocation != null) {
+ if (bestLocation == null || bestLocation.getTime() < lastKnownLocation.getTime()) {
+ bestLocation = lastKnownLocation;
+ }
+ }
+ }
+ return bestLocation;
+ }
+
+ /**
+ * @return the timeout for querying the location.
+ */
+ protected long getQueryLocationTimeout() {
+ return QUERY_LOCATION_TIMEOUT;
+ }
+
+ /**
+ * @return the total number of enabled location providers
+ */
+ protected int getTotalEnabledProviders() {
+ if (mEnabledProviders == null) {
+ mEnabledProviders = mLocationManager.getProviders(true);
+ }
+ return mEnabledProviders.size();
+ }
+
+ /**
+ * Start detecting the country.
+ * <p>
+ * Queries the location from all location providers, then starts a thread to query the
+ * country from GeoCoder.
+ */
+ @Override
+ public synchronized Country detectCountry() {
+ if (mLocationListeners != null) {
+ throw new IllegalStateException();
+ }
+ // Request the location from all enabled providers.
+ int totalProviders = getTotalEnabledProviders();
+ if (totalProviders > 0) {
+ mLocationListeners = new ArrayList<LocationListener>(totalProviders);
+ for (int i = 0; i < totalProviders; i++) {
+ LocationListener listener = new LocationListener () {
+ public void onLocationChanged(Location location) {
+ if (location != null) {
+ LocationBasedCountryDetector.this.stop();
+ queryCountryCode(location);
+ }
+ }
+ public void onProviderDisabled(String provider) {
+ }
+ public void onProviderEnabled(String provider) {
+ }
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ }
+ };
+ mLocationListeners.add(listener);
+ }
+ registerEnabledProviders(mLocationListeners);
+ mTimer = new Timer();
+ mTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ mTimer = null;
+ LocationBasedCountryDetector.this.stop();
+ // Looks like no provider could provide the location, let's try the last
+ // known location.
+ queryCountryCode(getLastKnownLocation());
+ }
+ }, getQueryLocationTimeout());
+ } else {
+ // There is no provider enabled.
+ queryCountryCode(getLastKnownLocation());
+ }
+ return mDetectedCountry;
+ }
+
+ /**
+ * Stop the current query without notifying the listener.
+ */
+ @Override
+ public synchronized void stop() {
+ if (mLocationListeners != null) {
+ unregisterProviders(mLocationListeners);
+ mLocationListeners = null;
+ }
+ if (mTimer != null) {
+ mTimer.cancel();
+ mTimer = null;
+ }
+ }
+
+ /**
+ * Start a new thread to query the country from Geocoder.
+ */
+ private synchronized void queryCountryCode(final Location location) {
+ if (location == null) {
+ notifyListener(null);
+ return;
+ }
+ if (mQueryThread != null) return;
+ mQueryThread = new Thread(new Runnable() {
+ public void run() {
+ String countryIso = null;
+ if (location != null) {
+ countryIso = getCountryFromLocation(location);
+ }
+ if (countryIso != null) {
+ mDetectedCountry = new Country(countryIso, Country.COUNTRY_SOURCE_LOCATION);
+ } else {
+ mDetectedCountry = null;
+ }
+ notifyListener(mDetectedCountry);
+ mQueryThread = null;
+ }
+ });
+ mQueryThread.start();
+ }
+}
diff --git a/services/jni/Android.mk b/services/jni/Android.mk
index c90879d..f5a5b4d 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -4,27 +4,37 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
com_android_server_AlarmManagerService.cpp \
com_android_server_BatteryService.cpp \
+ com_android_server_InputApplication.cpp \
+ com_android_server_InputApplicationHandle.cpp \
com_android_server_InputManager.cpp \
+ com_android_server_InputWindow.cpp \
+ com_android_server_InputWindowHandle.cpp \
com_android_server_LightsService.cpp \
com_android_server_PowerManagerService.cpp \
com_android_server_SystemServer.cpp \
com_android_server_VibratorService.cpp \
- com_android_server_location_GpsLocationProvider.cpp \
+ com_android_server_location_GpsLocationProvider.cpp \
onload.cpp
LOCAL_C_INCLUDES += \
- $(JNI_H_INCLUDE)
+ $(JNI_H_INCLUDE) \
+ frameworks/base/services \
+ frameworks/base/core/jni \
+ external/skia/include/core
LOCAL_SHARED_LIBRARIES := \
libandroid_runtime \
- libcutils \
- libhardware \
- libhardware_legacy \
- libnativehelper \
+ libcutils \
+ libhardware \
+ libhardware_legacy \
+ libnativehelper \
libsystem_server \
- libutils \
- libui \
- libsurfaceflinger_client
+ libutils \
+ libui \
+ libinput \
+ libskia \
+ libsurfaceflinger_client \
+ libusbhost
ifeq ($(TARGET_SIMULATOR),true)
ifeq ($(TARGET_OS),linux)
@@ -35,10 +45,9 @@ endif
endif
ifeq ($(WITH_MALLOC_LEAK_CHECK),true)
- LOCAL_CFLAGS += -DMALLOC_LEAK_CHECK
+ LOCAL_CFLAGS += -DMALLOC_LEAK_CHECK
endif
LOCAL_MODULE:= libandroid_servers
include $(BUILD_SHARED_LIBRARY)
-
diff --git a/services/jni/com_android_server_InputApplication.cpp b/services/jni/com_android_server_InputApplication.cpp
new file mode 100644
index 0000000..a46a162
--- /dev/null
+++ b/services/jni/com_android_server_InputApplication.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputApplication"
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include "com_android_server_InputApplication.h"
+#include "com_android_server_InputApplicationHandle.h"
+
+namespace android {
+
+static struct {
+ jclass clazz;
+
+ jfieldID inputApplicationHandle;
+ jfieldID name;
+ jfieldID dispatchingTimeoutNanos;
+} gInputApplicationClassInfo;
+
+
+// --- Global functions ---
+
+void android_server_InputApplication_toNative(
+ JNIEnv* env, jobject inputApplicationObj, InputApplication* outInputApplication) {
+ jobject inputApplicationHandleObj = env->GetObjectField(inputApplicationObj,
+ gInputApplicationClassInfo.inputApplicationHandle);
+ if (inputApplicationHandleObj) {
+ outInputApplication->inputApplicationHandle =
+ android_server_InputApplicationHandle_getHandle(env, inputApplicationHandleObj);
+ env->DeleteLocalRef(inputApplicationHandleObj);
+ } else {
+ outInputApplication->inputApplicationHandle = NULL;
+ }
+
+ jstring nameObj = jstring(env->GetObjectField(inputApplicationObj,
+ gInputApplicationClassInfo.name));
+ if (nameObj) {
+ const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
+ outInputApplication->name.setTo(nameStr);
+ env->ReleaseStringUTFChars(nameObj, nameStr);
+ env->DeleteLocalRef(nameObj);
+ } else {
+ LOGE("InputApplication.name should not be null.");
+ outInputApplication->name.setTo("unknown");
+ }
+
+ outInputApplication->dispatchingTimeout = env->GetLongField(inputApplicationObj,
+ gInputApplicationClassInfo.dispatchingTimeoutNanos);
+}
+
+
+// --- JNI ---
+
+#define FIND_CLASS(var, className) \
+ var = env->FindClass(className); \
+ LOG_FATAL_IF(! var, "Unable to find class " className); \
+ var = jclass(env->NewGlobalRef(var));
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+ var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_server_InputApplication(JNIEnv* env) {
+ FIND_CLASS(gInputApplicationClassInfo.clazz, "com/android/server/InputApplication");
+
+ GET_FIELD_ID(gInputApplicationClassInfo.inputApplicationHandle,
+ gInputApplicationClassInfo.clazz,
+ "inputApplicationHandle", "Lcom/android/server/InputApplicationHandle;");
+
+ GET_FIELD_ID(gInputApplicationClassInfo.name, gInputApplicationClassInfo.clazz,
+ "name", "Ljava/lang/String;");
+
+ GET_FIELD_ID(gInputApplicationClassInfo.dispatchingTimeoutNanos,
+ gInputApplicationClassInfo.clazz,
+ "dispatchingTimeoutNanos", "J");
+ return 0;
+}
+
+} /* namespace android */
diff --git a/services/jni/com_android_server_InputApplication.h b/services/jni/com_android_server_InputApplication.h
new file mode 100644
index 0000000..85fb891
--- /dev/null
+++ b/services/jni/com_android_server_InputApplication.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SERVER_INPUT_APPLICATION_H
+#define _ANDROID_SERVER_INPUT_APPLICATION_H
+
+#include <input/InputApplication.h>
+
+#include "JNIHelp.h"
+#include "jni.h"
+
+namespace android {
+
+extern void android_server_InputApplication_toNative(
+ JNIEnv* env, jobject inputApplicationObj, InputApplication* outInputApplication);
+
+} // namespace android
+
+#endif // _ANDROID_SERVER_INPUT_APPLICATION_H
diff --git a/services/jni/com_android_server_InputApplicationHandle.cpp b/services/jni/com_android_server_InputApplicationHandle.cpp
new file mode 100644
index 0000000..ab82635
--- /dev/null
+++ b/services/jni/com_android_server_InputApplicationHandle.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputApplicationHandle"
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/threads.h>
+
+#include "com_android_server_InputApplicationHandle.h"
+
+namespace android {
+
+static struct {
+ jclass clazz;
+
+ jfieldID ptr;
+} gInputApplicationHandleClassInfo;
+
+static Mutex gHandleMutex;
+
+
+// --- NativeInputApplicationHandle ---
+
+NativeInputApplicationHandle::NativeInputApplicationHandle(jweak objWeak) :
+ mObjWeak(objWeak) {
+}
+
+NativeInputApplicationHandle::~NativeInputApplicationHandle() {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->DeleteWeakGlobalRef(mObjWeak);
+}
+
+jobject NativeInputApplicationHandle::getInputApplicationHandleObjLocalRef(JNIEnv* env) {
+ return env->NewLocalRef(mObjWeak);
+}
+
+
+// --- Global functions ---
+
+sp<InputApplicationHandle> android_server_InputApplicationHandle_getHandle(
+ JNIEnv* env, jobject inputApplicationHandleObj) {
+ if (!inputApplicationHandleObj) {
+ return NULL;
+ }
+
+ AutoMutex _l(gHandleMutex);
+
+ int ptr = env->GetIntField(inputApplicationHandleObj, gInputApplicationHandleClassInfo.ptr);
+ NativeInputApplicationHandle* handle;
+ if (ptr) {
+ handle = reinterpret_cast<NativeInputApplicationHandle*>(ptr);
+ } else {
+ jweak objWeak = env->NewWeakGlobalRef(inputApplicationHandleObj);
+ handle = new NativeInputApplicationHandle(objWeak);
+ handle->incStrong(inputApplicationHandleObj);
+ env->SetIntField(inputApplicationHandleObj, gInputApplicationHandleClassInfo.ptr,
+ reinterpret_cast<int>(handle));
+ }
+ return handle;
+}
+
+
+// --- JNI ---
+
+static void android_server_InputApplicationHandle_nativeDispose(JNIEnv* env, jobject obj) {
+ AutoMutex _l(gHandleMutex);
+
+ int ptr = env->GetIntField(obj, gInputApplicationHandleClassInfo.ptr);
+ if (ptr) {
+ env->SetIntField(obj, gInputApplicationHandleClassInfo.ptr, 0);
+
+ NativeInputApplicationHandle* handle = reinterpret_cast<NativeInputApplicationHandle*>(ptr);
+ handle->decStrong(obj);
+ }
+}
+
+
+static JNINativeMethod gInputApplicationHandleMethods[] = {
+ /* name, signature, funcPtr */
+ { "nativeDispose", "()V",
+ (void*) android_server_InputApplicationHandle_nativeDispose },
+};
+
+#define FIND_CLASS(var, className) \
+ var = env->FindClass(className); \
+ LOG_FATAL_IF(! var, "Unable to find class " className); \
+ var = jclass(env->NewGlobalRef(var));
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+ var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_server_InputApplicationHandle(JNIEnv* env) {
+ int res = jniRegisterNativeMethods(env, "com/android/server/InputApplicationHandle",
+ gInputApplicationHandleMethods, NELEM(gInputApplicationHandleMethods));
+ LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+
+ FIND_CLASS(gInputApplicationHandleClassInfo.clazz, "com/android/server/InputApplicationHandle");
+
+ GET_FIELD_ID(gInputApplicationHandleClassInfo.ptr, gInputApplicationHandleClassInfo.clazz,
+ "ptr", "I");
+
+ return 0;
+}
+
+} /* namespace android */
diff --git a/services/jni/com_android_server_InputApplicationHandle.h b/services/jni/com_android_server_InputApplicationHandle.h
new file mode 100644
index 0000000..9d18721
--- /dev/null
+++ b/services/jni/com_android_server_InputApplicationHandle.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H
+#define _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H
+
+#include <input/InputApplication.h>
+
+#include "JNIHelp.h"
+#include "jni.h"
+
+namespace android {
+
+class NativeInputApplicationHandle : public InputApplicationHandle {
+public:
+ NativeInputApplicationHandle(jweak objWeak);
+ virtual ~NativeInputApplicationHandle();
+
+ jobject getInputApplicationHandleObjLocalRef(JNIEnv* env);
+
+private:
+ jweak mObjWeak;
+};
+
+
+extern sp<InputApplicationHandle> android_server_InputApplicationHandle_getHandle(
+ JNIEnv* env, jobject inputApplicationHandleObj);
+
+} // namespace android
+
+#endif // _ANDROID_SERVER_INPUT_APPLICATION_HANDLE_H
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index 171471e..5b329bb 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -24,25 +24,31 @@
// Log debug messages about InputDispatcherPolicy
#define DEBUG_INPUT_DISPATCHER_POLICY 0
+
#include "JNIHelp.h"
#include "jni.h"
#include <limits.h>
#include <android_runtime/AndroidRuntime.h>
-#include <ui/InputReader.h>
-#include <ui/InputDispatcher.h>
-#include <ui/InputManager.h>
-#include <ui/InputTransport.h>
+
#include <utils/Log.h>
#include <utils/threads.h>
-#include "../../core/jni/android_view_KeyEvent.h"
-#include "../../core/jni/android_view_MotionEvent.h"
-#include "../../core/jni/android_view_InputChannel.h"
+
+#include <input/InputManager.h>
+#include <input/PointerController.h>
+
+#include <android_view_KeyEvent.h>
+#include <android_view_MotionEvent.h>
+#include <android_view_InputChannel.h>
+#include <android/graphics/GraphicsJNI.h>
+
#include "com_android_server_PowerManagerService.h"
+#include "com_android_server_InputApplication.h"
+#include "com_android_server_InputApplicationHandle.h"
+#include "com_android_server_InputWindow.h"
+#include "com_android_server_InputWindowHandle.h"
namespace android {
-// ----------------------------------------------------------------------------
-
static struct {
jclass clazz;
@@ -52,73 +58,19 @@ static struct {
jmethodID notifyANR;
jmethodID interceptKeyBeforeQueueing;
jmethodID interceptKeyBeforeDispatching;
+ jmethodID dispatchUnhandledKey;
jmethodID checkInjectEventsPermission;
jmethodID filterTouchEvents;
jmethodID filterJumpyTouchEvents;
jmethodID getVirtualKeyQuietTimeMillis;
- jmethodID getVirtualKeyDefinitions;
- jmethodID getInputDeviceCalibration;
jmethodID getExcludedDeviceNames;
jmethodID getMaxEventsPerSecond;
+ jmethodID getPointerLayer;
+ jmethodID getPointerIcon;
} gCallbacksClassInfo;
static struct {
jclass clazz;
-
- jfieldID scanCode;
- jfieldID centerX;
- jfieldID centerY;
- jfieldID width;
- jfieldID height;
-} gVirtualKeyDefinitionClassInfo;
-
-static struct {
- jclass clazz;
-
- jfieldID keys;
- jfieldID values;
-} gInputDeviceCalibrationClassInfo;
-
-static struct {
- jclass clazz;
-
- jfieldID inputChannel;
- jfieldID name;
- jfieldID layoutParamsFlags;
- jfieldID layoutParamsType;
- jfieldID dispatchingTimeoutNanos;
- jfieldID frameLeft;
- jfieldID frameTop;
- jfieldID frameRight;
- jfieldID frameBottom;
- jfieldID visibleFrameLeft;
- jfieldID visibleFrameTop;
- jfieldID visibleFrameRight;
- jfieldID visibleFrameBottom;
- jfieldID touchableAreaLeft;
- jfieldID touchableAreaTop;
- jfieldID touchableAreaRight;
- jfieldID touchableAreaBottom;
- jfieldID visible;
- jfieldID canReceiveKeys;
- jfieldID hasFocus;
- jfieldID hasWallpaper;
- jfieldID paused;
- jfieldID layer;
- jfieldID ownerPid;
- jfieldID ownerUid;
-} gInputWindowClassInfo;
-
-static struct {
- jclass clazz;
-
- jfieldID name;
- jfieldID dispatchingTimeoutNanos;
- jfieldID token;
-} gInputApplicationClassInfo;
-
-static struct {
- jclass clazz;
} gKeyEventClassInfo;
static struct {
@@ -146,13 +98,37 @@ static struct {
jfieldID navigation;
} gConfigurationClassInfo;
-// ----------------------------------------------------------------------------
+static struct {
+ jclass clazz;
+
+ jfieldID bitmap;
+ jfieldID hotSpotX;
+ jfieldID hotSpotY;
+} gPointerIconClassInfo;
+
-static inline nsecs_t now() {
- return systemTime(SYSTEM_TIME_MONOTONIC);
+// --- Global functions ---
+
+static jobject getInputApplicationHandleObjLocalRef(JNIEnv* env,
+ const sp<InputApplicationHandle>& inputApplicationHandle) {
+ if (inputApplicationHandle == NULL) {
+ return NULL;
+ }
+ return static_cast<NativeInputApplicationHandle*>(inputApplicationHandle.get())->
+ getInputApplicationHandleObjLocalRef(env);
}
-// ----------------------------------------------------------------------------
+static jobject getInputWindowHandleObjLocalRef(JNIEnv* env,
+ const sp<InputWindowHandle>& inputWindowHandle) {
+ if (inputWindowHandle == NULL) {
+ return NULL;
+ }
+ return static_cast<NativeInputWindowHandle*>(inputWindowHandle.get())->
+ getInputWindowHandleObjLocalRef(env);
+}
+
+
+// --- NativeInputManager ---
class NativeInputManager : public virtual RefBase,
public virtual InputReaderPolicyInterface,
@@ -171,7 +147,7 @@ public:
void setDisplayOrientation(int32_t displayId, int32_t orientation);
status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel,
- jweak inputChannelObjWeak, bool monitor);
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor);
status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel);
void setInputWindows(JNIEnv* env, jobjectArray windowObjArray);
@@ -185,11 +161,8 @@ public:
virtual bool filterTouchEvents();
virtual bool filterJumpyTouchEvents();
virtual nsecs_t getVirtualKeyQuietTime();
- virtual void getVirtualKeyDefinitions(const String8& deviceName,
- Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions);
- virtual void getInputDeviceCalibration(const String8& deviceName,
- InputDeviceCalibration& outCalibration);
virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames);
+ virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId);
/* --- InputDispatcherPolicyInterface implementation --- */
@@ -197,37 +170,22 @@ public:
uint32_t policyFlags);
virtual void notifyConfigurationChanged(nsecs_t when);
virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
- const sp<InputChannel>& inputChannel);
- virtual void notifyInputChannelBroken(const sp<InputChannel>& inputChannel);
+ const sp<InputWindowHandle>& inputWindowHandle);
+ virtual void notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle);
virtual nsecs_t getKeyRepeatTimeout();
virtual nsecs_t getKeyRepeatDelay();
virtual int32_t getMaxEventsPerSecond();
- virtual void interceptKeyBeforeQueueing(nsecs_t when, int32_t deviceId,
- int32_t action, int32_t& flags, int32_t keyCode, int32_t scanCode,
- uint32_t& policyFlags);
+ virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags);
virtual void interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags);
- virtual bool interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel,
+ virtual bool interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle,
const KeyEvent* keyEvent, uint32_t policyFlags);
+ virtual bool dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle,
+ const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent);
virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType);
virtual bool checkInjectEventsPermissionNonReentrant(
int32_t injectorPid, int32_t injectorUid);
private:
- class ApplicationToken : public InputApplicationHandle {
- jweak mTokenObjWeak;
-
- public:
- ApplicationToken(jweak tokenObjWeak) :
- mTokenObjWeak(tokenObjWeak) { }
-
- virtual ~ApplicationToken() {
- JNIEnv* env = NativeInputManager::jniEnv();
- env->DeleteWeakGlobalRef(mTokenObjWeak);
- }
-
- inline jweak getTokenObj() { return mTokenObjWeak; }
- };
-
sp<InputManager> mInputManager;
jobject mCallbacksObj;
@@ -240,23 +198,20 @@ private:
// Cached throttling policy.
int32_t mMaxEventsPerSecond;
- // Cached display state. (lock mDisplayLock)
- Mutex mDisplayLock;
- int32_t mDisplayWidth, mDisplayHeight;
- int32_t mDisplayOrientation;
+ Mutex mLock;
+ struct Locked {
+ // Display size information.
+ int32_t displayWidth, displayHeight; // -1 when initialized
+ int32_t displayOrientation;
+
+ // Pointer controller singleton, created and destroyed as needed.
+ wp<PointerController> pointerController;
+ } mLocked;
// Power manager interactions.
bool isScreenOn();
bool isScreenBright();
- // Weak references to all currently registered input channels by connection pointer.
- Mutex mInputChannelRegistryLock;
- KeyedVector<InputChannel*, jweak> mInputChannelObjWeakTable;
-
- jobject getInputChannelObjLocal(JNIEnv* env, const sp<InputChannel>& inputChannel);
-
- static bool populateWindow(JNIEnv* env, jobject windowObj, InputWindow& outWindow);
-
static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
static inline JNIEnv* jniEnv() {
@@ -264,16 +219,22 @@ private:
}
};
-// ----------------------------------------------------------------------------
+
NativeInputManager::NativeInputManager(jobject callbacksObj) :
mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), mVirtualKeyQuietTime(-1),
- mMaxEventsPerSecond(-1),
- mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(ROTATION_0) {
+ mMaxEventsPerSecond(-1) {
JNIEnv* env = jniEnv();
mCallbacksObj = env->NewGlobalRef(callbacksObj);
+ {
+ AutoMutex _l(mLock);
+ mLocked.displayWidth = -1;
+ mLocked.displayHeight = -1;
+ mLocked.displayOrientation = ROTATION_0;
+ }
+
sp<EventHub> eventHub = new EventHub();
mInputManager = new InputManager(eventHub, this, this);
}
@@ -304,119 +265,62 @@ bool NativeInputManager::checkAndClearExceptionFromCallback(JNIEnv* env, const c
void NativeInputManager::setDisplaySize(int32_t displayId, int32_t width, int32_t height) {
if (displayId == 0) {
- AutoMutex _l(mDisplayLock);
+ AutoMutex _l(mLock);
- mDisplayWidth = width;
- mDisplayHeight = height;
+ if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
+ mLocked.displayWidth = width;
+ mLocked.displayHeight = height;
+
+ sp<PointerController> controller = mLocked.pointerController.promote();
+ if (controller != NULL) {
+ controller->setDisplaySize(width, height);
+ }
+ }
}
}
void NativeInputManager::setDisplayOrientation(int32_t displayId, int32_t orientation) {
if (displayId == 0) {
- AutoMutex _l(mDisplayLock);
+ AutoMutex _l(mLock);
- mDisplayOrientation = orientation;
- }
-}
+ if (mLocked.displayOrientation != orientation) {
+ mLocked.displayOrientation = orientation;
-status_t NativeInputManager::registerInputChannel(JNIEnv* env,
- const sp<InputChannel>& inputChannel, jobject inputChannelObj, bool monitor) {
- jweak inputChannelObjWeak = env->NewWeakGlobalRef(inputChannelObj);
- if (! inputChannelObjWeak) {
- LOGE("Could not create weak reference for input channel.");
- LOGE_EX(env);
- return NO_MEMORY;
- }
-
- status_t status;
- {
- AutoMutex _l(mInputChannelRegistryLock);
-
- ssize_t index = mInputChannelObjWeakTable.indexOfKey(inputChannel.get());
- if (index >= 0) {
- LOGE("Input channel object '%s' has already been registered",
- inputChannel->getName().string());
- status = INVALID_OPERATION;
- goto DeleteWeakRef;
+ sp<PointerController> controller = mLocked.pointerController.promote();
+ if (controller != NULL) {
+ controller->setDisplayOrientation(orientation);
+ }
}
-
- mInputChannelObjWeakTable.add(inputChannel.get(), inputChannelObjWeak);
- }
-
- status = mInputManager->getDispatcher()->registerInputChannel(inputChannel, monitor);
- if (! status) {
- // Success.
- return OK;
- }
-
- // Failed!
- {
- AutoMutex _l(mInputChannelRegistryLock);
- mInputChannelObjWeakTable.removeItem(inputChannel.get());
}
+}
-DeleteWeakRef:
- env->DeleteWeakGlobalRef(inputChannelObjWeak);
- return status;
+status_t NativeInputManager::registerInputChannel(JNIEnv* env,
+ const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
+ return mInputManager->getDispatcher()->registerInputChannel(
+ inputChannel, inputWindowHandle, monitor);
}
status_t NativeInputManager::unregisterInputChannel(JNIEnv* env,
const sp<InputChannel>& inputChannel) {
- jweak inputChannelObjWeak;
- {
- AutoMutex _l(mInputChannelRegistryLock);
-
- ssize_t index = mInputChannelObjWeakTable.indexOfKey(inputChannel.get());
- if (index < 0) {
- LOGE("Input channel object '%s' is not currently registered",
- inputChannel->getName().string());
- return INVALID_OPERATION;
- }
-
- inputChannelObjWeak = mInputChannelObjWeakTable.valueAt(index);
- mInputChannelObjWeakTable.removeItemsAt(index);
- }
-
- env->DeleteWeakGlobalRef(inputChannelObjWeak);
-
return mInputManager->getDispatcher()->unregisterInputChannel(inputChannel);
}
-jobject NativeInputManager::getInputChannelObjLocal(JNIEnv* env,
- const sp<InputChannel>& inputChannel) {
- InputChannel* inputChannelPtr = inputChannel.get();
- if (! inputChannelPtr) {
- return NULL;
- }
-
- {
- AutoMutex _l(mInputChannelRegistryLock);
-
- ssize_t index = mInputChannelObjWeakTable.indexOfKey(inputChannelPtr);
- if (index < 0) {
- return NULL;
- }
-
- jweak inputChannelObjWeak = mInputChannelObjWeakTable.valueAt(index);
- return env->NewLocalRef(inputChannelObjWeak);
- }
-}
-
bool NativeInputManager::getDisplayInfo(int32_t displayId,
int32_t* width, int32_t* height, int32_t* orientation) {
bool result = false;
if (displayId == 0) {
- AutoMutex _l(mDisplayLock);
+ AutoMutex _l(mLock);
- if (mDisplayWidth > 0) {
+ if (mLocked.displayWidth > 0 && mLocked.displayHeight > 0) {
if (width) {
- *width = mDisplayWidth;
+ *width = mLocked.displayWidth;
}
if (height) {
- *height = mDisplayHeight;
+ *height = mLocked.displayHeight;
}
if (orientation) {
- *orientation = mDisplayOrientation;
+ *orientation = mLocked.displayOrientation;
}
result = true;
}
@@ -472,83 +376,6 @@ nsecs_t NativeInputManager::getVirtualKeyQuietTime() {
return mVirtualKeyQuietTime;
}
-void NativeInputManager::getVirtualKeyDefinitions(const String8& deviceName,
- Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions) {
- outVirtualKeyDefinitions.clear();
-
- JNIEnv* env = jniEnv();
-
- jstring deviceNameStr = env->NewStringUTF(deviceName.string());
- if (! checkAndClearExceptionFromCallback(env, "getVirtualKeyDefinitions")) {
- jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacksObj,
- gCallbacksClassInfo.getVirtualKeyDefinitions, deviceNameStr));
- if (! checkAndClearExceptionFromCallback(env, "getVirtualKeyDefinitions") && result) {
- jsize length = env->GetArrayLength(result);
- for (jsize i = 0; i < length; i++) {
- jobject item = env->GetObjectArrayElement(result, i);
-
- outVirtualKeyDefinitions.add();
- outVirtualKeyDefinitions.editTop().scanCode =
- int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.scanCode));
- outVirtualKeyDefinitions.editTop().centerX =
- int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.centerX));
- outVirtualKeyDefinitions.editTop().centerY =
- int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.centerY));
- outVirtualKeyDefinitions.editTop().width =
- int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.width));
- outVirtualKeyDefinitions.editTop().height =
- int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.height));
-
- env->DeleteLocalRef(item);
- }
- env->DeleteLocalRef(result);
- }
- env->DeleteLocalRef(deviceNameStr);
- }
-}
-
-void NativeInputManager::getInputDeviceCalibration(const String8& deviceName,
- InputDeviceCalibration& outCalibration) {
- outCalibration.clear();
-
- JNIEnv* env = jniEnv();
-
- jstring deviceNameStr = env->NewStringUTF(deviceName.string());
- if (! checkAndClearExceptionFromCallback(env, "getInputDeviceCalibration")) {
- jobject result = env->CallObjectMethod(mCallbacksObj,
- gCallbacksClassInfo.getInputDeviceCalibration, deviceNameStr);
- if (! checkAndClearExceptionFromCallback(env, "getInputDeviceCalibration") && result) {
- jobjectArray keys = jobjectArray(env->GetObjectField(result,
- gInputDeviceCalibrationClassInfo.keys));
- jobjectArray values = jobjectArray(env->GetObjectField(result,
- gInputDeviceCalibrationClassInfo.values));
-
- jsize length = env->GetArrayLength(keys);
- for (jsize i = 0; i < length; i++) {
- jstring keyStr = jstring(env->GetObjectArrayElement(keys, i));
- jstring valueStr = jstring(env->GetObjectArrayElement(values, i));
-
- const char* keyChars = env->GetStringUTFChars(keyStr, NULL);
- String8 key(keyChars);
- env->ReleaseStringUTFChars(keyStr, keyChars);
-
- const char* valueChars = env->GetStringUTFChars(valueStr, NULL);
- String8 value(valueChars);
- env->ReleaseStringUTFChars(valueStr, valueChars);
-
- outCalibration.addProperty(key, value);
-
- env->DeleteLocalRef(keyStr);
- env->DeleteLocalRef(valueStr);
- }
- env->DeleteLocalRef(keys);
- env->DeleteLocalRef(values);
- env->DeleteLocalRef(result);
- }
- env->DeleteLocalRef(deviceNameStr);
- }
-}
-
void NativeInputManager::getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) {
outExcludedDeviceNames.clear();
@@ -571,6 +398,41 @@ void NativeInputManager::getExcludedDeviceNames(Vector<String8>& outExcludedDevi
}
}
+sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32_t deviceId) {
+ AutoMutex _l(mLock);
+
+ sp<PointerController> controller = mLocked.pointerController.promote();
+ if (controller == NULL) {
+ JNIEnv* env = jniEnv();
+ jint layer = env->CallIntMethod(mCallbacksObj, gCallbacksClassInfo.getPointerLayer);
+ if (checkAndClearExceptionFromCallback(env, "getPointerLayer")) {
+ layer = -1;
+ }
+
+ controller = new PointerController(layer);
+ mLocked.pointerController = controller;
+
+ controller->setDisplaySize(mLocked.displayWidth, mLocked.displayHeight);
+ controller->setDisplayOrientation(mLocked.displayOrientation);
+
+ jobject iconObj = env->CallObjectMethod(mCallbacksObj, gCallbacksClassInfo.getPointerIcon);
+ if (!checkAndClearExceptionFromCallback(env, "getPointerIcon") && iconObj) {
+ jfloat iconHotSpotX = env->GetFloatField(iconObj, gPointerIconClassInfo.hotSpotX);
+ jfloat iconHotSpotY = env->GetFloatField(iconObj, gPointerIconClassInfo.hotSpotY);
+ jobject iconBitmapObj = env->GetObjectField(iconObj, gPointerIconClassInfo.bitmap);
+ if (iconBitmapObj) {
+ SkBitmap* iconBitmap = GraphicsJNI::getNativeBitmap(env, iconBitmapObj);
+ if (iconBitmap) {
+ controller->setPointerIcon(iconBitmap, iconHotSpotX, iconHotSpotY);
+ }
+ env->DeleteLocalRef(iconBitmapObj);
+ }
+ env->DeleteLocalRef(iconObj);
+ }
+ }
+ return controller;
+}
+
void NativeInputManager::notifySwitch(nsecs_t when, int32_t switchCode,
int32_t switchValue, uint32_t policyFlags) {
#if DEBUG_INPUT_DISPATCHER_POLICY
@@ -601,50 +463,46 @@ void NativeInputManager::notifyConfigurationChanged(nsecs_t when) {
}
nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
- const sp<InputChannel>& inputChannel) {
+ const sp<InputWindowHandle>& inputWindowHandle) {
#if DEBUG_INPUT_DISPATCHER_POLICY
LOGD("notifyANR");
#endif
JNIEnv* env = jniEnv();
- jobject tokenObjLocal;
- if (inputApplicationHandle.get()) {
- ApplicationToken* token = static_cast<ApplicationToken*>(inputApplicationHandle.get());
- jweak tokenObjWeak = token->getTokenObj();
- tokenObjLocal = env->NewLocalRef(tokenObjWeak);
- } else {
- tokenObjLocal = NULL;
- }
+ jobject inputApplicationHandleObj =
+ getInputApplicationHandleObjLocalRef(env, inputApplicationHandle);
+ jobject inputWindowHandleObj =
+ getInputWindowHandleObjLocalRef(env, inputWindowHandle);
- jobject inputChannelObjLocal = getInputChannelObjLocal(env, inputChannel);
jlong newTimeout = env->CallLongMethod(mCallbacksObj,
- gCallbacksClassInfo.notifyANR, tokenObjLocal, inputChannelObjLocal);
+ gCallbacksClassInfo.notifyANR, inputApplicationHandleObj, inputWindowHandleObj);
if (checkAndClearExceptionFromCallback(env, "notifyANR")) {
newTimeout = 0; // abort dispatch
} else {
assert(newTimeout >= 0);
}
- env->DeleteLocalRef(tokenObjLocal);
- env->DeleteLocalRef(inputChannelObjLocal);
+ env->DeleteLocalRef(inputWindowHandleObj);
+ env->DeleteLocalRef(inputApplicationHandleObj);
return newTimeout;
}
-void NativeInputManager::notifyInputChannelBroken(const sp<InputChannel>& inputChannel) {
+void NativeInputManager::notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle) {
#if DEBUG_INPUT_DISPATCHER_POLICY
- LOGD("notifyInputChannelBroken - inputChannel='%s'", inputChannel->getName().string());
+ LOGD("notifyInputChannelBroken");
#endif
JNIEnv* env = jniEnv();
- jobject inputChannelObjLocal = getInputChannelObjLocal(env, inputChannel);
- if (inputChannelObjLocal) {
+ jobject inputWindowHandleObj =
+ getInputWindowHandleObjLocalRef(env, inputWindowHandle);
+ if (inputWindowHandleObj) {
env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyInputChannelBroken,
- inputChannelObjLocal);
+ inputWindowHandleObj);
checkAndClearExceptionFromCallback(env, "notifyInputChannelBroken");
- env->DeleteLocalRef(inputChannelObjLocal);
+ env->DeleteLocalRef(inputWindowHandleObj);
}
}
@@ -682,159 +540,32 @@ void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowObjArra
jsize length = env->GetArrayLength(windowObjArray);
for (jsize i = 0; i < length; i++) {
- jobject inputTargetObj = env->GetObjectArrayElement(windowObjArray, i);
- if (! inputTargetObj) {
+ jobject windowObj = env->GetObjectArrayElement(windowObjArray, i);
+ if (! windowObj) {
break; // found null element indicating end of used portion of the array
}
windows.push();
InputWindow& window = windows.editTop();
- bool valid = populateWindow(env, inputTargetObj, window);
- if (! valid) {
+ android_server_InputWindow_toNative(env, windowObj, &window);
+ if (window.inputChannel == NULL) {
windows.pop();
}
-
- env->DeleteLocalRef(inputTargetObj);
+ env->DeleteLocalRef(windowObj);
}
mInputManager->getDispatcher()->setInputWindows(windows);
}
-bool NativeInputManager::populateWindow(JNIEnv* env, jobject windowObj,
- InputWindow& outWindow) {
- bool valid = false;
-
- jobject inputChannelObj = env->GetObjectField(windowObj,
- gInputWindowClassInfo.inputChannel);
- if (inputChannelObj) {
- sp<InputChannel> inputChannel =
- android_view_InputChannel_getInputChannel(env, inputChannelObj);
- if (inputChannel != NULL) {
- jstring name = jstring(env->GetObjectField(windowObj,
- gInputWindowClassInfo.name));
- jint layoutParamsFlags = env->GetIntField(windowObj,
- gInputWindowClassInfo.layoutParamsFlags);
- jint layoutParamsType = env->GetIntField(windowObj,
- gInputWindowClassInfo.layoutParamsType);
- jlong dispatchingTimeoutNanos = env->GetLongField(windowObj,
- gInputWindowClassInfo.dispatchingTimeoutNanos);
- jint frameLeft = env->GetIntField(windowObj,
- gInputWindowClassInfo.frameLeft);
- jint frameTop = env->GetIntField(windowObj,
- gInputWindowClassInfo.frameTop);
- jint frameRight = env->GetIntField(windowObj,
- gInputWindowClassInfo.frameRight);
- jint frameBottom = env->GetIntField(windowObj,
- gInputWindowClassInfo.frameBottom);
- jint visibleFrameLeft = env->GetIntField(windowObj,
- gInputWindowClassInfo.visibleFrameLeft);
- jint visibleFrameTop = env->GetIntField(windowObj,
- gInputWindowClassInfo.visibleFrameTop);
- jint visibleFrameRight = env->GetIntField(windowObj,
- gInputWindowClassInfo.visibleFrameRight);
- jint visibleFrameBottom = env->GetIntField(windowObj,
- gInputWindowClassInfo.visibleFrameBottom);
- jint touchableAreaLeft = env->GetIntField(windowObj,
- gInputWindowClassInfo.touchableAreaLeft);
- jint touchableAreaTop = env->GetIntField(windowObj,
- gInputWindowClassInfo.touchableAreaTop);
- jint touchableAreaRight = env->GetIntField(windowObj,
- gInputWindowClassInfo.touchableAreaRight);
- jint touchableAreaBottom = env->GetIntField(windowObj,
- gInputWindowClassInfo.touchableAreaBottom);
- jboolean visible = env->GetBooleanField(windowObj,
- gInputWindowClassInfo.visible);
- jboolean canReceiveKeys = env->GetBooleanField(windowObj,
- gInputWindowClassInfo.canReceiveKeys);
- jboolean hasFocus = env->GetBooleanField(windowObj,
- gInputWindowClassInfo.hasFocus);
- jboolean hasWallpaper = env->GetBooleanField(windowObj,
- gInputWindowClassInfo.hasWallpaper);
- jboolean paused = env->GetBooleanField(windowObj,
- gInputWindowClassInfo.paused);
- jint layer = env->GetIntField(windowObj,
- gInputWindowClassInfo.layer);
- jint ownerPid = env->GetIntField(windowObj,
- gInputWindowClassInfo.ownerPid);
- jint ownerUid = env->GetIntField(windowObj,
- gInputWindowClassInfo.ownerUid);
-
- const char* nameStr = env->GetStringUTFChars(name, NULL);
-
- outWindow.inputChannel = inputChannel;
- outWindow.name.setTo(nameStr);
- outWindow.layoutParamsFlags = layoutParamsFlags;
- outWindow.layoutParamsType = layoutParamsType;
- outWindow.dispatchingTimeout = dispatchingTimeoutNanos;
- outWindow.frameLeft = frameLeft;
- outWindow.frameTop = frameTop;
- outWindow.frameRight = frameRight;
- outWindow.frameBottom = frameBottom;
- outWindow.visibleFrameLeft = visibleFrameLeft;
- outWindow.visibleFrameTop = visibleFrameTop;
- outWindow.visibleFrameRight = visibleFrameRight;
- outWindow.visibleFrameBottom = visibleFrameBottom;
- outWindow.touchableAreaLeft = touchableAreaLeft;
- outWindow.touchableAreaTop = touchableAreaTop;
- outWindow.touchableAreaRight = touchableAreaRight;
- outWindow.touchableAreaBottom = touchableAreaBottom;
- outWindow.visible = visible;
- outWindow.canReceiveKeys = canReceiveKeys;
- outWindow.hasFocus = hasFocus;
- outWindow.hasWallpaper = hasWallpaper;
- outWindow.paused = paused;
- outWindow.layer = layer;
- outWindow.ownerPid = ownerPid;
- outWindow.ownerUid = ownerUid;
-
- env->ReleaseStringUTFChars(name, nameStr);
- valid = true;
- } else {
- LOGW("Dropping input target because its input channel is not initialized.");
- }
-
- env->DeleteLocalRef(inputChannelObj);
- } else {
- LOGW("Dropping input target because the input channel object was null.");
- }
- return valid;
-}
-
void NativeInputManager::setFocusedApplication(JNIEnv* env, jobject applicationObj) {
if (applicationObj) {
- jstring nameObj = jstring(env->GetObjectField(applicationObj,
- gInputApplicationClassInfo.name));
- jlong dispatchingTimeoutNanos = env->GetLongField(applicationObj,
- gInputApplicationClassInfo.dispatchingTimeoutNanos);
- jobject tokenObj = env->GetObjectField(applicationObj,
- gInputApplicationClassInfo.token);
- jweak tokenObjWeak = env->NewWeakGlobalRef(tokenObj);
- if (! tokenObjWeak) {
- LOGE("Could not create weak reference for application token.");
- LOGE_EX(env);
- env->ExceptionClear();
- }
- env->DeleteLocalRef(tokenObj);
-
- String8 name;
- if (nameObj) {
- const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
- name.setTo(nameStr);
- env->ReleaseStringUTFChars(nameObj, nameStr);
- env->DeleteLocalRef(nameObj);
- } else {
- LOGE("InputApplication.name should not be null.");
- name.setTo("unknown");
- }
-
InputApplication application;
- application.name = name;
- application.dispatchingTimeout = dispatchingTimeoutNanos;
- application.handle = new ApplicationToken(tokenObjWeak);
- mInputManager->getDispatcher()->setFocusedApplication(& application);
- } else {
- mInputManager->getDispatcher()->setFocusedApplication(NULL);
+ android_server_InputApplication_toNative(env, applicationObj, &application);
+ if (application.inputApplicationHandle != NULL) {
+ mInputManager->getDispatcher()->setFocusedApplication(&application);
+ }
}
+ mInputManager->getDispatcher()->setFocusedApplication(NULL);
}
void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) {
@@ -849,20 +580,8 @@ bool NativeInputManager::isScreenBright() {
return android_server_PowerManagerService_isScreenBright();
}
-void NativeInputManager::interceptKeyBeforeQueueing(nsecs_t when,
- int32_t deviceId, int32_t action, int32_t &flags,
- int32_t keyCode, int32_t scanCode, uint32_t& policyFlags) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
- LOGD("interceptKeyBeforeQueueing - when=%lld, deviceId=%d, action=%d, flags=%d, "
- "keyCode=%d, scanCode=%d, policyFlags=0x%x",
- when, deviceId, action, flags, keyCode, scanCode, policyFlags);
-#endif
-
- if ((policyFlags & POLICY_FLAG_VIRTUAL) || (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)) {
- policyFlags |= POLICY_FLAG_VIRTUAL;
- flags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
- }
-
+void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
+ uint32_t& policyFlags) {
// Policy:
// - Ignore untrusted events and pass them along.
// - Ask the window manager what to do with normal events and trusted injected events.
@@ -872,21 +591,30 @@ void NativeInputManager::interceptKeyBeforeQueueing(nsecs_t when,
const int32_t WM_ACTION_POKE_USER_ACTIVITY = 2;
const int32_t WM_ACTION_GO_TO_SLEEP = 4;
+ nsecs_t when = keyEvent->getEventTime();
bool isScreenOn = this->isScreenOn();
bool isScreenBright = this->isScreenBright();
JNIEnv* env = jniEnv();
- jint wmActions = env->CallIntMethod(mCallbacksObj,
- gCallbacksClassInfo.interceptKeyBeforeQueueing,
- when, action, flags, keyCode, scanCode, policyFlags, isScreenOn);
- if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
+ jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
+ jint wmActions;
+ if (keyEventObj) {
+ wmActions = env->CallIntMethod(mCallbacksObj,
+ gCallbacksClassInfo.interceptKeyBeforeQueueing,
+ keyEventObj, policyFlags, isScreenOn);
+ if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
+ wmActions = 0;
+ }
+ android_view_KeyEvent_recycle(env, keyEventObj);
+ env->DeleteLocalRef(keyEventObj);
+ } else {
+ LOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
wmActions = 0;
}
- if (!(flags & POLICY_FLAG_INJECTED)) {
+ if (!(policyFlags & POLICY_FLAG_INJECTED)) {
if (!isScreenOn) {
policyFlags |= POLICY_FLAG_WOKE_HERE;
- flags |= AKEY_EVENT_FLAG_WOKE_HERE;
}
if (!isScreenBright) {
@@ -911,10 +639,6 @@ void NativeInputManager::interceptKeyBeforeQueueing(nsecs_t when,
}
void NativeInputManager::interceptGenericBeforeQueueing(nsecs_t when, uint32_t& policyFlags) {
-#if DEBUG_INPUT_DISPATCHER_POLICY
- LOGD("interceptGenericBeforeQueueing - when=%lld, policyFlags=0x%x", when, policyFlags);
-#endif
-
// Policy:
// - Ignore untrusted events and pass them along.
// - No special filtering for injected events required at this time.
@@ -933,29 +657,70 @@ void NativeInputManager::interceptGenericBeforeQueueing(nsecs_t when, uint32_t&
}
}
-bool NativeInputManager::interceptKeyBeforeDispatching(const sp<InputChannel>& inputChannel,
+bool NativeInputManager::interceptKeyBeforeDispatching(
+ const sp<InputWindowHandle>& inputWindowHandle,
const KeyEvent* keyEvent, uint32_t policyFlags) {
// Policy:
// - Ignore untrusted events and pass them along.
// - Filter normal events and trusted injected events through the window manager policy to
// handle the HOME key and the like.
+ bool result = false;
if (policyFlags & POLICY_FLAG_TRUSTED) {
JNIEnv* env = jniEnv();
- // Note: inputChannel may be null.
- jobject inputChannelObj = getInputChannelObjLocal(env, inputChannel);
- jboolean consumed = env->CallBooleanMethod(mCallbacksObj,
- gCallbacksClassInfo.interceptKeyBeforeDispatching,
- inputChannelObj, keyEvent->getAction(), keyEvent->getFlags(),
- keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(),
- keyEvent->getRepeatCount(), policyFlags);
- bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching");
-
- env->DeleteLocalRef(inputChannelObj);
- return consumed && ! error;
- } else {
- return false;
+ // Note: inputWindowHandle may be null.
+ jobject inputWindowHandleObj = getInputWindowHandleObjLocalRef(env, inputWindowHandle);
+ jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
+ if (keyEventObj) {
+ jboolean consumed = env->CallBooleanMethod(mCallbacksObj,
+ gCallbacksClassInfo.interceptKeyBeforeDispatching,
+ inputWindowHandleObj, keyEventObj, policyFlags);
+ bool error = checkAndClearExceptionFromCallback(env, "interceptKeyBeforeDispatching");
+ android_view_KeyEvent_recycle(env, keyEventObj);
+ env->DeleteLocalRef(keyEventObj);
+ result = consumed && !error;
+ } else {
+ LOGE("Failed to obtain key event object for interceptKeyBeforeDispatching.");
+ }
+ env->DeleteLocalRef(inputWindowHandleObj);
}
+ return result;
+}
+
+bool NativeInputManager::dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle,
+ const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) {
+ // Policy:
+ // - Ignore untrusted events and do not perform default handling.
+ bool result = false;
+ if (policyFlags & POLICY_FLAG_TRUSTED) {
+ JNIEnv* env = jniEnv();
+
+ // Note: inputWindowHandle may be null.
+ jobject inputWindowHandleObj = getInputWindowHandleObjLocalRef(env, inputWindowHandle);
+ jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
+ if (keyEventObj) {
+ jobject fallbackKeyEventObj = env->CallObjectMethod(mCallbacksObj,
+ gCallbacksClassInfo.dispatchUnhandledKey,
+ inputWindowHandleObj, keyEventObj, policyFlags);
+ checkAndClearExceptionFromCallback(env, "dispatchUnhandledKey");
+ android_view_KeyEvent_recycle(env, keyEventObj);
+ env->DeleteLocalRef(keyEventObj);
+
+ if (fallbackKeyEventObj) {
+ // Note: outFallbackKeyEvent may be the same object as keyEvent.
+ if (!android_view_KeyEvent_toNative(env, fallbackKeyEventObj,
+ outFallbackKeyEvent)) {
+ result = true;
+ }
+ android_view_KeyEvent_recycle(env, fallbackKeyEventObj);
+ env->DeleteLocalRef(fallbackKeyEventObj);
+ }
+ } else {
+ LOGE("Failed to obtain key event object for dispatchUnhandledKey.");
+ }
+ env->DeleteLocalRef(inputWindowHandleObj);
+ }
+ return result;
}
void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType) {
@@ -972,6 +737,7 @@ bool NativeInputManager::checkInjectEventsPermissionNonReentrant(
return result;
}
+
// ----------------------------------------------------------------------------
static sp<NativeInputManager> gNativeInputManager;
@@ -1096,7 +862,7 @@ static void android_server_InputManager_handleInputChannelDisposed(JNIEnv* env,
}
static void android_server_InputManager_nativeRegisterInputChannel(JNIEnv* env, jclass clazz,
- jobject inputChannelObj, jboolean monitor) {
+ jobject inputChannelObj, jobject inputWindowHandleObj, jboolean monitor) {
if (checkInputManagerUnitialized(env)) {
return;
}
@@ -1108,9 +874,11 @@ static void android_server_InputManager_nativeRegisterInputChannel(JNIEnv* env,
return;
}
+ sp<InputWindowHandle> inputWindowHandle =
+ android_server_InputWindowHandle_getHandle(env, inputWindowHandleObj);
status_t status = gNativeInputManager->registerInputChannel(
- env, inputChannel, inputChannelObj, monitor);
+ env, inputChannel, inputWindowHandle, monitor);
if (status) {
jniThrowRuntimeException(env, "Failed to register input channel. "
"Check logs for details.");
@@ -1154,13 +922,21 @@ static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jcla
if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
KeyEvent keyEvent;
- android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent);
+ status_t status = android_view_KeyEvent_toNative(env, inputEventObj, & keyEvent);
+ if (status) {
+ jniThrowRuntimeException(env, "Could not read contents of KeyEvent object.");
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent(
& keyEvent, injectorPid, injectorUid, syncMode, timeoutMillis);
} else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) {
MotionEvent motionEvent;
- android_view_MotionEvent_toNative(env, inputEventObj, & motionEvent);
+ status_t status = android_view_MotionEvent_toNative(env, inputEventObj, & motionEvent);
+ if (status) {
+ jniThrowRuntimeException(env, "Could not read contents of MotionEvent object.");
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent(
& motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis);
@@ -1271,6 +1047,25 @@ static void android_server_InputManager_nativeGetInputConfiguration(JNIEnv* env,
env->SetIntField(configObj, gConfigurationClassInfo.navigation, config.navigation);
}
+static jboolean android_server_InputManager_nativeTransferTouchFocus(JNIEnv* env,
+ jclass clazz, jobject fromChannelObj, jobject toChannelObj) {
+ if (checkInputManagerUnitialized(env)) {
+ return false;
+ }
+
+ sp<InputChannel> fromChannel =
+ android_view_InputChannel_getInputChannel(env, fromChannelObj);
+ sp<InputChannel> toChannel =
+ android_view_InputChannel_getInputChannel(env, toChannelObj);
+
+ if (fromChannel == NULL || toChannel == NULL) {
+ return false;
+ }
+
+ return gNativeInputManager->getInputManager()->getDispatcher()->
+ transferTouchFocus(fromChannel, toChannel);
+}
+
static jstring android_server_InputManager_nativeDump(JNIEnv* env, jclass clazz) {
if (checkInputManagerUnitialized(env)) {
return NULL;
@@ -1301,7 +1096,8 @@ static JNINativeMethod gInputManagerMethods[] = {
(void*) android_server_InputManager_nativeGetSwitchState },
{ "nativeHasKeys", "(II[I[Z)Z",
(void*) android_server_InputManager_nativeHasKeys },
- { "nativeRegisterInputChannel", "(Landroid/view/InputChannel;Z)V",
+ { "nativeRegisterInputChannel",
+ "(Landroid/view/InputChannel;Lcom/android/server/InputWindowHandle;Z)V",
(void*) android_server_InputManager_nativeRegisterInputChannel },
{ "nativeUnregisterInputChannel", "(Landroid/view/InputChannel;)V",
(void*) android_server_InputManager_nativeUnregisterInputChannel },
@@ -1319,6 +1115,8 @@ static JNINativeMethod gInputManagerMethods[] = {
(void*) android_server_InputManager_nativeGetInputDeviceIds },
{ "nativeGetInputConfiguration", "(Landroid/content/res/Configuration;)V",
(void*) android_server_InputManager_nativeGetInputConfiguration },
+ { "nativeTransferTouchFocus", "(Landroid/view/InputChannel;Landroid/view/InputChannel;)Z",
+ (void*) android_server_InputManager_nativeTransferTouchFocus },
{ "nativeDump", "()Ljava/lang/String;",
(void*) android_server_InputManager_nativeDump },
};
@@ -1352,16 +1150,22 @@ int register_android_server_InputManager(JNIEnv* env) {
"notifyLidSwitchChanged", "(JZ)V");
GET_METHOD_ID(gCallbacksClassInfo.notifyInputChannelBroken, gCallbacksClassInfo.clazz,
- "notifyInputChannelBroken", "(Landroid/view/InputChannel;)V");
+ "notifyInputChannelBroken", "(Lcom/android/server/InputWindowHandle;)V");
GET_METHOD_ID(gCallbacksClassInfo.notifyANR, gCallbacksClassInfo.clazz,
- "notifyANR", "(Ljava/lang/Object;Landroid/view/InputChannel;)J");
+ "notifyANR",
+ "(Lcom/android/server/InputApplicationHandle;Lcom/android/server/InputWindowHandle;)J");
GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeQueueing, gCallbacksClassInfo.clazz,
- "interceptKeyBeforeQueueing", "(JIIIIIZ)I");
+ "interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;IZ)I");
GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeDispatching, gCallbacksClassInfo.clazz,
- "interceptKeyBeforeDispatching", "(Landroid/view/InputChannel;IIIIIII)Z");
+ "interceptKeyBeforeDispatching",
+ "(Lcom/android/server/InputWindowHandle;Landroid/view/KeyEvent;I)Z");
+
+ GET_METHOD_ID(gCallbacksClassInfo.dispatchUnhandledKey, gCallbacksClassInfo.clazz,
+ "dispatchUnhandledKey",
+ "(Lcom/android/server/InputWindowHandle;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;");
GET_METHOD_ID(gCallbacksClassInfo.checkInjectEventsPermission, gCallbacksClassInfo.clazz,
"checkInjectEventsPermission", "(II)Z");
@@ -1375,143 +1179,17 @@ int register_android_server_InputManager(JNIEnv* env) {
GET_METHOD_ID(gCallbacksClassInfo.getVirtualKeyQuietTimeMillis, gCallbacksClassInfo.clazz,
"getVirtualKeyQuietTimeMillis", "()I");
- GET_METHOD_ID(gCallbacksClassInfo.getVirtualKeyDefinitions, gCallbacksClassInfo.clazz,
- "getVirtualKeyDefinitions",
- "(Ljava/lang/String;)[Lcom/android/server/InputManager$VirtualKeyDefinition;");
-
- GET_METHOD_ID(gCallbacksClassInfo.getInputDeviceCalibration, gCallbacksClassInfo.clazz,
- "getInputDeviceCalibration",
- "(Ljava/lang/String;)Lcom/android/server/InputManager$InputDeviceCalibration;");
-
GET_METHOD_ID(gCallbacksClassInfo.getExcludedDeviceNames, gCallbacksClassInfo.clazz,
"getExcludedDeviceNames", "()[Ljava/lang/String;");
GET_METHOD_ID(gCallbacksClassInfo.getMaxEventsPerSecond, gCallbacksClassInfo.clazz,
"getMaxEventsPerSecond", "()I");
- // VirtualKeyDefinition
-
- FIND_CLASS(gVirtualKeyDefinitionClassInfo.clazz,
- "com/android/server/InputManager$VirtualKeyDefinition");
-
- GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.scanCode, gVirtualKeyDefinitionClassInfo.clazz,
- "scanCode", "I");
-
- GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.centerX, gVirtualKeyDefinitionClassInfo.clazz,
- "centerX", "I");
-
- GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.centerY, gVirtualKeyDefinitionClassInfo.clazz,
- "centerY", "I");
-
- GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.width, gVirtualKeyDefinitionClassInfo.clazz,
- "width", "I");
-
- GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.height, gVirtualKeyDefinitionClassInfo.clazz,
- "height", "I");
-
- // InputDeviceCalibration
-
- FIND_CLASS(gInputDeviceCalibrationClassInfo.clazz,
- "com/android/server/InputManager$InputDeviceCalibration");
-
- GET_FIELD_ID(gInputDeviceCalibrationClassInfo.keys, gInputDeviceCalibrationClassInfo.clazz,
- "keys", "[Ljava/lang/String;");
-
- GET_FIELD_ID(gInputDeviceCalibrationClassInfo.values, gInputDeviceCalibrationClassInfo.clazz,
- "values", "[Ljava/lang/String;");
-
- // InputWindow
-
- FIND_CLASS(gInputWindowClassInfo.clazz, "com/android/server/InputWindow");
-
- GET_FIELD_ID(gInputWindowClassInfo.inputChannel, gInputWindowClassInfo.clazz,
- "inputChannel", "Landroid/view/InputChannel;");
-
- GET_FIELD_ID(gInputWindowClassInfo.name, gInputWindowClassInfo.clazz,
- "name", "Ljava/lang/String;");
-
- GET_FIELD_ID(gInputWindowClassInfo.layoutParamsFlags, gInputWindowClassInfo.clazz,
- "layoutParamsFlags", "I");
-
- GET_FIELD_ID(gInputWindowClassInfo.layoutParamsType, gInputWindowClassInfo.clazz,
- "layoutParamsType", "I");
-
- GET_FIELD_ID(gInputWindowClassInfo.dispatchingTimeoutNanos, gInputWindowClassInfo.clazz,
- "dispatchingTimeoutNanos", "J");
-
- GET_FIELD_ID(gInputWindowClassInfo.frameLeft, gInputWindowClassInfo.clazz,
- "frameLeft", "I");
-
- GET_FIELD_ID(gInputWindowClassInfo.frameTop, gInputWindowClassInfo.clazz,
- "frameTop", "I");
+ GET_METHOD_ID(gCallbacksClassInfo.getPointerLayer, gCallbacksClassInfo.clazz,
+ "getPointerLayer", "()I");
- GET_FIELD_ID(gInputWindowClassInfo.frameRight, gInputWindowClassInfo.clazz,
- "frameRight", "I");
-
- GET_FIELD_ID(gInputWindowClassInfo.frameBottom, gInputWindowClassInfo.clazz,
- "frameBottom", "I");
-
- GET_FIELD_ID(gInputWindowClassInfo.visibleFrameLeft, gInputWindowClassInfo.clazz,
- "visibleFrameLeft", "I");
-
- GET_FIELD_ID(gInputWindowClassInfo.visibleFrameTop, gInputWindowClassInfo.clazz,
- "visibleFrameTop", "I");
-
- GET_FIELD_ID(gInputWindowClassInfo.visibleFrameRight, gInputWindowClassInfo.clazz,
- "visibleFrameRight", "I");
-
- GET_FIELD_ID(gInputWindowClassInfo.visibleFrameBottom, gInputWindowClassInfo.clazz,
- "visibleFrameBottom", "I");
-
- GET_FIELD_ID(gInputWindowClassInfo.touchableAreaLeft, gInputWindowClassInfo.clazz,
- "touchableAreaLeft", "I");
-
- GET_FIELD_ID(gInputWindowClassInfo.touchableAreaTop, gInputWindowClassInfo.clazz,
- "touchableAreaTop", "I");
-
- GET_FIELD_ID(gInputWindowClassInfo.touchableAreaRight, gInputWindowClassInfo.clazz,
- "touchableAreaRight", "I");
-
- GET_FIELD_ID(gInputWindowClassInfo.touchableAreaBottom, gInputWindowClassInfo.clazz,
- "touchableAreaBottom", "I");
-
- GET_FIELD_ID(gInputWindowClassInfo.visible, gInputWindowClassInfo.clazz,
- "visible", "Z");
-
- GET_FIELD_ID(gInputWindowClassInfo.canReceiveKeys, gInputWindowClassInfo.clazz,
- "canReceiveKeys", "Z");
-
- GET_FIELD_ID(gInputWindowClassInfo.hasFocus, gInputWindowClassInfo.clazz,
- "hasFocus", "Z");
-
- GET_FIELD_ID(gInputWindowClassInfo.hasWallpaper, gInputWindowClassInfo.clazz,
- "hasWallpaper", "Z");
-
- GET_FIELD_ID(gInputWindowClassInfo.paused, gInputWindowClassInfo.clazz,
- "paused", "Z");
-
- GET_FIELD_ID(gInputWindowClassInfo.layer, gInputWindowClassInfo.clazz,
- "layer", "I");
-
- GET_FIELD_ID(gInputWindowClassInfo.ownerPid, gInputWindowClassInfo.clazz,
- "ownerPid", "I");
-
- GET_FIELD_ID(gInputWindowClassInfo.ownerUid, gInputWindowClassInfo.clazz,
- "ownerUid", "I");
-
- // InputApplication
-
- FIND_CLASS(gInputApplicationClassInfo.clazz, "com/android/server/InputApplication");
-
- GET_FIELD_ID(gInputApplicationClassInfo.name, gInputApplicationClassInfo.clazz,
- "name", "Ljava/lang/String;");
-
- GET_FIELD_ID(gInputApplicationClassInfo.dispatchingTimeoutNanos,
- gInputApplicationClassInfo.clazz,
- "dispatchingTimeoutNanos", "J");
-
- GET_FIELD_ID(gInputApplicationClassInfo.token, gInputApplicationClassInfo.clazz,
- "token", "Ljava/lang/Object;");
+ GET_METHOD_ID(gCallbacksClassInfo.getPointerIcon, gCallbacksClassInfo.clazz,
+ "getPointerIcon", "()Lcom/android/server/InputManager$PointerIcon;");
// KeyEvent
@@ -1559,6 +1237,19 @@ int register_android_server_InputManager(JNIEnv* env) {
GET_FIELD_ID(gConfigurationClassInfo.navigation, gConfigurationClassInfo.clazz,
"navigation", "I");
+ // PointerIcon
+
+ FIND_CLASS(gPointerIconClassInfo.clazz, "com/android/server/InputManager$PointerIcon");
+
+ GET_FIELD_ID(gPointerIconClassInfo.bitmap, gPointerIconClassInfo.clazz,
+ "bitmap", "Landroid/graphics/Bitmap;");
+
+ GET_FIELD_ID(gPointerIconClassInfo.hotSpotX, gPointerIconClassInfo.clazz,
+ "hotSpotX", "F");
+
+ GET_FIELD_ID(gPointerIconClassInfo.hotSpotY, gPointerIconClassInfo.clazz,
+ "hotSpotY", "F");
+
return 0;
}
diff --git a/services/jni/com_android_server_InputWindow.cpp b/services/jni/com_android_server_InputWindow.cpp
new file mode 100644
index 0000000..7515456
--- /dev/null
+++ b/services/jni/com_android_server_InputWindow.cpp
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputWindow"
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include <android_runtime/AndroidRuntime.h>
+
+#include <android_view_InputChannel.h>
+#include <android/graphics/Region.h>
+#include "com_android_server_InputWindow.h"
+#include "com_android_server_InputWindowHandle.h"
+
+namespace android {
+
+static struct {
+ jclass clazz;
+
+ jfieldID inputWindowHandle;
+ jfieldID inputChannel;
+ jfieldID name;
+ jfieldID layoutParamsFlags;
+ jfieldID layoutParamsType;
+ jfieldID dispatchingTimeoutNanos;
+ jfieldID frameLeft;
+ jfieldID frameTop;
+ jfieldID frameRight;
+ jfieldID frameBottom;
+ jfieldID touchableRegion;
+ jfieldID visible;
+ jfieldID canReceiveKeys;
+ jfieldID hasFocus;
+ jfieldID hasWallpaper;
+ jfieldID paused;
+ jfieldID layer;
+ jfieldID ownerPid;
+ jfieldID ownerUid;
+} gInputWindowClassInfo;
+
+
+// --- Global functions ---
+
+void android_server_InputWindow_toNative(
+ JNIEnv* env, jobject inputWindowObj, InputWindow* outInputWindow) {
+ jobject inputWindowHandleObj = env->GetObjectField(inputWindowObj,
+ gInputWindowClassInfo.inputWindowHandle);
+ if (inputWindowHandleObj) {
+ outInputWindow->inputWindowHandle =
+ android_server_InputWindowHandle_getHandle(env, inputWindowHandleObj);
+ env->DeleteLocalRef(inputWindowHandleObj);
+ } else {
+ outInputWindow->inputWindowHandle = NULL;
+ }
+
+ jobject inputChannelObj = env->GetObjectField(inputWindowObj,
+ gInputWindowClassInfo.inputChannel);
+ if (inputChannelObj) {
+ outInputWindow->inputChannel =
+ android_view_InputChannel_getInputChannel(env, inputChannelObj);
+ env->DeleteLocalRef(inputChannelObj);
+ } else {
+ outInputWindow->inputChannel = NULL;
+ }
+
+ jstring nameObj = jstring(env->GetObjectField(inputWindowObj,
+ gInputWindowClassInfo.name));
+ if (nameObj) {
+ const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
+ outInputWindow->name.setTo(nameStr);
+ env->ReleaseStringUTFChars(nameObj, nameStr);
+ env->DeleteLocalRef(nameObj);
+ } else {
+ LOGE("InputWindow.name should not be null.");
+ outInputWindow->name.setTo("unknown");
+ }
+
+ outInputWindow->layoutParamsFlags = env->GetIntField(inputWindowObj,
+ gInputWindowClassInfo.layoutParamsFlags);
+ outInputWindow->layoutParamsType = env->GetIntField(inputWindowObj,
+ gInputWindowClassInfo.layoutParamsType);
+ outInputWindow->dispatchingTimeout = env->GetLongField(inputWindowObj,
+ gInputWindowClassInfo.dispatchingTimeoutNanos);
+ outInputWindow->frameLeft = env->GetIntField(inputWindowObj,
+ gInputWindowClassInfo.frameLeft);
+ outInputWindow->frameTop = env->GetIntField(inputWindowObj,
+ gInputWindowClassInfo.frameTop);
+ outInputWindow->frameRight = env->GetIntField(inputWindowObj,
+ gInputWindowClassInfo.frameRight);
+ outInputWindow->frameBottom = env->GetIntField(inputWindowObj,
+ gInputWindowClassInfo.frameBottom);
+
+ jobject regionObj = env->GetObjectField(inputWindowObj,
+ gInputWindowClassInfo.touchableRegion);
+ if (regionObj) {
+ SkRegion* region = android_graphics_Region_getSkRegion(env, regionObj);
+ outInputWindow->touchableRegion.set(*region);
+ env->DeleteLocalRef(regionObj);
+ } else {
+ outInputWindow->touchableRegion.setEmpty();
+ }
+
+ outInputWindow->visible = env->GetBooleanField(inputWindowObj,
+ gInputWindowClassInfo.visible);
+ outInputWindow->canReceiveKeys = env->GetBooleanField(inputWindowObj,
+ gInputWindowClassInfo.canReceiveKeys);
+ outInputWindow->hasFocus = env->GetBooleanField(inputWindowObj,
+ gInputWindowClassInfo.hasFocus);
+ outInputWindow->hasWallpaper = env->GetBooleanField(inputWindowObj,
+ gInputWindowClassInfo.hasWallpaper);
+ outInputWindow->paused = env->GetBooleanField(inputWindowObj,
+ gInputWindowClassInfo.paused);
+ outInputWindow->layer = env->GetIntField(inputWindowObj,
+ gInputWindowClassInfo.layer);
+ outInputWindow->ownerPid = env->GetIntField(inputWindowObj,
+ gInputWindowClassInfo.ownerPid);
+ outInputWindow->ownerUid = env->GetIntField(inputWindowObj,
+ gInputWindowClassInfo.ownerUid);
+}
+
+
+// --- JNI ---
+
+#define FIND_CLASS(var, className) \
+ var = env->FindClass(className); \
+ LOG_FATAL_IF(! var, "Unable to find class " className); \
+ var = jclass(env->NewGlobalRef(var));
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+ var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_server_InputWindow(JNIEnv* env) {
+ FIND_CLASS(gInputWindowClassInfo.clazz, "com/android/server/InputWindow");
+
+ GET_FIELD_ID(gInputWindowClassInfo.inputWindowHandle, gInputWindowClassInfo.clazz,
+ "inputWindowHandle", "Lcom/android/server/InputWindowHandle;");
+
+ GET_FIELD_ID(gInputWindowClassInfo.inputChannel, gInputWindowClassInfo.clazz,
+ "inputChannel", "Landroid/view/InputChannel;");
+
+ GET_FIELD_ID(gInputWindowClassInfo.name, gInputWindowClassInfo.clazz,
+ "name", "Ljava/lang/String;");
+
+ GET_FIELD_ID(gInputWindowClassInfo.layoutParamsFlags, gInputWindowClassInfo.clazz,
+ "layoutParamsFlags", "I");
+
+ GET_FIELD_ID(gInputWindowClassInfo.layoutParamsType, gInputWindowClassInfo.clazz,
+ "layoutParamsType", "I");
+
+ GET_FIELD_ID(gInputWindowClassInfo.dispatchingTimeoutNanos, gInputWindowClassInfo.clazz,
+ "dispatchingTimeoutNanos", "J");
+
+ GET_FIELD_ID(gInputWindowClassInfo.frameLeft, gInputWindowClassInfo.clazz,
+ "frameLeft", "I");
+
+ GET_FIELD_ID(gInputWindowClassInfo.frameTop, gInputWindowClassInfo.clazz,
+ "frameTop", "I");
+
+ GET_FIELD_ID(gInputWindowClassInfo.frameRight, gInputWindowClassInfo.clazz,
+ "frameRight", "I");
+
+ GET_FIELD_ID(gInputWindowClassInfo.frameBottom, gInputWindowClassInfo.clazz,
+ "frameBottom", "I");
+
+ GET_FIELD_ID(gInputWindowClassInfo.touchableRegion, gInputWindowClassInfo.clazz,
+ "touchableRegion", "Landroid/graphics/Region;");
+
+ GET_FIELD_ID(gInputWindowClassInfo.visible, gInputWindowClassInfo.clazz,
+ "visible", "Z");
+
+ GET_FIELD_ID(gInputWindowClassInfo.canReceiveKeys, gInputWindowClassInfo.clazz,
+ "canReceiveKeys", "Z");
+
+ GET_FIELD_ID(gInputWindowClassInfo.hasFocus, gInputWindowClassInfo.clazz,
+ "hasFocus", "Z");
+
+ GET_FIELD_ID(gInputWindowClassInfo.hasWallpaper, gInputWindowClassInfo.clazz,
+ "hasWallpaper", "Z");
+
+ GET_FIELD_ID(gInputWindowClassInfo.paused, gInputWindowClassInfo.clazz,
+ "paused", "Z");
+
+ GET_FIELD_ID(gInputWindowClassInfo.layer, gInputWindowClassInfo.clazz,
+ "layer", "I");
+
+ GET_FIELD_ID(gInputWindowClassInfo.ownerPid, gInputWindowClassInfo.clazz,
+ "ownerPid", "I");
+
+ GET_FIELD_ID(gInputWindowClassInfo.ownerUid, gInputWindowClassInfo.clazz,
+ "ownerUid", "I");
+ return 0;
+}
+
+} /* namespace android */
diff --git a/services/jni/com_android_server_InputWindow.h b/services/jni/com_android_server_InputWindow.h
new file mode 100644
index 0000000..eaf7bde
--- /dev/null
+++ b/services/jni/com_android_server_InputWindow.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SERVER_INPUT_WINDOW_H
+#define _ANDROID_SERVER_INPUT_WINDOW_H
+
+#include <input/InputWindow.h>
+
+#include "JNIHelp.h"
+#include "jni.h"
+
+namespace android {
+
+extern void android_server_InputWindow_toNative(
+ JNIEnv* env, jobject inputWindowObj, InputWindow* outInputWindow);
+
+} // namespace android
+
+#endif // _ANDROID_SERVER_INPUT_WINDOW_H
diff --git a/services/jni/com_android_server_InputWindowHandle.cpp b/services/jni/com_android_server_InputWindowHandle.cpp
new file mode 100644
index 0000000..4d66212
--- /dev/null
+++ b/services/jni/com_android_server_InputWindowHandle.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputWindowHandle"
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/threads.h>
+
+#include "com_android_server_InputWindowHandle.h"
+#include "com_android_server_InputApplicationHandle.h"
+
+namespace android {
+
+static struct {
+ jclass clazz;
+
+ jfieldID ptr;
+ jfieldID inputApplicationHandle;
+} gInputWindowHandleClassInfo;
+
+static Mutex gHandleMutex;
+
+
+// --- NativeInputWindowHandle ---
+
+NativeInputWindowHandle::NativeInputWindowHandle(
+ const sp<InputApplicationHandle>& inputApplicationHandle, jweak objWeak) :
+ InputWindowHandle(inputApplicationHandle),
+ mObjWeak(objWeak) {
+}
+
+NativeInputWindowHandle::~NativeInputWindowHandle() {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->DeleteWeakGlobalRef(mObjWeak);
+}
+
+jobject NativeInputWindowHandle::getInputWindowHandleObjLocalRef(JNIEnv* env) {
+ return env->NewLocalRef(mObjWeak);
+}
+
+
+// --- Global functions ---
+
+sp<NativeInputWindowHandle> android_server_InputWindowHandle_getHandle(
+ JNIEnv* env, jobject inputWindowHandleObj) {
+ if (!inputWindowHandleObj) {
+ return NULL;
+ }
+
+ AutoMutex _l(gHandleMutex);
+
+ int ptr = env->GetIntField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr);
+ NativeInputWindowHandle* handle;
+ if (ptr) {
+ handle = reinterpret_cast<NativeInputWindowHandle*>(ptr);
+ } else {
+ jobject inputApplicationHandleObj = env->GetObjectField(inputWindowHandleObj,
+ gInputWindowHandleClassInfo.inputApplicationHandle);
+ sp<InputApplicationHandle> inputApplicationHandle =
+ android_server_InputApplicationHandle_getHandle(env, inputApplicationHandleObj);
+ env->DeleteLocalRef(inputApplicationHandleObj);
+
+ jweak objWeak = env->NewWeakGlobalRef(inputWindowHandleObj);
+ handle = new NativeInputWindowHandle(inputApplicationHandle, objWeak);
+ handle->incStrong(inputWindowHandleObj);
+ env->SetIntField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr,
+ reinterpret_cast<int>(handle));
+ }
+ return handle;
+}
+
+
+// --- JNI ---
+
+static void android_server_InputWindowHandle_nativeDispose(JNIEnv* env, jobject obj) {
+ AutoMutex _l(gHandleMutex);
+
+ int ptr = env->GetIntField(obj, gInputWindowHandleClassInfo.ptr);
+ if (ptr) {
+ env->SetIntField(obj, gInputWindowHandleClassInfo.ptr, 0);
+
+ NativeInputWindowHandle* handle = reinterpret_cast<NativeInputWindowHandle*>(ptr);
+ handle->decStrong(obj);
+ }
+}
+
+
+static JNINativeMethod gInputWindowHandleMethods[] = {
+ /* name, signature, funcPtr */
+ { "nativeDispose", "()V",
+ (void*) android_server_InputWindowHandle_nativeDispose },
+};
+
+#define FIND_CLASS(var, className) \
+ var = env->FindClass(className); \
+ LOG_FATAL_IF(! var, "Unable to find class " className); \
+ var = jclass(env->NewGlobalRef(var));
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+ var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_server_InputWindowHandle(JNIEnv* env) {
+ int res = jniRegisterNativeMethods(env, "com/android/server/InputWindowHandle",
+ gInputWindowHandleMethods, NELEM(gInputWindowHandleMethods));
+ LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+
+ FIND_CLASS(gInputWindowHandleClassInfo.clazz, "com/android/server/InputWindowHandle");
+
+ GET_FIELD_ID(gInputWindowHandleClassInfo.ptr, gInputWindowHandleClassInfo.clazz,
+ "ptr", "I");
+
+ GET_FIELD_ID(gInputWindowHandleClassInfo.inputApplicationHandle,
+ gInputWindowHandleClassInfo.clazz,
+ "inputApplicationHandle", "Lcom/android/server/InputApplicationHandle;");
+
+ return 0;
+}
+
+} /* namespace android */
diff --git a/services/jni/com_android_server_InputWindowHandle.h b/services/jni/com_android_server_InputWindowHandle.h
new file mode 100644
index 0000000..43f2a6b
--- /dev/null
+++ b/services/jni/com_android_server_InputWindowHandle.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H
+#define _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H
+
+#include <input/InputWindow.h>
+
+#include "JNIHelp.h"
+#include "jni.h"
+
+namespace android {
+
+class NativeInputWindowHandle : public InputWindowHandle {
+public:
+ NativeInputWindowHandle(const sp<InputApplicationHandle>& inputApplicationHandle,
+ jweak objWeak);
+ virtual ~NativeInputWindowHandle();
+
+ jobject getInputWindowHandleObjLocalRef(JNIEnv* env);
+
+private:
+ jweak mObjWeak;
+};
+
+
+extern sp<NativeInputWindowHandle> android_server_InputWindowHandle_getHandle(
+ JNIEnv* env, jobject inputWindowHandleObj);
+
+} // namespace android
+
+#endif // _ANDROID_SERVER_INPUT_WINDOW_HANDLE_H
diff --git a/services/jni/com_android_server_VibratorService.cpp b/services/jni/com_android_server_VibratorService.cpp
index 6ec5c07..0912d43 100644
--- a/services/jni/com_android_server_VibratorService.cpp
+++ b/services/jni/com_android_server_VibratorService.cpp
@@ -29,6 +29,11 @@
namespace android
{
+static jboolean vibratorExists(JNIEnv *env, jobject clazz)
+{
+ return vibrator_exists() > 0 ? JNI_TRUE : JNI_FALSE;
+}
+
static void vibratorOn(JNIEnv *env, jobject clazz, jlong timeout_ms)
{
// LOGI("vibratorOn\n");
@@ -42,6 +47,7 @@ static void vibratorOff(JNIEnv *env, jobject clazz)
}
static JNINativeMethod method_table[] = {
+ { "vibratorExists", "()Z", (void*)vibratorExists },
{ "vibratorOn", "(J)V", (void*)vibratorOn },
{ "vibratorOff", "()V", (void*)vibratorOff }
};
diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp
index cd4f0a4..bdd6d80 100644
--- a/services/jni/onload.cpp
+++ b/services/jni/onload.cpp
@@ -6,6 +6,10 @@
namespace android {
int register_android_server_AlarmManagerService(JNIEnv* env);
int register_android_server_BatteryService(JNIEnv* env);
+int register_android_server_InputApplication(JNIEnv* env);
+int register_android_server_InputApplicationHandle(JNIEnv* env);
+int register_android_server_InputWindow(JNIEnv* env);
+int register_android_server_InputWindowHandle(JNIEnv* env);
int register_android_server_InputManager(JNIEnv* env);
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
@@ -28,6 +32,10 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
LOG_ASSERT(env, "Could not retrieve the env!");
register_android_server_PowerManagerService(env);
+ register_android_server_InputApplication(env);
+ register_android_server_InputApplicationHandle(env);
+ register_android_server_InputWindow(env);
+ register_android_server_InputWindowHandle(env);
register_android_server_InputManager(env);
register_android_server_LightsService(env);
register_android_server_AlarmManagerService(env);
diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk
index c5bdaa1..8a00a2e 100644
--- a/services/surfaceflinger/Android.mk
+++ b/services/surfaceflinger/Android.mk
@@ -5,12 +5,10 @@ LOCAL_SRC_FILES:= \
clz.cpp.arm \
DisplayHardware/DisplayHardware.cpp \
DisplayHardware/DisplayHardwareBase.cpp \
- BlurFilter.cpp.arm \
+ DisplayHardware/HWComposer.cpp \
GLExtensions.cpp \
Layer.cpp \
LayerBase.cpp \
- LayerBuffer.cpp \
- LayerBlur.cpp \
LayerDim.cpp \
MessageQueue.cpp \
SurfaceFlinger.cpp \
diff --git a/services/surfaceflinger/BlurFilter.cpp b/services/surfaceflinger/BlurFilter.cpp
deleted file mode 100644
index 1ffbd5b..0000000
--- a/services/surfaceflinger/BlurFilter.cpp
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
-**
-** Copyright 2006, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdint.h>
-#include <utils/Errors.h>
-
-#include <pixelflinger/pixelflinger.h>
-
-#include "clz.h"
-
-#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true ))
-#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false ))
-
-namespace android {
-
-#if BYTE_ORDER == LITTLE_ENDIAN
-inline uint32_t BLUR_RGBA_TO_HOST(uint32_t v) {
- return v;
-}
-inline uint32_t BLUR_HOST_TO_RGBA(uint32_t v) {
- return v;
-}
-#else
-inline uint32_t BLUR_RGBA_TO_HOST(uint32_t v) {
- return (v<<24) | (v>>24) | ((v<<8)&0xff0000) | ((v>>8)&0xff00);
-}
-inline uint32_t BLUR_HOST_TO_RGBA(uint32_t v) {
- return (v<<24) | (v>>24) | ((v<<8)&0xff0000) | ((v>>8)&0xff00);
-}
-#endif
-
-const int BLUR_DITHER_BITS = 6; // dither weights stored on 6 bits
-const int BLUR_DITHER_ORDER_SHIFT= 3;
-const int BLUR_DITHER_ORDER = (1<<BLUR_DITHER_ORDER_SHIFT);
-const int BLUR_DITHER_SIZE = BLUR_DITHER_ORDER * BLUR_DITHER_ORDER;
-const int BLUR_DITHER_MASK = BLUR_DITHER_ORDER-1;
-
-static const uint8_t gDitherMatrix[BLUR_DITHER_SIZE] = {
- 0, 32, 8, 40, 2, 34, 10, 42,
- 48, 16, 56, 24, 50, 18, 58, 26,
- 12, 44, 4, 36, 14, 46, 6, 38,
- 60, 28, 52, 20, 62, 30, 54, 22,
- 3, 35, 11, 43, 1, 33, 9, 41,
- 51, 19, 59, 27, 49, 17, 57, 25,
- 15, 47, 7, 39, 13, 45, 5, 37,
- 63, 31, 55, 23, 61, 29, 53, 21
-};
-
-
-template <int FACTOR = 0>
-struct BlurColor565
-{
- typedef uint16_t type;
- int r, g, b;
- inline BlurColor565() { }
- inline BlurColor565(uint16_t v) {
- r = v >> 11;
- g = (v >> 5) & 0x3E;
- b = v & 0x1F;
- }
- inline void clear() { r=g=b=0; }
- inline uint16_t to(int shift, int last, int dither) const {
- int R = r;
- int G = g;
- int B = b;
- if (UNLIKELY(last)) {
- if (FACTOR>0) {
- int L = (R+G+B)>>1;
- R += (((L>>1) - R) * FACTOR) >> 8;
- G += (((L ) - G) * FACTOR) >> 8;
- B += (((L>>1) - B) * FACTOR) >> 8;
- }
- R += (dither << shift) >> BLUR_DITHER_BITS;
- G += (dither << shift) >> BLUR_DITHER_BITS;
- B += (dither << shift) >> BLUR_DITHER_BITS;
- }
- R >>= shift;
- G >>= shift;
- B >>= shift;
- return (R<<11) | (G<<5) | B;
- }
- inline BlurColor565& operator += (const BlurColor565& rhs) {
- r += rhs.r;
- g += rhs.g;
- b += rhs.b;
- return *this;
- }
- inline BlurColor565& operator -= (const BlurColor565& rhs) {
- r -= rhs.r;
- g -= rhs.g;
- b -= rhs.b;
- return *this;
- }
-};
-
-template <int FACTOR = 0>
-struct BlurColor888X
-{
- typedef uint32_t type;
- int r, g, b;
- inline BlurColor888X() { }
- inline BlurColor888X(uint32_t v) {
- v = BLUR_RGBA_TO_HOST(v);
- r = v & 0xFF;
- g = (v >> 8) & 0xFF;
- b = (v >> 16) & 0xFF;
- }
- inline void clear() { r=g=b=0; }
- inline uint32_t to(int shift, int last, int dither) const {
- int R = r;
- int G = g;
- int B = b;
- if (UNLIKELY(last)) {
- if (FACTOR>0) {
- int L = (R+G+G+B)>>2;
- R += ((L - R) * FACTOR) >> 8;
- G += ((L - G) * FACTOR) >> 8;
- B += ((L - B) * FACTOR) >> 8;
- }
- }
- R >>= shift;
- G >>= shift;
- B >>= shift;
- return BLUR_HOST_TO_RGBA((0xFF<<24) | (B<<16) | (G<<8) | R);
- }
- inline BlurColor888X& operator += (const BlurColor888X& rhs) {
- r += rhs.r;
- g += rhs.g;
- b += rhs.b;
- return *this;
- }
- inline BlurColor888X& operator -= (const BlurColor888X& rhs) {
- r -= rhs.r;
- g -= rhs.g;
- b -= rhs.b;
- return *this;
- }
-};
-
-struct BlurGray565
-{
- typedef uint16_t type;
- int l;
- inline BlurGray565() { }
- inline BlurGray565(uint16_t v) {
- int r = v >> 11;
- int g = (v >> 5) & 0x3F;
- int b = v & 0x1F;
- l = (r + g + b + 1)>>1;
- }
- inline void clear() { l=0; }
- inline uint16_t to(int shift, int last, int dither) const {
- int L = l;
- if (UNLIKELY(last)) {
- L += (dither << shift) >> BLUR_DITHER_BITS;
- }
- L >>= shift;
- return ((L>>1)<<11) | (L<<5) | (L>>1);
- }
- inline BlurGray565& operator += (const BlurGray565& rhs) {
- l += rhs.l;
- return *this;
- }
- inline BlurGray565& operator -= (const BlurGray565& rhs) {
- l -= rhs.l;
- return *this;
- }
-};
-
-struct BlurGray8888
-{
- typedef uint32_t type;
- int l, a;
- inline BlurGray8888() { }
- inline BlurGray8888(uint32_t v) {
- v = BLUR_RGBA_TO_HOST(v);
- int r = v & 0xFF;
- int g = (v >> 8) & 0xFF;
- int b = (v >> 16) & 0xFF;
- a = v >> 24;
- l = r + g + g + b;
- }
- inline void clear() { l=a=0; }
- inline uint32_t to(int shift, int last, int dither) const {
- int L = l;
- int A = a;
- if (UNLIKELY(last)) {
- L += (dither << (shift+2)) >> BLUR_DITHER_BITS;
- A += (dither << shift) >> BLUR_DITHER_BITS;
- }
- L >>= (shift+2);
- A >>= shift;
- return BLUR_HOST_TO_RGBA((A<<24) | (L<<16) | (L<<8) | L);
- }
- inline BlurGray8888& operator += (const BlurGray8888& rhs) {
- l += rhs.l;
- a += rhs.a;
- return *this;
- }
- inline BlurGray8888& operator -= (const BlurGray8888& rhs) {
- l -= rhs.l;
- a -= rhs.a;
- return *this;
- }
-};
-
-
-template<typename PIXEL>
-static status_t blurFilter(
- GGLSurface const* dst,
- GGLSurface const* src,
- int kernelSizeUser,
- int repeat)
-{
- typedef typename PIXEL::type TYPE;
-
- const int shift = 31 - clz(kernelSizeUser);
- const int areaShift = shift*2;
- const int kernelSize = 1<<shift;
- const int kernelHalfSize = kernelSize/2;
- const int mask = kernelSize-1;
- const int w = src->width;
- const int h = src->height;
- const uint8_t* ditherMatrix = gDitherMatrix;
-
- // we need a temporary buffer to store one line of blurred columns
- // as well as kernelSize lines of source pixels organized as a ring buffer.
- void* const temporary_buffer = malloc(
- (w + kernelSize) * sizeof(PIXEL) +
- (src->stride * kernelSize) * sizeof(TYPE));
- if (!temporary_buffer)
- return NO_MEMORY;
-
- PIXEL* const sums = (PIXEL*)temporary_buffer;
- TYPE* const scratch = (TYPE*)(sums + w + kernelSize);
-
- // Apply the blur 'repeat' times, this is used to approximate
- // gaussian blurs. 3 times gives good results.
- for (int k=0 ; k<repeat ; k++) {
-
- // Clear the columns sums for this round
- memset(sums, 0, (w + kernelSize) * sizeof(PIXEL));
- TYPE* head;
- TYPE pixel;
- PIXEL current;
-
- // Since we're going to override the source data we need
- // to copy it in a temporary buffer. Only kernelSize lines are
- // required. But since we start in the center of the kernel,
- // we only copy half of the data, and fill the rest with zeros
- // (assuming black/transparent pixels).
- memcpy( scratch + src->stride*kernelHalfSize,
- src->data,
- src->stride*kernelHalfSize*sizeof(TYPE));
-
- // sum half of each column, because we assume the first half is
- // zeros (black/transparent).
- for (int y=0 ; y<kernelHalfSize ; y++) {
- head = (TYPE*)src->data + y*src->stride;
- for (int x=0 ; x<w ; x++)
- sums[x] += PIXEL( *head++ );
- }
-
- for (int y=0 ; y<h ; y++) {
- TYPE* fb = (TYPE*)dst->data + y*dst->stride;
-
- // compute the dither matrix line
- uint8_t const * ditherY = ditherMatrix
- + (y & BLUR_DITHER_MASK)*BLUR_DITHER_ORDER;
-
- // Horizontal blur pass on the columns sums
- int count, dither, x=0;
- PIXEL const * out= sums;
- PIXEL const * in = sums;
- current.clear();
-
- count = kernelHalfSize;
- do {
- current += *in;
- in++;
- } while (--count);
-
- count = kernelHalfSize;
- do {
- current += *in;
- dither = *(ditherY + ((x++)&BLUR_DITHER_MASK));
- *fb++ = current.to(areaShift, k==repeat-1, dither);
- in++;
- } while (--count);
-
- count = w-kernelSize;
- do {
- current += *in;
- current -= *out;
- dither = *(ditherY + ((x++)&BLUR_DITHER_MASK));
- *fb++ = current.to(areaShift, k==repeat-1, dither);
- in++, out++;
- } while (--count);
-
- count = kernelHalfSize;
- do {
- current -= *out;
- dither = *(ditherY + ((x++)&BLUR_DITHER_MASK));
- *fb++ = current.to(areaShift, k==repeat-1, dither);
- out++;
- } while (--count);
-
- // vertical blur pass, subtract the oldest line from each columns
- // and add a new line. Subtract or add zeros at the top
- // and bottom edges.
- TYPE* const tail = scratch + (y & mask) * src->stride;
- if (y >= kernelHalfSize) {
- for (int x=0 ; x<w ; x++)
- sums[x] -= PIXEL( tail[x] );
- }
- if (y < h-kernelSize) {
- memcpy( tail,
- (TYPE*)src->data + (y+kernelHalfSize)*src->stride,
- src->stride*sizeof(TYPE));
- for (int x=0 ; x<w ; x++)
- sums[x] += PIXEL( tail[x] );
- }
- }
-
- // The subsequent passes are always done in-place.
- src = dst;
- }
-
- free(temporary_buffer);
-
- return NO_ERROR;
-}
-
-template status_t blurFilter< BlurColor565<0x80> >(
- GGLSurface const* dst,
- GGLSurface const* src,
- int kernelSizeUser,
- int repeat);
-
-status_t blurFilter(
- GGLSurface const* image,
- int kernelSizeUser,
- int repeat)
-{
- status_t err = BAD_VALUE;
- if (image->format == GGL_PIXEL_FORMAT_RGB_565) {
- err = blurFilter< BlurColor565<0x80> >(image, image, kernelSizeUser, repeat);
- } else if (image->format == GGL_PIXEL_FORMAT_RGBX_8888) {
- err = blurFilter< BlurColor888X<0x80> >(image, image, kernelSizeUser, repeat);
- }
- return err;
-}
-
-} // namespace android
-
-//err = blur< BlurColor565<0x80> >(dst, src, kernelSizeUser, repeat);
-//err = blur<BlurGray565>(dst, src, kernelSizeUser, repeat);
-//err = blur<BlurGray8888>(dst, src, kernelSizeUser, repeat);
diff --git a/services/surfaceflinger/BlurFilter.h b/services/surfaceflinger/BlurFilter.h
deleted file mode 100644
index 294db43..0000000
--- a/services/surfaceflinger/BlurFilter.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
-**
-** Copyright 2006, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-#ifndef ANDROID_BLUR_FILTER_H
-#define ANDROID_BLUR_FILTER_H
-
-#include <stdint.h>
-#include <utils/Errors.h>
-
-#include <pixelflinger/pixelflinger.h>
-
-namespace android {
-
-status_t blurFilter(
- GGLSurface const* image,
- int kernelSizeUser,
- int repeat);
-
-} // namespace android
-
-#endif // ANDROID_BLUR_FILTER_H
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
index 818774d..64cff96 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
@@ -36,11 +36,10 @@
#include "DisplayHardware/DisplayHardware.h"
-#include <hardware/copybit.h>
-#include <hardware/overlay.h>
#include <hardware/gralloc.h>
#include "GLExtensions.h"
+#include "HWComposer.h"
using namespace android;
@@ -76,7 +75,7 @@ DisplayHardware::DisplayHardware(
const sp<SurfaceFlinger>& flinger,
uint32_t dpy)
: DisplayHardwareBase(flinger, dpy),
- mFlags(0)
+ mFlags(0), mHwc(0)
{
init(dpy);
}
@@ -104,12 +103,6 @@ void DisplayHardware::init(uint32_t dpy)
mDpiY = mNativeWindow->ydpi;
mRefreshRate = fbDev->fps;
- mOverlayEngine = NULL;
- hw_module_t const* module;
- if (hw_get_module(OVERLAY_HARDWARE_MODULE_ID, &module) == 0) {
- overlay_control_open(module, &mOverlayEngine);
- }
-
EGLint w, h, dummy;
EGLint numConfigs=0;
EGLSurface surface;
@@ -272,6 +265,17 @@ void DisplayHardware::init(uint32_t dpy)
// Unbind the context from this thread
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+
+
+ // initialize the H/W composer
+ mHwc = new HWComposer();
+ if (mHwc->initCheck() == NO_ERROR) {
+ mHwc->setFrameBuffer(mDisplay, mSurface);
+ }
+}
+
+HWComposer& DisplayHardware::getHwComposer() const {
+ return *mHwc;
}
/*
@@ -285,12 +289,14 @@ void DisplayHardware::fini()
{
eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglTerminate(mDisplay);
- overlay_control_close(mOverlayEngine);
}
void DisplayHardware::releaseScreen() const
{
DisplayHardwareBase::releaseScreen();
+ if (mHwc->initCheck() == NO_ERROR) {
+ mHwc->release();
+ }
}
void DisplayHardware::acquireScreen() const
@@ -331,7 +337,12 @@ void DisplayHardware::flip(const Region& dirty) const
}
mPageFlipCount++;
- eglSwapBuffers(dpy, surface);
+
+ if (mHwc->initCheck() == NO_ERROR) {
+ mHwc->commit();
+ } else {
+ eglSwapBuffers(dpy, surface);
+ }
checkEGLErrors("eglSwapBuffers");
// for debugging
@@ -339,12 +350,6 @@ void DisplayHardware::flip(const Region& dirty) const
//glClear(GL_COLOR_BUFFER_BIT);
}
-status_t DisplayHardware::postBypassBuffer(const native_handle_t* handle) const
-{
- framebuffer_device_t *fbDev = (framebuffer_device_t *)mNativeWindow->getDevice();
- return fbDev->post(fbDev, handle);
-}
-
uint32_t DisplayHardware::getFlags() const
{
return mFlags;
@@ -354,3 +359,8 @@ void DisplayHardware::makeCurrent() const
{
eglMakeCurrent(mDisplay, mSurface, mSurface, mContext);
}
+
+void DisplayHardware::dump(String8& res) const
+{
+ mNativeWindow->dump(res);
+}
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.h b/services/surfaceflinger/DisplayHardware/DisplayHardware.h
index 79ef2a7..ee7a2af 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.h
@@ -33,13 +33,10 @@
#include "DisplayHardware/DisplayHardwareBase.h"
-struct overlay_control_device_t;
-struct framebuffer_device_t;
-struct copybit_image_t;
-
namespace android {
class FramebufferNativeWindow;
+class HWComposer;
class DisplayHardware : public DisplayHardwareBase
{
@@ -64,7 +61,6 @@ public:
// Flip the front and back buffers if the back buffer is "dirty". Might
// be instantaneous, might involve copying the frame buffer around.
void flip(const Region& dirty) const;
- status_t postBypassBuffer(const native_handle_t* handle) const;
float getDpiX() const;
float getDpiY() const;
@@ -80,7 +76,11 @@ public:
uint32_t getPageFlipCount() const;
EGLDisplay getEGLDisplay() const { return mDisplay; }
- overlay_control_device_t* getOverlayEngine() const { return mOverlayEngine; }
+
+ void dump(String8& res) const;
+
+ // Hardware Composer
+ HWComposer& getHwComposer() const;
status_t compositionComplete() const;
@@ -111,8 +111,9 @@ private:
GLint mMaxViewportDims;
GLint mMaxTextureSize;
+ HWComposer* mHwc;
+
sp<FramebufferNativeWindow> mNativeWindow;
- overlay_control_device_t* mOverlayEngine;
};
}; // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
new file mode 100644
index 0000000..4a3b20d
--- /dev/null
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+#include <utils/String8.h>
+
+#include <hardware/hardware.h>
+
+#include <cutils/log.h>
+
+#include <EGL/egl.h>
+
+#include "HWComposer.h"
+
+namespace android {
+// ---------------------------------------------------------------------------
+
+HWComposer::HWComposer()
+ : mModule(0), mHwc(0), mList(0), mCapacity(0),
+ mDpy(EGL_NO_DISPLAY), mSur(EGL_NO_SURFACE)
+{
+ int err = hw_get_module(HWC_HARDWARE_MODULE_ID, &mModule);
+ LOGW_IF(err, "%s module not found", HWC_HARDWARE_MODULE_ID);
+ if (err == 0) {
+ err = hwc_open(mModule, &mHwc);
+ LOGE_IF(err, "%s device failed to initialize (%s)",
+ HWC_HARDWARE_COMPOSER, strerror(-err));
+ }
+}
+
+HWComposer::~HWComposer() {
+ free(mList);
+ if (mHwc) {
+ hwc_close(mHwc);
+ }
+}
+
+status_t HWComposer::initCheck() const {
+ return mHwc ? NO_ERROR : NO_INIT;
+}
+
+void HWComposer::setFrameBuffer(EGLDisplay dpy, EGLSurface sur) {
+ mDpy = (hwc_display_t)dpy;
+ mSur = (hwc_surface_t)sur;
+}
+
+status_t HWComposer::createWorkList(size_t numLayers) {
+ if (mHwc) {
+ if (!mList || mCapacity < numLayers) {
+ free(mList);
+ size_t size = sizeof(hwc_layer_list) + numLayers*sizeof(hwc_layer_t);
+ mList = (hwc_layer_list_t*)malloc(size);
+ mCapacity = numLayers;
+ }
+ mList->flags = HWC_GEOMETRY_CHANGED;
+ mList->numHwLayers = numLayers;
+ }
+ return NO_ERROR;
+}
+
+status_t HWComposer::prepare() const {
+ int err = mHwc->prepare(mHwc, mList);
+ return (status_t)err;
+}
+
+status_t HWComposer::commit() const {
+ int err = mHwc->set(mHwc, mDpy, mSur, mList);
+ if (mList) {
+ mList->flags &= ~HWC_GEOMETRY_CHANGED;
+ }
+ return (status_t)err;
+}
+
+status_t HWComposer::release() const {
+ int err = mHwc->set(mHwc, NULL, NULL, NULL);
+ return (status_t)err;
+}
+
+size_t HWComposer::getNumLayers() const {
+ return mList ? mList->numHwLayers : 0;
+}
+
+hwc_layer_t* HWComposer::getLayers() const {
+ return mList ? mList->hwLayers : 0;
+}
+
+void HWComposer::dump(String8& result, char* buffer, size_t SIZE) const {
+ if (mHwc && mList) {
+ result.append("Hardware Composer state:\n");
+
+ snprintf(buffer, SIZE, " numHwLayers=%u, flags=%08x\n",
+ mList->numHwLayers, mList->flags);
+ result.append(buffer);
+
+ for (size_t i=0 ; i<mList->numHwLayers ; i++) {
+ const hwc_layer_t& l(mList->hwLayers[i]);
+ snprintf(buffer, SIZE, " %8s | %08x | %08x | %02x | %04x | [%5d,%5d,%5d,%5d] | [%5d,%5d,%5d,%5d]\n",
+ l.compositionType ? "OVERLAY" : "FB",
+ l.hints, l.flags, l.transform, l.blending,
+ l.sourceCrop.left, l.sourceCrop.top, l.sourceCrop.right, l.sourceCrop.bottom,
+ l.displayFrame.left, l.displayFrame.top, l.displayFrame.right, l.displayFrame.bottom);
+ result.append(buffer);
+ }
+
+ }
+ if (mHwc && mHwc->common.version >= 1 && mHwc->dump) {
+ mHwc->dump(mHwc, buffer, SIZE);
+ result.append(buffer);
+ }
+}
+
+// ---------------------------------------------------------------------------
+}; // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
new file mode 100644
index 0000000..5a9e9eb
--- /dev/null
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SF_HWCOMPOSER_H
+#define ANDROID_SF_HWCOMPOSER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <EGL/egl.h>
+
+#include <hardware/hwcomposer.h>
+
+namespace android {
+// ---------------------------------------------------------------------------
+
+class String8;
+
+class HWComposer
+{
+public:
+
+ HWComposer();
+ ~HWComposer();
+
+ status_t initCheck() const;
+
+ // tells the HAL what the framebuffer is
+ void setFrameBuffer(EGLDisplay dpy, EGLSurface sur);
+
+ // create a work list for numLayers layer
+ status_t createWorkList(size_t numLayers);
+
+ // Asks the HAL what it can do
+ status_t prepare() const;
+
+ // commits the list
+ status_t commit() const;
+
+ // release hardware resources
+ status_t release() const;
+
+ size_t getNumLayers() const;
+ hwc_layer_t* getLayers() const;
+
+ // for debugging
+ void dump(String8& out, char* scratch, size_t SIZE) const;
+
+private:
+ hw_module_t const* mModule;
+ hwc_composer_device_t* mHwc;
+ hwc_layer_list_t* mList;
+ size_t mCapacity;
+ hwc_display_t mDpy;
+ hwc_surface_t mSur;
+};
+
+
+// ---------------------------------------------------------------------------
+}; // namespace android
+
+#endif // ANDROID_SF_HWCOMPOSER_H
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index c9dcef3..f64fd7b 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -35,6 +35,7 @@
#include "Layer.h"
#include "SurfaceFlinger.h"
#include "DisplayHardware/DisplayHardware.h"
+#include "DisplayHardware/HWComposer.h"
#define DEBUG_RESIZE 0
@@ -57,8 +58,7 @@ Layer::Layer(SurfaceFlinger* flinger,
mSecure(false),
mTextureManager(),
mBufferManager(mTextureManager),
- mWidth(0), mHeight(0), mNeedsScaling(false), mFixedSize(false),
- mBypassState(false)
+ mWidth(0), mHeight(0), mNeedsScaling(false), mFixedSize(false)
{
}
@@ -83,8 +83,28 @@ status_t Layer::setToken(const sp<UserClient>& userClient,
sharedClient, token, mBufferManager.getDefaultBufferCount(),
getIdentity());
- status_t err = mUserClientRef.setToken(userClient, lcblk, token);
+ sp<UserClient> ourClient(mUserClientRef.getClient());
+
+ /*
+ * Here it is guaranteed that userClient != ourClient
+ * (see UserClient::getTokenForSurface()).
+ *
+ * We release the token used by this surface in ourClient below.
+ * This should be safe to do so now, since this layer won't be attached
+ * to this client, it should be okay to reuse that id.
+ *
+ * If this causes problems, an other solution would be to keep a list
+ * of all the {UserClient, token} ever used and release them when the
+ * Layer is destroyed.
+ *
+ */
+
+ if (ourClient != 0) {
+ ourClient->detachLayer(this);
+ }
+
+ status_t err = mUserClientRef.setToken(userClient, lcblk, token);
LOGE_IF(err != NO_ERROR,
"ClientRef::setToken(%p, %p, %u) failed",
userClient.get(), lcblk.get(), token);
@@ -130,8 +150,7 @@ status_t Layer::ditch()
// the layer is not on screen anymore. free as much resources as possible
mFreezeLock.clear();
- EGLDisplay dpy(mFlinger->graphicPlane(0).getEGLDisplay());
- mBufferManager.destroy(dpy);
+ // Free our own reference to ISurface
mSurface.clear();
Mutex::Autolock _l(mLock);
@@ -171,7 +190,8 @@ status_t Layer::setBuffers( uint32_t w, uint32_t h,
mReqHeight = h;
mSecure = (flags & ISurfaceComposer::eSecure) ? true : false;
- mNeedsBlending = (info.h_alpha - info.l_alpha) > 0;
+ mNeedsBlending = (info.h_alpha - info.l_alpha) > 0 &&
+ (flags & ISurfaceComposer::eOpaque) == 0;
// we use the red index
int displayRedSize = displayInfo.getSize(PixelFormatInfo::INDEX_RED);
@@ -182,6 +202,71 @@ status_t Layer::setBuffers( uint32_t w, uint32_t h,
return NO_ERROR;
}
+void Layer::setGeometry(hwc_layer_t* hwcl)
+{
+ hwcl->compositionType = HWC_FRAMEBUFFER;
+ hwcl->hints = 0;
+ hwcl->flags = 0;
+ hwcl->transform = 0;
+ hwcl->blending = HWC_BLENDING_NONE;
+
+ // we can't do alpha-fade with the hwc HAL
+ const State& s(drawingState());
+ if (s.alpha < 0xFF) {
+ hwcl->flags = HWC_SKIP_LAYER;
+ return;
+ }
+
+ // we can only handle simple transformation
+ if (mOrientation & Transform::ROT_INVALID) {
+ hwcl->flags = HWC_SKIP_LAYER;
+ return;
+ }
+
+ Transform tr(Transform(mOrientation) * Transform(mBufferTransform));
+ hwcl->transform = tr.getOrientation();
+
+ if (needsBlending()) {
+ hwcl->blending = mPremultipliedAlpha ?
+ HWC_BLENDING_PREMULT : HWC_BLENDING_COVERAGE;
+ }
+
+ hwcl->displayFrame.left = mTransformedBounds.left;
+ hwcl->displayFrame.top = mTransformedBounds.top;
+ hwcl->displayFrame.right = mTransformedBounds.right;
+ hwcl->displayFrame.bottom = mTransformedBounds.bottom;
+
+ hwcl->visibleRegionScreen.rects =
+ reinterpret_cast<hwc_rect_t const *>(
+ visibleRegionScreen.getArray(
+ &hwcl->visibleRegionScreen.numRects));
+}
+
+void Layer::setPerFrameData(hwc_layer_t* hwcl) {
+ sp<GraphicBuffer> buffer(mBufferManager.getActiveBuffer());
+ if (buffer == NULL) {
+ // this can happen if the client never drew into this layer yet,
+ // or if we ran out of memory. In that case, don't let
+ // HWC handle it.
+ hwcl->flags |= HWC_SKIP_LAYER;
+ hwcl->handle = NULL;
+ return;
+ }
+ hwcl->handle = buffer->handle;
+
+ if (!mBufferCrop.isEmpty()) {
+ hwcl->sourceCrop.left = mBufferCrop.left;
+ hwcl->sourceCrop.top = mBufferCrop.top;
+ hwcl->sourceCrop.right = mBufferCrop.right;
+ hwcl->sourceCrop.bottom = mBufferCrop.bottom;
+ } else {
+ hwcl->sourceCrop.left = 0;
+ hwcl->sourceCrop.top = 0;
+ hwcl->sourceCrop.right = buffer->width;
+ hwcl->sourceCrop.bottom = buffer->height;
+ }
+}
+
void Layer::reloadTexture(const Region& dirty)
{
sp<GraphicBuffer> buffer(mBufferManager.getActiveBuffer());
@@ -252,29 +337,6 @@ void Layer::onDraw(const Region& clip) const
}
return;
}
-
-#ifdef USE_COMPOSITION_BYPASS
- sp<GraphicBuffer> buffer(mBufferManager.getActiveBuffer());
- if ((buffer != NULL) && (buffer->transform)) {
- // Here we have a "bypass" buffer, but we need to composite it
- // most likely because it's not fullscreen anymore.
- // Since the buffer may have a transformation applied by the client
- // we need to inverse this transformation here.
-
- // calculate the inverse of the buffer transform
- const uint32_t mask = HAL_TRANSFORM_FLIP_V | HAL_TRANSFORM_FLIP_H;
- const uint32_t bufferTransformInverse = buffer->transform ^ mask;
-
- // To accomplish the inverse transform, we use "mBufferTransform"
- // which is not used by Layer.cpp
- const_cast<Layer*>(this)->mBufferTransform = bufferTransformInverse;
- drawWithOpenGL(clip, tex);
- // reset to "no transfrom"
- const_cast<Layer*>(this)->mBufferTransform = 0;
- return;
- }
-#endif
-
drawWithOpenGL(clip, tex);
}
@@ -302,8 +364,10 @@ status_t Layer::setBufferCount(int bufferCount)
// NOTE: lcblk->resize() is protected by an internal lock
status_t err = lcblk->resize(bufferCount);
- if (err == NO_ERROR)
- mBufferManager.resize(bufferCount);
+ if (err == NO_ERROR) {
+ EGLDisplay dpy(mFlinger->graphicPlane(0).getEGLDisplay());
+ mBufferManager.resize(bufferCount, mFlinger, dpy);
+ }
return err;
}
@@ -335,14 +399,13 @@ sp<GraphicBuffer> Layer::requestBuffer(int index,
* buffer 'index' as our front buffer.
*/
- uint32_t w, h, f, bypass;
+ status_t err = NO_ERROR;
+ uint32_t w, h, f;
{ // scope for the lock
Mutex::Autolock _l(mLock);
- bypass = mBypassState;
-
// zero means default
- mFixedSize = reqWidth && reqHeight;
+ const bool fixedSize = reqWidth && reqHeight;
if (!reqFormat) reqFormat = mFormat;
if (!reqWidth) reqWidth = mWidth;
if (!reqHeight) reqHeight = mHeight;
@@ -356,6 +419,7 @@ sp<GraphicBuffer> Layer::requestBuffer(int index,
mReqWidth = reqWidth;
mReqHeight = reqHeight;
mReqFormat = reqFormat;
+ mFixedSize = fixedSize;
mNeedsScaling = mWidth != mReqWidth || mHeight != mReqHeight;
lcblk->reallocateAllExcept(index);
@@ -365,40 +429,9 @@ sp<GraphicBuffer> Layer::requestBuffer(int index,
// here we have to reallocate a new buffer because the buffer could be
// used as the front buffer, or by a client in our process
// (eg: status bar), and we can't release the handle under its feet.
- uint32_t effectiveUsage = getEffectiveUsage(usage);
-
- status_t err = NO_MEMORY;
-
-#ifdef USE_COMPOSITION_BYPASS
- if (!mSecure && bypass && (effectiveUsage & GRALLOC_USAGE_HW_RENDER)) {
- // always allocate a buffer matching the screen size. the size
- // may be different from (w,h) if the buffer is rotated.
- const DisplayHardware& hw(graphicPlane(0).displayHardware());
- int32_t w = hw.getWidth();
- int32_t h = hw.getHeight();
- int32_t f = hw.getFormat();
-
- buffer = new GraphicBuffer(w, h, f, effectiveUsage | GRALLOC_USAGE_HW_FB);
- err = buffer->initCheck();
- buffer->transform = uint8_t(getOrientation());
-
- if (err != NO_ERROR) {
- // allocation didn't succeed, probably because an older bypass
- // window hasn't released all its resources yet.
- ClientRef::Access sharedClient(mUserClientRef);
- SharedBufferServer* lcblk(sharedClient.get());
- if (lcblk) {
- // all buffers need reallocation
- lcblk->reallocateAll();
- }
- }
- }
-#endif
-
- if (err != NO_ERROR) {
- buffer = new GraphicBuffer(w, h, f, effectiveUsage);
- err = buffer->initCheck();
- }
+ const uint32_t effectiveUsage = getEffectiveUsage(usage);
+ buffer = new GraphicBuffer(w, h, f, effectiveUsage);
+ err = buffer->initCheck();
if (err || buffer->handle == 0) {
GraphicBuffer::dumpAllocationsToSystemLog();
@@ -445,39 +478,6 @@ uint32_t Layer::getEffectiveUsage(uint32_t usage) const
return usage;
}
-bool Layer::setBypass(bool enable)
-{
- Mutex::Autolock _l(mLock);
-
- if (mNeedsScaling || mNeedsFiltering) {
- return false;
- }
-
- if (mBypassState != enable) {
- mBypassState = enable;
- ClientRef::Access sharedClient(mUserClientRef);
- SharedBufferServer* lcblk(sharedClient.get());
- if (lcblk) {
- // all buffers need reallocation
- lcblk->reallocateAll();
- }
- }
-
- return true;
-}
-
-void Layer::updateBuffersOrientation()
-{
- sp<GraphicBuffer> buffer(getBypassBuffer());
- if (buffer != NULL && mOrientation != buffer->transform) {
- ClientRef::Access sharedClient(mUserClientRef);
- SharedBufferServer* lcblk(sharedClient.get());
- if (lcblk) { // all buffers need reallocation
- lcblk->reallocateAll();
- }
- }
-}
-
uint32_t Layer::doTransaction(uint32_t flags)
{
const Layer::State& front(drawingState());
@@ -581,12 +581,20 @@ void Layer::lockPageFlip(bool& recomputeVisibleRegions)
}
// we retired a buffer, which becomes the new front buffer
+
+ const bool noActiveBuffer = !mBufferManager.hasActiveBuffer();
if (mBufferManager.setActiveBufferIndex(buf) < NO_ERROR) {
LOGE("retireAndLock() buffer index (%d) out of range", int(buf));
mPostedDirtyRegion.clear();
return;
}
+ if (noActiveBuffer) {
+ // we didn't have an active buffer, we need to recompute
+ // our visible region
+ recomputeVisibleRegions = true;
+ }
+
sp<GraphicBuffer> newFrontBuffer(getBuffer(buf));
if (newFrontBuffer != NULL) {
// get the dirty region
@@ -712,9 +720,9 @@ void Layer::dump(String8& result, char* buffer, size_t SIZE) const
snprintf(buffer, SIZE,
" "
"format=%2d, [%3ux%3u:%3u] [%3ux%3u:%3u],"
- " freezeLock=%p, bypass=%d, dq-q-time=%u us\n",
+ " freezeLock=%p, dq-q-time=%u us\n",
mFormat, w0, h0, s0, w1, h1, s1,
- getFreezeLock().get(), mBypassState, totalTime);
+ getFreezeLock().get(), totalTime);
result.append(buffer);
}
@@ -779,7 +787,7 @@ Layer::ClientRef::Access::~Access()
Layer::BufferManager::BufferManager(TextureManager& tm)
: mNumBuffers(NUM_BUFFERS), mTextureManager(tm),
- mActiveBuffer(-1), mFailover(false)
+ mActiveBufferIndex(-1), mFailover(false)
{
}
@@ -787,9 +795,52 @@ Layer::BufferManager::~BufferManager()
{
}
-status_t Layer::BufferManager::resize(size_t size)
+status_t Layer::BufferManager::resize(size_t size,
+ const sp<SurfaceFlinger>& flinger, EGLDisplay dpy)
{
Mutex::Autolock _l(mLock);
+
+ if (size < mNumBuffers) {
+ // Move the active texture into slot 0
+ BufferData activeBufferData = mBufferData[mActiveBufferIndex];
+ mBufferData[mActiveBufferIndex] = mBufferData[0];
+ mBufferData[0] = activeBufferData;
+ mActiveBufferIndex = 0;
+
+ // Free the buffers that are no longer needed.
+ for (size_t i = size; i < mNumBuffers; i++) {
+ mBufferData[i].buffer = 0;
+
+ // Create a message to destroy the textures on SurfaceFlinger's GL
+ // thread.
+ class MessageDestroyTexture : public MessageBase {
+ Image mTexture;
+ EGLDisplay mDpy;
+ public:
+ MessageDestroyTexture(const Image& texture, EGLDisplay dpy)
+ : mTexture(texture), mDpy(dpy) { }
+ virtual bool handler() {
+ status_t err = Layer::BufferManager::destroyTexture(
+ &mTexture, mDpy);
+ LOGE_IF(err<0, "error destroying texture: %d (%s)",
+ mTexture.name, strerror(-err));
+ return true; // XXX: err == 0; ????
+ }
+ };
+
+ MessageDestroyTexture *msg = new MessageDestroyTexture(
+ mBufferData[i].texture, dpy);
+
+ // Don't allow this texture to be cleaned up by
+ // BufferManager::destroy.
+ mBufferData[i].texture.name = -1U;
+ mBufferData[i].texture.image = EGL_NO_IMAGE_KHR;
+
+ // Post the message to the SurfaceFlinger object.
+ flinger->postMessageAsync(msg);
+ }
+ }
+
mNumBuffers = size;
return NO_ERROR;
}
@@ -800,33 +851,33 @@ sp<GraphicBuffer> Layer::BufferManager::getBuffer(size_t index) const {
}
status_t Layer::BufferManager::setActiveBufferIndex(size_t index) {
- mActiveBuffer = index;
+ BufferData const * const buffers = mBufferData;
+ Mutex::Autolock _l(mLock);
+ mActiveBuffer = buffers[index].buffer;
+ mActiveBufferIndex = index;
return NO_ERROR;
}
size_t Layer::BufferManager::getActiveBufferIndex() const {
- return mActiveBuffer;
+ return mActiveBufferIndex;
}
Texture Layer::BufferManager::getActiveTexture() const {
Texture res;
- if (mFailover || mActiveBuffer<0) {
+ if (mFailover || mActiveBufferIndex<0) {
res = mFailoverTexture;
} else {
- static_cast<Image&>(res) = mBufferData[mActiveBuffer].texture;
+ static_cast<Image&>(res) = mBufferData[mActiveBufferIndex].texture;
}
return res;
}
sp<GraphicBuffer> Layer::BufferManager::getActiveBuffer() const {
- sp<GraphicBuffer> result;
- const ssize_t activeBuffer = mActiveBuffer;
- if (activeBuffer >= 0) {
- BufferData const * const buffers = mBufferData;
- Mutex::Autolock _l(mLock);
- result = buffers[activeBuffer].buffer;
- }
- return result;
+ return mActiveBuffer;
+}
+
+bool Layer::BufferManager::hasActiveBuffer() const {
+ return mActiveBufferIndex >= 0;
}
sp<GraphicBuffer> Layer::BufferManager::detachBuffer(size_t index)
@@ -871,7 +922,7 @@ status_t Layer::BufferManager::initEglImage(EGLDisplay dpy,
const sp<GraphicBuffer>& buffer)
{
status_t err = NO_INIT;
- ssize_t index = mActiveBuffer;
+ ssize_t index = mActiveBufferIndex;
if (index >= 0) {
if (!mFailover) {
Image& texture(mBufferData[index].texture);
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 7bb207a..2908119 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -68,6 +68,8 @@ public:
bool isFixedSize() const;
// LayerBase interface
+ virtual void setGeometry(hwc_layer_t* hwcl);
+ virtual void setPerFrameData(hwc_layer_t* hwcl);
virtual void drawForSreenShot() const;
virtual void onDraw(const Region& clip) const;
virtual uint32_t doTransaction(uint32_t transactionFlags);
@@ -80,12 +82,6 @@ public:
virtual sp<Surface> createSurface() const;
virtual status_t ditch();
virtual void onRemoved();
- virtual bool setBypass(bool enable);
-
- void updateBuffersOrientation();
-
- inline sp<GraphicBuffer> getBypassBuffer() const {
- return mBufferManager.getActiveBuffer(); }
// only for debugging
inline sp<GraphicBuffer> getBuffer(int i) const {
@@ -167,7 +163,8 @@ private:
size_t mNumBuffers;
Texture mFailoverTexture;
TextureManager& mTextureManager;
- ssize_t mActiveBuffer;
+ ssize_t mActiveBufferIndex;
+ sp<GraphicBuffer> mActiveBuffer;
bool mFailover;
static status_t destroyTexture(Image* tex, EGLDisplay dpy);
@@ -180,7 +177,8 @@ private:
sp<GraphicBuffer> detachBuffer(size_t index);
status_t attachBuffer(size_t index, const sp<GraphicBuffer>& buffer);
// resize the number of active buffers
- status_t resize(size_t size);
+ status_t resize(size_t size, const sp<SurfaceFlinger>& flinger,
+ EGLDisplay dpy);
// ----------------------------------------------
// must be called from GL thread
@@ -190,6 +188,8 @@ private:
size_t getActiveBufferIndex() const;
// return the active buffer
sp<GraphicBuffer> getActiveBuffer() const;
+ // return wether we have an active buffer
+ bool hasActiveBuffer() const;
// return the active texture (or fail-over)
Texture getActiveTexture() const;
// frees resources associated with all buffers
@@ -237,7 +237,6 @@ private:
uint32_t mReqFormat;
bool mNeedsScaling;
bool mFixedSize;
- bool mBypassState;
};
// ---------------------------------------------------------------------------
diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp
index 916d420..86057f8 100644
--- a/services/surfaceflinger/LayerBase.cpp
+++ b/services/surfaceflinger/LayerBase.cpp
@@ -301,6 +301,15 @@ void LayerBase::drawRegion(const Region& reg) const
}
}
+void LayerBase::setGeometry(hwc_layer_t* hwcl) {
+ hwcl->flags |= HWC_SKIP_LAYER;
+}
+
+void LayerBase::setPerFrameData(hwc_layer_t* hwcl) {
+ hwcl->compositionType = HWC_FRAMEBUFFER;
+ hwcl->handle = NULL;
+}
+
void LayerBase::draw(const Region& clip) const
{
// reset GL state
@@ -489,13 +498,17 @@ void LayerBase::drawWithOpenGL(const Region& clip, const Texture& texture) const
}
void LayerBase::setBufferCrop(const Rect& crop) {
- if (!crop.isEmpty()) {
+ if (mBufferCrop != crop) {
mBufferCrop = crop;
+ mFlinger->invalidateHwcGeometry();
}
}
void LayerBase::setBufferTransform(uint32_t transform) {
- mBufferTransform = transform;
+ if (mBufferTransform != transform) {
+ mBufferTransform = transform;
+ mFlinger->invalidateHwcGeometry();
+ }
}
void LayerBase::dump(String8& result, char* buffer, size_t SIZE) const
@@ -548,10 +561,15 @@ sp<LayerBaseClient::Surface> LayerBaseClient::getSurface()
if (s == 0) {
s = createSurface();
mClientSurface = s;
+ mClientSurfaceBinder = s->asBinder();
}
return s;
}
+wp<IBinder> LayerBaseClient::getSurfaceBinder() const {
+ return mClientSurfaceBinder;
+}
+
sp<LayerBaseClient::Surface> LayerBaseClient::createSurface() const
{
return new Surface(mFlinger, mIdentity,
@@ -609,21 +627,6 @@ sp<LayerBaseClient> LayerBaseClient::Surface::getOwner() const {
status_t LayerBaseClient::Surface::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
- switch (code) {
- case REGISTER_BUFFERS:
- case UNREGISTER_BUFFERS:
- case CREATE_OVERLAY:
- {
- if (!mFlinger->mAccessSurfaceFlinger.checkCalling()) {
- IPCThreadState* ipc = IPCThreadState::self();
- const int pid = ipc->getCallingPid();
- const int uid = ipc->getCallingUid();
- LOGE("Permission Denial: "
- "can't access SurfaceFlinger pid=%d, uid=%d", pid, uid);
- return PERMISSION_DENIED;
- }
- }
- }
return BnSurface::onTransact(code, data, reply, flags);
}
@@ -638,26 +641,6 @@ status_t LayerBaseClient::Surface::setBufferCount(int bufferCount)
return INVALID_OPERATION;
}
-status_t LayerBaseClient::Surface::registerBuffers(
- const ISurface::BufferHeap& buffers)
-{
- return INVALID_OPERATION;
-}
-
-void LayerBaseClient::Surface::postBuffer(ssize_t offset)
-{
-}
-
-void LayerBaseClient::Surface::unregisterBuffers()
-{
-}
-
-sp<OverlayRef> LayerBaseClient::Surface::createOverlay(
- uint32_t w, uint32_t h, int32_t format, int32_t orientation)
-{
- return NULL;
-};
-
// ---------------------------------------------------------------------------
}; // namespace android
diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h
index 1470729..8ed4749 100644
--- a/services/surfaceflinger/LayerBase.h
+++ b/services/surfaceflinger/LayerBase.h
@@ -27,7 +27,6 @@
#include <utils/RefBase.h>
#include <ui/Region.h>
-#include <ui/Overlay.h>
#include <surfaceflinger/ISurfaceComposerClient.h>
#include <private/surfaceflinger/SharedBufferStack.h>
@@ -35,6 +34,8 @@
#include <pixelflinger/pixelflinger.h>
+#include <hardware/hwcomposer.h>
+
#include "DisplayHardware/DisplayHardware.h"
#include "Transform.h"
@@ -109,6 +110,10 @@ public:
virtual const char* getTypeId() const { return "LayerBase"; }
+ virtual void setGeometry(hwc_layer_t* hwcl);
+
+ virtual void setPerFrameData(hwc_layer_t* hwcl);
+
/**
* draw - performs some global clipping optimizations
* and calls onDraw().
@@ -119,11 +124,6 @@ public:
virtual void drawForSreenShot() const;
/**
- * bypass mode
- */
- virtual bool setBypass(bool enable) { return false; }
-
- /**
* onDraw - draws the surface.
*/
virtual void onDraw(const Region& clip) const = 0;
@@ -285,6 +285,7 @@ public:
virtual ~LayerBaseClient();
sp<Surface> getSurface();
+ wp<IBinder> getSurfaceBinder() const;
virtual sp<Surface> createSurface() const;
virtual sp<LayerBaseClient> getLayerBaseClient() const {
return const_cast<LayerBaseClient*>(this); }
@@ -309,12 +310,6 @@ public:
uint32_t w, uint32_t h, uint32_t format, uint32_t usage);
virtual status_t setBufferCount(int bufferCount);
- virtual status_t registerBuffers(const ISurface::BufferHeap& buffers);
- virtual void postBuffer(ssize_t offset);
- virtual void unregisterBuffers();
- virtual sp<OverlayRef> createOverlay(uint32_t w, uint32_t h,
- int32_t format, int32_t orientation);
-
protected:
friend class LayerBaseClient;
sp<SurfaceFlinger> mFlinger;
@@ -331,6 +326,7 @@ protected:
private:
mutable Mutex mLock;
mutable wp<Surface> mClientSurface;
+ wp<IBinder> mClientSurfaceBinder;
const wp<Client> mClientRef;
// only read
const uint32_t mIdentity;
diff --git a/services/surfaceflinger/LayerBlur.cpp b/services/surfaceflinger/LayerBlur.cpp
deleted file mode 100644
index 4cfcfe3..0000000
--- a/services/surfaceflinger/LayerBlur.cpp
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <sys/types.h>
-
-#include <utils/Errors.h>
-#include <utils/Log.h>
-
-#include <GLES/gl.h>
-#include <GLES/glext.h>
-
-#include "clz.h"
-#include "BlurFilter.h"
-#include "LayerBlur.h"
-#include "SurfaceFlinger.h"
-#include "DisplayHardware/DisplayHardware.h"
-
-namespace android {
-// ---------------------------------------------------------------------------
-
-LayerBlur::LayerBlur(SurfaceFlinger* flinger, DisplayID display,
- const sp<Client>& client)
- : LayerBaseClient(flinger, display, client), mCacheDirty(true),
- mRefreshCache(true), mCacheAge(0), mTextureName(-1U),
- mWidthScale(1.0f), mHeightScale(1.0f),
- mBlurFormat(GGL_PIXEL_FORMAT_RGB_565)
-{
-}
-
-LayerBlur::~LayerBlur()
-{
- if (mTextureName != -1U) {
- glDeleteTextures(1, &mTextureName);
- }
-}
-
-void LayerBlur::setVisibleRegion(const Region& visibleRegion)
-{
- LayerBaseClient::setVisibleRegion(visibleRegion);
- if (visibleRegionScreen.isEmpty()) {
- if (mTextureName != -1U) {
- // We're not visible, free the texture up.
- glBindTexture(GL_TEXTURE_2D, 0);
- glDeleteTextures(1, &mTextureName);
- mTextureName = -1U;
- }
- }
-}
-
-uint32_t LayerBlur::doTransaction(uint32_t flags)
-{
- // we're doing a transaction, refresh the cache!
- if (!mFlinger->isFrozen()) {
- mRefreshCache = true;
- mCacheDirty = true;
- flags |= eVisibleRegion;
- this->contentDirty = true;
- }
- return LayerBase::doTransaction(flags);
-}
-
-void LayerBlur::unlockPageFlip(const Transform& planeTransform, Region& outDirtyRegion)
-{
- // this code-path must be as tight as possible, it's called each time
- // the screen is composited.
- if (UNLIKELY(!visibleRegionScreen.isEmpty())) {
- // if anything visible below us is invalidated, the cache becomes dirty
- if (!mCacheDirty &&
- !visibleRegionScreen.intersect(outDirtyRegion).isEmpty()) {
- mCacheDirty = true;
- }
- if (mCacheDirty) {
- if (!mFlinger->isFrozen()) {
- // update everything below us that is visible
- outDirtyRegion.orSelf(visibleRegionScreen);
- nsecs_t now = systemTime();
- if ((now - mCacheAge) >= ms2ns(500)) {
- mCacheAge = now;
- mRefreshCache = true;
- mCacheDirty = false;
- } else {
- if (!mAutoRefreshPending) {
- mFlinger->postMessageAsync(
- new MessageBase(MessageQueue::INVALIDATE),
- ms2ns(500));
- mAutoRefreshPending = true;
- }
- }
- }
- }
- }
- LayerBase::unlockPageFlip(planeTransform, outDirtyRegion);
-}
-
-void LayerBlur::onDraw(const Region& clip) const
-{
- const DisplayHardware& hw(graphicPlane(0).displayHardware());
- const uint32_t fbHeight = hw.getHeight();
- int x = mTransformedBounds.left;
- int y = mTransformedBounds.top;
- int w = mTransformedBounds.width();
- int h = mTransformedBounds.height();
- GLint X = x;
- GLint Y = fbHeight - (y + h);
- if (X < 0) {
- w += X;
- X = 0;
- }
- if (Y < 0) {
- h += Y;
- Y = 0;
- }
- if (w<0 || h<0) {
- // we're outside of the framebuffer
- return;
- }
-
- if (mTextureName == -1U) {
- // create the texture name the first time
- // can't do that in the ctor, because it runs in another thread.
- glGenTextures(1, &mTextureName);
- glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES, &mReadFormat);
- glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE_OES, &mReadType);
- if (mReadFormat != GL_RGB || mReadType != GL_UNSIGNED_SHORT_5_6_5) {
- mReadFormat = GL_RGBA;
- mReadType = GL_UNSIGNED_BYTE;
- mBlurFormat = GGL_PIXEL_FORMAT_RGBX_8888;
- }
- }
-
- Region::const_iterator it = clip.begin();
- Region::const_iterator const end = clip.end();
- if (it != end) {
-#if defined(GL_OES_EGL_image_external)
- if (GLExtensions::getInstance().haveTextureExternal()) {
- glDisable(GL_TEXTURE_EXTERNAL_OES);
- }
-#endif
- glEnable(GL_TEXTURE_2D);
- glBindTexture(GL_TEXTURE_2D, mTextureName);
-
- if (mRefreshCache) {
- mRefreshCache = false;
- mAutoRefreshPending = false;
-
- int32_t pixelSize = 4;
- int32_t s = w;
- if (mReadType == GL_UNSIGNED_SHORT_5_6_5) {
- // allocate enough memory for 4-bytes (2 pixels) aligned data
- s = (w + 1) & ~1;
- pixelSize = 2;
- }
-
- uint16_t* const pixels = (uint16_t*)malloc(s*h*pixelSize);
-
- // This reads the frame-buffer, so a h/w GL would have to
- // finish() its rendering first. we don't want to do that
- // too often. Read data is 4-bytes aligned.
- glReadPixels(X, Y, w, h, mReadFormat, mReadType, pixels);
-
- // blur that texture.
- GGLSurface bl;
- bl.version = sizeof(GGLSurface);
- bl.width = w;
- bl.height = h;
- bl.stride = s;
- bl.format = mBlurFormat;
- bl.data = (GGLubyte*)pixels;
- blurFilter(&bl, 8, 2);
-
- if (GLExtensions::getInstance().haveNpot()) {
- glTexImage2D(GL_TEXTURE_2D, 0, mReadFormat, w, h, 0,
- mReadFormat, mReadType, pixels);
- mWidthScale = 1.0f / w;
- mHeightScale =-1.0f / h;
- mYOffset = 0;
- } else {
- GLuint tw = 1 << (31 - clz(w));
- GLuint th = 1 << (31 - clz(h));
- if (tw < GLuint(w)) tw <<= 1;
- if (th < GLuint(h)) th <<= 1;
- glTexImage2D(GL_TEXTURE_2D, 0, mReadFormat, tw, th, 0,
- mReadFormat, mReadType, NULL);
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h,
- mReadFormat, mReadType, pixels);
- mWidthScale = 1.0f / tw;
- mHeightScale =-1.0f / th;
- mYOffset = th-h;
- }
-
- free((void*)pixels);
- }
-
- const State& s = drawingState();
- if (UNLIKELY(s.alpha < 0xFF)) {
- const GLfloat alpha = s.alpha * (1.0f/255.0f);
- glColor4f(0, 0, 0, alpha);
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
- } else {
- glDisable(GL_BLEND);
- }
-
- if (mFlags & DisplayHardware::SLOW_CONFIG) {
- glDisable(GL_DITHER);
- } else {
- glEnable(GL_DITHER);
- }
-
- glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-
- glMatrixMode(GL_TEXTURE);
- glLoadIdentity();
- glScalef(mWidthScale, mHeightScale, 1);
- glTranslatef(-x, mYOffset - y, 0);
- glEnableClientState(GL_TEXTURE_COORD_ARRAY);
- glVertexPointer(2, GL_FLOAT, 0, mVertices);
- glTexCoordPointer(2, GL_FLOAT, 0, mVertices);
- while (it != end) {
- const Rect& r = *it++;
- const GLint sy = fbHeight - (r.top + r.height());
- glScissor(r.left, sy, r.width(), r.height());
- glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
- }
- glDisableClientState(GL_TEXTURE_COORD_ARRAY);
- glLoadIdentity();
- glMatrixMode(GL_MODELVIEW);
- }
-}
-
-// ---------------------------------------------------------------------------
-
-}; // namespace android
diff --git a/services/surfaceflinger/LayerBlur.h b/services/surfaceflinger/LayerBlur.h
deleted file mode 100644
index 4c9ec64..0000000
--- a/services/surfaceflinger/LayerBlur.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_LAYER_BLUR_H
-#define ANDROID_LAYER_BLUR_H
-
-#include <stdint.h>
-#include <sys/types.h>
-
-#include <ui/Region.h>
-
-#include "LayerBase.h"
-
-namespace android {
-
-// ---------------------------------------------------------------------------
-
-class LayerBlur : public LayerBaseClient
-{
-public:
- LayerBlur(SurfaceFlinger* flinger, DisplayID display,
- const sp<Client>& client);
- virtual ~LayerBlur();
-
- virtual void onDraw(const Region& clip) const;
- virtual bool needsBlending() const { return true; }
- virtual bool isSecure() const { return false; }
- virtual const char* getTypeId() const { return "LayerBlur"; }
-
- virtual uint32_t doTransaction(uint32_t flags);
- virtual void setVisibleRegion(const Region& visibleRegion);
- virtual void unlockPageFlip(const Transform& planeTransform, Region& outDirtyRegion);
-
-private:
- bool mCacheDirty;
- mutable bool mRefreshCache;
- mutable bool mAutoRefreshPending;
- nsecs_t mCacheAge;
- mutable GLuint mTextureName;
- mutable GLfloat mWidthScale;
- mutable GLfloat mHeightScale;
- mutable GLfloat mYOffset;
- mutable GLint mReadFormat;
- mutable GLint mReadType;
- mutable uint32_t mBlurFormat;
-};
-
-// ---------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_LAYER_BLUR_H
diff --git a/services/surfaceflinger/LayerBuffer.cpp b/services/surfaceflinger/LayerBuffer.cpp
deleted file mode 100644
index 23506cf..0000000
--- a/services/surfaceflinger/LayerBuffer.cpp
+++ /dev/null
@@ -1,701 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <math.h>
-#include <sys/types.h>
-
-#include <utils/Errors.h>
-#include <utils/Log.h>
-#include <utils/StopWatch.h>
-
-#include <ui/GraphicBuffer.h>
-#include <ui/PixelFormat.h>
-#include <ui/FramebufferNativeWindow.h>
-#include <ui/Rect.h>
-#include <ui/Region.h>
-
-#include <hardware/copybit.h>
-
-#include "LayerBuffer.h"
-#include "SurfaceFlinger.h"
-#include "DisplayHardware/DisplayHardware.h"
-
-namespace android {
-
-// ---------------------------------------------------------------------------
-
-gralloc_module_t const* LayerBuffer::sGrallocModule = 0;
-
-// ---------------------------------------------------------------------------
-
-LayerBuffer::LayerBuffer(SurfaceFlinger* flinger, DisplayID display,
- const sp<Client>& client)
- : LayerBaseClient(flinger, display, client),
- mNeedsBlending(false), mBlitEngine(0)
-{
-}
-
-LayerBuffer::~LayerBuffer()
-{
- if (mBlitEngine) {
- copybit_close(mBlitEngine);
- }
-}
-
-void LayerBuffer::onFirstRef()
-{
- LayerBaseClient::onFirstRef();
- mSurface = new SurfaceLayerBuffer(mFlinger, this);
-
- hw_module_t const* module = (hw_module_t const*)sGrallocModule;
- if (!module) {
- // NOTE: technically there is a race here, but it shouldn't
- // cause any problem since hw_get_module() always returns
- // the same value.
- if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module) == 0) {
- sGrallocModule = (gralloc_module_t const *)module;
- }
- }
-
- if (hw_get_module(COPYBIT_HARDWARE_MODULE_ID, &module) == 0) {
- copybit_open(module, &mBlitEngine);
- }
-}
-
-sp<LayerBaseClient::Surface> LayerBuffer::createSurface() const
-{
- return mSurface;
-}
-
-status_t LayerBuffer::ditch()
-{
- mSurface.clear();
- return NO_ERROR;
-}
-
-bool LayerBuffer::needsBlending() const {
- return mNeedsBlending;
-}
-
-void LayerBuffer::setNeedsBlending(bool blending) {
- mNeedsBlending = blending;
-}
-
-void LayerBuffer::postBuffer(ssize_t offset)
-{
- sp<Source> source(getSource());
- if (source != 0)
- source->postBuffer(offset);
-}
-
-void LayerBuffer::unregisterBuffers()
-{
- sp<Source> source(clearSource());
- if (source != 0)
- source->unregisterBuffers();
-}
-
-uint32_t LayerBuffer::doTransaction(uint32_t flags)
-{
- sp<Source> source(getSource());
- if (source != 0)
- source->onTransaction(flags);
- uint32_t res = LayerBase::doTransaction(flags);
- // we always want filtering for these surfaces
- mNeedsFiltering = !(mFlags & DisplayHardware::SLOW_CONFIG);
- return res;
-}
-
-void LayerBuffer::unlockPageFlip(const Transform& planeTransform,
- Region& outDirtyRegion)
-{
- // this code-path must be as tight as possible, it's called each time
- // the screen is composited.
- sp<Source> source(getSource());
- if (source != 0)
- source->onVisibilityResolved(planeTransform);
- LayerBase::unlockPageFlip(planeTransform, outDirtyRegion);
-}
-
-void LayerBuffer::validateVisibility(const Transform& globalTransform)
-{
- sp<Source> source(getSource());
- if (source != 0)
- source->onvalidateVisibility(globalTransform);
- LayerBase::validateVisibility(globalTransform);
-}
-
-void LayerBuffer::drawForSreenShot() const
-{
- const DisplayHardware& hw(graphicPlane(0).displayHardware());
- clearWithOpenGL( Region(hw.bounds()) );
-}
-
-void LayerBuffer::onDraw(const Region& clip) const
-{
- sp<Source> source(getSource());
- if (LIKELY(source != 0)) {
- source->onDraw(clip);
- } else {
- clearWithOpenGL(clip);
- }
-}
-
-void LayerBuffer::serverDestroy()
-{
- sp<Source> source(clearSource());
- if (source != 0) {
- source->destroy();
- }
-}
-
-/**
- * This creates a "buffer" source for this surface
- */
-status_t LayerBuffer::registerBuffers(const ISurface::BufferHeap& buffers)
-{
- Mutex::Autolock _l(mLock);
- if (mSource != 0)
- return INVALID_OPERATION;
-
- sp<BufferSource> source = new BufferSource(*this, buffers);
-
- status_t result = source->getStatus();
- if (result == NO_ERROR) {
- mSource = source;
- }
- return result;
-}
-
-/**
- * This creates an "overlay" source for this surface
- */
-sp<OverlayRef> LayerBuffer::createOverlay(uint32_t w, uint32_t h, int32_t f,
- int32_t orientation)
-{
- sp<OverlayRef> result;
- Mutex::Autolock _l(mLock);
- if (mSource != 0)
- return result;
-
- sp<OverlaySource> source = new OverlaySource(*this, &result, w, h, f, orientation);
- if (result != 0) {
- mSource = source;
- }
- return result;
-}
-
-sp<LayerBuffer::Source> LayerBuffer::getSource() const {
- Mutex::Autolock _l(mLock);
- return mSource;
-}
-
-sp<LayerBuffer::Source> LayerBuffer::clearSource() {
- sp<Source> source;
- Mutex::Autolock _l(mLock);
- source = mSource;
- mSource.clear();
- return source;
-}
-
-// ============================================================================
-// LayerBuffer::SurfaceLayerBuffer
-// ============================================================================
-
-LayerBuffer::SurfaceLayerBuffer::SurfaceLayerBuffer(
- const sp<SurfaceFlinger>& flinger, const sp<LayerBuffer>& owner)
- : LayerBaseClient::Surface(flinger, owner->getIdentity(), owner)
-{
-}
-
-LayerBuffer::SurfaceLayerBuffer::~SurfaceLayerBuffer()
-{
- unregisterBuffers();
-}
-
-status_t LayerBuffer::SurfaceLayerBuffer::registerBuffers(
- const ISurface::BufferHeap& buffers)
-{
- sp<LayerBuffer> owner(getOwner());
- if (owner != 0)
- return owner->registerBuffers(buffers);
- return NO_INIT;
-}
-
-void LayerBuffer::SurfaceLayerBuffer::postBuffer(ssize_t offset)
-{
- sp<LayerBuffer> owner(getOwner());
- if (owner != 0)
- owner->postBuffer(offset);
-}
-
-void LayerBuffer::SurfaceLayerBuffer::unregisterBuffers()
-{
- sp<LayerBuffer> owner(getOwner());
- if (owner != 0)
- owner->unregisterBuffers();
-}
-
-sp<OverlayRef> LayerBuffer::SurfaceLayerBuffer::createOverlay(
- uint32_t w, uint32_t h, int32_t format, int32_t orientation) {
- sp<OverlayRef> result;
- sp<LayerBuffer> owner(getOwner());
- if (owner != 0)
- result = owner->createOverlay(w, h, format, orientation);
- return result;
-}
-
-// ============================================================================
-// LayerBuffer::Buffer
-// ============================================================================
-
-LayerBuffer::Buffer::Buffer(const ISurface::BufferHeap& buffers,
- ssize_t offset, size_t bufferSize)
- : mBufferHeap(buffers), mSupportsCopybit(false)
-{
- NativeBuffer& src(mNativeBuffer);
- src.crop.l = 0;
- src.crop.t = 0;
- src.crop.r = buffers.w;
- src.crop.b = buffers.h;
-
- src.img.w = buffers.hor_stride ?: buffers.w;
- src.img.h = buffers.ver_stride ?: buffers.h;
- src.img.format = buffers.format;
- src.img.base = (void*)(intptr_t(buffers.heap->base()) + offset);
- src.img.handle = 0;
-
- gralloc_module_t const * module = LayerBuffer::getGrallocModule();
- if (module && module->perform) {
- int err = module->perform(module,
- GRALLOC_MODULE_PERFORM_CREATE_HANDLE_FROM_BUFFER,
- buffers.heap->heapID(), bufferSize,
- offset, buffers.heap->base(),
- &src.img.handle);
-
- // we can fail here is the passed buffer is purely software
- mSupportsCopybit = (err == NO_ERROR);
- }
- }
-
-LayerBuffer::Buffer::~Buffer()
-{
- NativeBuffer& src(mNativeBuffer);
- if (src.img.handle) {
- native_handle_delete(src.img.handle);
- }
-}
-
-// ============================================================================
-// LayerBuffer::Source
-// LayerBuffer::BufferSource
-// LayerBuffer::OverlaySource
-// ============================================================================
-
-LayerBuffer::Source::Source(LayerBuffer& layer)
- : mLayer(layer)
-{
-}
-LayerBuffer::Source::~Source() {
-}
-void LayerBuffer::Source::onDraw(const Region& clip) const {
-}
-void LayerBuffer::Source::onTransaction(uint32_t flags) {
-}
-void LayerBuffer::Source::onVisibilityResolved(
- const Transform& planeTransform) {
-}
-void LayerBuffer::Source::postBuffer(ssize_t offset) {
-}
-void LayerBuffer::Source::unregisterBuffers() {
-}
-
-// ---------------------------------------------------------------------------
-
-LayerBuffer::BufferSource::BufferSource(LayerBuffer& layer,
- const ISurface::BufferHeap& buffers)
- : Source(layer), mStatus(NO_ERROR), mBufferSize(0)
-{
- if (buffers.heap == NULL) {
- // this is allowed, but in this case, it is illegal to receive
- // postBuffer(). The surface just erases the framebuffer with
- // fully transparent pixels.
- mBufferHeap = buffers;
- mLayer.setNeedsBlending(false);
- return;
- }
-
- status_t err = (buffers.heap->heapID() >= 0) ? NO_ERROR : NO_INIT;
- if (err != NO_ERROR) {
- LOGE("LayerBuffer::BufferSource: invalid heap (%s)", strerror(err));
- mStatus = err;
- return;
- }
-
- PixelFormatInfo info;
- err = getPixelFormatInfo(buffers.format, &info);
- if (err != NO_ERROR) {
- LOGE("LayerBuffer::BufferSource: invalid format %d (%s)",
- buffers.format, strerror(err));
- mStatus = err;
- return;
- }
-
- if (buffers.hor_stride<0 || buffers.ver_stride<0) {
- LOGE("LayerBuffer::BufferSource: invalid parameters "
- "(w=%d, h=%d, xs=%d, ys=%d)",
- buffers.w, buffers.h, buffers.hor_stride, buffers.ver_stride);
- mStatus = BAD_VALUE;
- return;
- }
-
- mBufferHeap = buffers;
- mLayer.setNeedsBlending((info.h_alpha - info.l_alpha) > 0);
- mBufferSize = info.getScanlineSize(buffers.hor_stride)*buffers.ver_stride;
- mLayer.forceVisibilityTransaction();
-}
-
-LayerBuffer::BufferSource::~BufferSource()
-{
- class MessageDestroyTexture : public MessageBase {
- SurfaceFlinger* flinger;
- GLuint name;
- public:
- MessageDestroyTexture(
- SurfaceFlinger* flinger, GLuint name)
- : flinger(flinger), name(name) { }
- virtual bool handler() {
- glDeleteTextures(1, &name);
- return true;
- }
- };
-
- if (mTexture.name != -1U) {
- // GL textures can only be destroyed from the GL thread
- getFlinger()->mEventQueue.postMessage(
- new MessageDestroyTexture(getFlinger(), mTexture.name) );
- }
- if (mTexture.image != EGL_NO_IMAGE_KHR) {
- EGLDisplay dpy(getFlinger()->graphicPlane(0).getEGLDisplay());
- eglDestroyImageKHR(dpy, mTexture.image);
- }
-}
-
-void LayerBuffer::BufferSource::postBuffer(ssize_t offset)
-{
- ISurface::BufferHeap buffers;
- { // scope for the lock
- Mutex::Autolock _l(mBufferSourceLock);
- buffers = mBufferHeap;
- if (buffers.heap != 0) {
- const size_t memorySize = buffers.heap->getSize();
- if ((size_t(offset) + mBufferSize) > memorySize) {
- LOGE("LayerBuffer::BufferSource::postBuffer() "
- "invalid buffer (offset=%d, size=%d, heap-size=%d",
- int(offset), int(mBufferSize), int(memorySize));
- return;
- }
- }
- }
-
- sp<Buffer> buffer;
- if (buffers.heap != 0) {
- buffer = new LayerBuffer::Buffer(buffers, offset, mBufferSize);
- if (buffer->getStatus() != NO_ERROR)
- buffer.clear();
- setBuffer(buffer);
- mLayer.invalidate();
- }
-}
-
-void LayerBuffer::BufferSource::unregisterBuffers()
-{
- Mutex::Autolock _l(mBufferSourceLock);
- mBufferHeap.heap.clear();
- mBuffer.clear();
- mLayer.invalidate();
-}
-
-sp<LayerBuffer::Buffer> LayerBuffer::BufferSource::getBuffer() const
-{
- Mutex::Autolock _l(mBufferSourceLock);
- return mBuffer;
-}
-
-void LayerBuffer::BufferSource::setBuffer(const sp<LayerBuffer::Buffer>& buffer)
-{
- Mutex::Autolock _l(mBufferSourceLock);
- mBuffer = buffer;
-}
-
-void LayerBuffer::BufferSource::onDraw(const Region& clip) const
-{
- sp<Buffer> ourBuffer(getBuffer());
- if (UNLIKELY(ourBuffer == 0)) {
- // nothing to do, we don't have a buffer
- mLayer.clearWithOpenGL(clip);
- return;
- }
-
- status_t err = NO_ERROR;
- NativeBuffer src(ourBuffer->getBuffer());
- const Rect transformedBounds(mLayer.getTransformedBounds());
-
-#if defined(EGL_ANDROID_image_native_buffer)
- if (GLExtensions::getInstance().haveDirectTexture()) {
- err = INVALID_OPERATION;
- if (ourBuffer->supportsCopybit()) {
- copybit_device_t* copybit = mLayer.mBlitEngine;
- if (copybit && err != NO_ERROR) {
- // create our EGLImageKHR the first time
- err = initTempBuffer();
- if (err == NO_ERROR) {
- // NOTE: Assume the buffer is allocated with the proper USAGE flags
- const NativeBuffer& dst(mTempBuffer);
- region_iterator clip(Region(Rect(dst.crop.r, dst.crop.b)));
- copybit->set_parameter(copybit, COPYBIT_TRANSFORM, 0);
- copybit->set_parameter(copybit, COPYBIT_PLANE_ALPHA, 0xFF);
- copybit->set_parameter(copybit, COPYBIT_DITHER, COPYBIT_ENABLE);
- err = copybit->stretch(copybit, &dst.img, &src.img,
- &dst.crop, &src.crop, &clip);
- if (err != NO_ERROR) {
- clearTempBufferImage();
- }
- }
- }
- }
- }
-#endif
- else {
- err = INVALID_OPERATION;
- }
-
- if (err != NO_ERROR) {
- // slower fallback
- GGLSurface t;
- t.version = sizeof(GGLSurface);
- t.width = src.crop.r;
- t.height = src.crop.b;
- t.stride = src.img.w;
- t.vstride= src.img.h;
- t.format = src.img.format;
- t.data = (GGLubyte*)src.img.base;
- const Region dirty(Rect(t.width, t.height));
- mTextureManager.loadTexture(&mTexture, dirty, t);
- }
-
- mLayer.setBufferTransform(mBufferHeap.transform);
- mLayer.drawWithOpenGL(clip, mTexture);
-}
-
-status_t LayerBuffer::BufferSource::initTempBuffer() const
-{
- // figure out the size we need now
- const ISurface::BufferHeap& buffers(mBufferHeap);
- uint32_t w = mLayer.mTransformedBounds.width();
- uint32_t h = mLayer.mTransformedBounds.height();
- if (mLayer.getOrientation() & (Transform::ROT_90 | Transform::ROT_270)) {
- int t = w; w = h; h = t;
- }
-
- // we're in the copybit case, so make sure we can handle this blit
- // we don't have to keep the aspect ratio here
- copybit_device_t* copybit = mLayer.mBlitEngine;
- const int down = copybit->get(copybit, COPYBIT_MINIFICATION_LIMIT);
- const int up = copybit->get(copybit, COPYBIT_MAGNIFICATION_LIMIT);
- if (buffers.w > w*down) w = buffers.w / down;
- else if (w > buffers.w*up) w = buffers.w*up;
- if (buffers.h > h*down) h = buffers.h / down;
- else if (h > buffers.h*up) h = buffers.h*up;
-
- if (mTexture.image != EGL_NO_IMAGE_KHR) {
- // we have an EGLImage, make sure the needed size didn't change
- if (w!=mTexture.width || h!= mTexture.height) {
- // delete the EGLImage and texture
- clearTempBufferImage();
- } else {
- // we're good, we have an EGLImageKHR and it's (still) the
- // right size
- return NO_ERROR;
- }
- }
-
- // figure out if we need linear filtering
- if (buffers.w * h == buffers.h * w) {
- // same pixel area, don't use filtering
- mLayer.mNeedsFiltering = false;
- }
-
- // Allocate a temporary buffer and create the corresponding EGLImageKHR
- // once the EGLImage has been created we don't need the
- // graphic buffer reference anymore.
- sp<GraphicBuffer> buffer = new GraphicBuffer(
- w, h, HAL_PIXEL_FORMAT_RGB_565,
- GraphicBuffer::USAGE_HW_TEXTURE |
- GraphicBuffer::USAGE_HW_2D);
-
- status_t err = buffer->initCheck();
- if (err == NO_ERROR) {
- NativeBuffer& dst(mTempBuffer);
- dst.img.w = buffer->getStride();
- dst.img.h = h;
- dst.img.format = buffer->getPixelFormat();
- dst.img.handle = (native_handle_t *)buffer->handle;
- dst.img.base = 0;
- dst.crop.l = 0;
- dst.crop.t = 0;
- dst.crop.r = w;
- dst.crop.b = h;
-
- EGLDisplay dpy(getFlinger()->graphicPlane(0).getEGLDisplay());
- err = mTextureManager.initEglImage(&mTexture, dpy, buffer);
- }
-
- return err;
-}
-
-void LayerBuffer::BufferSource::clearTempBufferImage() const
-{
- // delete the image
- EGLDisplay dpy(getFlinger()->graphicPlane(0).getEGLDisplay());
- eglDestroyImageKHR(dpy, mTexture.image);
-
- // and the associated texture (recreate a name)
- glDeleteTextures(1, &mTexture.name);
- Texture defaultTexture;
- mTexture = defaultTexture;
-}
-
-// ---------------------------------------------------------------------------
-
-LayerBuffer::OverlaySource::OverlaySource(LayerBuffer& layer,
- sp<OverlayRef>* overlayRef,
- uint32_t w, uint32_t h, int32_t format, int32_t orientation)
- : Source(layer), mVisibilityChanged(false),
- mOverlay(0), mOverlayHandle(0), mOverlayDevice(0), mOrientation(orientation)
-{
- overlay_control_device_t* overlay_dev = getFlinger()->getOverlayEngine();
- if (overlay_dev == NULL) {
- // overlays not supported
- return;
- }
-
- mOverlayDevice = overlay_dev;
- overlay_t* overlay = overlay_dev->createOverlay(overlay_dev, w, h, format);
- if (overlay == NULL) {
- // couldn't create the overlay (no memory? no more overlays?)
- return;
- }
-
- // enable dithering...
- overlay_dev->setParameter(overlay_dev, overlay,
- OVERLAY_DITHER, OVERLAY_ENABLE);
-
- mOverlay = overlay;
- mWidth = overlay->w;
- mHeight = overlay->h;
- mFormat = overlay->format;
- mWidthStride = overlay->w_stride;
- mHeightStride = overlay->h_stride;
- mInitialized = false;
-
- mOverlayHandle = overlay->getHandleRef(overlay);
-
- sp<OverlayChannel> channel = new OverlayChannel( &layer );
-
- *overlayRef = new OverlayRef(mOverlayHandle, channel,
- mWidth, mHeight, mFormat, mWidthStride, mHeightStride);
- getFlinger()->signalEvent();
-}
-
-LayerBuffer::OverlaySource::~OverlaySource()
-{
- if (mOverlay && mOverlayDevice) {
- overlay_control_device_t* overlay_dev = mOverlayDevice;
- overlay_dev->destroyOverlay(overlay_dev, mOverlay);
- }
-}
-
-void LayerBuffer::OverlaySource::onDraw(const Region& clip) const
-{
- // this would be where the color-key would be set, should we need it.
- GLclampf red = 0;
- GLclampf green = 0;
- GLclampf blue = 0;
- mLayer.clearWithOpenGL(clip, red, green, blue, 0);
-}
-
-void LayerBuffer::OverlaySource::onTransaction(uint32_t flags)
-{
- const Layer::State& front(mLayer.drawingState());
- const Layer::State& temp(mLayer.currentState());
- if (temp.sequence != front.sequence) {
- mVisibilityChanged = true;
- }
-}
-
-void LayerBuffer::OverlaySource::onvalidateVisibility(const Transform&)
-{
- mVisibilityChanged = true;
-}
-
-void LayerBuffer::OverlaySource::onVisibilityResolved(
- const Transform& planeTransform)
-{
- // this code-path must be as tight as possible, it's called each time
- // the screen is composited.
- if (UNLIKELY(mOverlay != 0)) {
- if (mVisibilityChanged || !mInitialized) {
- mVisibilityChanged = false;
- mInitialized = true;
- const Rect bounds(mLayer.getTransformedBounds());
- int x = bounds.left;
- int y = bounds.top;
- int w = bounds.width();
- int h = bounds.height();
-
- // we need a lock here to protect "destroy"
- Mutex::Autolock _l(mOverlaySourceLock);
- if (mOverlay) {
- overlay_control_device_t* overlay_dev = mOverlayDevice;
- overlay_dev->setPosition(overlay_dev, mOverlay, x,y,w,h);
- // we need to combine the layer orientation and the
- // user-requested orientation.
- Transform finalTransform(Transform(mLayer.getOrientation()) *
- Transform(mOrientation));
- overlay_dev->setParameter(overlay_dev, mOverlay,
- OVERLAY_TRANSFORM, finalTransform.getOrientation());
- overlay_dev->commit(overlay_dev, mOverlay);
- }
- }
- }
-}
-
-void LayerBuffer::OverlaySource::destroy()
-{
- // we need a lock here to protect "onVisibilityResolved"
- Mutex::Autolock _l(mOverlaySourceLock);
- if (mOverlay && mOverlayDevice) {
- overlay_control_device_t* overlay_dev = mOverlayDevice;
- overlay_dev->destroyOverlay(overlay_dev, mOverlay);
- mOverlay = 0;
- }
-}
-
-// ---------------------------------------------------------------------------
-}; // namespace android
diff --git a/services/surfaceflinger/LayerBuffer.h b/services/surfaceflinger/LayerBuffer.h
deleted file mode 100644
index a89d8fe..0000000
--- a/services/surfaceflinger/LayerBuffer.h
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_LAYER_BUFFER_H
-#define ANDROID_LAYER_BUFFER_H
-
-#include <stdint.h>
-#include <sys/types.h>
-
-#include "LayerBase.h"
-#include "TextureManager.h"
-
-struct copybit_device_t;
-
-namespace android {
-
-// ---------------------------------------------------------------------------
-
-class Buffer;
-class Region;
-class OverlayRef;
-
-// ---------------------------------------------------------------------------
-
-class LayerBuffer : public LayerBaseClient
-{
- class Source : public LightRefBase<Source> {
- public:
- Source(LayerBuffer& layer);
- virtual ~Source();
- virtual void onDraw(const Region& clip) const;
- virtual void onTransaction(uint32_t flags);
- virtual void onVisibilityResolved(const Transform& planeTransform);
- virtual void onvalidateVisibility(const Transform& globalTransform) { }
- virtual void postBuffer(ssize_t offset);
- virtual void unregisterBuffers();
- virtual void destroy() { }
- SurfaceFlinger* getFlinger() const { return mLayer.mFlinger.get(); }
- protected:
- LayerBuffer& mLayer;
- };
-
-public:
- LayerBuffer(SurfaceFlinger* flinger, DisplayID display,
- const sp<Client>& client);
- virtual ~LayerBuffer();
-
- virtual void onFirstRef();
- virtual bool needsBlending() const;
- virtual const char* getTypeId() const { return "LayerBuffer"; }
-
- virtual sp<LayerBaseClient::Surface> createSurface() const;
- virtual status_t ditch();
- virtual void onDraw(const Region& clip) const;
- virtual void drawForSreenShot() const;
- virtual uint32_t doTransaction(uint32_t flags);
- virtual void unlockPageFlip(const Transform& planeTransform, Region& outDirtyRegion);
- virtual void validateVisibility(const Transform& globalTransform);
-
- status_t registerBuffers(const ISurface::BufferHeap& buffers);
- void postBuffer(ssize_t offset);
- void unregisterBuffers();
- sp<OverlayRef> createOverlay(uint32_t w, uint32_t h, int32_t format,
- int32_t orientation);
-
- sp<Source> getSource() const;
- sp<Source> clearSource();
- void setNeedsBlending(bool blending);
- Rect getTransformedBounds() const {
- return mTransformedBounds;
- }
-
- void serverDestroy();
-
-private:
- struct NativeBuffer {
- copybit_image_t img;
- copybit_rect_t crop;
- };
-
- static gralloc_module_t const* sGrallocModule;
- static gralloc_module_t const* getGrallocModule() {
- return sGrallocModule;
- }
-
- class Buffer : public LightRefBase<Buffer> {
- public:
- Buffer(const ISurface::BufferHeap& buffers,
- ssize_t offset, size_t bufferSize);
- inline bool supportsCopybit() const {
- return mSupportsCopybit;
- }
- inline status_t getStatus() const {
- return mBufferHeap.heap!=0 ? NO_ERROR : NO_INIT;
- }
- inline const NativeBuffer& getBuffer() const {
- return mNativeBuffer;
- }
- protected:
- friend class LightRefBase<Buffer>;
- Buffer& operator = (const Buffer& rhs);
- Buffer(const Buffer& rhs);
- ~Buffer();
- private:
- ISurface::BufferHeap mBufferHeap;
- NativeBuffer mNativeBuffer;
- bool mSupportsCopybit;
- };
-
- class BufferSource : public Source {
- public:
- BufferSource(LayerBuffer& layer, const ISurface::BufferHeap& buffers);
- virtual ~BufferSource();
-
- status_t getStatus() const { return mStatus; }
- sp<Buffer> getBuffer() const;
- void setBuffer(const sp<Buffer>& buffer);
-
- virtual void onDraw(const Region& clip) const;
- virtual void postBuffer(ssize_t offset);
- virtual void unregisterBuffers();
- virtual void destroy() { }
- private:
- status_t initTempBuffer() const;
- void clearTempBufferImage() const;
- mutable Mutex mBufferSourceLock;
- sp<Buffer> mBuffer;
- status_t mStatus;
- ISurface::BufferHeap mBufferHeap;
- size_t mBufferSize;
- mutable Texture mTexture;
- mutable NativeBuffer mTempBuffer;
- mutable TextureManager mTextureManager;
- };
-
- class OverlaySource : public Source {
- public:
- OverlaySource(LayerBuffer& layer,
- sp<OverlayRef>* overlayRef,
- uint32_t w, uint32_t h, int32_t format, int32_t orientation);
- virtual ~OverlaySource();
- virtual void onDraw(const Region& clip) const;
- virtual void onTransaction(uint32_t flags);
- virtual void onVisibilityResolved(const Transform& planeTransform);
- virtual void onvalidateVisibility(const Transform& globalTransform);
- virtual void destroy();
- private:
-
- class OverlayChannel : public BnOverlay {
- wp<LayerBuffer> mLayer;
- virtual void destroy() {
- sp<LayerBuffer> layer(mLayer.promote());
- if (layer != 0) {
- layer->serverDestroy();
- }
- }
- public:
- OverlayChannel(const sp<LayerBuffer>& layer)
- : mLayer(layer) {
- }
- };
-
- friend class OverlayChannel;
- bool mVisibilityChanged;
-
- overlay_t* mOverlay;
- overlay_handle_t mOverlayHandle;
- overlay_control_device_t* mOverlayDevice;
- uint32_t mWidth;
- uint32_t mHeight;
- int32_t mFormat;
- int32_t mWidthStride;
- int32_t mHeightStride;
- int32_t mOrientation;
- mutable Mutex mOverlaySourceLock;
- bool mInitialized;
- };
-
-
- class SurfaceLayerBuffer : public LayerBaseClient::Surface
- {
- public:
- SurfaceLayerBuffer(const sp<SurfaceFlinger>& flinger,
- const sp<LayerBuffer>& owner);
- virtual ~SurfaceLayerBuffer();
-
- virtual status_t registerBuffers(const ISurface::BufferHeap& buffers);
- virtual void postBuffer(ssize_t offset);
- virtual void unregisterBuffers();
-
- virtual sp<OverlayRef> createOverlay(
- uint32_t w, uint32_t h, int32_t format, int32_t orientation);
- private:
- sp<LayerBuffer> getOwner() const {
- return static_cast<LayerBuffer*>(Surface::getOwner().get());
- }
- };
-
- mutable Mutex mLock;
- sp<Source> mSource;
- sp<Surface> mSurface;
- bool mInvalidate;
- bool mNeedsBlending;
- copybit_device_t* mBlitEngine;
-};
-
-// ---------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_LAYER_BUFFER_H
diff --git a/services/surfaceflinger/LayerDim.cpp b/services/surfaceflinger/LayerDim.cpp
index 80cc52c..f79166d 100644
--- a/services/surfaceflinger/LayerDim.cpp
+++ b/services/surfaceflinger/LayerDim.cpp
@@ -30,29 +30,12 @@
namespace android {
// ---------------------------------------------------------------------------
-bool LayerDim::sUseTexture;
-GLuint LayerDim::sTexId;
-EGLImageKHR LayerDim::sImage;
-int32_t LayerDim::sWidth;
-int32_t LayerDim::sHeight;
-
-// ---------------------------------------------------------------------------
-
LayerDim::LayerDim(SurfaceFlinger* flinger, DisplayID display,
const sp<Client>& client)
: LayerBaseClient(flinger, display, client)
{
}
-void LayerDim::initDimmer(SurfaceFlinger* flinger, uint32_t w, uint32_t h)
-{
- sTexId = -1;
- sImage = EGL_NO_IMAGE_KHR;
- sWidth = w;
- sHeight = h;
- sUseTexture = false;
-}
-
LayerDim::~LayerDim()
{
}
@@ -67,8 +50,14 @@ void LayerDim::onDraw(const Region& clip) const
const GLfloat alpha = s.alpha/255.0f;
const uint32_t fbHeight = hw.getHeight();
glDisable(GL_DITHER);
- glEnable(GL_BLEND);
- glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+ if (s.alpha == 0xFF) {
+ glDisable(GL_BLEND);
+ } else {
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ }
+
glColor4f(0, 0, 0, alpha);
#if defined(GL_OES_EGL_image_external)
@@ -78,15 +67,7 @@ void LayerDim::onDraw(const Region& clip) const
#endif
glDisable(GL_TEXTURE_2D);
- GLshort w = sWidth;
- GLshort h = sHeight;
- const GLshort vertices[4][2] = {
- { 0, 0 },
- { 0, h },
- { w, h },
- { w, 0 }
- };
- glVertexPointer(2, GL_SHORT, 0, vertices);
+ glVertexPointer(2, GL_FLOAT, 0, mVertices);
while (it != end) {
const Rect& r = *it++;
diff --git a/services/surfaceflinger/LayerDim.h b/services/surfaceflinger/LayerDim.h
index f032314..5631c0a 100644
--- a/services/surfaceflinger/LayerDim.h
+++ b/services/surfaceflinger/LayerDim.h
@@ -31,11 +31,6 @@ namespace android {
class LayerDim : public LayerBaseClient
{
- static bool sUseTexture;
- static GLuint sTexId;
- static EGLImageKHR sImage;
- static int32_t sWidth;
- static int32_t sHeight;
public:
LayerDim(SurfaceFlinger* flinger, DisplayID display,
const sp<Client>& client);
@@ -45,8 +40,6 @@ public:
virtual bool needsBlending() const { return true; }
virtual bool isSecure() const { return false; }
virtual const char* getTypeId() const { return "LayerDim"; }
-
- static void initDimmer(SurfaceFlinger* flinger, uint32_t w, uint32_t h);
};
// ---------------------------------------------------------------------------
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 4b3235d..40882d8 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -47,12 +47,11 @@
#include "clz.h"
#include "GLExtensions.h"
#include "Layer.h"
-#include "LayerBlur.h"
-#include "LayerBuffer.h"
#include "LayerDim.h"
#include "SurfaceFlinger.h"
#include "DisplayHardware/DisplayHardware.h"
+#include "DisplayHardware/HWComposer.h"
/* ideally AID_GRAPHICS would be in a semi-public header
* or there would be a way to map a user/group name to its id
@@ -61,10 +60,6 @@
#define AID_GRAPHICS 1003
#endif
-#ifdef USE_COMPOSITION_BYPASS
-#warning "using COMPOSITION_BYPASS"
-#endif
-
#define DISPLAY_COUNT 1
namespace android {
@@ -82,6 +77,7 @@ SurfaceFlinger::SurfaceFlinger()
mReadFramebuffer("android.permission.READ_FRAME_BUFFER"),
mDump("android.permission.DUMP"),
mVisibleRegionsDirty(false),
+ mHwWorkListDirty(false),
mDeferReleaseConsole(false),
mFreezeDisplay(false),
mElectronBeamAnimationMode(0),
@@ -89,6 +85,7 @@ SurfaceFlinger::SurfaceFlinger()
mFreezeDisplayTime(0),
mDebugRegion(0),
mDebugBackground(0),
+ mDebugDisableHWC(0),
mDebugInSwapBuffers(0),
mLastSwapBufferTime(0),
mDebugInTransaction(0),
@@ -120,11 +117,6 @@ SurfaceFlinger::~SurfaceFlinger()
glDeleteTextures(1, &mWormholeTexName);
}
-overlay_control_device_t* SurfaceFlinger::getOverlayEngine() const
-{
- return graphicPlane(0).displayHardware().getOverlayEngine();
-}
-
sp<IMemoryHeap> SurfaceFlinger::getCblk() const
{
return mServerHeap;
@@ -152,6 +144,11 @@ sp<ISurfaceComposerClient> SurfaceFlinger::createClientConnection()
return bclient;
}
+sp<IGraphicBufferAlloc> SurfaceFlinger::createGraphicBufferAlloc()
+{
+ sp<GraphicBufferAlloc> gba(new GraphicBufferAlloc());
+ return gba;
+}
const GraphicPlane& SurfaceFlinger::graphicPlane(int dpy) const
{
@@ -170,7 +167,7 @@ void SurfaceFlinger::bootFinished()
{
const nsecs_t now = systemTime();
const nsecs_t duration = now - mBootTime;
- LOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) );
+ LOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) );
mBootFinished = true;
property_set("ctl.stop", "bootanim");
}
@@ -206,10 +203,10 @@ status_t SurfaceFlinger::readyToRun()
mServerHeap = new MemoryHeapBase(4096,
MemoryHeapBase::READ_ONLY, "SurfaceFlinger read-only heap");
LOGE_IF(mServerHeap==0, "can't create shared memory dealer");
-
+
mServerCblk = static_cast<surface_flinger_cblk_t*>(mServerHeap->getBase());
LOGE_IF(mServerCblk==0, "can't get to shared control block's address");
-
+
new(mServerCblk) surface_flinger_cblk_t;
// initialize primary screen
@@ -238,7 +235,7 @@ status_t SurfaceFlinger::readyToRun()
// Initialize OpenGL|ES
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
- glPixelStorei(GL_PACK_ALIGNMENT, 4);
+ glPixelStorei(GL_PACK_ALIGNMENT, 4);
glEnableClientState(GL_VERTEX_ARRAY);
glEnable(GL_SCISSOR_TEST);
glShadeModel(GL_FLAT);
@@ -262,8 +259,6 @@ status_t SurfaceFlinger::readyToRun()
glLoadIdentity();
glOrthof(0, w, h, 0, 0, 1);
- LayerDim::initDimmer(this, w, h);
-
mReadyToRunBarrier.open();
/*
@@ -272,7 +267,7 @@ status_t SurfaceFlinger::readyToRun()
// start boot animation
property_set("ctl.start", "bootanim");
-
+
return NO_ERROR;
}
@@ -375,17 +370,15 @@ bool SurfaceFlinger::threadLoop()
// post surfaces (if needed)
handlePageFlip();
+ if (UNLIKELY(mHwWorkListDirty)) {
+ // build the h/w work list
+ handleWorkList();
+ }
+
const DisplayHardware& hw(graphicPlane(0).displayHardware());
if (LIKELY(hw.canDraw() && !isFrozen())) {
-
-#ifdef USE_COMPOSITION_BYPASS
- if (handleBypassLayer()) {
- unlockClients();
- return true;
- }
-#endif
-
// repaint the framebuffer (if needed)
+
const int index = hw.getCurrentBufferIndex();
GraphicLog& logger(GraphicLog::getInstance());
@@ -408,20 +401,6 @@ bool SurfaceFlinger::threadLoop()
return true;
}
-bool SurfaceFlinger::handleBypassLayer()
-{
- sp<Layer> bypassLayer(mBypassLayer.promote());
- if (bypassLayer != 0) {
- sp<GraphicBuffer> buffer(bypassLayer->getBypassBuffer());
- if (buffer!=0 && (buffer->usage & GRALLOC_USAGE_HW_FB)) {
- const DisplayHardware& hw(graphicPlane(0).displayHardware());
- hw.postBypassBuffer(buffer->handle);
- return true;
- }
- }
- return false;
-}
-
void SurfaceFlinger::postFramebuffer()
{
if (!mInvalidRegion.isEmpty()) {
@@ -480,6 +459,7 @@ void SurfaceFlinger::handleTransaction(uint32_t transactionFlags)
handleTransactionLocked(transactionFlags, ditchedLayers);
mLastTransactionTime = systemTime() - now;
mDebugInTransaction = 0;
+ invalidateHwcGeometry();
// here the transaction has been committed
}
@@ -487,6 +467,7 @@ void SurfaceFlinger::handleTransaction(uint32_t transactionFlags)
* Clean-up all layers that went away
* (do this without the lock held)
*/
+
const size_t count = ditchedLayers.size();
for (size_t i=0 ; i<count ; i++) {
if (ditchedLayers[i] != 0) {
@@ -690,7 +671,7 @@ void SurfaceFlinger::computeVisibleRegions(
// Update aboveOpaqueLayers for next (lower) layer
aboveOpaqueLayers.orSelf(opaqueRegion);
-
+
// Store the visible region is screen space
layer->setVisibleRegion(visibleRegion);
layer->setCoveredRegion(coveredRegion);
@@ -717,37 +698,11 @@ void SurfaceFlinger::commitTransaction()
mTransactionCV.broadcast();
}
-void SurfaceFlinger::setBypassLayer(const sp<LayerBase>& layer)
-{
- // if this layer is already the bypass layer, do nothing
- sp<Layer> cur(mBypassLayer.promote());
- if (mBypassLayer == layer) {
- if (cur != NULL) {
- cur->updateBuffersOrientation();
- }
- return;
- }
-
- // clear the current bypass layer
- mBypassLayer.clear();
- if (cur != 0) {
- cur->setBypass(false);
- cur.clear();
- }
-
- // set new bypass layer
- if (layer != 0) {
- if (layer->setBypass(true)) {
- mBypassLayer = static_cast<Layer*>(layer.get());
- }
- }
-}
-
void SurfaceFlinger::handlePageFlip()
{
bool visibleRegions = mVisibleRegionsDirty;
- LayerVector& currentLayers = const_cast<LayerVector&>(
- mDrawingState.layersSortedByZ);
+ LayerVector& currentLayers(
+ const_cast<LayerVector&>(mDrawingState.layersSortedByZ));
visibleRegions |= lockPageFlip(currentLayers);
const DisplayHardware& hw = graphicPlane(0).displayHardware();
@@ -768,29 +723,20 @@ void SurfaceFlinger::handlePageFlip()
mVisibleLayersSortedByZ.add(currentLayers[i]);
}
-#ifdef USE_COMPOSITION_BYPASS
- sp<LayerBase> bypassLayer;
- const size_t numVisibleLayers = mVisibleLayersSortedByZ.size();
- if (numVisibleLayers == 1) {
- const sp<LayerBase>& candidate(mVisibleLayersSortedByZ[0]);
- const Region& visibleRegion(candidate->visibleRegionScreen);
- const Region reminder(screenRegion.subtract(visibleRegion));
- if (reminder.isEmpty()) {
- // fullscreen candidate!
- bypassLayer = candidate;
- }
- }
- setBypassLayer(bypassLayer);
-#endif
-
mWormholeRegion = screenRegion.subtract(opaqueRegion);
mVisibleRegionsDirty = false;
+ invalidateHwcGeometry();
}
unlockPageFlip(currentLayers);
mDirtyRegion.andSelf(screenRegion);
}
+void SurfaceFlinger::invalidateHwcGeometry()
+{
+ mHwWorkListDirty = true;
+}
+
bool SurfaceFlinger::lockPageFlip(const LayerVector& currentLayers)
{
bool recomputeVisibleRegions = false;
@@ -815,15 +761,29 @@ void SurfaceFlinger::unlockPageFlip(const LayerVector& currentLayers)
}
}
+void SurfaceFlinger::handleWorkList()
+{
+ mHwWorkListDirty = false;
+ HWComposer& hwc(graphicPlane(0).displayHardware().getHwComposer());
+ if (hwc.initCheck() == NO_ERROR) {
+ const Vector< sp<LayerBase> >& currentLayers(mVisibleLayersSortedByZ);
+ const size_t count = currentLayers.size();
+ hwc.createWorkList(count);
+ hwc_layer_t* const cur(hwc.getLayers());
+ for (size_t i=0 ; cur && i<count ; i++) {
+ currentLayers[i]->setGeometry(&cur[i]);
+ if (mDebugDisableHWC) {
+ cur[i].compositionType = HWC_FRAMEBUFFER;
+ cur[i].flags |= HWC_SKIP_LAYER;
+ }
+ }
+ }
+}
void SurfaceFlinger::handleRepaint()
{
// compute the invalid region
mInvalidRegion.orSelf(mDirtyRegion);
- if (mInvalidRegion.isEmpty()) {
- // nothing to do
- return;
- }
if (UNLIKELY(mDebugRegion)) {
debugFlashRegions();
@@ -835,8 +795,8 @@ void SurfaceFlinger::handleRepaint()
glLoadIdentity();
uint32_t flags = hw.getFlags();
- if ((flags & DisplayHardware::SWAP_RECTANGLE) ||
- (flags & DisplayHardware::BUFFER_PRESERVED))
+ if ((flags & DisplayHardware::SWAP_RECTANGLE) ||
+ (flags & DisplayHardware::BUFFER_PRESERVED))
{
// we can redraw only what's dirty, but since SWAP_RECTANGLE only
// takes a rectangle, we must make sure to update that whole
@@ -879,9 +839,79 @@ void SurfaceFlinger::composeSurfaces(const Region& dirty)
// draw something...
drawWormhole();
}
+
+ status_t err = NO_ERROR;
const Vector< sp<LayerBase> >& layers(mVisibleLayersSortedByZ);
- const size_t count = layers.size();
- for (size_t i=0 ; i<count ; ++i) {
+ size_t count = layers.size();
+
+ const DisplayHardware& hw(graphicPlane(0).displayHardware());
+ HWComposer& hwc(hw.getHwComposer());
+ hwc_layer_t* const cur(hwc.getLayers());
+
+ LOGE_IF(cur && hwc.getNumLayers() != count,
+ "HAL number of layers (%d) doesn't match surfaceflinger (%d)",
+ hwc.getNumLayers(), count);
+
+ // just to be extra-safe, use the smallest count
+ if (hwc.initCheck() == NO_ERROR) {
+ count = count < hwc.getNumLayers() ? count : hwc.getNumLayers();
+ }
+
+ /*
+ * update the per-frame h/w composer data for each layer
+ * and build the transparent region of the FB
+ */
+ Region transparent;
+ if (cur) {
+ for (size_t i=0 ; i<count ; i++) {
+ const sp<LayerBase>& layer(layers[i]);
+ layer->setPerFrameData(&cur[i]);
+ }
+ err = hwc.prepare();
+ LOGE_IF(err, "HWComposer::prepare failed (%s)", strerror(-err));
+
+ if (err == NO_ERROR) {
+ for (size_t i=0 ; i<count ; i++) {
+ if (cur[i].hints & HWC_HINT_CLEAR_FB) {
+ const sp<LayerBase>& layer(layers[i]);
+ if (!(layer->needsBlending())) {
+ transparent.orSelf(layer->visibleRegionScreen);
+ }
+ }
+ }
+
+ /*
+ * clear the area of the FB that need to be transparent
+ */
+ transparent.andSelf(dirty);
+ if (!transparent.isEmpty()) {
+ glClearColor(0,0,0,0);
+ Region::const_iterator it = transparent.begin();
+ Region::const_iterator const end = transparent.end();
+ const int32_t height = hw.getHeight();
+ while (it != end) {
+ const Rect& r(*it++);
+ const GLint sy = height - (r.top + r.height());
+ glScissor(r.left, sy, r.width(), r.height());
+ glClear(GL_COLOR_BUFFER_BIT);
+ }
+ }
+ }
+ }
+
+
+ /*
+ * and then, render the layers targeted at the framebuffer
+ */
+ for (size_t i=0 ; i<count ; i++) {
+ if (cur) {
+ if ((cur[i].compositionType != HWC_FRAMEBUFFER) &&
+ !(cur[i].flags & HWC_SKIP_LAYER)) {
+ // skip layers handled by the HAL
+ continue;
+ }
+ }
+
const sp<LayerBase>& layer(layers[i]);
const Region clip(dirty.intersect(layer->visibleRegionScreen));
if (!clip.isEmpty()) {
@@ -993,6 +1023,8 @@ void SurfaceFlinger::drawWormhole() const
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+ glLoadIdentity();
+ glMatrixMode(GL_MODELVIEW);
}
}
@@ -1054,7 +1086,7 @@ status_t SurfaceFlinger::removeLayer_l(const sp<LayerBase>& layerBase)
{
sp<LayerBaseClient> lbc(layerBase->getLayerBaseClient());
if (lbc != 0) {
- mLayerMap.removeItem( lbc->getSurface()->asBinder() );
+ mLayerMap.removeItem( lbc->getSurfaceBinder() );
}
ssize_t index = mCurrentState.layersSortedByZ.remove(layerBase);
if (index >= 0) {
@@ -1113,7 +1145,7 @@ void SurfaceFlinger::closeGlobalTransaction()
if (android_atomic_dec(&mTransactionCount) == 1) {
signalEvent();
- // if there is a transaction with a resize, wait for it to
+ // if there is a transaction with a resize, wait for it to
// take effect before returning.
Mutex::Autolock _l(mStateLock);
while (mResizeTransationPending) {
@@ -1157,7 +1189,7 @@ status_t SurfaceFlinger::unfreezeDisplay(DisplayID dpy, uint32_t flags)
return NO_ERROR;
}
-int SurfaceFlinger::setOrientation(DisplayID dpy,
+int SurfaceFlinger::setOrientation(DisplayID dpy,
int orientation, uint32_t flags)
{
if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT))
@@ -1190,21 +1222,17 @@ sp<ISurface> SurfaceFlinger::createSurface(const sp<Client>& client, int pid,
int(w), int(h));
return surfaceHandle;
}
-
+
//LOGD("createSurface for pid %d (%d x %d)", pid, w, h);
sp<Layer> normalLayer;
switch (flags & eFXSurfaceMask) {
case eFXSurfaceNormal:
- if (UNLIKELY(flags & ePushBuffers)) {
- layer = createPushBuffersSurface(client, d, w, h, flags);
- } else {
- normalLayer = createNormalSurface(client, d, w, h, flags, format);
- layer = normalLayer;
- }
+ normalLayer = createNormalSurface(client, d, w, h, flags, format);
+ layer = normalLayer;
break;
case eFXSurfaceBlur:
- layer = createBlurSurface(client, d, w, h, flags);
- break;
+ // for now we treat Blur as Dim, until we can implement it
+ // efficiently.
case eFXSurfaceDim:
layer = createDimSurface(client, d, w, h, flags);
break;
@@ -1216,7 +1244,7 @@ sp<ISurface> SurfaceFlinger::createSurface(const sp<Client>& client, int pid,
ssize_t token = addClientLayer(client, layer);
surfaceHandle = layer->getSurface();
- if (surfaceHandle != 0) {
+ if (surfaceHandle != 0) {
params->token = token;
params->identity = surfaceHandle->getIdentity();
params->width = w;
@@ -1268,15 +1296,6 @@ sp<Layer> SurfaceFlinger::createNormalSurface(
return layer;
}
-sp<LayerBlur> SurfaceFlinger::createBlurSurface(
- const sp<Client>& client, DisplayID display,
- uint32_t w, uint32_t h, uint32_t flags)
-{
- sp<LayerBlur> layer = new LayerBlur(this, display, client);
- layer->initStates(w, h, flags);
- return layer;
-}
-
sp<LayerDim> SurfaceFlinger::createDimSurface(
const sp<Client>& client, DisplayID display,
uint32_t w, uint32_t h, uint32_t flags)
@@ -1286,21 +1305,12 @@ sp<LayerDim> SurfaceFlinger::createDimSurface(
return layer;
}
-sp<LayerBuffer> SurfaceFlinger::createPushBuffersSurface(
- const sp<Client>& client, DisplayID display,
- uint32_t w, uint32_t h, uint32_t flags)
-{
- sp<LayerBuffer> layer = new LayerBuffer(this, display, client);
- layer->initStates(w, h, flags);
- return layer;
-}
-
status_t SurfaceFlinger::removeSurface(const sp<Client>& client, SurfaceID sid)
{
/*
* called by the window manager, when a surface should be marked for
* destruction.
- *
+ *
* The surface is removed from the current and drawing lists, but placed
* in the purgatory queue, so it's not destroyed right-away (we need
* to wait for all client's references to go away first).
@@ -1321,7 +1331,7 @@ status_t SurfaceFlinger::removeSurface(const sp<Client>& client, SurfaceID sid)
status_t SurfaceFlinger::destroySurface(const sp<LayerBaseClient>& layer)
{
// called by ~ISurface() when all references are gone
-
+
class MessageDestroySurface : public MessageBase {
SurfaceFlinger* flinger;
sp<LayerBaseClient> layer;
@@ -1334,9 +1344,9 @@ status_t SurfaceFlinger::destroySurface(const sp<LayerBaseClient>& layer)
layer.clear(); // clear it outside of the lock;
Mutex::Autolock _l(flinger->mStateLock);
/*
- * remove the layer from the current list -- chances are that it's
- * not in the list anyway, because it should have been removed
- * already upon request of the client (eg: window manager).
+ * remove the layer from the current list -- chances are that it's
+ * not in the list anyway, because it should have been removed
+ * already upon request of the client (eg: window manager).
* However, a buggy client could have not done that.
* Since we know we don't have any more clients, we don't need
* to use the purgatory.
@@ -1437,7 +1447,7 @@ void SurfaceFlinger::screenAcquired(int dpy)
status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args)
{
- const size_t SIZE = 1024;
+ const size_t SIZE = 4096;
char buffer[SIZE];
String8 result;
if (!mDump.checkCalling()) {
@@ -1464,7 +1474,7 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args)
}
const bool locked(retry >= 0);
if (!locked) {
- snprintf(buffer, SIZE,
+ snprintf(buffer, SIZE,
"SurfaceFlinger appears to be unresponsive, "
"dumping anyways (no locks held)\n");
result.append(buffer);
@@ -1507,9 +1517,9 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args)
mWormholeRegion.dump(result, "WormholeRegion");
const DisplayHardware& hw(graphicPlane(0).displayHardware());
snprintf(buffer, SIZE,
- " display frozen: %s, freezeCount=%d, orientation=%d, bypass=%p, canDraw=%d\n",
+ " display frozen: %s, freezeCount=%d, orientation=%d, canDraw=%d\n",
mFreezeDisplay?"yes":"no", mFreezeCount,
- mCurrentState.orientation, mBypassLayer.unsafe_get(), hw.canDraw());
+ mCurrentState.orientation, hw.canDraw());
result.append(buffer);
snprintf(buffer, SIZE,
" last eglSwapBuffers() time: %f us\n"
@@ -1530,10 +1540,21 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args)
}
/*
+ * Dump HWComposer state
+ */
+ HWComposer& hwc(hw.getHwComposer());
+ snprintf(buffer, SIZE, " h/w composer %s and %s\n",
+ hwc.initCheck()==NO_ERROR ? "present" : "not present",
+ mDebugDisableHWC ? "disabled" : "enabled");
+ result.append(buffer);
+ hwc.dump(result, buffer, SIZE);
+
+ /*
* Dump gralloc state
*/
const GraphicBufferAllocator& alloc(GraphicBufferAllocator::get());
alloc.dump(result);
+ hw.dump(result);
if (locked) {
mStateLock.unlock();
@@ -1607,6 +1628,11 @@ status_t SurfaceFlinger::onTransact(
n = data.readInt32();
mDebugBackground = n ? 1 : 0;
return NO_ERROR;
+ case 1008: // toggle use of hw composer
+ n = data.readInt32();
+ mDebugDisableHWC = n ? 1 : 0;
+ invalidateHwcGeometry();
+ // fall-through...
case 1004:{ // repaint everything
Mutex::Autolock _l(mStateLock);
const DisplayHardware& hw(graphicPlane(0).displayHardware());
@@ -2100,7 +2126,8 @@ status_t SurfaceFlinger::turnElectronBeamOn(int32_t mode)
status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy,
sp<IMemoryHeap>* heap,
uint32_t* w, uint32_t* h, PixelFormat* f,
- uint32_t sw, uint32_t sh)
+ uint32_t sw, uint32_t sh,
+ uint32_t minLayerZ, uint32_t maxLayerZ)
{
status_t result = PERMISSION_DENIED;
@@ -2123,6 +2150,9 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy,
sh = (!sh) ? hw_h : sh;
const size_t size = sw * sh * 4;
+ LOGD("screenshot: sw=%d, sh=%d, minZ=%d, maxZ=%d",
+ sw, sh, minLayerZ, maxLayerZ);
+
// make sure to clear all GL error flags
while ( glGetError() != GL_NO_ERROR ) ;
@@ -2137,6 +2167,7 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy,
GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, tname);
GLenum status = glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);
+
if (status == GL_FRAMEBUFFER_COMPLETE_OES) {
// invert everything, b/c glReadPixel() below will invert the FB
@@ -2156,7 +2187,10 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy,
const size_t count = layers.size();
for (size_t i=0 ; i<count ; ++i) {
const sp<LayerBase>& layer(layers[i]);
- layer->drawForSreenShot();
+ const uint32_t z = layer->drawingState().z;
+ if (z >= minLayerZ && z <= maxLayerZ) {
+ layer->drawForSreenShot();
+ }
}
// XXX: this is needed on tegra
@@ -2191,8 +2225,6 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy,
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
-
-
} else {
result = BAD_VALUE;
}
@@ -2204,6 +2236,8 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy,
hw.compositionComplete();
+ LOGD("screenshot: result = %s", result<0 ? strerror(result) : "OK");
+
return result;
}
@@ -2211,7 +2245,8 @@ status_t SurfaceFlinger::captureScreenImplLocked(DisplayID dpy,
status_t SurfaceFlinger::captureScreen(DisplayID dpy,
sp<IMemoryHeap>* heap,
uint32_t* width, uint32_t* height, PixelFormat* format,
- uint32_t sw, uint32_t sh)
+ uint32_t sw, uint32_t sh,
+ uint32_t minLayerZ, uint32_t maxLayerZ)
{
// only one display supported for now
if (UNLIKELY(uint32_t(dpy) >= DISPLAY_COUNT))
@@ -2229,13 +2264,18 @@ status_t SurfaceFlinger::captureScreen(DisplayID dpy,
PixelFormat* f;
uint32_t sw;
uint32_t sh;
+ uint32_t minLayerZ;
+ uint32_t maxLayerZ;
status_t result;
public:
MessageCaptureScreen(SurfaceFlinger* flinger, DisplayID dpy,
sp<IMemoryHeap>* heap, uint32_t* w, uint32_t* h, PixelFormat* f,
- uint32_t sw, uint32_t sh)
+ uint32_t sw, uint32_t sh,
+ uint32_t minLayerZ, uint32_t maxLayerZ)
: flinger(flinger), dpy(dpy),
- heap(heap), w(w), h(h), f(f), sw(sw), sh(sh), result(PERMISSION_DENIED)
+ heap(heap), w(w), h(h), f(f), sw(sw), sh(sh),
+ minLayerZ(minLayerZ), maxLayerZ(maxLayerZ),
+ result(PERMISSION_DENIED)
{
}
status_t getResult() const {
@@ -2249,14 +2289,14 @@ status_t SurfaceFlinger::captureScreen(DisplayID dpy,
return true;
result = flinger->captureScreenImplLocked(dpy,
- heap, w, h, f, sw, sh);
+ heap, w, h, f, sw, sh, minLayerZ, maxLayerZ);
return true;
}
};
sp<MessageBase> msg = new MessageCaptureScreen(this,
- dpy, heap, width, height, format, sw, sh);
+ dpy, heap, width, height, format, sw, sh, minLayerZ, maxLayerZ);
status_t res = postMessageSync(msg);
if (res == NO_ERROR) {
res = static_cast<MessageCaptureScreen*>( msg.get() )->getResult();
@@ -2406,12 +2446,15 @@ ssize_t UserClient::getTokenForSurface(const sp<ISurface>& sur) const
{
int32_t name = NAME_NOT_FOUND;
sp<Layer> layer(mFlinger->getLayer(sur));
- if (layer == 0) return name;
+ if (layer == 0) {
+ return name;
+ }
// if this layer already has a token, just return it
name = layer->getToken();
- if ((name >= 0) && (layer->getClient() == this))
+ if ((name >= 0) && (layer->getClient() == this)) {
return name;
+ }
name = 0;
do {
@@ -2452,6 +2495,39 @@ status_t UserClient::setState(int32_t count, const layer_state_t* states) {
// ---------------------------------------------------------------------------
+GraphicBufferAlloc::GraphicBufferAlloc() {}
+
+GraphicBufferAlloc::~GraphicBufferAlloc() {}
+
+sp<GraphicBuffer> GraphicBufferAlloc::createGraphicBuffer(uint32_t w, uint32_t h,
+ PixelFormat format, uint32_t usage) {
+ sp<GraphicBuffer> graphicBuffer(new GraphicBuffer(w, h, format, usage));
+ status_t err = graphicBuffer->initCheck();
+ if (err != 0) {
+ LOGE("createGraphicBuffer: init check failed: %d", err);
+ return 0;
+ } else if (graphicBuffer->handle == 0) {
+ LOGE("createGraphicBuffer: unable to create GraphicBuffer");
+ return 0;
+ }
+ Mutex::Autolock _l(mLock);
+ mBuffers.add(graphicBuffer);
+ return graphicBuffer;
+}
+
+void GraphicBufferAlloc::freeAllGraphicBuffersExcept(int bufIdx) {
+ Mutex::Autolock _l(mLock);
+ if (0 <= bufIdx && bufIdx < mBuffers.size()) {
+ sp<GraphicBuffer> b(mBuffers[bufIdx]);
+ mBuffers.clear();
+ mBuffers.add(b);
+ } else {
+ mBuffers.clear();
+ }
+}
+
+// ---------------------------------------------------------------------------
+
GraphicPlane::GraphicPlane()
: mHw(0)
{
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index a023347..6dd91ac 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -34,6 +34,7 @@
#include <ui/PixelFormat.h>
#include <surfaceflinger/ISurfaceComposer.h>
#include <surfaceflinger/ISurfaceComposerClient.h>
+#include <surfaceflinger/IGraphicBufferAlloc.h>
#include "Barrier.h"
#include "Layer.h"
@@ -48,9 +49,7 @@ class Client;
class DisplayHardware;
class FreezeLock;
class Layer;
-class LayerBlur;
class LayerDim;
-class LayerBuffer;
#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true ))
#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false ))
@@ -121,6 +120,21 @@ private:
sp<SurfaceFlinger> mFlinger;
};
+class GraphicBufferAlloc : public BnGraphicBufferAlloc
+{
+public:
+ GraphicBufferAlloc();
+ virtual ~GraphicBufferAlloc();
+
+ virtual sp<GraphicBuffer> createGraphicBuffer(uint32_t w, uint32_t h,
+ PixelFormat format, uint32_t usage);
+ virtual void freeAllGraphicBuffersExcept(int bufIdx);
+
+private:
+ Vector<sp<GraphicBuffer> > mBuffers;
+ Mutex mLock;
+};
+
// ---------------------------------------------------------------------------
class GraphicPlane
@@ -186,6 +200,7 @@ public:
// ISurfaceComposer interface
virtual sp<ISurfaceComposerClient> createConnection();
virtual sp<ISurfaceComposerClient> createClientConnection();
+ virtual sp<IGraphicBufferAlloc> createGraphicBufferAlloc();
virtual sp<IMemoryHeap> getCblk() const;
virtual void bootFinished();
virtual void openGlobalTransaction();
@@ -194,35 +209,32 @@ public:
virtual status_t unfreezeDisplay(DisplayID dpy, uint32_t flags);
virtual int setOrientation(DisplayID dpy, int orientation, uint32_t flags);
virtual void signal() const;
- virtual status_t captureScreen(DisplayID dpy,
- sp<IMemoryHeap>* heap,
- uint32_t* width,
- uint32_t* height,
- PixelFormat* format,
- uint32_t reqWidth,
- uint32_t reqHeight);
+
+ virtual status_t captureScreen(DisplayID dpy,
+ sp<IMemoryHeap>* heap,
+ uint32_t* width, uint32_t* height,
+ PixelFormat* format, uint32_t reqWidth, uint32_t reqHeight,
+ uint32_t minLayerZ, uint32_t maxLayerZ);
+
virtual status_t turnElectronBeamOff(int32_t mode);
virtual status_t turnElectronBeamOn(int32_t mode);
void screenReleased(DisplayID dpy);
void screenAcquired(DisplayID dpy);
- overlay_control_device_t* getOverlayEngine() const;
-
status_t removeLayer(const sp<LayerBase>& layer);
status_t addLayer(const sp<LayerBase>& layer);
status_t invalidateLayerVisibility(const sp<LayerBase>& layer);
+ void invalidateHwcGeometry();
sp<Layer> getLayer(const sp<ISurface>& sur) const;
private:
friend class Client;
friend class LayerBase;
- friend class LayerBuffer;
friend class LayerBaseClient;
friend class LayerBaseClient::Surface;
friend class Layer;
- friend class LayerBlur;
friend class LayerDim;
sp<ISurface> createSurface(const sp<Client>& client,
@@ -236,18 +248,10 @@ private:
uint32_t w, uint32_t h, uint32_t flags,
PixelFormat& format);
- sp<LayerBlur> createBlurSurface(
- const sp<Client>& client, DisplayID display,
- uint32_t w, uint32_t h, uint32_t flags);
-
sp<LayerDim> createDimSurface(
const sp<Client>& client, DisplayID display,
uint32_t w, uint32_t h, uint32_t flags);
- sp<LayerBuffer> createPushBuffersSurface(
- const sp<Client>& client, DisplayID display,
- uint32_t w, uint32_t h, uint32_t flags);
-
status_t removeSurface(const sp<Client>& client, SurfaceID sid);
status_t destroySurface(const sp<LayerBaseClient>& layer);
status_t setClientState(const sp<Client>& client,
@@ -306,8 +310,8 @@ private:
void handlePageFlip();
bool lockPageFlip(const LayerVector& currentLayers);
void unlockPageFlip(const LayerVector& currentLayers);
+ void handleWorkList();
void handleRepaint();
- bool handleBypassLayer();
void postFramebuffer();
void composeSurfaces(const Region& dirty);
@@ -322,12 +326,12 @@ private:
uint32_t setTransactionFlags(uint32_t flags);
void commitTransaction();
- void setBypassLayer(const sp<LayerBase>& layer);
status_t captureScreenImplLocked(DisplayID dpy,
sp<IMemoryHeap>* heap,
uint32_t* width, uint32_t* height, PixelFormat* format,
- uint32_t reqWidth = 0, uint32_t reqHeight = 0);
+ uint32_t reqWidth, uint32_t reqHeight,
+ uint32_t minLayerZ, uint32_t maxLayerZ);
status_t turnElectronBeamOffImplLocked(int32_t mode);
status_t turnElectronBeamOnImplLocked(int32_t mode);
@@ -395,18 +399,19 @@ private:
Region mInvalidRegion;
Region mWormholeRegion;
bool mVisibleRegionsDirty;
+ bool mHwWorkListDirty;
bool mDeferReleaseConsole;
bool mFreezeDisplay;
int32_t mElectronBeamAnimationMode;
int32_t mFreezeCount;
nsecs_t mFreezeDisplayTime;
Vector< sp<LayerBase> > mVisibleLayersSortedByZ;
- wp<Layer> mBypassLayer;
// don't use a lock for these, we don't care
int mDebugRegion;
int mDebugBackground;
+ int mDebugDisableHWC;
volatile nsecs_t mDebugInSwapBuffers;
nsecs_t mLastSwapBufferTime;
volatile nsecs_t mDebugInTransaction;
diff --git a/services/surfaceflinger/tests/overlays/Android.mk b/services/surfaceflinger/tests/overlays/Android.mk
deleted file mode 100644
index 592b601..0000000
--- a/services/surfaceflinger/tests/overlays/Android.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
- overlays.cpp
-
-LOCAL_SHARED_LIBRARIES := \
- libcutils \
- libutils \
- libui \
- libsurfaceflinger_client
-
-LOCAL_MODULE:= test-overlays
-
-LOCAL_MODULE_TAGS := tests
-
-include $(BUILD_EXECUTABLE)
diff --git a/services/surfaceflinger/tests/overlays/overlays.cpp b/services/surfaceflinger/tests/overlays/overlays.cpp
deleted file mode 100644
index c248a61..0000000
--- a/services/surfaceflinger/tests/overlays/overlays.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-#include <binder/IPCThreadState.h>
-#include <binder/ProcessState.h>
-#include <binder/IServiceManager.h>
-#include <utils/Log.h>
-
-#include <ui/Overlay.h>
-
-#include <surfaceflinger/Surface.h>
-#include <surfaceflinger/ISurface.h>
-#include <surfaceflinger/SurfaceComposerClient.h>
-
-using namespace android;
-
-namespace android {
-class Test {
-public:
- static const sp<ISurface>& getISurface(const sp<Surface>& s) {
- return s->getISurface();
- }
-};
-};
-
-int main(int argc, char** argv)
-{
- // set up the thread-pool
- sp<ProcessState> proc(ProcessState::self());
- ProcessState::self()->startThreadPool();
-
- // create a client to surfaceflinger
- sp<SurfaceComposerClient> client = new SurfaceComposerClient();
-
- // create pushbuffer surface
- sp<Surface> surface = client->createSurface(getpid(), 0, 320, 240,
- PIXEL_FORMAT_UNKNOWN, ISurfaceComposer::ePushBuffers);
-
- // get to the isurface
- sp<ISurface> isurface = Test::getISurface(surface);
- printf("isurface = %p\n", isurface.get());
-
- // now request an overlay
- sp<OverlayRef> ref = isurface->createOverlay(320, 240, PIXEL_FORMAT_RGB_565);
- sp<Overlay> overlay = new Overlay(ref);
-
-
- /*
- * here we can use the overlay API
- */
-
- overlay_buffer_t buffer;
- overlay->dequeueBuffer(&buffer);
- printf("buffer = %p\n", buffer);
-
- void* address = overlay->getBufferAddress(buffer);
- printf("address = %p\n", address);
-
- overlay->queueBuffer(buffer);
-
- return 0;
-}
diff --git a/services/surfaceflinger/tests/resize/resize.cpp b/services/surfaceflinger/tests/resize/resize.cpp
index 127cca3..99f4b4f 100644
--- a/services/surfaceflinger/tests/resize/resize.cpp
+++ b/services/surfaceflinger/tests/resize/resize.cpp
@@ -10,8 +10,6 @@
#include <surfaceflinger/ISurface.h>
#include <surfaceflinger/SurfaceComposerClient.h>
-#include <ui/Overlay.h>
-
using namespace android;
namespace android {
@@ -32,7 +30,6 @@ int main(int argc, char** argv)
// create a client to surfaceflinger
sp<SurfaceComposerClient> client = new SurfaceComposerClient();
- // create pushbuffer surface
sp<Surface> surface = client->createSurface(getpid(), 0, 160, 240,
PIXEL_FORMAT_RGB_565);
diff --git a/services/surfaceflinger/tests/surface/surface.cpp b/services/surfaceflinger/tests/surface/surface.cpp
index b4de4b4..194fbb6 100644
--- a/services/surfaceflinger/tests/surface/surface.cpp
+++ b/services/surfaceflinger/tests/surface/surface.cpp
@@ -10,8 +10,6 @@
#include <surfaceflinger/ISurface.h>
#include <surfaceflinger/SurfaceComposerClient.h>
-#include <ui/Overlay.h>
-
using namespace android;
int main(int argc, char** argv)
@@ -23,7 +21,6 @@ int main(int argc, char** argv)
// create a client to surfaceflinger
sp<SurfaceComposerClient> client = new SurfaceComposerClient();
- // create pushbuffer surface
sp<SurfaceControl> surfaceControl = client->createSurface(
getpid(), 0, 160, 240, PIXEL_FORMAT_RGB_565);
client->openTransaction();
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index b07a10b..186b349 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -7,8 +7,10 @@ LOCAL_MODULE_TAGS := tests
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := easymocklib
LOCAL_JAVA_LIBRARIES := android.test.runner services
+
LOCAL_PACKAGE_NAME := FrameworksServicesTests
LOCAL_CERTIFICATE := platform
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 5ce109f..f115f42 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -23,6 +23,19 @@
<application>
<uses-library android:name="android.test.runner" />
+
+ <service android:name="com.android.server.AccessibilityManagerServiceTest$MyFirstMockAccessibilityService">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService"/>
+ </intent-filter>
+ </service>
+
+ <service android:name="com.android.server.AccessibilityManagerServiceTest$MySecondMockAccessibilityService">
+ <intent-filter>
+ <action android:name="android.accessibilityservice.AccessibilityService"/>
+ </intent-filter>
+ </service>
+
</application>
<instrumentation
diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java
new file mode 100644
index 0000000..2bc6825
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java
@@ -0,0 +1,725 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.IAccessibilityManager;
+import android.view.accessibility.IAccessibilityManagerClient;
+
+/**
+ * This test exercises the
+ * {@link com.android.server.AccessibilityManagerService} by mocking the
+ * {@link android.view.accessibility.AccessibilityManager} which talks to to the
+ * service. The service itself is interacting with the platform. Note: Testing
+ * the service in full isolation would require significant amount of work for
+ * mocking all system interactions. It would also require a lot of mocking code.
+ */
+public class AccessibilityManagerServiceTest extends AndroidTestCase {
+
+ /**
+ * Timeout required for pending Binder calls or event processing to
+ * complete.
+ */
+ private static final long TIMEOUT_BINDER_CALL = 100;
+
+ /**
+ * Timeout in which we are waiting for the system to start the mock
+ * accessibility services.
+ */
+ private static final long TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES = 300;
+
+ /**
+ * Timeout used for testing that a service is notified only upon a
+ * notification timeout.
+ */
+ private static final long TIMEOUT_TEST_NOTIFICATION_TIMEOUT = 300;
+
+ /**
+ * The interface used to talk to the tested service.
+ */
+ private IAccessibilityManager mManagerService;
+
+ @Override
+ public void setContext(Context context) {
+ super.setContext(context);
+ if (MyFirstMockAccessibilityService.sComponentName == null) {
+ MyFirstMockAccessibilityService.sComponentName = new ComponentName(
+ context.getPackageName(), MyFirstMockAccessibilityService.class.getName())
+ .flattenToShortString();
+ }
+ if (MySecondMockAccessibilityService.sComponentName == null) {
+ MySecondMockAccessibilityService.sComponentName = new ComponentName(
+ context.getPackageName(), MySecondMockAccessibilityService.class.getName())
+ .flattenToShortString();
+ }
+ }
+
+ /**
+ * Creates a new instance.
+ */
+ public AccessibilityManagerServiceTest() {
+ IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
+ mManagerService = IAccessibilityManager.Stub.asInterface(iBinder);
+ }
+
+ @LargeTest
+ public void testAddClient_AccessibilityDisabledThenEnabled() throws Exception {
+ // make sure accessibility is disabled
+ ensureAccessibilityEnabled(mContext, false);
+
+ // create a client mock instance
+ MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient();
+
+ // invoke the method under test
+ boolean enabledAccessibilityDisabled = mManagerService.addClient(mockClient);
+
+ // check expected result
+ assertFalse("The client must be disabled since accessibility is disabled.",
+ enabledAccessibilityDisabled);
+
+ // enable accessibility
+ ensureAccessibilityEnabled(mContext, true);
+
+ // invoke the method under test
+ boolean enabledAccessibilityEnabled = mManagerService.addClient(mockClient);
+
+ // check expected result
+ assertTrue("The client must be enabled since accessibility is enabled.",
+ enabledAccessibilityEnabled);
+ }
+
+ @LargeTest
+ public void testAddClient_AccessibilityEnabledThenDisabled() throws Exception {
+ // enable accessibility before registering the client
+ ensureAccessibilityEnabled(mContext, true);
+
+ // create a client mock instance
+ MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient();
+
+ // invoke the method under test
+ boolean enabledAccessibilityEnabled = mManagerService.addClient(mockClient);
+
+ // check expected result
+ assertTrue("The client must be enabled since accessibility is enabled.",
+ enabledAccessibilityEnabled);
+
+ // disable accessibility
+ ensureAccessibilityEnabled(mContext, false);
+
+ // invoke the method under test
+ boolean enabledAccessibilityDisabled = mManagerService.addClient(mockClient);
+
+ // check expected result
+ assertFalse("The client must be disabled since accessibility is disabled.",
+ enabledAccessibilityDisabled);
+ }
+
+ @LargeTest
+ public void testGetAccessibilityServicesList() throws Exception {
+ boolean firstMockServiceInstalled = false;
+ boolean secondMockServiceInstalled = false;
+
+ String packageName = getContext().getPackageName();
+ String firstMockServiceClassName = MyFirstMockAccessibilityService.class.getName();
+ String secondMockServiceClassName = MySecondMockAccessibilityService.class.getName();
+
+ // look for the two mock services
+ for (ServiceInfo serviceInfo : mManagerService.getAccessibilityServiceList()) {
+ if (packageName.equals(serviceInfo.packageName)) {
+ if (firstMockServiceClassName.equals(serviceInfo.name)) {
+ firstMockServiceInstalled = true;
+ } else if (secondMockServiceClassName.equals(serviceInfo.name)) {
+ secondMockServiceInstalled = true;
+ }
+ }
+ }
+
+ // check expected result
+ assertTrue("First mock service must be installed", firstMockServiceInstalled);
+ assertTrue("Second mock service must be installed", secondMockServiceInstalled);
+ }
+
+ @LargeTest
+ public void testSendAccessibilityEvent_OneService_MatchingPackageAndEventType()
+ throws Exception {
+ // set the accessibility setting value
+ ensureAccessibilityEnabled(mContext, true);
+
+ // enable the mock accessibility service
+ ensureOnlyMockServicesEnabled(mContext, true, false);
+
+ // configure the mock service
+ MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance;
+ service.setServiceInfo(MockAccessibilityService.createDefaultInfo());
+
+ // wait for the binder call to #setService to complete
+ Thread.sleep(TIMEOUT_BINDER_CALL);
+
+ // create and populate an event to be sent
+ AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+ fullyPopulateDefaultAccessibilityEvent(sentEvent);
+
+ // set expectations
+ service.expectEvent(sentEvent);
+ service.replay();
+
+ // send the event
+ mManagerService.sendAccessibilityEvent(sentEvent);
+
+ // verify if all expected methods have been called
+ assertMockServiceVerifiedWithinTimeout(service);
+ }
+
+ @LargeTest
+ public void testSendAccessibilityEvent_OneService_NotMatchingPackage() throws Exception {
+ // set the accessibility setting value
+ ensureAccessibilityEnabled(mContext, true);
+
+ // enable the mock accessibility service
+ ensureOnlyMockServicesEnabled(mContext, true, false);
+
+ // configure the mock service
+ MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance;
+ service.setServiceInfo(MockAccessibilityService.createDefaultInfo());
+
+ // wait for the binder call to #setService to complete
+ Thread.sleep(TIMEOUT_BINDER_CALL);
+
+ // create and populate an event to be sent
+ AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+ fullyPopulateDefaultAccessibilityEvent(sentEvent);
+ sentEvent.setPackageName("no.service.registered.for.this.package");
+
+ // set expectations
+ service.replay();
+
+ // send the event
+ mManagerService.sendAccessibilityEvent(sentEvent);
+
+ // verify if all expected methods have been called
+ assertMockServiceVerifiedWithinTimeout(service);
+ }
+
+ @LargeTest
+ public void testSendAccessibilityEvent_OneService_NotMatchingEventType() throws Exception {
+ // set the accessibility setting value
+ ensureAccessibilityEnabled(mContext, true);
+
+ // enable the mock accessibility service
+ ensureOnlyMockServicesEnabled(mContext, true, false);
+
+ // configure the mock service
+ MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance;
+ service.setServiceInfo(MockAccessibilityService.createDefaultInfo());
+
+ // wait for the binder call to #setService to complete
+ Thread.sleep(TIMEOUT_BINDER_CALL);
+
+ // create and populate an event to be sent
+ AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+ fullyPopulateDefaultAccessibilityEvent(sentEvent);
+ sentEvent.setEventType(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
+
+ // set expectations
+ service.replay();
+
+ // send the event
+ mManagerService.sendAccessibilityEvent(sentEvent);
+
+ // verify if all expected methods have been called
+ assertMockServiceVerifiedWithinTimeout(service);
+ }
+
+ @LargeTest
+ public void testSendAccessibilityEvent_OneService_NotifivationAfterTimeout() throws Exception {
+ // set the accessibility setting value
+ ensureAccessibilityEnabled(mContext, true);
+
+ // enable the mock accessibility service
+ ensureOnlyMockServicesEnabled(mContext, true, false);
+
+ // configure the mock service
+ MockAccessibilityService service = MyFirstMockAccessibilityService.sInstance;
+ AccessibilityServiceInfo info = MockAccessibilityService.createDefaultInfo();
+ info.notificationTimeout = TIMEOUT_TEST_NOTIFICATION_TIMEOUT;
+ service.setServiceInfo(info);
+
+ // wait for the binder call to #setService to complete
+ Thread.sleep(TIMEOUT_BINDER_CALL);
+
+ // create and populate the first event to be sent
+ AccessibilityEvent firstEvent = AccessibilityEvent.obtain();
+ fullyPopulateDefaultAccessibilityEvent(firstEvent);
+
+ // create and populate the second event to be sent
+ AccessibilityEvent secondEvent = AccessibilityEvent.obtain();
+ fullyPopulateDefaultAccessibilityEvent(secondEvent);
+
+ // set expectations
+ service.expectEvent(secondEvent);
+ service.replay();
+
+ // send the events
+ mManagerService.sendAccessibilityEvent(firstEvent);
+ mManagerService.sendAccessibilityEvent(secondEvent);
+
+ // wait for #sendAccessibilityEvent to reach the backing service
+ Thread.sleep(TIMEOUT_BINDER_CALL);
+
+ try {
+ service.verify();
+ fail("No events must be dispatched before the expiration of the notification timeout.");
+ } catch (IllegalStateException ise) {
+ /* expected */
+ }
+
+ // wait for the configured notification timeout to expire
+ Thread.sleep(TIMEOUT_TEST_NOTIFICATION_TIMEOUT);
+
+ // verify if all expected methods have been called
+ assertMockServiceVerifiedWithinTimeout(service);
+ }
+
+ @LargeTest
+ public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_DiffFeedback()
+ throws Exception {
+ // set the accessibility setting value
+ ensureAccessibilityEnabled(mContext, true);
+
+ // enable the mock accessibility services
+ ensureOnlyMockServicesEnabled(mContext, true, true);
+
+ // configure the first mock service
+ MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
+ AccessibilityServiceInfo firstInfo = MockAccessibilityService.createDefaultInfo();
+ firstInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE;
+ firstService.setServiceInfo(firstInfo);
+
+ // configure the second mock service
+ MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
+ AccessibilityServiceInfo secondInfo = MockAccessibilityService.createDefaultInfo();
+ secondInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_HAPTIC;
+ secondService.setServiceInfo(secondInfo);
+
+ // wait for the binder calls to #setService to complete
+ Thread.sleep(TIMEOUT_BINDER_CALL);
+
+ // create and populate an event to be sent
+ AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+ fullyPopulateDefaultAccessibilityEvent(sentEvent);
+
+ // set expectations for the first mock service
+ firstService.expectEvent(sentEvent);
+ firstService.replay();
+
+ // set expectations for the second mock service
+ secondService.expectEvent(sentEvent);
+ secondService.replay();
+
+ // send the event
+ mManagerService.sendAccessibilityEvent(sentEvent);
+
+ // verify if all expected methods have been called
+ assertMockServiceVerifiedWithinTimeout(firstService);
+ assertMockServiceVerifiedWithinTimeout(secondService);
+ }
+
+ @LargeTest
+ public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType()
+ throws Exception {
+ // set the accessibility setting value
+ ensureAccessibilityEnabled(mContext, true);
+
+ // enable the mock accessibility services
+ ensureOnlyMockServicesEnabled(mContext, true, true);
+
+ // configure the first mock service
+ MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
+ firstService.setServiceInfo(MockAccessibilityService.createDefaultInfo());
+
+ // configure the second mock service
+ MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
+ secondService.setServiceInfo(MockAccessibilityService.createDefaultInfo());
+
+ // wait for the binder calls to #setService to complete
+ Thread.sleep(TIMEOUT_BINDER_CALL);
+
+ // create and populate an event to be sent
+ AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+ fullyPopulateDefaultAccessibilityEvent(sentEvent);
+
+ // set expectations for the first mock service
+ firstService.expectEvent(sentEvent);
+ firstService.replay();
+
+ // set expectations for the second mock service
+ secondService.replay();
+
+ // send the event
+ mManagerService.sendAccessibilityEvent(sentEvent);
+
+ // verify if all expected methods have been called
+ assertMockServiceVerifiedWithinTimeout(firstService);
+ assertMockServiceVerifiedWithinTimeout(secondService);
+ }
+
+ @LargeTest
+ public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_OneDefault()
+ throws Exception {
+ // set the accessibility setting value
+ ensureAccessibilityEnabled(mContext, true);
+
+ // enable the mock accessibility services
+ ensureOnlyMockServicesEnabled(mContext, true, true);
+
+ // configure the first mock service
+ MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
+ AccessibilityServiceInfo firstInfo = MyFirstMockAccessibilityService.createDefaultInfo();
+ firstInfo.flags = AccessibilityServiceInfo.DEFAULT;
+ firstService.setServiceInfo(firstInfo);
+
+ // configure the second mock service
+ MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
+ secondService.setServiceInfo(MySecondMockAccessibilityService.createDefaultInfo());
+
+ // wait for the binder calls to #setService to complete
+ Thread.sleep(TIMEOUT_BINDER_CALL);
+
+ // create and populate an event to be sent
+ AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+ fullyPopulateDefaultAccessibilityEvent(sentEvent);
+
+ // set expectations for the first mock service
+ firstService.replay();
+
+ // set expectations for the second mock service
+ secondService.expectEvent(sentEvent);
+ secondService.replay();
+
+ // send the event
+ mManagerService.sendAccessibilityEvent(sentEvent);
+
+ // verify if all expected methods have been called
+ assertMockServiceVerifiedWithinTimeout(firstService);
+ assertMockServiceVerifiedWithinTimeout(secondService);
+ }
+
+ @LargeTest
+ public void testSendAccessibilityEvent_TwoServices_MatchingPackageAndEventType_TwoDefault()
+ throws Exception {
+ // set the accessibility setting value
+ ensureAccessibilityEnabled(mContext, true);
+
+ // enable the mock accessibility services
+ ensureOnlyMockServicesEnabled(mContext, true, true);
+
+ // configure the first mock service
+ MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
+ AccessibilityServiceInfo firstInfo = MyFirstMockAccessibilityService.createDefaultInfo();
+ firstInfo.flags = AccessibilityServiceInfo.DEFAULT;
+ firstService.setServiceInfo(firstInfo);
+
+ // configure the second mock service
+ MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
+ AccessibilityServiceInfo secondInfo = MyFirstMockAccessibilityService.createDefaultInfo();
+ secondInfo.flags = AccessibilityServiceInfo.DEFAULT;
+ secondService.setServiceInfo(firstInfo);
+
+ // wait for the binder calls to #setService to complete
+ Thread.sleep(TIMEOUT_BINDER_CALL);
+
+ // create and populate an event to be sent
+ AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+ fullyPopulateDefaultAccessibilityEvent(sentEvent);
+
+ // set expectations for the first mock service
+ firstService.expectEvent(sentEvent);
+ firstService.replay();
+
+ // set expectations for the second mock service
+ secondService.replay();
+
+ // send the event
+ mManagerService.sendAccessibilityEvent(sentEvent);
+
+ // verify if all expected methods have been called
+ assertMockServiceVerifiedWithinTimeout(firstService);
+ assertMockServiceVerifiedWithinTimeout(secondService);
+ }
+
+ @LargeTest
+ public void testInterrupt() throws Exception {
+ // set the accessibility setting value
+ ensureAccessibilityEnabled(mContext, true);
+
+ // enable the mock accessibility services
+ ensureOnlyMockServicesEnabled(mContext, true, true);
+
+ // configure the first mock service
+ MockAccessibilityService firstService = MyFirstMockAccessibilityService.sInstance;
+ firstService.setServiceInfo(MockAccessibilityService.createDefaultInfo());
+
+ // configure the second mock service
+ MockAccessibilityService secondService = MySecondMockAccessibilityService.sInstance;
+ secondService.setServiceInfo(MockAccessibilityService.createDefaultInfo());
+
+ // wait for the binder calls to #setService to complete
+ Thread.sleep(TIMEOUT_BINDER_CALL);
+
+ // set expectations for the first mock service
+ firstService.expectInterrupt();
+ firstService.replay();
+
+ // set expectations for the second mock service
+ secondService.expectInterrupt();
+ secondService.replay();
+
+ // call the method under test
+ mManagerService.interrupt();
+
+ // verify if all expected methods have been called
+ assertMockServiceVerifiedWithinTimeout(firstService);
+ assertMockServiceVerifiedWithinTimeout(secondService);
+ }
+
+ /**
+ * Fully populates the {@link AccessibilityEvent} to marshal.
+ *
+ * @param sentEvent The event to populate.
+ */
+ private void fullyPopulateDefaultAccessibilityEvent(AccessibilityEvent sentEvent) {
+ sentEvent.setAddedCount(1);
+ sentEvent.setBeforeText("BeforeText");
+ sentEvent.setChecked(true);
+ sentEvent.setClassName("foo.bar.baz.Class");
+ sentEvent.setContentDescription("ContentDescription");
+ sentEvent.setCurrentItemIndex(1);
+ sentEvent.setEnabled(true);
+ sentEvent.setEventType(AccessibilityEvent.TYPE_VIEW_CLICKED);
+ sentEvent.setEventTime(1000);
+ sentEvent.setFromIndex(1);
+ sentEvent.setFullScreen(true);
+ sentEvent.setItemCount(1);
+ sentEvent.setPackageName("foo.bar.baz");
+ sentEvent.setParcelableData(Message.obtain(null, 1, null));
+ sentEvent.setPassword(true);
+ sentEvent.setRemovedCount(1);
+ }
+
+ /**
+ * This class is a mock {@link IAccessibilityManagerClient}.
+ */
+ public class MyMockAccessibilityManagerClient extends IAccessibilityManagerClient.Stub {
+ boolean mIsEnabled;
+
+ public void setEnabled(boolean enabled) {
+ mIsEnabled = enabled;
+ }
+ }
+
+ /**
+ * Ensures accessibility is in a given state by writing the state to the
+ * settings and waiting until the accessibility manager service pick it up.
+ *
+ * @param context A context handle to access the settings.
+ * @param enabled The accessibility state to write to the settings.
+ * @throws Exception If any error occurs.
+ */
+ private void ensureAccessibilityEnabled(Context context, boolean enabled) throws Exception {
+ boolean isEnabled = (Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1 ? true : false);
+
+ if (isEnabled == enabled) {
+ return;
+ }
+
+ Settings.Secure.putInt(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED,
+ enabled ? 1 : 0);
+
+ // wait the accessibility manager service to pick the change up
+ Thread.sleep(TIMEOUT_BINDER_CALL);
+ }
+
+ /**
+ * Ensures the only {@link MockAccessibilityService}s with given component
+ * names are enabled by writing to the system settings and waiting until the
+ * accessibility manager service picks that up or the
+ * {@link #TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES} is exceeded.
+ *
+ * @param context A context handle to access the settings.
+ * @param firstMockServiceEnabled If the first mock accessibility service is enabled.
+ * @param secondMockServiceEnabled If the second mock accessibility service is enabled.
+ * @throws IllegalStateException If some of the requested for enabling mock services
+ * is not properly started.
+ * @throws Exception Exception If any error occurs.
+ */
+ private void ensureOnlyMockServicesEnabled(Context context, boolean firstMockServiceEnabled,
+ boolean secondMockServiceEnabled) throws Exception {
+ String enabledServices = Settings.Secure.getString(context.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+
+ StringBuilder servicesToEnable = new StringBuilder();
+ if (firstMockServiceEnabled) {
+ servicesToEnable.append(MyFirstMockAccessibilityService.sComponentName).append(":");
+ }
+ if (secondMockServiceEnabled) {
+ servicesToEnable.append(MySecondMockAccessibilityService.sComponentName).append(":");
+ }
+
+ if (servicesToEnable.equals(enabledServices)) {
+ return;
+ }
+
+ Settings.Secure.putString(context.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, servicesToEnable.toString());
+
+ // we have enabled the services of interest and need to wait until they
+ // are instantiated and started (if needed) and the system binds to them
+ boolean firstMockServiceOK = false;
+ boolean secondMockServiceOK = false;
+ long start = SystemClock.uptimeMillis();
+ long pollingInterval = TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES / 6;
+
+ while (SystemClock.uptimeMillis() - start < TIMEOUT_START_MOCK_ACCESSIBILITY_SERVICES) {
+ firstMockServiceOK = !firstMockServiceEnabled
+ || (MyFirstMockAccessibilityService.sInstance != null
+ && MyFirstMockAccessibilityService.sInstance.isSystemBoundAsClient());
+
+ secondMockServiceOK = !secondMockServiceEnabled
+ || (MySecondMockAccessibilityService.sInstance != null
+ && MySecondMockAccessibilityService.sInstance.isSystemBoundAsClient());
+
+ if (firstMockServiceOK && secondMockServiceOK) {
+ return;
+ }
+
+ Thread.sleep(pollingInterval);
+ }
+
+ StringBuilder message = new StringBuilder();
+ message.append("Mock accessibility services not started or system not bound as a client: ");
+ if (!firstMockServiceOK) {
+ message.append(MyFirstMockAccessibilityService.sComponentName);
+ message.append(" ");
+ }
+ if (!secondMockServiceOK) {
+ message.append(MySecondMockAccessibilityService.sComponentName);
+ }
+ throw new IllegalStateException(message.toString());
+ }
+
+ /**
+ * Asserts the the mock accessibility service has been successfully verified
+ * (which is it has received the expected method calls with expected
+ * arguments) within the {@link #TIMEOUT_BINDER_CALL}. The verified state is
+ * checked by polling upon small intervals.
+ *
+ * @param service The service to verify.
+ * @throws Exception If the verification has failed with exception after the
+ * {@link #TIMEOUT_BINDER_CALL}.
+ */
+ private void assertMockServiceVerifiedWithinTimeout(MockAccessibilityService service)
+ throws Exception {
+ Exception lastVerifyException = null;
+ long beginTime = SystemClock.uptimeMillis();
+ long pollTmeout = TIMEOUT_BINDER_CALL / 5;
+
+ // poll until the timeout has elapsed
+ while (SystemClock.uptimeMillis() - beginTime < TIMEOUT_BINDER_CALL) {
+ // sleep first since immediate call will always fail
+ try {
+ Thread.sleep(pollTmeout);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ // poll for verification and if this fails save the exception and
+ // keep polling
+ try {
+ service.verify();
+ // reset so it does not accept more events
+ service.reset();
+ return;
+ } catch (Exception e) {
+ lastVerifyException = e;
+ }
+ }
+
+ // reset, we have already failed
+ service.reset();
+
+ // always not null
+ throw lastVerifyException;
+ }
+
+ /**
+ * This class is the first mock {@link AccessibilityService}.
+ */
+ public static class MyFirstMockAccessibilityService extends MockAccessibilityService {
+
+ /**
+ * The service {@link ComponentName} flattened as a string.
+ */
+ static String sComponentName;
+
+ /**
+ * Handle to the service instance.
+ */
+ static MyFirstMockAccessibilityService sInstance;
+
+ /**
+ * Creates a new instance.
+ */
+ public MyFirstMockAccessibilityService() {
+ sInstance = this;
+ }
+ }
+
+ /**
+ * This class is the first mock {@link AccessibilityService}.
+ */
+ public static class MySecondMockAccessibilityService extends MockAccessibilityService {
+
+ /**
+ * The service {@link ComponentName} flattened as a string.
+ */
+ static String sComponentName;
+
+ /**
+ * Handle to the service instance.
+ */
+ static MySecondMockAccessibilityService sInstance;
+
+ /**
+ * Creates a new instance.
+ */
+ public MySecondMockAccessibilityService() {
+ sInstance = this;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java
new file mode 100644
index 0000000..38fed22
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static org.easymock.EasyMock.createStrictMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reportMatcher;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+
+import org.easymock.IArgumentMatcher;
+
+import android.content.pm.ServiceInfo;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManager;
+import android.view.accessibility.IAccessibilityManagerClient;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for the AccessibilityManager which mocking the backing service.
+ */
+public class AccessibilityManagerTest extends AndroidTestCase {
+
+ /**
+ * Timeout required for pending Binder calls or event processing to
+ * complete.
+ */
+ public static final long TIMEOUT_BINDER_CALL = 50;
+
+ /**
+ * The reusable mock {@link IAccessibilityManager}.
+ */
+ private final IAccessibilityManager mMockServiceInterface =
+ createStrictMock(IAccessibilityManager.class);
+
+ @Override
+ public void setUp() throws Exception {
+ reset(mMockServiceInterface);
+ }
+
+ @MediumTest
+ public void testGetAccessibilityServiceList() throws Exception {
+ // create a list of installed accessibility services the mock service returns
+ List<ServiceInfo> expectedServices = new ArrayList<ServiceInfo>();
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.name = "TestServiceInfoName";
+ expectedServices.add(serviceInfo);
+
+ // configure the mock service behavior
+ IAccessibilityManager mockServiceInterface = mMockServiceInterface;
+ expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true);
+ expect(mockServiceInterface.getAccessibilityServiceList()).andReturn(expectedServices);
+ replay(mockServiceInterface);
+
+ // invoke the method under test
+ AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface);
+ List<ServiceInfo> receivedServices = manager.getAccessibilityServiceList();
+
+ // check expected result (list equals() compares it contents as well)
+ assertEquals("All expected services must be returned", receivedServices, expectedServices);
+
+ // verify the mock service was properly called
+ verify(mockServiceInterface);
+ }
+
+ @MediumTest
+ public void testInterrupt() throws Exception {
+ // configure the mock service behavior
+ IAccessibilityManager mockServiceInterface = mMockServiceInterface;
+ expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true);
+ mockServiceInterface.interrupt();
+ replay(mockServiceInterface);
+
+ // invoke the method under test
+ AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface);
+ manager.interrupt();
+
+ // verify the mock service was properly called
+ verify(mockServiceInterface);
+ }
+
+ @LargeTest
+ public void testIsEnabled() throws Exception {
+ // configure the mock service behavior
+ IAccessibilityManager mockServiceInterface = mMockServiceInterface;
+ expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true);
+ replay(mockServiceInterface);
+
+ // invoke the method under test
+ AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface);
+ boolean isEnabledServiceEnabled = manager.isEnabled();
+
+ // check expected result
+ assertTrue("Must be enabled since the mock service is enabled", isEnabledServiceEnabled);
+
+ // disable accessibility
+ manager.getClient().setEnabled(false);
+
+ // wait for the asynchronous IBinder call to complete
+ Thread.sleep(TIMEOUT_BINDER_CALL);
+
+ // invoke the method under test
+ boolean isEnabledServcieDisabled = manager.isEnabled();
+
+ // check expected result
+ assertFalse("Must be disabled since the mock service is disabled",
+ isEnabledServcieDisabled);
+
+ // verify the mock service was properly called
+ verify(mockServiceInterface);
+ }
+
+ @MediumTest
+ public void testSendAccessibilityEvent_AccessibilityEnabled() throws Exception {
+ // create an event to be dispatched
+ AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+
+ // configure the mock service behavior
+ IAccessibilityManager mockServiceInterface = mMockServiceInterface;
+ expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true);
+ expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent)))
+ .andReturn(true);
+ expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent)))
+ .andReturn(false);
+ replay(mockServiceInterface);
+
+ // invoke the method under test (manager and service in different processes)
+ AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface);
+ manager.sendAccessibilityEvent(sentEvent);
+
+ // check expected result
+ AccessibilityEvent nextEventDifferentProcesses = AccessibilityEvent.obtain();
+ assertSame("The manager and the service are in different processes, so the event must be " +
+ "recycled", sentEvent, nextEventDifferentProcesses);
+
+ // invoke the method under test (manager and service in the same process)
+ manager.sendAccessibilityEvent(sentEvent);
+
+ // check expected result
+ AccessibilityEvent nextEventSameProcess = AccessibilityEvent.obtain();
+ assertNotSame("The manager and the service are in the same process, so the event must not" +
+ "be recycled", sentEvent, nextEventSameProcess);
+
+ // verify the mock service was properly called
+ verify(mockServiceInterface);
+ }
+
+ @MediumTest
+ public void testSendAccessibilityEvent_AccessibilityDisabled() throws Exception {
+ // create an event to be dispatched
+ AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
+
+ // configure the mock service behavior
+ IAccessibilityManager mockServiceInterface = mMockServiceInterface;
+ expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(false);
+ replay(mockServiceInterface);
+
+ // invoke the method under test (accessibility disabled)
+ AccessibilityManager manager = new AccessibilityManager(mContext, mockServiceInterface);
+ try {
+ manager.sendAccessibilityEvent(sentEvent);
+ fail("No accessibility events are sent if accessibility is disabled");
+ } catch (IllegalStateException ise) {
+ // check expected result
+ assertEquals("Accessibility off. Did you forget to check that?", ise.getMessage());
+ }
+
+ // verify the mock service was properly called
+ verify(mockServiceInterface);
+ }
+
+ /**
+ * Determines if an {@link AccessibilityEvent} passed as a method argument
+ * matches expectations.
+ *
+ * @param matched The event to check.
+ * @return True if expectations are matched.
+ */
+ private static AccessibilityEvent eqAccessibilityEvent(AccessibilityEvent matched) {
+ reportMatcher(new AccessibilityEventMather(matched));
+ return null;
+ }
+
+ /**
+ * Determines if an {@link IAccessibilityManagerClient} passed as a method argument
+ * matches expectations which in this case are that any instance is accepted.
+ *
+ * @return <code>null</code>.
+ */
+ private static IAccessibilityManagerClient anyIAccessibilityManagerClient() {
+ reportMatcher(new AnyIAccessibilityManagerClientMather());
+ return null;
+ }
+
+ /**
+ * Matcher for {@link AccessibilityEvent}s.
+ */
+ private static class AccessibilityEventMather implements IArgumentMatcher {
+ private AccessibilityEvent mExpectedEvent;
+
+ public AccessibilityEventMather(AccessibilityEvent expectedEvent) {
+ mExpectedEvent = expectedEvent;
+ }
+
+ public boolean matches(Object matched) {
+ if (!(matched instanceof AccessibilityEvent)) {
+ return false;
+ }
+ AccessibilityEvent receivedEvent = (AccessibilityEvent) matched;
+ return mExpectedEvent.getEventType() == receivedEvent.getEventType();
+ }
+
+ public void appendTo(StringBuffer buffer) {
+ buffer.append("sendAccessibilityEvent()");
+ buffer.append(" with event type \"");
+ buffer.append(mExpectedEvent.getEventType());
+ buffer.append("\"");
+ }
+ }
+
+ /**
+ * Matcher for {@link IAccessibilityManagerClient}s.
+ */
+ private static class AnyIAccessibilityManagerClientMather implements IArgumentMatcher {
+ public boolean matches(Object matched) {
+ if (!(matched instanceof IAccessibilityManagerClient)) {
+ return false;
+ }
+ return true;
+ }
+
+ public void appendTo(StringBuffer buffer) {
+ buffer.append("addClient() with any IAccessibilityManagerClient");
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
new file mode 100644
index 0000000..17a1585
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.location.Country;
+import android.location.CountryListener;
+import android.location.ICountryListener;
+import android.os.RemoteException;
+import android.test.AndroidTestCase;
+
+public class CountryDetectorServiceTest extends AndroidTestCase {
+ private class CountryListenerTester extends ICountryListener.Stub {
+ private Country mCountry;
+
+ @Override
+ public void onCountryDetected(Country country) throws RemoteException {
+ mCountry = country;
+ }
+
+ public Country getCountry() {
+ return mCountry;
+ }
+
+ public boolean isNotified() {
+ return mCountry != null;
+ }
+ }
+
+ private class CountryDetectorServiceTester extends CountryDetectorService {
+
+ private CountryListener mListener;
+
+ public CountryDetectorServiceTester(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void notifyReceivers(Country country) {
+ super.notifyReceivers(country);
+ }
+
+ @Override
+ protected void setCountryListener(final CountryListener listener) {
+ mListener = listener;
+ }
+
+ public boolean isListenerSet() {
+ return mListener != null;
+ }
+ }
+
+ public void testAddRemoveListener() throws RemoteException {
+ CountryDetectorServiceTester serviceTester = new CountryDetectorServiceTester(getContext());
+ serviceTester.systemReady();
+ waitForSystemReady(serviceTester);
+ CountryListenerTester listenerTester = new CountryListenerTester();
+ serviceTester.addCountryListener(listenerTester);
+ assertTrue(serviceTester.isListenerSet());
+ serviceTester.removeCountryListener(listenerTester);
+ assertFalse(serviceTester.isListenerSet());
+ }
+
+ public void testNotifyListeners() throws RemoteException {
+ CountryDetectorServiceTester serviceTester = new CountryDetectorServiceTester(getContext());
+ CountryListenerTester listenerTesterA = new CountryListenerTester();
+ CountryListenerTester listenerTesterB = new CountryListenerTester();
+ Country country = new Country("US", Country.COUNTRY_SOURCE_NETWORK);
+ serviceTester.systemReady();
+ waitForSystemReady(serviceTester);
+ serviceTester.addCountryListener(listenerTesterA);
+ serviceTester.addCountryListener(listenerTesterB);
+ serviceTester.notifyReceivers(country);
+ assertTrue(serviceTester.isListenerSet());
+ assertTrue(listenerTesterA.isNotified());
+ assertTrue(listenerTesterB.isNotified());
+ serviceTester.removeCountryListener(listenerTesterA);
+ serviceTester.removeCountryListener(listenerTesterB);
+ assertFalse(serviceTester.isListenerSet());
+ }
+
+ private void waitForSystemReady(CountryDetectorService service) {
+ int count = 5;
+ while (count-- > 0) {
+ try {
+ Thread.sleep(500);
+ } catch (Exception e) {
+ }
+ if (service.isSystemReady()) {
+ return;
+ }
+ }
+ throw new RuntimeException("Wait System Ready timeout");
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java b/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java
new file mode 100644
index 0000000..1bc9b86
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/MockAccessibilityService.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.Intent;
+import android.os.Message;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+import junit.framework.TestCase;
+
+/**
+ * This is the base class for mock {@link AccessibilityService}s.
+ */
+public abstract class MockAccessibilityService extends AccessibilityService {
+
+ /**
+ * The event this service expects to receive.
+ */
+ private final Queue<AccessibilityEvent> mExpectedEvents = new LinkedList<AccessibilityEvent>();
+
+ /**
+ * Interruption call this service expects to receive.
+ */
+ private boolean mExpectedInterrupt;
+
+ /**
+ * Flag if the mock is currently replaying.
+ */
+ private boolean mReplaying;
+
+ /**
+ * Flag if the system is bound as a client to this service.
+ */
+ private boolean mIsSystemBoundAsClient;
+
+ /**
+ * Creates an {@link AccessibilityServiceInfo} populated with default
+ * values.
+ *
+ * @return The default info.
+ */
+ public static AccessibilityServiceInfo createDefaultInfo() {
+ AccessibilityServiceInfo defaultInfo = new AccessibilityServiceInfo();
+ defaultInfo.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED;
+ defaultInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE;
+ defaultInfo.flags = 0;
+ defaultInfo.notificationTimeout = 0;
+ defaultInfo.packageNames = new String[] {
+ "foo.bar.baz"
+ };
+
+ return defaultInfo;
+ }
+
+ /**
+ * Starts replaying the mock.
+ */
+ public void replay() {
+ mReplaying = true;
+ }
+
+ /**
+ * Verifies if all expected service methods have been called.
+ */
+ public void verify() {
+ if (!mReplaying) {
+ throw new IllegalStateException("Did you forget to call replay()");
+ }
+
+ if (mExpectedInterrupt) {
+ throw new IllegalStateException("Expected call to #interrupt() not received");
+ }
+ if (!mExpectedEvents.isEmpty()) {
+ throw new IllegalStateException("Expected a call to onAccessibilityEvent() for "
+ + "events \"" + mExpectedEvents + "\" not received");
+ }
+ }
+
+ /**
+ * Resets this instance so it can be reused.
+ */
+ public void reset() {
+ mExpectedEvents.clear();
+ mExpectedInterrupt = false;
+ mReplaying = false;
+ }
+
+ /**
+ * Sets an expected call to
+ * {@link #onAccessibilityEvent(AccessibilityEvent)} with given event as
+ * argument.
+ *
+ * @param expectedEvent The expected event argument.
+ */
+ public void expectEvent(AccessibilityEvent expectedEvent) {
+ mExpectedEvents.add(expectedEvent);
+ }
+
+ /**
+ * Sets an expected call of {@link #onInterrupt()}.
+ */
+ public void expectInterrupt() {
+ mExpectedInterrupt = true;
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent receivedEvent) {
+ if (!mReplaying) {
+ return;
+ }
+
+ if (mExpectedEvents.isEmpty()) {
+ throw new IllegalStateException("Unexpected event: " + receivedEvent);
+ }
+
+ AccessibilityEvent expectedEvent = mExpectedEvents.poll();
+ assertEqualsAccessiblityEvent(expectedEvent, receivedEvent);
+ }
+
+ @Override
+ public void onInterrupt() {
+ if (!mReplaying) {
+ return;
+ }
+
+ if (!mExpectedInterrupt) {
+ throw new IllegalStateException("Unexpected call to onInterrupt()");
+ }
+
+ mExpectedInterrupt = false;
+ }
+
+ @Override
+ protected void onServiceConnected() {
+ mIsSystemBoundAsClient = true;
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ mIsSystemBoundAsClient = false;
+ return false;
+ }
+
+ /**
+ * Returns if the system is bound as client to this service.
+ *
+ * @return True if the system is bound, false otherwise.
+ */
+ public boolean isSystemBoundAsClient() {
+ return mIsSystemBoundAsClient;
+ }
+
+ /**
+ * Compares all properties of the <code>expectedEvent</code> and the
+ * <code>receviedEvent</code> to verify that the received event is the one
+ * that is expected.
+ */
+ private void assertEqualsAccessiblityEvent(AccessibilityEvent expectedEvent,
+ AccessibilityEvent receivedEvent) {
+ TestCase.assertEquals("addedCount has incorrect value", expectedEvent.getAddedCount(),
+ receivedEvent.getAddedCount());
+ TestCase.assertEquals("beforeText has incorrect value", expectedEvent.getBeforeText(),
+ receivedEvent.getBeforeText());
+ TestCase.assertEquals("checked has incorrect value", expectedEvent.isChecked(),
+ receivedEvent.isChecked());
+ TestCase.assertEquals("className has incorrect value", expectedEvent.getClassName(),
+ receivedEvent.getClassName());
+ TestCase.assertEquals("contentDescription has incorrect value", expectedEvent
+ .getContentDescription(), receivedEvent.getContentDescription());
+ TestCase.assertEquals("currentItemIndex has incorrect value", expectedEvent
+ .getCurrentItemIndex(), receivedEvent.getCurrentItemIndex());
+ TestCase.assertEquals("enabled has incorrect value", expectedEvent.isEnabled(),
+ receivedEvent.isEnabled());
+ TestCase.assertEquals("eventType has incorrect value", expectedEvent.getEventType(),
+ receivedEvent.getEventType());
+ TestCase.assertEquals("fromIndex has incorrect value", expectedEvent.getFromIndex(),
+ receivedEvent.getFromIndex());
+ TestCase.assertEquals("fullScreen has incorrect value", expectedEvent.isFullScreen(),
+ receivedEvent.isFullScreen());
+ TestCase.assertEquals("itemCount has incorrect value", expectedEvent.getItemCount(),
+ receivedEvent.getItemCount());
+ assertEqualsNotificationAsParcelableData(expectedEvent, receivedEvent);
+ TestCase.assertEquals("password has incorrect value", expectedEvent.isPassword(),
+ receivedEvent.isPassword());
+ TestCase.assertEquals("removedCount has incorrect value", expectedEvent.getRemovedCount(),
+ receivedEvent.getRemovedCount());
+ assertEqualsText(expectedEvent, receivedEvent);
+ }
+
+ /**
+ * Compares the {@link android.os.Parcelable} data of the
+ * <code>expectedEvent</code> and <code>receivedEvent</code> to verify that
+ * the received event is the one that is expected.
+ */
+ private void assertEqualsNotificationAsParcelableData(AccessibilityEvent expectedEvent,
+ AccessibilityEvent receivedEvent) {
+ String message = "parcelableData has incorrect value";
+ Message expectedMessage = (Message) expectedEvent.getParcelableData();
+ Message receivedMessage = (Message) receivedEvent.getParcelableData();
+
+ if (expectedMessage == null) {
+ if (receivedMessage == null) {
+ return;
+ }
+ }
+
+ TestCase.assertNotNull(message, receivedMessage);
+
+ // we do a very simple sanity check since we do not test Message
+ TestCase.assertEquals(message, expectedMessage.what, receivedMessage.what);
+ }
+
+ /**
+ * Compares the text of the <code>expectedEvent</code> and
+ * <code>receivedEvent</code> by comparing the string representation of the
+ * corresponding {@link CharSequence}s.
+ */
+ private void assertEqualsText(AccessibilityEvent expectedEvent,
+ AccessibilityEvent receivedEvent) {
+ String message = "text has incorrect value";
+ List<CharSequence> expectedText = expectedEvent.getText();
+ List<CharSequence> receivedText = receivedEvent.getText();
+
+ TestCase.assertEquals(message, expectedText.size(), receivedText.size());
+
+ Iterator<CharSequence> expectedTextIterator = expectedText.iterator();
+ Iterator<CharSequence> receivedTextIterator = receivedText.iterator();
+
+ for (int i = 0; i < expectedText.size(); i++) {
+ // compare the string representation
+ TestCase.assertEquals(message, expectedTextIterator.next().toString(),
+ receivedTextIterator.next().toString());
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/location/ComprehensiveCountryDetectorTest.java b/services/tests/servicestests/src/com/android/server/location/ComprehensiveCountryDetectorTest.java
new file mode 100644
index 0000000..98966c0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/ComprehensiveCountryDetectorTest.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.location;
+
+import android.location.Country;
+import android.location.CountryListener;
+import android.test.AndroidTestCase;
+
+public class ComprehensiveCountryDetectorTest extends AndroidTestCase {
+ private class TestCountryDetector extends ComprehensiveCountryDetector {
+ public final static String COUNTRY_ISO = "us";
+ private boolean mLocationBasedDetectorStarted;
+ private boolean mLocationBasedDetectorStopped;
+ protected boolean mNotified;
+ private boolean listenerAdded = false;
+
+ private Country mNotifiedCountry;
+ public TestCountryDetector() {
+ super(getContext());
+ }
+
+ public void notifyLocationBasedListener(Country country) {
+ mNotified = true;
+ mNotifiedCountry = country;
+ mLocationBasedCountryDetector.notifyListener(country);
+ }
+
+ public boolean locationBasedDetectorStarted() {
+ return mLocationBasedCountryDetector != null && mLocationBasedDetectorStarted;
+ }
+
+ public boolean locationBasedDetectorStopped() {
+ return mLocationBasedCountryDetector == null && mLocationBasedDetectorStopped;
+ }
+
+ public boolean locationRefreshStarted() {
+ return mLocationRefreshTimer != null;
+ }
+
+ public boolean locationRefreshCancelled() {
+ return mLocationRefreshTimer == null;
+ }
+
+ @Override
+ protected CountryDetectorBase createLocationBasedCountryDetector() {
+ return new CountryDetectorBase(mContext) {
+ @Override
+ public Country detectCountry() {
+ mLocationBasedDetectorStarted = true;
+ return null;
+ }
+
+ @Override
+ public void stop() {
+ mLocationBasedDetectorStopped = true;
+ }
+ };
+ }
+
+ @Override
+ protected Country getNetworkBasedCountry() {
+ return null;
+ }
+
+ @Override
+ protected Country getLastKnownLocationBasedCountry() {
+ return mNotifiedCountry;
+ }
+
+ @Override
+ protected Country getSimBasedCountry() {
+ return null;
+ }
+
+ @Override
+ protected Country getLocaleCountry() {
+ return null;
+ }
+
+ @Override
+ protected void runAfterDetectionAsync(final Country country, final Country detectedCountry,
+ final boolean notifyChange, final boolean startLocationBasedDetection) {
+ runAfterDetection(country, detectedCountry, notifyChange, startLocationBasedDetection);
+ };
+
+ @Override
+ protected boolean isAirplaneModeOff() {
+ return true;
+ }
+
+ @Override
+ protected synchronized void addPhoneStateListener() {
+ listenerAdded = true;
+ }
+
+ @Override
+ protected synchronized void removePhoneStateListener() {
+ listenerAdded = false;
+ }
+
+ @Override
+ protected boolean isGeoCoderImplemented() {
+ return true;
+ }
+
+ public boolean isPhoneStateListenerAdded() {
+ return listenerAdded;
+ }
+ }
+
+ private class CountryListenerImpl implements CountryListener {
+ private boolean mNotified;
+ private Country mCountry;
+
+ public void onCountryDetected(Country country) {
+ mNotified = true;
+ mCountry = country;
+ }
+
+ public boolean notified() {
+ return mNotified;
+ }
+
+ public Country getCountry() {
+ return mCountry;
+ }
+ }
+
+ public void testDetectNetworkBasedCountry() {
+ final Country resultCountry = new Country(
+ TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_NETWORK);
+ TestCountryDetector countryDetector = new TestCountryDetector() {
+ @Override
+ protected Country getNetworkBasedCountry() {
+ return resultCountry;
+ }
+ };
+ CountryListenerImpl listener = new CountryListenerImpl();
+ countryDetector.setCountryListener(listener);
+ Country country = countryDetector.detectCountry();
+ assertTrue(sameCountry(country, resultCountry));
+ assertFalse(listener.notified());
+ assertFalse(countryDetector.locationBasedDetectorStarted());
+ assertFalse(countryDetector.locationRefreshStarted());
+ countryDetector.stop();
+ }
+
+ public void testDetectLocationBasedCountry() {
+ final Country resultCountry = new Country(
+ TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_SIM);
+ final Country locationBasedCountry = new Country(
+ TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_LOCATION);
+ TestCountryDetector countryDetector = new TestCountryDetector() {
+ @Override
+ protected Country getSimBasedCountry() {
+ return resultCountry;
+ }
+ };
+ CountryListenerImpl listener = new CountryListenerImpl();
+ countryDetector.setCountryListener(listener);
+ Country country = countryDetector.detectCountry();
+ assertTrue(sameCountry(country, resultCountry));
+ assertTrue(countryDetector.locationBasedDetectorStarted());
+ countryDetector.notifyLocationBasedListener(locationBasedCountry);
+ assertTrue(listener.notified());
+ assertTrue(sameCountry(listener.getCountry(), locationBasedCountry));
+ assertTrue(countryDetector.locationBasedDetectorStopped());
+ assertTrue(countryDetector.locationRefreshStarted());
+ countryDetector.stop();
+ assertTrue(countryDetector.locationRefreshCancelled());
+ }
+
+ public void testLocaleBasedCountry() {
+ final Country resultCountry = new Country(
+ TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_LOCALE);
+ TestCountryDetector countryDetector = new TestCountryDetector() {
+ @Override
+ protected Country getLocaleCountry() {
+ return resultCountry;
+ }
+ };
+ CountryListenerImpl listener = new CountryListenerImpl();
+ countryDetector.setCountryListener(listener);
+ Country country = countryDetector.detectCountry();
+ assertTrue(sameCountry(country, resultCountry));
+ assertFalse(listener.notified());
+ assertTrue(countryDetector.locationBasedDetectorStarted());
+ assertTrue(countryDetector.locationRefreshStarted());
+ countryDetector.stop();
+ assertTrue(countryDetector.locationRefreshCancelled());
+ }
+
+ public void testStoppingDetector() {
+ // Test stopping detector when LocationBasedCountryDetector was started
+ final Country resultCountry = new Country(
+ TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_SIM);
+ TestCountryDetector countryDetector = new TestCountryDetector() {
+ @Override
+ protected Country getSimBasedCountry() {
+ return resultCountry;
+ }
+ };
+ CountryListenerImpl listener = new CountryListenerImpl();
+ countryDetector.setCountryListener(listener);
+ Country country = countryDetector.detectCountry();
+ assertTrue(sameCountry(country, resultCountry));
+ assertTrue(countryDetector.locationBasedDetectorStarted());
+ countryDetector.stop();
+ // The LocationBasedDetector should be stopped.
+ assertTrue(countryDetector.locationBasedDetectorStopped());
+ // The location refresh should not running.
+ assertTrue(countryDetector.locationRefreshCancelled());
+ }
+
+ public void testLocationBasedCountryNotFound() {
+ final Country resultCountry = new Country(
+ TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_SIM);
+ TestCountryDetector countryDetector = new TestCountryDetector() {
+ @Override
+ protected Country getSimBasedCountry() {
+ return resultCountry;
+ }
+ };
+ CountryListenerImpl listener = new CountryListenerImpl();
+ countryDetector.setCountryListener(listener);
+ Country country = countryDetector.detectCountry();
+ assertTrue(sameCountry(country, resultCountry));
+ assertTrue(countryDetector.locationBasedDetectorStarted());
+ countryDetector.notifyLocationBasedListener(null);
+ assertFalse(listener.notified());
+ assertTrue(sameCountry(listener.getCountry(), null));
+ assertTrue(countryDetector.locationBasedDetectorStopped());
+ assertTrue(countryDetector.locationRefreshStarted());
+ countryDetector.stop();
+ assertTrue(countryDetector.locationRefreshCancelled());
+ }
+
+ public void testNoCountryFound() {
+ TestCountryDetector countryDetector = new TestCountryDetector();
+ CountryListenerImpl listener = new CountryListenerImpl();
+ countryDetector.setCountryListener(listener);
+ Country country = countryDetector.detectCountry();
+ assertTrue(sameCountry(country, null));
+ assertTrue(countryDetector.locationBasedDetectorStarted());
+ countryDetector.notifyLocationBasedListener(null);
+ assertFalse(listener.notified());
+ assertTrue(sameCountry(listener.getCountry(), null));
+ assertTrue(countryDetector.locationBasedDetectorStopped());
+ assertTrue(countryDetector.locationRefreshStarted());
+ countryDetector.stop();
+ assertTrue(countryDetector.locationRefreshCancelled());
+ }
+
+ public void testAddRemoveListener() {
+ TestCountryDetector countryDetector = new TestCountryDetector();
+ CountryListenerImpl listener = new CountryListenerImpl();
+ countryDetector.setCountryListener(listener);
+ assertTrue(countryDetector.isPhoneStateListenerAdded());
+ assertTrue(countryDetector.locationBasedDetectorStarted());
+ countryDetector.setCountryListener(null);
+ assertFalse(countryDetector.isPhoneStateListenerAdded());
+ assertTrue(countryDetector.locationBasedDetectorStopped());
+ }
+
+ public void testGeocoderNotImplemented() {
+ TestCountryDetector countryDetector = new TestCountryDetector() {
+ @Override
+ protected boolean isGeoCoderImplemented() {
+ return false;
+ }
+ };
+ CountryListenerImpl listener = new CountryListenerImpl();
+ countryDetector.setCountryListener(listener);
+ assertTrue(countryDetector.isPhoneStateListenerAdded());
+ assertFalse(countryDetector.locationBasedDetectorStarted());
+ countryDetector.setCountryListener(null);
+ assertFalse(countryDetector.isPhoneStateListenerAdded());
+ }
+
+ private boolean sameCountry(Country country1, Country country2) {
+ return country1 == null && country2 == null || country1 != null && country2 != null &&
+ country1.getCountryIso().equalsIgnoreCase(country2.getCountryIso()) &&
+ country1.getSource() == country2.getSource();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java b/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java
new file mode 100755
index 0000000..71e8e2a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.server.location;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+
+import android.location.Country;
+import android.location.CountryListener;
+import android.location.Location;
+import android.location.LocationListener;
+import android.test.AndroidTestCase;
+
+public class LocationBasedCountryDetectorTest extends AndroidTestCase {
+ private class TestCountryDetector extends LocationBasedCountryDetector {
+ public static final int TOTAL_PROVIDERS = 2;
+ protected Object countryFoundLocker = new Object();
+ protected boolean notifyCountry = false;
+ private final Location mLocation;
+ private final String mCountry;
+ private final long mQueryLocationTimeout;
+ private List<LocationListener> mListeners;
+
+ public TestCountryDetector(String country, String provider) {
+ this(country, provider, 1000 * 60 * 5);
+ }
+
+ public TestCountryDetector(String country, String provider, long queryLocationTimeout) {
+ super(getContext());
+ mCountry = country;
+ mLocation = new Location(provider);
+ mQueryLocationTimeout = queryLocationTimeout;
+ mListeners = new ArrayList<LocationListener>();
+ }
+
+ @Override
+ protected String getCountryFromLocation(Location location) {
+ synchronized (countryFoundLocker) {
+ if (!notifyCountry) {
+ try {
+ countryFoundLocker.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ if (mLocation.getProvider().endsWith(location.getProvider())) {
+ return mCountry;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ protected Location getLastKnownLocation() {
+ return mLocation;
+ }
+
+ @Override
+ protected void registerEnabledProviders(List<LocationListener> listeners) {
+ mListeners.addAll(listeners);
+ }
+
+ @Override
+ protected void unregisterProviders(List<LocationListener> listeners) {
+ for (LocationListener listener : mLocationListeners) {
+ assertTrue(mListeners.remove(listener));
+ }
+ }
+
+ @Override
+ protected long getQueryLocationTimeout() {
+ return mQueryLocationTimeout;
+ }
+
+ @Override
+ protected int getTotalEnabledProviders() {
+ return TOTAL_PROVIDERS;
+ }
+
+ public void notifyLocationFound() {
+ // Listener could be removed in the notification.
+ LocationListener[] listeners = new LocationListener[mListeners.size()];
+ mLocationListeners.toArray(listeners);
+ for (LocationListener listener :listeners) {
+ listener.onLocationChanged(mLocation);
+ }
+ }
+
+ public int getListenersCount() {
+ return mListeners.size();
+ }
+
+ public void notifyCountryFound() {
+ synchronized (countryFoundLocker) {
+ notifyCountry = true;
+ countryFoundLocker.notify();
+ }
+ }
+
+ public Timer getTimer() {
+ return mTimer;
+ }
+
+ public Thread getQueryThread() {
+ return mQueryThread;
+ }
+ }
+
+ private class CountryListenerImpl implements CountryListener {
+ private boolean mNotified;
+ private String mCountryCode;
+ public void onCountryDetected(Country country) {
+ mNotified = true;
+ if (country != null) {
+ mCountryCode = country.getCountryIso();
+ }
+ }
+
+ public boolean notified() {
+ return mNotified;
+ }
+
+ public String getCountry() {
+ return mCountryCode;
+ }
+ }
+
+ public void testFindingCountry() {
+ final String country = "us";
+ final String provider = "Good";
+ CountryListenerImpl countryListener = new CountryListenerImpl();
+ TestCountryDetector detector = new TestCountryDetector(country, provider);
+ detector.setCountryListener(countryListener);
+ detector.detectCountry();
+ assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+ detector.notifyLocationFound();
+ // All listeners should be unregistered
+ assertEquals(detector.getListenersCount(), 0);
+ assertNull(detector.getTimer());
+ Thread queryThread = waitForQueryThreadLaunched(detector);
+ detector.notifyCountryFound();
+ // Wait for query thread ending
+ waitForThreadEnding(queryThread);
+ // QueryThread should be set to NULL
+ assertNull(detector.getQueryThread());
+ assertTrue(countryListener.notified());
+ assertEquals(countryListener.getCountry(), country);
+ }
+
+ public void testFindingCountryCancelled() {
+ final String country = "us";
+ final String provider = "Good";
+ CountryListenerImpl countryListener = new CountryListenerImpl();
+ TestCountryDetector detector = new TestCountryDetector(country, provider);
+ detector.setCountryListener(countryListener);
+ detector.detectCountry();
+ assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+ detector.notifyLocationFound();
+ // All listeners should be unregistered
+ assertEquals(detector.getListenersCount(), 0);
+ // The time should be stopped
+ assertNull(detector.getTimer());
+ Thread queryThread = waitForQueryThreadLaunched(detector);
+ detector.stop();
+ // There is no way to stop the thread, let's test it could be stopped, after get country
+ detector.notifyCountryFound();
+ // Wait for query thread ending
+ waitForThreadEnding(queryThread);
+ // QueryThread should be set to NULL
+ assertNull(detector.getQueryThread());
+ assertTrue(countryListener.notified());
+ assertEquals(countryListener.getCountry(), country);
+ }
+
+ public void testFindingLocationCancelled() {
+ final String country = "us";
+ final String provider = "Good";
+ CountryListenerImpl countryListener = new CountryListenerImpl();
+ TestCountryDetector detector = new TestCountryDetector(country, provider);
+ detector.setCountryListener(countryListener);
+ detector.detectCountry();
+ assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+ detector.stop();
+ // All listeners should be unregistered
+ assertEquals(detector.getListenersCount(), 0);
+ // The time should be stopped
+ assertNull(detector.getTimer());
+ // QueryThread should still be NULL
+ assertNull(detector.getQueryThread());
+ assertFalse(countryListener.notified());
+ }
+
+ public void testFindingLocationFailed() {
+ final String country = "us";
+ final String provider = "Good";
+ long timeout = 1000;
+ TestCountryDetector detector = new TestCountryDetector(country, provider, timeout) {
+ @Override
+ protected Location getLastKnownLocation() {
+ return null;
+ }
+ };
+ CountryListenerImpl countryListener = new CountryListenerImpl();
+ detector.setCountryListener(countryListener);
+ detector.detectCountry();
+ assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+ waitForTimerReset(detector);
+ // All listeners should be unregistered
+ assertEquals(detector.getListenersCount(), 0);
+ // QueryThread should still be NULL
+ assertNull(detector.getQueryThread());
+ assertTrue(countryListener.notified());
+ assertNull(countryListener.getCountry());
+ }
+
+ public void testFindingCountryFailed() {
+ final String country = "us";
+ final String provider = "Good";
+ TestCountryDetector detector = new TestCountryDetector(country, provider) {
+ @Override
+ protected String getCountryFromLocation(Location location) {
+ synchronized (countryFoundLocker) {
+ if (! notifyCountry) {
+ try {
+ countryFoundLocker.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ // We didn't find country.
+ return null;
+ }
+ };
+ CountryListenerImpl countryListener = new CountryListenerImpl();
+ detector.setCountryListener(countryListener);
+ detector.detectCountry();
+ assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+ detector.notifyLocationFound();
+ // All listeners should be unregistered
+ assertEquals(detector.getListenersCount(), 0);
+ assertNull(detector.getTimer());
+ Thread queryThread = waitForQueryThreadLaunched(detector);
+ detector.notifyCountryFound();
+ // Wait for query thread ending
+ waitForThreadEnding(queryThread);
+ // QueryThread should be set to NULL
+ assertNull(detector.getQueryThread());
+ // CountryListener should be notified
+ assertTrue(countryListener.notified());
+ assertNull(countryListener.getCountry());
+ }
+
+ public void testFindingCountryWithLastKnownLocation() {
+ final String country = "us";
+ final String provider = "Good";
+ long timeout = 1000;
+ TestCountryDetector detector = new TestCountryDetector(country, provider, timeout);
+ CountryListenerImpl countryListener = new CountryListenerImpl();
+ detector.setCountryListener(countryListener);
+ detector.detectCountry();
+ assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+ waitForTimerReset(detector);
+ // All listeners should be unregistered
+ assertEquals(detector.getListenersCount(), 0);
+ Thread queryThread = waitForQueryThreadLaunched(detector);
+ detector.notifyCountryFound();
+ // Wait for query thread ending
+ waitForThreadEnding(queryThread);
+ // QueryThread should be set to NULL
+ assertNull(detector.getQueryThread());
+ // CountryListener should be notified
+ assertTrue(countryListener.notified());
+ assertEquals(countryListener.getCountry(), country);
+ }
+
+ private void waitForTimerReset(TestCountryDetector detector) {
+ int count = 5;
+ long interval = 1000;
+ try {
+ while (count-- > 0 && detector.getTimer() != null) {
+ Thread.sleep(interval);
+ }
+ } catch (InterruptedException e) {
+ }
+ Timer timer = detector.getTimer();
+ assertTrue(timer == null);
+ }
+
+ private void waitForThreadEnding(Thread thread) {
+ try {
+ thread.join(5000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private Thread waitForQueryThreadLaunched(TestCountryDetector detector) {
+ int count = 5;
+ long interval = 1000;
+ try {
+ while (count-- > 0 && detector.getQueryThread() == null) {
+ Thread.sleep(interval);
+ }
+ } catch (InterruptedException e) {
+ }
+ Thread thread = detector.getQueryThread();
+ assertTrue(thread != null);
+ return thread;
+ }
+}