summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
Diffstat (limited to 'services')
-rw-r--r--services/audioflinger/AudioPolicyManagerBase.cpp35
-rw-r--r--services/camera/libcameraservice/CameraHardwareStub.cpp4
-rw-r--r--services/camera/libcameraservice/CameraHardwareStub.h2
-rw-r--r--services/camera/libcameraservice/CameraService.cpp102
-rw-r--r--services/camera/libcameraservice/CameraService.h9
-rw-r--r--services/java/com/android/server/AccessibilityManagerService.java10
-rw-r--r--services/java/com/android/server/AppWidgetService.java64
-rw-r--r--services/java/com/android/server/ClipboardService.java59
-rw-r--r--services/java/com/android/server/ConnectivityService.java388
-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.java767
-rw-r--r--services/java/com/android/server/InputMethodManagerService.java10
-rw-r--r--services/java/com/android/server/LocationManagerService.java5
-rw-r--r--services/java/com/android/server/MountService.java25
-rw-r--r--services/java/com/android/server/NativeDaemonConnector.java12
-rw-r--r--services/java/com/android/server/NetworkManagementService.java8
-rwxr-xr-xservices/java/com/android/server/NotificationManagerService.java5
-rw-r--r--services/java/com/android/server/PackageManagerService.java3
-rw-r--r--services/java/com/android/server/SamplingProfilerService.java118
-rw-r--r--services/java/com/android/server/StatusBarManagerService.java7
-rw-r--r--services/java/com/android/server/SystemServer.java83
-rw-r--r--services/java/com/android/server/TelephonyRegistry.java141
-rw-r--r--services/java/com/android/server/ThrottleService.java234
-rw-r--r--services/java/com/android/server/UsbObserver.java36
-rw-r--r--services/java/com/android/server/WifiService.java1835
-rw-r--r--services/java/com/android/server/WifiWatchdogService.java34
-rw-r--r--services/java/com/android/server/WindowManagerService.java109
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java68
-rw-r--r--services/java/com/android/server/connectivity/Tethering.java140
-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.mk3
-rw-r--r--services/jni/com_android_server_UsbObserver.cpp174
-rwxr-xr-xservices/jni/com_android_server_location_GpsLocationProvider.cpp10
-rw-r--r--services/jni/onload.cpp2
-rw-r--r--services/surfaceflinger/Android.mk3
-rw-r--r--services/surfaceflinger/DisplayHardware/DisplayHardware.cpp22
-rw-r--r--services/surfaceflinger/DisplayHardware/DisplayHardware.h8
-rw-r--r--services/surfaceflinger/DisplayHardware/HWComposer.cpp99
-rw-r--r--services/surfaceflinger/DisplayHardware/HWComposer.h68
-rw-r--r--services/surfaceflinger/Layer.cpp57
-rw-r--r--services/surfaceflinger/Layer.h2
-rw-r--r--services/surfaceflinger/LayerBase.cpp9
-rw-r--r--services/surfaceflinger/LayerBase.h6
-rw-r--r--services/surfaceflinger/SurfaceFlinger.cpp143
-rw-r--r--services/surfaceflinger/SurfaceFlinger.h2
-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.java729
-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
56 files changed, 5748 insertions, 2172 deletions
diff --git a/services/audioflinger/AudioPolicyManagerBase.cpp b/services/audioflinger/AudioPolicyManagerBase.cpp
index 1d87c0d..4614c8d 100644
--- a/services/audioflinger/AudioPolicyManagerBase.cpp
+++ b/services/audioflinger/AudioPolicyManagerBase.cpp
@@ -1002,8 +1002,8 @@ AudioPolicyManagerBase::AudioPolicyManagerBase(AudioPolicyClientInterface *clien
#ifdef AUDIO_POLICY_TEST
Thread(false),
#endif //AUDIO_POLICY_TEST
- mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), mMusicStopTime(0),
- mLimitRingtoneVolume(false), mTotalEffectsCpuLoad(0), mTotalEffectsMemory(0)
+ mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), mMusicStopTime(0), mLimitRingtoneVolume(false),
+ mLastVoiceVolume(-1.0f), mTotalEffectsCpuLoad(0), mTotalEffectsMemory(0)
{
mpClientInterface = clientInterface;
@@ -1829,29 +1829,38 @@ status_t AudioPolicyManagerBase::checkAndSetVolume(int stream, int index, audio_
}
float volume = computeVolume(stream, index, output, device);
- // do not set volume if the float value did not change
- if (volume != mOutputs.valueFor(output)->mCurVolume[stream] || force) {
+ // We actually change the volume if:
+ // - the float value returned by computeVolume() changed
+ // - the force flag is set
+ if (volume != mOutputs.valueFor(output)->mCurVolume[stream] ||
+ force) {
mOutputs.valueFor(output)->mCurVolume[stream] = volume;
LOGV("setStreamVolume() for output %d stream %d, volume %f, delay %d", output, stream, volume, delayMs);
if (stream == AudioSystem::VOICE_CALL ||
stream == AudioSystem::DTMF ||
stream == AudioSystem::BLUETOOTH_SCO) {
- float voiceVolume = -1.0;
// offset value to reflect actual hardware volume that never reaches 0
// 1% corresponds roughly to first step in VOICE_CALL stream volume setting (see AudioService.java)
volume = 0.01 + 0.99 * volume;
- if (stream == AudioSystem::VOICE_CALL) {
- voiceVolume = (float)index/(float)mStreams[stream].mIndexMax;
- } else if (stream == AudioSystem::BLUETOOTH_SCO) {
- voiceVolume = 1.0;
- }
- if (voiceVolume >= 0 && output == mHardwareOutput) {
- mpClientInterface->setVoiceVolume(voiceVolume, delayMs);
- }
}
mpClientInterface->setStreamVolume((AudioSystem::stream_type)stream, volume, output, delayMs);
}
+ if (stream == AudioSystem::VOICE_CALL ||
+ stream == AudioSystem::BLUETOOTH_SCO) {
+ float voiceVolume;
+ // Force voice volume to max for bluetooth SCO as volume is managed by the headset
+ if (stream == AudioSystem::VOICE_CALL) {
+ voiceVolume = (float)index/(float)mStreams[stream].mIndexMax;
+ } else {
+ voiceVolume = 1.0;
+ }
+ if (voiceVolume != mLastVoiceVolume && output == mHardwareOutput) {
+ mpClientInterface->setVoiceVolume(voiceVolume, delayMs);
+ mLastVoiceVolume = voiceVolume;
+ }
+ }
+
return NO_ERROR;
}
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 ea2c5d4..c6a9909 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -336,16 +336,9 @@ 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);
+ // Clean up the ANativeWindow
+ if (mSurface != 0) {
+ setPreviewDisplay(0);
}
// set mClientPid to let disconnet() tear down the hardware
@@ -465,6 +458,11 @@ void CameraService::Client::disconnect() {
if (mUseOverlay) {
mOverlayRef = 0;
}
+ // Release the held ANativeWindow resources.
+ if (mPreviewWindow != 0) {
+ mPreviewWindow = 0;
+ mHardware->setPreviewWindow(mPreviewWindow);
+ }
mHardware.clear();
mCameraService->removeClient(mCameraClient);
@@ -475,8 +473,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();
@@ -486,7 +484,7 @@ 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->asBinder()) {
return result;
}
@@ -496,44 +494,28 @@ status_t CameraService::Client::setPreviewDisplay(const sp<ISurface>& surface) {
// Force the destruction of any previous overlay
sp<Overlay> dummy;
mHardware->setOverlay(dummy);
- } else {
- mSurface->unregisterBuffers();
}
}
- mSurface = surface;
+ if (surface != 0) {
+ mSurface = getISurface(surface);
+ } else {
+ mSurface = 0;
+ }
+ mPreviewWindow = surface;
mOverlayRef = 0;
// If preview has been already started, set overlay or register preview
// buffers now.
if (mHardware->previewEnabled()) {
if (mUseOverlay) {
result = setOverlay();
- } else if (mSurface != 0) {
- result = registerPreviewBuffers();
+ } else if (mPreviewWindow != 0) {
+ 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());
-
- status_t result = mSurface->registerBuffers(buffers);
- if (result != NO_ERROR) {
- LOGE("registerBuffers failed with status %d", result);
- }
- return result;
-}
-
status_t CameraService::Client::setOverlay() {
int w, h;
CameraParameters params(mHardware->getParameters());
@@ -624,14 +606,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();
@@ -657,16 +639,10 @@ status_t CameraService::Client::startPreviewMode() {
if (result != NO_ERROR) return result;
result = mHardware->startPreview();
} else {
+ // XXX: Set the orientation of the ANativeWindow.
+ mHardware->setPreviewWindow(mPreviewWindow);
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();
- }
}
return result;
}
@@ -704,13 +680,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();
}
@@ -998,11 +971,6 @@ void CameraService::Client::dataCallbackTimestamp(nsecs_t timestamp,
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();
- }
-
sp<ICameraClient> c = mCameraClient;
if (c != 0) {
mLock.unlock();
@@ -1030,7 +998,6 @@ void CameraService::Client::handleShutter(image_rect_type *size) {
HAL_PIXEL_FORMAT_YCrCb_420_SP, mOrientation, 0,
mHardware->getRawHeap());
- mSurface->registerBuffers(buffers);
IPCThreadState::self()->flushCommands();
}
@@ -1043,12 +1010,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;
@@ -1108,11 +1069,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) {
@@ -1270,4 +1226,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 0d69836..7ed192e 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,7 +93,7 @@ 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 void setPreviewCallbackFlag(int flag);
virtual status_t startPreview();
virtual void stopPreview();
@@ -169,6 +175,7 @@ private:
// Ensures atomicity among the public methods
mutable Mutex mLock;
sp<ISurface> 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/java/com/android/server/AccessibilityManagerService.java b/services/java/com/android/server/AccessibilityManagerService.java
index 87de79a..83ce3e3 100644
--- a/services/java/com/android/server/AccessibilityManagerService.java
+++ b/services/java/com/android/server/AccessibilityManagerService.java
@@ -269,14 +269,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;
}
}
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index 3ed6c12..f825df9 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -425,6 +425,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);
@@ -442,11 +476,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) {
@@ -462,6 +502,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);
@@ -741,6 +800,9 @@ 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);
+
sa.recycle();
} catch (Exception e) {
// Ok to catch Exception here, because anything going wrong because
diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java
index aa8cded..4e4fc0c 100644
--- a/services/java/com/android/server/ClipboardService.java
+++ b/services/java/com/android/server/ClipboardService.java
@@ -16,42 +16,77 @@
package com.android.server;
-import android.text.IClipboard;
+import android.content.ClippedData;
+import android.content.IClipboard;
+import android.content.IOnPrimaryClipChangedListener;
import android.content.Context;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
/**
* Implementation of the clipboard for copy and paste.
*/
public class ClipboardService extends IClipboard.Stub {
- private CharSequence mClipboard = "";
+ private ClippedData mPrimaryClip;
+ private final RemoteCallbackList<IOnPrimaryClipChangedListener> mPrimaryClipListeners
+ = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
/**
* Instantiates the clipboard.
*/
public ClipboardService(Context context) { }
- // javadoc from interface
- public void setClipboardText(CharSequence text) {
+ public void setPrimaryClip(ClippedData clip) {
synchronized (this) {
- if (text == null) {
- text = "";
+ if (clip != null && clip.getItemCount() <= 0) {
+ throw new IllegalArgumentException("No items");
}
+ 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 ClippedData getPrimaryClip() {
+ synchronized (this) {
+ return mPrimaryClip;
+ }
+ }
+
+ public boolean hasPrimaryClip() {
+ synchronized (this) {
+ return mPrimaryClip != null;
}
}
- // javadoc from interface
- public CharSequence getClipboardText() {
+ public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
synchronized (this) {
- return mClipboard;
+ 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.getItem(0).getText();
+ return text != null && text.length() > 0;
+ }
+ return false;
}
}
}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 041c13b..4da0bac 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -26,13 +26,16 @@ import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.net.MobileDataStateTracker;
import android.net.NetworkInfo;
+import android.net.NetworkProperties;
import android.net.NetworkStateTracker;
import android.net.wifi.WifiStateTracker;
+import android.net.NetworkUtils;
import android.os.Binder;
import android.os.Handler;
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;
@@ -46,8 +49,13 @@ 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.List;
/**
@@ -64,7 +72,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;
@@ -81,6 +88,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 +113,11 @@ 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 static class NetworkAttributes {
/**
* Class for holding settings read from resources.
@@ -194,6 +208,12 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
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();
@@ -293,18 +313,21 @@ public class ConnectivityService extends IConnectivityManager.Stub {
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);
+ 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();
+ mNetTrackers[netType] = new MobileDataStateTracker(netType,
+ mNetAttributes[netType].mName);
+ mNetTrackers[netType].startMonitoring(context, mHandler);
if (noMobileData) {
if (DBG) Slog.d(TAG, "tearing down Mobile networks due to setting");
mNetTrackers[netType].teardown();
@@ -321,7 +344,8 @@ 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);
}
@@ -597,15 +621,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
network.reconnect();
return Phone.APN_REQUEST_STARTED;
} else {
- synchronized(this) {
- mFeatureUsers.add(f);
- }
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK,
- f), getRestoreDefaultNetworkDelay());
-
- return network.startUsingNetworkFeature(feature,
- getCallingPid(), getCallingUid());
+ return -1;
}
}
return Phone.APN_TYPE_NOT_AVAILABLE;
@@ -724,8 +740,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
tracker.teardown();
return 1;
} else {
- // do it the old fashioned way
- return tracker.stopUsingNetworkFeature(feature, pid, uid);
+ return -1;
}
}
@@ -736,6 +751,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
* specified host is to be routed
* @param hostAddress the IP address of the host to which the route is
* desired
+ * todo - deprecate (only v4!)
* @return {@code true} on success, {@code false} on failure
*/
public boolean requestRouteToHost(int networkType, int hostAddress) {
@@ -752,7 +768,39 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
return false;
}
- return tracker.requestRouteToHost(hostAddress);
+ try {
+ InetAddress addr = InetAddress.getByAddress(NetworkUtils.v4IntToArray(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;
+ }
+
+ NetworkProperties p = nt.getNetworkProperties();
+ if (p == null) return false;
+ String interfaceName = p.getInterfaceName();
+
+ if (DBG) {
+ Slog.d(TAG, "Requested host route to " + hostAddress + "(" + interfaceName + ")");
+ }
+ if (interfaceName != null) {
+ return NetworkUtils.addHostRoute(interfaceName, hostAddress) == 0;
+ } else {
+ if (DBG) Slog.e(TAG, "addHostRoute failed due to null interface name");
+ return false;
+ }
}
/**
@@ -859,6 +907,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
@@ -1131,44 +1185,30 @@ public class ConnectivityService extends IConnectivityManager.Stub {
Slog.e(TAG, "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(
+ NetworkStateTracker.EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+ mNetTransitionWakeLockSerialNumber, 0),
+ 1000);
}
}
mActiveDefaultNetwork = type;
}
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.
*/
@@ -1181,19 +1221,158 @@ public class ConnectivityService extends IConnectivityManager.Stub {
if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
if (mNetAttributes[netType].isDefault()) {
- mNetTrackers[netType].addDefaultRoute();
+ addDefaultRoute(mNetTrackers[netType]);
} else {
- mNetTrackers[netType].addPrivateDnsRoutes();
+ addPrivateDnsRoutes(mNetTrackers[netType]);
}
} else {
if (mNetAttributes[netType].isDefault()) {
- mNetTrackers[netType].removeDefaultRoute();
+ removeDefaultRoute(mNetTrackers[netType]);
+ } else {
+ removePrivateDnsRoutes(mNetTrackers[netType]);
+ }
+ }
+ }
+
+ private void addPrivateDnsRoutes(NetworkStateTracker nt) {
+ boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet();
+ NetworkProperties p = nt.getNetworkProperties();
+ if (p == null) return;
+ String interfaceName = p.getInterfaceName();
+
+ if (DBG) {
+ Slog.d(TAG, "addPrivateDnsRoutes for " + nt +
+ "(" + interfaceName + ") - mPrivateDnsRouteSet = " + privateDnsRouteSet);
+ }
+ if (interfaceName != null && !privateDnsRouteSet) {
+ Collection<InetAddress> dnsList = p.getDnses();
+ for (InetAddress dns : dnsList) {
+ if (DBG) Slog.d(TAG, " adding " + dns);
+ NetworkUtils.addHostRoute(interfaceName, dns);
+ }
+ 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
+ NetworkProperties p = nt.getNetworkProperties();
+ if (p == null) return;
+ String interfaceName = p.getInterfaceName();
+ boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet();
+ if (interfaceName != null && privateDnsRouteSet) {
+ if (DBG) {
+ Slog.d(TAG, "removePrivateDnsRoutes for " + nt.getNetworkInfo().getTypeName() +
+ " (" + interfaceName + ")");
+ }
+ NetworkUtils.removeHostRoutes(interfaceName);
+ nt.privateDnsRouteSet(false);
+ }
+ }
+
+
+ private void addDefaultRoute(NetworkStateTracker nt) {
+ NetworkProperties p = nt.getNetworkProperties();
+ if (p == null) return;
+ String interfaceName = p.getInterfaceName();
+ InetAddress defaultGatewayAddr = p.getGateway();
+
+ if ((interfaceName != null) && (defaultGatewayAddr != null )) {
+ if ((NetworkUtils.setDefaultRoute(interfaceName, defaultGatewayAddr) >= 0) && DBG) {
+ NetworkInfo networkInfo = nt.getNetworkInfo();
+ Slog.d(TAG, "addDefaultRoute for " + networkInfo.getTypeName() +
+ " (" + interfaceName + "), GatewayAddr=" + defaultGatewayAddr);
+ }
+ }
+ }
+
+
+ public void removeDefaultRoute(NetworkStateTracker nt) {
+ NetworkProperties p = nt.getNetworkProperties();
+ if (p == null) return;
+ String interfaceName = p.getInterfaceName();
+
+ if (interfaceName != null) {
+ if ((NetworkUtils.removeDefaultRoute(interfaceName) >= 0) && DBG) {
+ NetworkInfo networkInfo = nt.getNetworkInfo();
+ Slog.d(TAG, "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) {
+ Slog.e(TAG, 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) {
+ Slog.v(TAG, "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 {
- mNetTrackers[netType].removePrivateDnsRoutes();
+ Slog.e(TAG, "Invalid buffersize string: " + bufferSizes);
}
+ } catch (IOException e) {
+ Slog.e(TAG, "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.
@@ -1209,12 +1388,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
NetworkStateTracker nt = mNetTrackers[i];
if (nt.getNetworkInfo().isConnected() &&
!nt.isTeardownRequested()) {
+ NetworkProperties p = nt.getNetworkProperties();
+ 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();
}
@@ -1236,12 +1417,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
}
}
- private void writePidDns(String[] dnsList, int pid) {
+ private void 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);
- }
+ for (InetAddress dns : dnses) {
+ SystemProperties.set("net.dns" + j++ + "." + pid, dns.getHostAddress());
}
}
@@ -1264,17 +1443,17 @@ public class ConnectivityService extends IConnectivityManager.Stub {
// add default net's dns entries
NetworkStateTracker nt = mNetTrackers[netType];
if (nt != null && nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) {
- String[] dnsList = nt.getNameServers();
+ NetworkProperties p = nt.getNetworkProperties();
+ if (p == null) return;
+ Collection<InetAddress> dnses = p.getDnses();
if (mNetAttributes[netType].isDefault()) {
int j = 1;
- for (String dns : dnsList) {
- if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) {
- if (DBG) {
- Slog.d(TAG, "adding dns " + dns + " for " +
- nt.getNetworkInfo().getTypeName());
- }
- SystemProperties.set("net.dns" + j++, dns);
+ for (InetAddress dns : dnses) {
+ if (DBG) {
+ Slog.d(TAG, "adding dns " + dns + " for " +
+ nt.getNetworkInfo().getTypeName());
}
+ SystemProperties.set("net.dns" + j++, dns.getHostAddress());
}
for (int k=j ; k<mNumDnsEntries; k++) {
if (DBG) Slog.d(TAG, "erasing net.dns" + k);
@@ -1286,11 +1465,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());
+ writePidDns(dnses, pid.intValue());
}
}
+ bumpDns();
}
- bumpDns();
}
private int getRestoreDefaultNetworkDelay() {
@@ -1345,6 +1524,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);
}
@@ -1411,34 +1597,30 @@ 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:
+ // TODO - make this handle ip/proxy/gateway/dns changes
info = (NetworkInfo) msg.obj;
type = info.getType();
handleDnsConfigurationChange(type);
break;
-
- case NetworkStateTracker.EVENT_ROAMING_CHANGED:
- // fill me in
- break;
-
- case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED:
- // fill me in
- break;
case NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK:
FeatureUser u = (FeatureUser)msg.obj;
u.expire();
break;
+ case NetworkStateTracker.EVENT_CLEAR_NET_TRANSITION_WAKELOCK:
+ String causedBy = null;
+ synchronized (ConnectivityService.this) {
+ if (msg.arg1 == mNetTransitionWakeLockSerialNumber &&
+ mNetTransitionWakeLock.isHeld()) {
+ mNetTransitionWakeLock.release();
+ causedBy = mNetTransitionWakeLockCausedBy;
+ }
+ }
+ if (causedBy != null) {
+ Slog.d(TAG, "NetTransition Wakelock for " +
+ causedBy + " released by timeout");
+ }
+ break;
}
}
}
@@ -1495,6 +1677,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() {
@@ -1522,4 +1713,23 @@ public class ConnectivityService extends IConnectivityManager.Stub {
Settings.Secure.TETHER_SUPPORTED, defaultVal) != 0);
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(
+ NetworkStateTracker.EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+ mNetTransitionWakeLockSerialNumber, 0),
+ mNetTransitionWakeLockTimeout);
+ return;
+ }
}
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 19d146d..0c3a0e6 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -33,6 +33,7 @@ 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.pm.PackageManager;
@@ -46,6 +47,8 @@ import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.net.Proxy;
+import android.provider.Settings;
import android.util.Slog;
import android.util.PrintWriterPrinter;
import android.util.Printer;
@@ -58,46 +61,66 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
+import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Set;
/**
* Implementation of the device policy APIs.
*/
public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
static final String TAG = "DevicePolicyManagerService";
-
+
final Context mContext;
final MyPackageMonitor mMonitor;
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;
-
+
final HashMap<ComponentName, ActiveAdmin> mAdminMap
= new HashMap<ComponentName, ActiveAdmin>();
final ArrayList<ActiveAdmin> mAdminList
= new ArrayList<ActiveAdmin>();
-
+
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;
-
+
+ // 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");
@@ -110,7 +133,42 @@ 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) {
@@ -123,8 +181,23 @@ 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");
+ }
+ }
}
-
+
void readFromXml(XmlPullParser parser)
throws XmlPullParserException, IOException {
int outerDepth = parser.getDepth();
@@ -143,19 +216,49 @@ 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.getBoolean(
+ 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 {
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:");
@@ -166,23 +269,47 @@ 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);
+ if (globalProxySpec != null) {
+ pw.print(prefix); pw.print("globalProxySpec=");
+ pw.println(globalProxySpec);
+ }
+ if (globalProxyExclusionList != null) {
+ pw.print(prefix); pw.print("globalProxyEclusionList=");
+ pw.println(globalProxyExclusionList);
+ }
}
}
-
+
class MyPackageMonitor extends PackageMonitor {
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: "
@@ -207,7 +334,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
/**
* Instantiates the service.
*/
@@ -224,7 +351,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
return mIPowerManager;
}
-
+
ActiveAdmin getActiveAdminUncheckedLocked(ComponentName who) {
ActiveAdmin admin = mAdminMap.get(who);
if (admin != null
@@ -234,7 +361,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
return null;
}
-
+
ActiveAdmin getActiveAdminForCallerLocked(ComponentName who, int reqPolicy)
throws SecurityException {
final int callingUid = Binder.getCallingUid();
@@ -265,13 +392,13 @@ 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());
mContext.sendBroadcast(intent);
}
-
+
void sendAdminCommandLocked(String action, int reqPolicy) {
final int N = mAdminList.size();
if (N > 0) {
@@ -283,19 +410,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);
@@ -304,7 +436,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) {
@@ -315,7 +447,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"));
@@ -331,7 +463,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);
@@ -342,26 +474,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();
@@ -439,6 +581,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);
@@ -476,10 +630,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;
@@ -498,12 +658,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;
@@ -520,17 +681,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
public void systemReady() {
synchronized (this) {
loadSettingsLocked();
}
}
-
+
public void setActiveAdmin(ComponentName adminReceiver) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-
+
DeviceAdminInfo info = findAdmin(adminReceiver);
if (info == null) {
throw new IllegalArgumentException("Bad admin: " + adminReceiver);
@@ -552,13 +713,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
public boolean isAdminActive(ComponentName adminReceiver) {
synchronized (this) {
return getActiveAdminUncheckedLocked(adminReceiver) != null;
}
}
-
+
public List<ComponentName> getActiveAdmins() {
synchronized (this) {
final int N = mAdminList.size();
@@ -572,7 +733,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return res;
}
}
-
+
public boolean packageHasActiveAdmins(String packageName) {
synchronized (this) {
final int N = mAdminList.size();
@@ -584,7 +745,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return false;
}
}
-
+
public void removeActiveAdmin(ComponentName adminReceiver) {
synchronized (this) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver);
@@ -603,10 +764,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");
@@ -619,16 +780,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);
@@ -639,7 +800,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return mode;
}
}
-
+
public void setPasswordMinimumLength(ComponentName who, int length) {
synchronized (this) {
if (who == null) {
@@ -653,16 +814,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);
@@ -673,18 +834,267 @@ 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 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,
@@ -694,7 +1104,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,
@@ -709,16 +1119,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);
@@ -732,7 +1142,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return count;
}
}
-
+
public boolean resetPassword(String password, int flags) {
int quality;
synchronized (this) {
@@ -743,14 +1153,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) {
@@ -758,14 +1169,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();
@@ -783,10 +1266,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) {
@@ -796,16 +1279,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) {
@@ -817,16 +1300,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);
@@ -840,7 +1323,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,
@@ -857,7 +1340,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
void wipeDataLocked(int flags) {
try {
RecoverySystem.rebootWipeUserData(mContext);
@@ -865,7 +1348,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Slog.w(TAG, "Failed requesting data wipe", e);
}
}
-
+
public void wipeData(int flags) {
synchronized (this) {
// This API can only be called by an active device admin,
@@ -880,11 +1363,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) {
@@ -907,20 +1390,30 @@ 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();
sendAdminCommandLocked(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED,
@@ -931,11 +1424,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
-
+
public void reportFailedPasswordAttempt() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
-
+
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
@@ -952,11 +1445,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();
@@ -972,7 +1465,95 @@ 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();
+ exclusionList = exclusionList.trim();
+ ContentResolver res = mContext.getContentResolver();
+ Settings.Secure.putString(res, Settings.Secure.HTTP_PROXY, proxySpec);
+ Settings.Secure.putString(res, Settings.Secure.HTTP_PROXY_EXCLUSION_LIST, exclusionList);
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -983,12 +1564,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++) {
@@ -999,11 +1580,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/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 2e33cf1..c61baad 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -475,8 +475,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mContext.getContentResolver(),
Settings.Secure.ENABLED_INPUT_METHODS);
Slog.i(TAG, "Enabled input methods: " + enabledStr);
- if (enabledStr == null) {
- Slog.i(TAG, "Enabled input methods has not been set, enabling all");
+ final String defaultIme = Settings.Secure.getString(mContext
+ .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+ if (enabledStr == null || TextUtils.isEmpty(defaultIme)) {
+ Slog.i(TAG, "Enabled input methods or default IME has not been set, enabling all");
InputMethodInfo defIm = null;
StringBuilder sb = new StringBuilder(256);
final int N = mMethodList.size();
@@ -981,7 +983,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
void setInputMethodLocked(String id) {
InputMethodInfo info = mMethodMap.get(id);
if (info == null) {
- throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
+ throw new IllegalArgumentException("Unknown id: " + id);
}
if (id.equals(mCurMethodId)) {
@@ -1479,7 +1481,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
String defaultIme = Settings.Secure.getString(mContext
.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
- if (!map.containsKey(defaultIme)) {
+ if (!TextUtils.isEmpty(defaultIme) && !map.containsKey(defaultIme)) {
if (chooseNewDefaultIMELocked()) {
updateFromSettingsLocked();
}
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 3bcf427..5697181 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -465,10 +465,11 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
mEnabledProviders.add(passiveProvider.getName());
// initialize external network location and geocoder services
+ PackageManager pm = mContext. getPackageManager();
Resources resources = mContext.getResources();
String serviceName = resources.getString(
com.android.internal.R.string.config_networkLocationProvider);
- if (serviceName != null) {
+ if (serviceName != null && pm.resolveService(new Intent(serviceName), 0) != null) {
mNetworkLocationProvider =
new LocationProviderProxy(mContext, LocationManager.NETWORK_PROVIDER,
serviceName, mLocationHandler);
@@ -476,7 +477,7 @@ public class LocationManagerService extends ILocationManager.Stub implements Run
}
serviceName = resources.getString(com.android.internal.R.string.config_geocodeProvider);
- if (serviceName != null) {
+ if (serviceName != null && pm.resolveService(new Intent(serviceName), 0) != null) {
mGeocodeProvider = new GeocoderProxy(mContext, serviceName);
}
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index fded623..d604886 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -135,6 +135,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.
@@ -395,7 +397,9 @@ class MountService extends IMountService.Stub
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));
@@ -466,11 +470,13 @@ class MountService extends IMountService.Stub
Slog.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
return;
}
- // Update state on PackageManager
- if (Environment.MEDIA_UNMOUNTED.equals(state)) {
- mPms.updateExternalMediaStatus(false, false);
- } else if (Environment.MEDIA_MOUNTED.equals(state)) {
- mPms.updateExternalMediaStatus(true, false);
+ // Update state on PackageManager, but only of real events
+ if (!mEmulateExternalStorage) {
+ if (Environment.MEDIA_UNMOUNTED.equals(state)) {
+ mPms.updateExternalMediaStatus(false, false);
+ } else if (Environment.MEDIA_MOUNTED.equals(state)) {
+ mPms.updateExternalMediaStatus(true, false);
+ }
}
String oldState = mLegacyState;
mLegacyState = state;
@@ -970,6 +976,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");
diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java
index c452590..249c32b 100644
--- a/services/java/com/android/server/NativeDaemonConnector.java
+++ b/services/java/com/android/server/NativeDaemonConnector.java
@@ -128,11 +128,12 @@ final class NativeDaemonConnector implements Runnable {
Slog.e(TAG, String.format(
"Error handling '%s'", event), ex);
}
- }
- try {
- mResponseQueue.put(event);
- } catch (InterruptedException ex) {
- Slog.e(TAG, "Failed to put response onto queue", ex);
+ } else {
+ try {
+ mResponseQueue.put(event);
+ } catch (InterruptedException ex) {
+ Slog.e(TAG, "Failed to put response onto queue", ex);
+ }
}
} catch (NumberFormatException nfe) {
Slog.w(TAG, String.format("Bad msg (%s)", event));
@@ -209,6 +210,7 @@ final class NativeDaemonConnector implements Runnable {
*/
public synchronized ArrayList<String> doCommand(String cmd)
throws NativeDaemonConnectorException {
+ mResponseQueue.clear();
sendCommand(cmd);
ArrayList<String> response = new ArrayList<String>();
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index c156150..cb8fa35 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -203,10 +203,10 @@ class NetworkManagementService extends INetworkManagementService.Stub {
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;
+ int a = Integer.parseInt(parts[0]) << 24;
+ int b = Integer.parseInt(parts[1]) << 16;
+ int c = Integer.parseInt(parts[2]) << 8;
+ int d = Integer.parseInt(parts[3]) ;
return a | b | c | d;
} catch (NumberFormatException ex) {
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index ed8b90b..9d262b6 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -61,6 +61,7 @@ import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
+import android.widget.Toast;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -281,6 +282,10 @@ public class NotificationManagerService extends INotificationManager.Stub
Notification.FLAG_FOREGROUND_SERVICE);
}
+ public void onNotificationClear(String pkg, String tag, int id) {
+ cancelNotification(pkg, tag, id, 0, 0); // maybe add some flags?
+ }
+
public void onPanelRevealed() {
synchronized (mNotificationList) {
// sound
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 0ff33d1..8a876a2 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -9594,7 +9594,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/SamplingProfilerService.java b/services/java/com/android/server/SamplingProfilerService.java
new file mode 100644
index 0000000..26af7f7
--- /dev/null
+++ b/services/java/com/android/server/SamplingProfilerService.java
@@ -0,0 +1,118 @@
+/*
+ * 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_HZ),
+ 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 samplingProfilerHz = Settings.Secure.getInt(
+ mContentResolver, Settings.Secure.SAMPLING_PROFILER_HZ, 0);
+ // setting this secure property will start or stop sampling profiler,
+ // as well as adjust the frequency of taking snapshots.
+ SystemProperties.set("persist.sys.profiler_hz", samplingProfilerHz.toString());
+ }
+ }
+}
+
diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java
index 4177432..717c309 100644
--- a/services/java/com/android/server/StatusBarManagerService.java
+++ b/services/java/com/android/server/StatusBarManagerService.java
@@ -84,6 +84,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);
@@ -302,6 +303,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();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1a209e2..d44ce97 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -24,26 +24,26 @@ 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;
import android.content.ContentResolver;
import android.content.ContentService;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.IPackageManager;
import android.database.ContentObserver;
-import android.database.Cursor;
import android.media.AudioService;
-import android.os.*;
-import android.provider.Contacts.People;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.server.BluetoothA2dpService;
import android.server.BluetoothService;
import android.server.search.SearchManagerService;
import android.util.EventLog;
import android.util.Slog;
-import android.accounts.AccountManagerService;
import java.io.File;
import java.util.Timer;
@@ -51,11 +51,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() {
@@ -210,6 +207,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 {
@@ -320,6 +318,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));
@@ -327,11 +333,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,
@@ -418,10 +419,22 @@ class ServerThread extends Thread {
}
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, "Sip Service");
ServiceManager.addService("sip", new SipService(context));
} catch (Throwable e) {
- Slog.e(TAG, "Failure starting DiskStats Service", e);
+ Slog.e(TAG, "Failure starting SIP Service", e);
}
}
@@ -483,6 +496,7 @@ class ServerThread extends Thread {
final InputMethodManagerService immF = imm;
final RecognitionManagerService recognitionF = recognition;
final LocationManagerService locationF = location;
+ final CountryDetectorService countryDetectorF = countryDetector;
// 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
@@ -510,6 +524,7 @@ 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();
}
});
@@ -519,37 +534,7 @@ class ServerThread extends Thread {
}
}
-class DemoThread extends Thread
-{
- DemoThread(Context context)
- {
- mContext = context;
- }
-
- @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;
@@ -573,7 +558,7 @@ public class SystemServer
timer.schedule(new TimerTask() {
@Override
public void run() {
- SamplingProfilerIntegration.writeSnapshot("system_server");
+ SamplingProfilerIntegration.writeSnapshot("system_server", null);
}
}, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL);
}
@@ -581,7 +566,7 @@ public class SystemServer
// 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 7e23422..73234df 100644
--- a/services/java/com/android/server/TelephonyRegistry.java
+++ b/services/java/com/android/server/TelephonyRegistry.java
@@ -19,7 +19,7 @@ package com.android.server;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.net.NetworkUtils;
+import android.net.NetworkProperties;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -35,6 +35,7 @@ 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;
@@ -89,9 +90,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
private String mDataConnectionApn = "";
- private String[] mDataConnectionApnTypes = null;
+ private ArrayList<String> mConnectedApns;
- private String mDataConnectionInterfaceName = "";
+ private NetworkProperties mDataConnectionProperties;
private Bundle mCellLocation = new Bundle();
@@ -121,6 +122,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
mContext = context;
mBatteryStats = BatteryStatsService.getService();
+ mConnectedApns = new ArrayList<String>();
}
public void listen(String pkgForDebug, IPhoneStateListener callback, int events,
@@ -233,19 +235,20 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (!checkNotifyPermission("notifyCallState()")) {
return;
}
+ ArrayList<IBinder> removeList = new ArrayList<IBinder>();
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);
+ removeList.add(r.binder);
}
}
}
+ for (IBinder b : removeList) remove(b);
}
broadcastCallStateChanged(state, incomingNumber);
}
@@ -256,8 +259,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
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);
}
@@ -270,10 +272,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (!checkNotifyPermission("notifySignalStrength()")) {
return;
}
+ ArrayList<IBinder> removeList = new ArrayList<IBinder>();
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);
}
@@ -283,10 +285,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
: gsmSignalStrength));
} catch (RemoteException ex) {
- remove(r.binder);
+ removeList.add(r.binder);
}
}
}
+ for (IBinder b : removeList) remove(b);
}
broadcastSignalStrengthChanged(signalStrength);
}
@@ -295,18 +298,19 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (!checkNotifyPermission("notifyMessageWaitingChanged()")) {
return;
}
+ ArrayList<IBinder> removeList = new ArrayList<IBinder>();
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);
+ removeList.add(r.binder);
}
}
}
+ for (IBinder b : removeList) remove(b);
}
}
@@ -314,18 +318,19 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (!checkNotifyPermission("notifyCallForwardingChanged()")) {
return;
}
+ ArrayList<IBinder> removeList = new ArrayList<IBinder>();
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);
+ removeList.add(r.binder);
}
}
}
+ for (IBinder b : removeList) remove(b);
}
}
@@ -333,57 +338,81 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
if (!checkNotifyPermission("notifyDataActivity()" )) {
return;
}
+ ArrayList<IBinder> removeList = new ArrayList<IBinder>();
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);
+ removeList.add(r.binder);
}
}
}
+ for (IBinder b : removeList) remove(b);
}
}
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, NetworkProperties networkProperties,
+ int networkType) {
if (!checkNotifyPermission("notifyDataConnection()" )) {
return;
}
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 {
+ 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);
+ mDataConnectionProperties = networkProperties;
+ if (mDataConnectionNetworkType != networkType) {
+ mDataConnectionNetworkType = networkType;
+ modified = true;
+ }
+ if (modified) {
+ ArrayList<IBinder> removeList = new ArrayList<IBinder>();
+ for (Record r : mRecords) {
+ if ((r.events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) {
+ try {
+ r.callback.onDataConnectionStateChanged(state, networkType);
+ } catch (RemoteException ex) {
+ removeList.add(r.binder);
+ }
}
}
+ for (IBinder b : removeList) remove(b);
}
}
broadcastDataConnectionStateChanged(state, isDataConnectivityPossible, reason, apn,
- apnTypes, interfaceName, gateway);
+ apnType, networkProperties);
}
- 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();
@@ -395,7 +424,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
*/
- broadcastDataConnectionFailed(reason);
+ broadcastDataConnectionFailed(reason, apnType);
}
public void notifyCellLocation(Bundle cellLocation) {
@@ -404,8 +433,7 @@ 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);
}
@@ -416,7 +444,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
/**
* Copy the service state object so they can't mess it up in the local calls
*/
- public void sendServiceState(Record r, ServiceState state) {
+ private void sendServiceState(Record r, ServiceState state) {
try {
r.callback.onServiceStateChanged(new ServiceState(state));
} catch (RemoteException ex) {
@@ -462,11 +490,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
pw.println(" mDataConnectionPossible=" + mDataConnectionPossible);
pw.println(" mDataConnectionReason=" + mDataConnectionReason);
pw.println(" mDataConnectionApn=" + mDataConnectionApn);
- pw.println(" mDataConnectionInterfaceName=" + mDataConnectionInterfaceName);
+ pw.println(" mDataConnectionProperties=" + mDataConnectionProperties);
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));
}
}
@@ -537,7 +564,7 @@ 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, NetworkProperties networkProperties) {
// 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.
@@ -550,29 +577,23 @@ 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 (networkProperties != null) {
+ intent.putExtra(Phone.DATA_NETWORK_PROPERTIES_KEY, networkProperties);
+ NetworkInterface iface = networkProperties.getInterface();
+ if (iface != null) {
+ intent.putExtra(Phone.DATA_IFACE_NAME_KEY, iface.getName());
}
}
- intent.putExtra(Phone.DATA_APN_TYPES_KEY, types);
- intent.putExtra(Phone.DATA_IFACE_NAME_KEY, interfaceName);
- int gatewayAddr = 0;
- if (gateway != null) {
- gatewayAddr = NetworkUtils.v4StringToInt(gateway);
- }
- intent.putExtra(Phone.DATA_GATEWAY_KEY, gatewayAddr);
-
+ 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);
}
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
index d08fe9b..546e5f8 100644
--- a/services/java/com/android/server/UsbObserver.java
+++ b/services/java/com/android/server/UsbObserver.java
@@ -24,6 +24,7 @@ import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.os.UEventObserver;
+import android.provider.Mtp;
import android.provider.Settings;
import android.util.Log;
import android.util.Slog;
@@ -147,8 +148,43 @@ class UsbObserver extends UEventObserver {
}
}
+ private native void monitorUsbHostBus();
+
+ // called from JNI in monitorUsbHostBus()
+ private void usbCameraAdded(int deviceID) {
+ Intent intent = new Intent(Usb.ACTION_USB_CAMERA_ATTACHED,
+ Mtp.Device.getContentUri(deviceID));
+ Log.d(TAG, "usbCameraAdded, sending " + intent);
+ mContext.sendBroadcast(intent);
+ }
+
+ // called from JNI in monitorUsbHostBus()
+ private void usbCameraRemoved(int deviceID) {
+ Intent intent = new Intent(Usb.ACTION_USB_CAMERA_DETACHED,
+ Mtp.Device.getContentUri(deviceID));
+ Log.d(TAG, "usbCameraRemoved, sending " + intent);
+ mContext.sendBroadcast(intent);
+ }
+
+ private void initHostSupport() {
+ // Create a thread to call into native code to wait for USB host events.
+ // This thread will call us back on usbCameraAdded and usbCameraRemoved.
+ Runnable runnable = new Runnable() {
+ public void run() {
+ monitorUsbHostBus();
+ }
+ };
+ new Thread(null, runnable, "UsbObserver host thread").start();
+ }
+
void systemReady() {
synchronized (this) {
+ if (mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_hasUsbHostSupport)) {
+ // start monitoring for connected USB devices
+ initHostSupport();
+ }
+
update();
mSystemReady = true;
}
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 0eca082..8b02355 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,19 +16,9 @@
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;
@@ -38,69 +28,60 @@ 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.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.provider.Settings;
-import android.util.Slog;
import android.text.TextUtils;
+import android.util.Slog;
import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
import java.util.Set;
-import java.util.regex.Pattern;
+import java.util.concurrent.atomic.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.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;
@@ -109,15 +90,11 @@ public class WifiService extends IWifiManager.Stub {
private boolean mDeviceIdle;
private int mPluggedType;
- private enum DriverAction {DRIVER_UNLOAD, NO_DRIVER_UNLOAD};
-
// true if the user enabled Wifi while in airplane mode
- private boolean mAirplaneModeOverwridden;
+ private AtomicBoolean mAirplaneModeOverwridden = new AtomicBoolean(false);
private final LockList mLocks = new LockList();
// some wifi lock statistics
- private int mFullHighPerfLocksAcquired;
- private int mFullHighPerfLocksReleased;
private int mFullLocksAcquired;
private int mFullLocksReleased;
private int mScanLocksAcquired;
@@ -130,9 +107,7 @@ public class WifiService extends IWifiManager.Stub {
private final IBatteryStats mBatteryStats;
- private INetworkManagementService nwService;
ConnectivityManager mCm;
- private WifiWatchdogService mWifiWatchdogService = null;
private String[] mWifiRegexs;
/**
@@ -144,111 +119,89 @@ public class WifiService extends IWifiManager.Stub {
*/
private static final long DEFAULT_IDLE_MILLIS = 15 * 60 * 1000; /* 15 minutes */
- private static final String WAKELOCK_TAG = "WifiService";
-
/**
- * 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.
+ * Number of allowed radio frequency channels in various regulatory domains.
+ * This list is sufficient for 802.11b/g networks (2.4GHz range).
*/
- private static final int DEFAULT_WAKELOCK_TIMEOUT = 8000;
+ private static int[] sValidRegulatoryChannelCounts = new int[] {11, 13, 14};
- // 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 String ACTION_DEVICE_IDLE =
+ "com.android.server.WifiManager.action.DEVICE_IDLE";
- 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 boolean mIsReceiverRegistered = false;
- private final WifiHandler mWifiHandler;
+ NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
- /*
- * Cache of scan results objects (size is somewhat arbitrary)
+ // Variables relating to the 'available networks' notification
+ /**
+ * 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 SCAN_RESULT_CACHE_SIZE = 80;
- private final LinkedHashMap<String, ScanResult> mScanResultCache;
-
- /*
- * Character buffer used to parse scan results (optimization)
+ 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_BUFFER_SIZE = 512;
- private boolean mNeedReconfig;
-
- /*
- * Last UID that asked to enable WIFI.
+ private final long NOTIFICATION_REPEAT_DELAY_MS;
+ /**
+ * Whether the user has set the setting to show the 'available networks' notification.
*/
- private int mLastEnableUid = Process.myUid();
-
- /*
- * Last UID that asked to enable WIFI AP.
+ private boolean mNotificationEnabled;
+ /**
+ * Observes the user setting to keep {@link #mNotificationEnabled} in sync.
*/
- private int mLastApEnableUid = Process.myUid();
-
-
+ private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
/**
- * Number of allowed radio frequency channels in various regulatory domains.
- * This list is sufficient for 802.11b/g networks (2.4GHz range).
+ * The {@link System#currentTimeMillis()} must be at least this value for us
+ * to show the notification again.
*/
- private static int[] sValidRegulatoryChannelCounts = new int[] {11, 13, 14};
+ private long mNotificationRepeatTime;
+ /**
+ * The Notification object given to the NotificationManager.
+ */
+ private Notification mNotification;
+ /**
+ * Whether the notification is being shown, as set by us. That is, if the
+ * user cancels the notification, we will not receive the callback so this
+ * will still be true. We only guarantee if this is false, then the
+ * notification is not showing.
+ */
+ private boolean mNotificationShown;
+ /**
+ * The number of continuous scans that must occur before consider the
+ * supplicant in a scanning state. This allows supplicant to associate with
+ * remembered networks that are in the scan results.
+ */
+ private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
+ /**
+ * The number of scans since the last network state change. When this
+ * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the
+ * supplicant to actually be scanning. When the network state changes to
+ * something other than scanning, we reset this to 0.
+ */
+ private int mNumScansSinceNetworkStateChange;
- private static final String ACTION_DEVICE_IDLE =
- "com.android.server.WifiManager.action.DEVICE_IDLE";
- WifiService(Context context, WifiStateTracker tracker) {
+ WifiService(Context context) {
mContext = context;
- mWifiStateTracker = tracker;
- mWifiStateTracker.enableRssiPolling(true);
+ mWifiStateMachine = new WifiStateMachine(mContext);
+ mWifiStateMachine.enableRssiPolling(true);
mBatteryStats = BatteryStatsService.getService();
- IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
- nwService = INetworkManagementService.Stub.asInterface(b);
-
- 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();
- }
- };
-
- HandlerThread wifiThread = new HandlerThread("WifiService");
- wifiThread.start();
- mWifiHandler = new WifiHandler(wifiThread.getLooper());
-
- mWifiStateTracker.setWifiState(WIFI_STATE_DISABLED);
- mWifiApState = WIFI_AP_STATE_DISABLED;
-
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();
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);
@@ -263,14 +216,50 @@ 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();
}
/**
@@ -279,7 +268,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());
@@ -296,7 +285,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) {
@@ -316,17 +308,14 @@ public class WifiService extends IWifiManager.Stub {
}
} 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;
}
@@ -362,39 +351,38 @@ 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();
+ return mWifiStateMachine.syncPingSupplicant();
}
/**
* see {@link android.net.wifi.WifiManager#startScan()}
- * @return {@code true} if the operation succeeds
*/
- public boolean startScan(boolean forceActive) {
+ public void startScan(boolean forceActive) {
enforceChangePermission();
+ 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");
- switch (mWifiStateTracker.getSupplicantState()) {
- case DISCONNECTED:
- case INACTIVE:
- case SCANNING:
- case DORMANT:
- break;
- default:
- mWifiStateTracker.setScanResultHandling(
- WifiStateTracker.SUPPL_SCAN_HANDLING_LIST_ONLY);
- break;
- }
- return mWifiStateTracker.scan(forceActive);
+ }
+
+ private void enforceMulticastChangePermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE,
+ "WifiService");
}
/**
@@ -403,168 +391,41 @@ 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;
- }
+ 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(uid);
- } else if (wifiState == WIFI_STATE_DISABLED) {
- mBatteryStats.noteWifiOff(uid);
- }
- } 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},
@@ -575,66 +436,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;
@@ -652,7 +506,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;
@@ -668,143 +526,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(uid);
- } else if (wifiAPState == WIFI_AP_STATE_DISABLED) {
- mBatteryStats.noteWifiOff(uid);
- }
- } 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();
}
/**
@@ -813,217 +555,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();
}
/**
@@ -1033,280 +565,10 @@ 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;
+ return mWifiStateMachine.syncAddOrUpdateNetwork(config);
}
- 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);
- }
-
- return -1;
- }
-
- /**
+ /**
* See {@link android.net.wifi.WifiManager#removeNetwork(int)}
* @param netId the integer that identifies the network configuration
* to the supplicant
@@ -1314,8 +576,7 @@ public class WifiService extends IWifiManager.Stub {
*/
public boolean removeNetwork(int netId) {
enforceChangePermission();
-
- return mWifiStateTracker.removeNetwork(netId);
+ return mWifiStateMachine.syncRemoveNetwork(netId);
}
/**
@@ -1327,14 +588,7 @@ 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);
- }
- return result;
+ return mWifiStateMachine.syncEnableNetwork(netId, disableOthers);
}
/**
@@ -1345,8 +599,7 @@ public class WifiService extends IWifiManager.Stub {
*/
public boolean disableNetwork(int netId) {
enforceChangePermission();
-
- return mWifiStateTracker.disableNetwork(netId);
+ return mWifiStateMachine.syncDisableNetwork(netId);
}
/**
@@ -1359,7 +612,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();
}
/**
@@ -1369,180 +622,19 @@ public class WifiService extends IWifiManager.Stub {
*/
public List<ScanResult> getScanResults() {
enforceAccessPermission();
- String reply;
-
- reply = mWifiStateTracker.scanResults();
- if (reply == null) {
- return null;
- }
-
- 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;
- }
-
- /**
- * Parse the scan result line passed to us by wpa_supplicant (helper).
- * @param line the line to parse
- * @return the {@link ScanResult} object
- */
- 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;
- }
-
- /*
- * 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);
- }
- }
- }
-
- return scanResult;
- }
-
- /**
- * 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
- */
- WifiConfiguration parseScanFlags(String flags) {
- WifiConfiguration config = new WifiConfiguration();
-
- if (flags.length() == 0) {
- config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
- }
- // ... to be implemented
- return config;
+ return mWifiStateMachine.syncGetScanResultsList();
}
/**
* 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;
+ boolean result = true;
enforceChangePermission();
-
- 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;
+ return mWifiStateMachine.syncSaveConfig();
}
/**
@@ -1557,7 +649,7 @@ public class WifiService extends IWifiManager.Stub {
* @return {@code true} if the operation succeeds, {@code false} otherwise, e.g.,
* {@code numChannels} is outside the valid range.
*/
- public boolean setNumAllowedChannels(int numChannels, boolean persist) {
+ public synchronized boolean setNumAllowedChannels(int numChannels, boolean persist) {
Slog.i(TAG, "WifiService trying to setNumAllowed to "+numChannels+
" with persist set to "+persist);
enforceChangePermission();
@@ -1579,28 +671,15 @@ public class WifiService extends IWifiManager.Stub {
return false;
}
- if (mWifiHandler == null) return false;
-
- Message.obtain(mWifiHandler,
- MESSAGE_SET_CHANNELS, numChannels, (persist ? 1 : 0)).sendToTarget();
-
- return true;
- }
-
- /**
- * 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
- */
- 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);
+
+ mWifiStateMachine.setNumAllowedChannels(numChannels);
+
+ return true;
}
/**
@@ -1618,7 +697,7 @@ public class WifiService extends IWifiManager.Stub {
* Wi-Fi is not currently enabled), get the value from
* Settings.
*/
- numChannels = mWifiStateTracker.getNumAllowedChannels();
+ numChannels = mWifiStateMachine.getNumAllowedChannels();
if (numChannels < 0) {
numChannels = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS,
@@ -1644,7 +723,76 @@ public class WifiService extends IWifiManager.Stub {
*/
public DhcpInfo getDhcpInfo() {
enforceAccessPermission();
- return mWifiStateTracker.getDhcpInfo();
+ return mWifiStateMachine.syncGetDhcpInfo();
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#startWifi}
+ *
+ */
+ 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
+ */
+
+ mWifiStateMachine.setDriverStart(true);
+ mWifiStateMachine.reconnectCommand();
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#stopWifi}
+ *
+ */
+ public void stopWifi() {
+ enforceChangePermission();
+ /* 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
+ */
+ mWifiStateMachine.setDriverStart(false);
+ }
+
+
+ /**
+ * see {@link android.net.wifi.WifiManager#addToBlacklist}
+ *
+ */
+ public void addToBlacklist(String bssid) {
+ enforceChangePermission();
+
+ mWifiStateMachine.addToBlacklist(bssid);
+ }
+
+ /**
+ * see {@link android.net.wifi.WifiManager#clearBlacklist}
+ *
+ */
+ public void clearBlacklist() {
+ enforceChangePermission();
+
+ mWifiStateMachine.clearBlacklist();
+ }
+
+ public void connectNetworkWithId(int networkId) {
+ enforceChangePermission();
+ mWifiStateMachine.connectNetwork(networkId);
+ }
+
+ public void connectNetworkWithConfig(WifiConfiguration config) {
+ enforceChangePermission();
+ mWifiStateMachine.connectNetwork(config);
+ }
+
+ public void saveNetwork(WifiConfiguration config) {
+ enforceChangePermission();
+ mWifiStateMachine.saveNetwork(config);
+ }
+
+ public void forgetNetwork(int netId) {
+ enforceChangePermission();
+ mWifiStateMachine.forgetNetwork(netId);
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -1663,17 +811,11 @@ public class WifiService extends IWifiManager.Stub {
mAlarmManager.cancel(mIdleIntent);
mDeviceIdle = false;
mScreenOff = false;
- mWifiStateTracker.enableRssiPolling(true);
- /* DHCP or other temporary failures in the past can prevent
- * a disabled network from being connected to, enable on screen on
- */
- if (mWifiStateTracker.isAnyNetworkDisabled()) {
- sendEnableNetworksMessage();
- }
+ mWifiStateMachine.enableRssiPolling(true);
} else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
Slog.d(TAG, "ACTION_SCREEN_OFF");
mScreenOff = true;
- mWifiStateTracker.enableRssiPolling(false);
+ mWifiStateMachine.enableRssiPolling(false);
/*
* 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
@@ -1681,11 +823,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
Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for 120,000 ms");
@@ -1732,7 +874,7 @@ public class WifiService extends IWifiManager.Stub {
isBluetoothPlaying = true;
}
}
- mWifiStateTracker.setBluetoothScanMode(isBluetoothPlaying);
+ mWifiStateMachine.setBluetoothScanMode(isBluetoothPlaying);
} else {
return;
@@ -1784,85 +926,36 @@ 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 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 airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden.get();
boolean lockHeld = mLocks.hasLocks();
- int strongestLockMode = WifiManager.WIFI_MODE_FULL;
+ int strongestLockMode;
boolean wifiShouldBeEnabled = wifiEnabled && !airplaneMode;
boolean wifiShouldBeStarted = !mDeviceIdle || lockHeld;
-
- if (lockHeld) {
+ if (mDeviceIdle && lockHeld) {
strongestLockMode = mLocks.getStrongestLockMode();
- }
- /* If device is not idle, lockmode cannot be scan only */
- if (!mDeviceIdle && strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY) {
+ } else {
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) {
+ mWifiStateMachine.setWifiEnabled(true);
+ mWifiStateMachine.setScanOnlyMode(
+ strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY);
+ mWifiStateMachine.setDriverStart(true);
} else {
- sWakeLock.acquire();
- sendEnableMessage(false, false, mLastEnableUid);
+ mWifiStateMachine.requestCmWakeLock();
+ mWifiStateMachine.setDriverStart(false);
}
+ } else {
+ mWifiStateMachine.setWifiEnabled(false);
}
}
@@ -1900,81 +993,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:
- 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;
-
- }
- }
- }
-
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -1984,17 +1002,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) {
@@ -2008,33 +1026,14 @@ public class WifiService extends IWifiManager.Stub {
}
pw.println();
pw.println("Locks acquired: " + mFullLocksAcquired + " full, " +
- mFullHighPerfLocksAcquired + " full high perf, " +
mScanLocksAcquired + " scan");
pw.println("Locks released: " + mFullLocksReleased + " full, " +
- mFullHighPerfLocksReleased + " full high perf, " +
mScanLocksReleased + " scan");
pw.println();
pw.println("Locks held:");
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) {
super(lockMode, tag, binder);
@@ -2066,15 +1065,11 @@ public class WifiService extends IWifiManager.Stub {
if (mList.isEmpty()) {
return WifiManager.WIFI_MODE_FULL;
}
-
- if (mFullHighPerfLocksAcquired > mFullHighPerfLocksReleased) {
- return WifiManager.WIFI_MODE_FULL_HIGH_PERF;
- }
-
- if (mFullLocksAcquired > mFullLocksReleased) {
- return WifiManager.WIFI_MODE_FULL;
+ for (WifiLock l : mList) {
+ if (l.mMode == WifiManager.WIFI_MODE_FULL) {
+ return WifiManager.WIFI_MODE_FULL;
+ }
}
-
return WifiManager.WIFI_MODE_SCAN_ONLY;
}
@@ -2113,11 +1108,7 @@ public class WifiService extends IWifiManager.Stub {
public boolean acquireWifiLock(IBinder binder, int lockMode, String tag) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
- if (lockMode != WifiManager.WIFI_MODE_FULL &&
- lockMode != WifiManager.WIFI_MODE_SCAN_ONLY &&
- lockMode != WifiManager.WIFI_MODE_FULL_HIGH_PERF) {
- Slog.e(TAG, "Illegal argument, lockMode= " + lockMode);
- if (DBG) throw new IllegalArgumentException("lockMode=" + lockMode);
+ if (lockMode != WifiManager.WIFI_MODE_FULL && lockMode != WifiManager.WIFI_MODE_SCAN_ONLY) {
return false;
}
WifiLock wifiLock = new WifiLock(lockMode, tag, binder);
@@ -2139,12 +1130,6 @@ public class WifiService extends IWifiManager.Stub {
++mFullLocksAcquired;
mBatteryStats.noteFullWifiLockAcquired(uid);
break;
- case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
- ++mFullHighPerfLocksAcquired;
- /* Treat high power as a full lock for battery stats */
- mBatteryStats.noteFullWifiLockAcquired(uid);
- break;
-
case WifiManager.WIFI_MODE_SCAN_ONLY:
++mScanLocksAcquired;
mBatteryStats.noteScanWifiLockAcquired(uid);
@@ -2184,10 +1169,6 @@ public class WifiService extends IWifiManager.Stub {
++mFullLocksReleased;
mBatteryStats.noteFullWifiLockReleased(uid);
break;
- case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
- ++mFullHighPerfLocksReleased;
- mBatteryStats.noteFullWifiLockReleased(uid);
- break;
case WifiManager.WIFI_MODE_SCAN_ONLY:
++mScanLocksReleased;
mBatteryStats.noteScanWifiLockReleased(uid);
@@ -2258,7 +1239,7 @@ public class WifiService extends IWifiManager.Stub {
if (mMulticasters.size() != 0) {
return;
} else {
- mWifiStateTracker.startPacketFiltering();
+ mWifiStateMachine.startPacketFiltering();
}
}
}
@@ -2273,7 +1254,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();
@@ -2310,7 +1291,7 @@ public class WifiService extends IWifiManager.Stub {
removed.unlinkDeathRecipient();
}
if (mMulticasters.size() == 0) {
- mWifiStateTracker.startPacketFiltering();
+ mWifiStateMachine.startPacketFiltering();
}
Long ident = Binder.clearCallingIdentity();
@@ -2329,4 +1310,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/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 e259887..57f93c4 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -162,7 +162,6 @@ public class WindowManagerService extends IWindowManager.Stub
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 SHOW_TRANSACTIONS = false;
static final boolean HIDE_STACK_CRAWLS = true;
static final boolean MEASURE_LATENCY = false;
@@ -1798,18 +1797,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())) {
@@ -1915,7 +1907,7 @@ public class WindowManagerService extends IWindowManager.Stub
res = WindowManagerImpl.ADD_OKAY;
- final long origId = Binder.clearCallingIdentity();
+ origId = Binder.clearCallingIdentity();
if (addToken) {
mTokenMap.put(attrs.token, token);
@@ -1992,14 +1984,10 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- // 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;
@@ -3112,8 +3100,11 @@ public class WindowManagerService extends IWindowManager.Stub
} else if (currentConfig != null) {
// No obvious action we need to take, but if our current
- // state mismatches the activity maanager's, update it
+ // state mismatches the activity manager's, update it,
+ // disregarding font scale, which should remain set to
+ // the value of the previous configuration.
mTempConfiguration.setToDefaults();
+ mTempConfiguration.fontScale = currentConfig.fontScale;
if (computeNewConfigurationLocked(mTempConfiguration)) {
if (currentConfig.diff(mTempConfiguration) != 0) {
mWaitingForConfig = true;
@@ -4437,8 +4428,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;
}
}
@@ -5602,6 +5592,22 @@ public class WindowManagerService extends IWindowManager.Stub
}
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, mInitialDisplayWidth, mInitialDisplayHeight);
+ }
+
+ try {
+ mActivityManager.updateConfiguration(null);
+ } catch (RemoteException e) {
+ }
+
mPolicy.systemReady();
}
@@ -6879,7 +6885,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;
}
@@ -6983,14 +6989,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;
}
public boolean fillsScreenLw(int screenWidth, int screenHeight,
@@ -8511,6 +8515,11 @@ public class WindowManagerService extends IWindowManager.Stub
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();
@@ -9299,12 +9308,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);
@@ -9701,30 +9704,26 @@ 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);
- if (!mDisplayFrozen) {
- setHoldScreenLocked(holdScreen != null);
- if (screenBrightness < 0 || screenBrightness > 1.0f) {
- mPowerManager.setScreenBrightnessOverride(-1);
- } else {
- mPowerManager.setScreenBrightnessOverride((int)
- (screenBrightness * Power.BRIGHTNESS_ON));
- }
- if (buttonBrightness < 0 || buttonBrightness > 1.0f) {
- mPowerManager.setButtonBrightnessOverride(-1);
- } else {
- mPowerManager.setButtonBrightnessOverride((int)
- (buttonBrightness * Power.BRIGHTNESS_ON));
- }
- if (holdScreen != mHoldingScreenOn) {
- mHoldingScreenOn = holdScreen;
- Message m = mH.obtainMessage(H.HOLD_SCREEN_CHANGED, holdScreen);
- mH.sendMessage(m);
- }
+
+ setHoldScreenLocked(holdScreen != null);
+ if (screenBrightness < 0 || screenBrightness > 1.0f) {
+ mPowerManager.setScreenBrightnessOverride(-1);
+ } else {
+ mPowerManager.setScreenBrightnessOverride((int)
+ (screenBrightness * Power.BRIGHTNESS_ON));
+ }
+ if (buttonBrightness < 0 || buttonBrightness > 1.0f) {
+ mPowerManager.setButtonBrightnessOverride(-1);
+ } else {
+ 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 (mTurnOnScreen) {
@@ -10013,8 +10012,6 @@ public class WindowManagerService extends IWindowManager.Stub
mFreezeGcPending = now;
}
- if (DEBUG_FREEZE) Slog.v(TAG, "*** FREEZING DISPLAY", new RuntimeException());
-
mDisplayFrozen = true;
mInputMonitor.freezeInputDispatchingLw();
@@ -10041,8 +10038,6 @@ 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) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 21df14a..56159b5 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -2375,6 +2375,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
}
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);
@@ -11873,7 +11877,69 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
}
}
}
-
+
+ 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/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index eb0a8a9..53ce5c0 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -19,8 +19,8 @@ package com.android.server.connectivity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.bluetooth.BluetoothPan;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -32,9 +32,9 @@ import android.net.InterfaceConfiguration;
import android.net.IConnectivityManager;
import android.net.INetworkManagementEventObserver;
import android.net.NetworkInfo;
-import android.net.NetworkUtils;
import android.os.Binder;
import android.os.Environment;
+import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.INetworkManagementService;
@@ -53,6 +53,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.Set;
/**
* @hide
@@ -65,7 +66,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 = false;
private boolean mBooted = false;
//used to remember if we got connected before boot finished
@@ -74,6 +76,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;
@@ -93,6 +96,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
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.9";
+ private static final String DHCP_DEFAULT_RANGE3_STOP = "192.168.44.254";
private String[] mDnsServers;
private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8";
@@ -110,6 +115,14 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
private boolean mUsbMassStorageOff; // track the status of USB Mass Storage
private boolean mUsbConnected; // track the status of USB connection
+ // mUsbHandler message
+ static final int USB_STATE_CHANGE = 1;
+ static final int USB_DISCONNECTED = 0;
+ static final int USB_CONNECTED = 1;
+
+ // Time to delay before processing USB disconnect events
+ static final long USB_DISCONNECT_DELAY = 1000;
+
public Tethering(Context context, Looper looper) {
Log.d(TAG, "Tethering starting");
mContext = context;
@@ -152,11 +165,13 @@ 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[6];
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;
}
mDunRequired = false; // resample when we turn on
@@ -164,6 +179,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
com.android.internal.R.array.config_tether_usb_regexs);
mTetherableWifiRegexs = context.getResources().getStringArray(
com.android.internal.R.array.config_tether_wifi_regexs);
+ mTetherableBluetoothRegexs = context.getResources().getStringArray(
+ com.android.internal.R.array.config_tether_bluetooth_regexs);
mUpstreamIfaceRegexs = context.getResources().getStringArray(
com.android.internal.R.array.config_tether_upstream_regexs);
@@ -174,7 +191,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
}
public void interfaceLinkStatusChanged(String iface, boolean link) {
- Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link);
+ if (DEBUG) Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link);
boolean found = false;
boolean usb = false;
if (isWifi(iface)) {
@@ -182,6 +199,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
} else if (isUsb(iface)) {
found = true;
usb = true;
+ } else if (isBluetooth(iface)) {
+ found = true;
}
if (found == false) return;
@@ -216,6 +235,12 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
return false;
}
+ public boolean isBluetooth(String iface) {
+ for (String regex : mTetherableBluetoothRegexs) {
+ if (iface.matches(regex)) return true;
+ }
+ return false;
+ }
public void interfaceAdded(String iface) {
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
@@ -228,29 +253,34 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
found = true;
usb = true;
}
+ if (isBluetooth(iface)) {
+ found = true;
+ }
if (found == false) {
- Log.d(TAG, iface + " is not a tetherable iface, ignoring");
+ if (DEBUG) Log.d(TAG, iface + " is not a tetherable iface, ignoring");
return;
}
synchronized (mIfaces) {
TetherInterfaceSM sm = mIfaces.get(iface);
if (sm != null) {
- Log.e(TAG, "active iface (" + iface + ") reported as added, ignoring");
+ if (DEBUG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring");
return;
}
sm = new TetherInterfaceSM(iface, mLooper, usb);
mIfaces.put(iface, sm);
sm.start();
}
- Log.d(TAG, "interfaceAdded :" + iface);
+ if (DEBUG) Log.d(TAG, "interfaceAdded :" + iface);
}
public void interfaceRemoved(String iface) {
synchronized (mIfaces) {
TetherInterfaceSM sm = mIfaces.get(iface);
if (sm == null) {
- Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring");
+ if (DEBUG) {
+ Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring");
+ }
return;
}
sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN);
@@ -321,13 +351,14 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
boolean wifiTethered = false;
boolean usbTethered = false;
+ boolean bluetoothTethered = false;
synchronized (mIfaces) {
Set ifaces = mIfaces.keySet();
for (Object iface : ifaces) {
TetherInterfaceSM sm = mIfaces.get(iface);
if (sm != null) {
- if(sm.isErrored()) {
+ if (sm.isErrored()) {
erroredList.add((String)iface);
} else if (sm.isAvailable()) {
availableList.add((String)iface);
@@ -336,6 +367,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
usbTethered = true;
} else if (isWifi((String)iface)) {
wifiTethered = true;
+ } else if (isBluetooth((String)iface)) {
+ bluetoothTethered = true;
}
activeList.add((String)iface);
}
@@ -350,17 +383,25 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ERRORED_TETHER,
erroredList);
mContext.sendStickyBroadcast(broadcast);
- Log.d(TAG, "sendTetherStateChangedBroadcast " + availableList.size() + ", " +
- activeList.size() + ", " + erroredList.size());
+ if (DEBUG) {
+ Log.d(TAG, "sendTetherStateChangedBroadcast " + availableList.size() + ", " +
+ activeList.size() + ", " + erroredList.size());
+ }
if (usbTethered) {
- if (wifiTethered) {
+ if (wifiTethered || bluetoothTethered) {
showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_general);
} else {
showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_usb);
}
} else if (wifiTethered) {
- showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_wifi);
+ if (bluetoothTethered) {
+ showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_general);
+ } else {
+ showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_wifi);
+ }
+ } else if (bluetoothTethered) {
+ showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_bluetooth);
} else {
clearTetheredNotification();
}
@@ -391,7 +432,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;
}
@@ -421,12 +462,25 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
}
}
+ private Handler mUsbHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ mUsbConnected = (msg.arg1 == USB_CONNECTED);
+ updateUsbStatus();
+ }
+ };
+
private class StateReceiver extends BroadcastReceiver {
public void onReceive(Context content, Intent intent) {
String action = intent.getAction();
if (action.equals(Usb.ACTION_USB_STATE)) {
- mUsbConnected = intent.getExtras().getBoolean(Usb.USB_CONNECTED);
- updateUsbStatus();
+ // process connect events immediately, but delay handling disconnects
+ // to debounce USB configuration changes
+ boolean connected = intent.getExtras().getBoolean(Usb.USB_CONNECTED);
+ Message msg = Message.obtain(mUsbHandler, USB_STATE_CHANGE,
+ (connected ? USB_CONNECTED : USB_DISCONNECTED), 0);
+ mUsbHandler.removeMessages(USB_STATE_CHANGE);
+ mUsbHandler.sendMessageDelayed(msg, connected ? 0 : USB_DISCONNECT_DELAY);
} else if (action.equals(Intent.ACTION_MEDIA_SHARED)) {
mUsbMassStorageOff = false;
updateUsbStatus();
@@ -435,7 +489,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
mUsbMassStorageOff = true;
updateUsbStatus();
} else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
- Log.d(TAG, "Tethering got 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;
@@ -466,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);
@@ -493,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);
@@ -549,6 +603,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
return mTetherableWifiRegexs;
}
+ public String[] getTetherableBluetoothRegexs() {
+ return mTetherableBluetoothRegexs;
+ }
+
public String[] getUpstreamIfaceRegexs() {
return mUpstreamIfaceRegexs;
}
@@ -740,7 +798,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:
@@ -781,7 +839,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?
@@ -833,7 +891,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();
@@ -844,7 +902,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) {
@@ -887,7 +945,7 @@ 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);
@@ -960,7 +1018,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)) {
@@ -1198,8 +1256,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b);
mConnectionRequested = false;
- Log.d(TAG, "chooseUpstreamType(" + tryCell + "), dunRequired ="
- + mDunRequired + ", iface=" + iface);
+ if (DEBUG) {
+ Log.d(TAG, "chooseUpstreamType(" + tryCell + "), dunRequired ="
+ + mDunRequired + ", iface=" + iface);
+ }
if (iface != null) {
try {
if (mDunRequired) {
@@ -1207,7 +1267,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();
@@ -1219,11 +1279,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();
@@ -1245,7 +1305,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
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;
@@ -1262,19 +1322,19 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
}
@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);
@@ -1304,7 +1364,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:
@@ -1332,8 +1392,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub {
// 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 (DEBUG) {
+ Log.d(TAG, "renewing mobile connection - requeuing for another " +
+ CELL_CONNECTION_RENEW_MS + "ms");
+ }
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..e692f8d
--- /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 java.util.Locale;
+import java.util.Timer;
+import java.util.TimerTask;
+
+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;
+
+/**
+ * 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.isImplemented();
+ }
+}
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 cdc0a6f..459551d 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -8,6 +8,7 @@ LOCAL_SRC_FILES:= \
com_android_server_LightsService.cpp \
com_android_server_PowerManagerService.cpp \
com_android_server_SystemServer.cpp \
+ com_android_server_UsbObserver.cpp \
com_android_server_VibratorService.cpp \
com_android_server_location_GpsLocationProvider.cpp \
onload.cpp
@@ -25,6 +26,8 @@ LOCAL_SHARED_LIBRARIES := \
libutils \
libui
+LOCAL_STATIC_LIBRARIES := libusbhost
+
ifeq ($(TARGET_SIMULATOR),true)
ifeq ($(TARGET_OS),linux)
ifeq ($(TARGET_ARCH),x86)
diff --git a/services/jni/com_android_server_UsbObserver.cpp b/services/jni/com_android_server_UsbObserver.cpp
new file mode 100644
index 0000000..7c478d5
--- /dev/null
+++ b/services/jni/com_android_server_UsbObserver.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "UsbObserver"
+#include "utils/Log.h"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "utils/Vector.h"
+
+#include <usbhost/usbhost.h>
+#include <linux/version.h>
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 20)
+#include <linux/usb/ch9.h>
+#else
+#include <linux/usb_ch9.h>
+#endif
+
+#include <stdio.h>
+
+namespace android
+{
+
+static jmethodID method_usbCameraAdded;
+static jmethodID method_usbCameraRemoved;
+
+Vector<int> mDeviceList;
+
+static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
+ if (env->ExceptionCheck()) {
+ LOGE("An exception was thrown by callback '%s'.", methodName);
+ LOGE_EX(env);
+ env->ExceptionClear();
+ }
+}
+
+static int usb_device_added(const char *devname, void* client_data) {
+ // check to see if it is a camera
+ struct usb_descriptor_header* desc;
+ struct usb_descriptor_iter iter;
+
+ struct usb_device *device = usb_device_open(devname);
+ if (!device) {
+ LOGE("usb_device_open failed\n");
+ return 0;
+ }
+
+ usb_descriptor_iter_init(device, &iter);
+
+ while ((desc = usb_descriptor_iter_next(&iter)) != NULL) {
+ if (desc->bDescriptorType == USB_DT_INTERFACE) {
+ struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)desc;
+
+ if (interface->bInterfaceClass == USB_CLASS_STILL_IMAGE &&
+ interface->bInterfaceSubClass == 1 && // Still Image Capture
+ interface->bInterfaceProtocol == 1) // Picture Transfer Protocol (PIMA 15470)
+ {
+ LOGD("Found camera: \"%s\" \"%s\"\n", usb_device_get_manufacturer_name(device),
+ usb_device_get_product_name(device));
+
+ // interface should be followed by three endpoints
+ struct usb_endpoint_descriptor *ep;
+ struct usb_endpoint_descriptor *ep_in_desc = NULL;
+ struct usb_endpoint_descriptor *ep_out_desc = NULL;
+ struct usb_endpoint_descriptor *ep_intr_desc = NULL;
+ for (int i = 0; i < 3; i++) {
+ ep = (struct usb_endpoint_descriptor *)usb_descriptor_iter_next(&iter);
+ if (!ep || ep->bDescriptorType != USB_DT_ENDPOINT) {
+ LOGE("endpoints not found\n");
+ goto done;
+ }
+ if (ep->bmAttributes == USB_ENDPOINT_XFER_BULK) {
+ if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
+ ep_in_desc = ep;
+ else
+ ep_out_desc = ep;
+ } else if (ep->bmAttributes == USB_ENDPOINT_XFER_INT &&
+ ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) {
+ ep_intr_desc = ep;
+ }
+ }
+ if (!ep_in_desc || !ep_out_desc || !ep_intr_desc) {
+ LOGE("endpoints not found\n");
+ goto done;
+ }
+
+ // if we got here, we found a camera
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jobject thiz = (jobject)client_data;
+
+ int id = usb_device_get_unique_id_from_name(devname);
+ mDeviceList.add(id);
+
+ env->CallVoidMethod(thiz, method_usbCameraAdded, id);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ }
+ }
+ }
+done:
+ usb_device_close(device);
+ return 0;
+}
+
+static int usb_device_removed(const char *devname, void* client_data) {
+ int id = usb_device_get_unique_id_from_name(devname);
+
+ // see if it is a device we know about
+ for (int i = 0; i < mDeviceList.size(); i++) {
+ if (id == mDeviceList[i]) {
+ mDeviceList.removeAt(i);
+
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jobject thiz = (jobject)client_data;
+
+ env->CallVoidMethod(thiz, method_usbCameraRemoved, id);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ break;
+ }
+ }
+ return 0;
+}
+
+static void android_server_UsbObserver_monitorUsbHostBus(JNIEnv *env, jobject thiz)
+{
+ struct usb_host_context* context = usb_host_init();
+ if (!context) {
+ LOGE("usb_host_init failed");
+ return;
+ }
+ // this will never return so it is safe to pass thiz directly
+ usb_host_run(context, usb_device_added, usb_device_removed, NULL, (void *)thiz);
+}
+
+static JNINativeMethod method_table[] = {
+ { "monitorUsbHostBus", "()V", (void*)android_server_UsbObserver_monitorUsbHostBus }
+};
+
+int register_android_server_UsbObserver(JNIEnv *env)
+{
+ jclass clazz = env->FindClass("com/android/server/UsbObserver");
+ if (clazz == NULL) {
+ LOGE("Can't find com/android/server/UsbObserver");
+ return -1;
+ }
+ method_usbCameraAdded = env->GetMethodID(clazz, "usbCameraAdded", "(I)V");
+ if (method_usbCameraAdded == NULL) {
+ LOGE("Can't find usbCameraAdded");
+ return -1;
+ }
+ method_usbCameraRemoved = env->GetMethodID(clazz, "usbCameraRemoved", "(I)V");
+ if (method_usbCameraRemoved == NULL) {
+ LOGE("Can't find usbCameraRemoved");
+ return -1;
+ }
+
+ return jniRegisterNativeMethods(env, "com/android/server/UsbObserver",
+ method_table, NELEM(method_table));
+}
+
+};
diff --git a/services/jni/com_android_server_location_GpsLocationProvider.cpp b/services/jni/com_android_server_location_GpsLocationProvider.cpp
index 59d7cde..93068e6 100755
--- a/services/jni/com_android_server_location_GpsLocationProvider.cpp
+++ b/services/jni/com_android_server_location_GpsLocationProvider.cpp
@@ -245,9 +245,9 @@ static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject o
sAGpsInterface->init(&sAGpsCallbacks);
if (!sGpsNiInterface)
- sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
+ sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
if (sGpsNiInterface)
- sGpsNiInterface->init(&sGpsNiCallbacks);
+ sGpsNiInterface->init(&sGpsNiCallbacks);
if (!sGpsDebugInterface)
sGpsDebugInterface = (const GpsDebugInterface*)sGpsInterface->get_extension(GPS_DEBUG_INTERFACE);
@@ -413,12 +413,10 @@ static void android_location_GpsLocationProvider_set_agps_server(JNIEnv* env, jo
static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* env, jobject obj,
jint notifId, jint response)
{
- if (!sGpsNiInterface) {
+ if (!sGpsNiInterface)
sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
- }
- if (sGpsNiInterface) {
+ if (sGpsNiInterface)
sGpsNiInterface->respond(notifId, response);
- }
}
static jstring android_location_GpsLocationProvider_get_internal_state(JNIEnv* env, jobject obj)
diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp
index cd4f0a4..3502aca 100644
--- a/services/jni/onload.cpp
+++ b/services/jni/onload.cpp
@@ -9,6 +9,7 @@ int register_android_server_BatteryService(JNIEnv* env);
int register_android_server_InputManager(JNIEnv* env);
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
+int register_android_server_UsbObserver(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_location_GpsLocationProvider(JNIEnv* env);
@@ -32,6 +33,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
register_android_server_LightsService(env);
register_android_server_AlarmManagerService(env);
register_android_server_BatteryService(env);
+ register_android_server_UsbObserver(env);
register_android_server_VibratorService(env);
register_android_server_SystemServer(env);
register_android_server_location_GpsLocationProvider(env);
diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk
index a14bfb5..e4825d0 100644
--- a/services/surfaceflinger/Android.mk
+++ b/services/surfaceflinger/Android.mk
@@ -5,6 +5,7 @@ LOCAL_SRC_FILES:= \
clz.cpp.arm \
DisplayHardware/DisplayHardware.cpp \
DisplayHardware/DisplayHardwareBase.cpp \
+ DisplayHardware/HWComposer.cpp \
BlurFilter.cpp.arm \
GLExtensions.cpp \
Layer.cpp \
@@ -21,7 +22,7 @@ LOCAL_CFLAGS:= -DLOG_TAG=\"SurfaceFlinger\"
LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
ifeq ($(TARGET_BOARD_PLATFORM), omap3)
- LOCAL_CFLAGS += -DNO_RGBX_8888
+ LOCAL_CFLAGS += -DNO_RGBX_8888 -DHAS_PUSH_BUFFERS
endif
# need "-lrt" on Linux simulator to pick up clock_gettime
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
index 2eac0a8..166c528 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
@@ -36,11 +36,11 @@
#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 +76,7 @@ DisplayHardware::DisplayHardware(
const sp<SurfaceFlinger>& flinger,
uint32_t dpy)
: DisplayHardwareBase(flinger, dpy),
- mFlags(0)
+ mFlags(0), mHwc(0)
{
init(dpy);
}
@@ -262,6 +262,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;
}
/*
@@ -317,7 +328,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
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.h b/services/surfaceflinger/DisplayHardware/DisplayHardware.h
index 66bf521..f2cfd2d 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.h
@@ -34,12 +34,11 @@
#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
{
@@ -80,6 +79,9 @@ public:
uint32_t getPageFlipCount() const;
EGLDisplay getEGLDisplay() const { return mDisplay; }
overlay_control_device_t* getOverlayEngine() const { return mOverlayEngine; }
+
+ // Hardware Composer
+ HWComposer& getHwComposer() const;
status_t compositionComplete() const;
@@ -107,6 +109,8 @@ private:
GLint mMaxViewportDims;
GLint mMaxTextureSize;
+ HWComposer* mHwc;
+
sp<FramebufferNativeWindow> mNativeWindow;
overlay_control_device_t* mOverlayEngine;
};
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
new file mode 100644
index 0000000..0291d78
--- /dev/null
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include <utils/Errors.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);
+ mList->flags &= ~HWC_GEOMETRY_CHANGED;
+ 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;
+}
+
+// ---------------------------------------------------------------------------
+}; // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
new file mode 100644
index 0000000..c5d5c2b
--- /dev/null
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -0,0 +1,68 @@
+/*
+ * 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 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;
+
+
+ size_t getNumLayers() const;
+ hwc_layer_t* getLayers() 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 629d993..3720e1669 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
@@ -177,6 +178,62 @@ 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;
+ }
+
+ hwcl->transform = mOrientation;
+
+ 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 situation can happen if we ran out of memory for instance.
+ // not much we can do. continue to use whatever texture was bound
+ // to this context.
+ hwcl->handle = NULL;
+ return;
+ }
+ hwcl->handle = const_cast<native_handle_t*>(buffer->handle);
+ // TODO: set the crop value properly
+ 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());
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index e1d283b..188da6a 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 onDraw(const Region& clip) const;
virtual uint32_t doTransaction(uint32_t transactionFlags);
virtual void lockPageFlip(bool& recomputeVisibleRegions);
diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp
index 91ac915..043d54d 100644
--- a/services/surfaceflinger/LayerBase.cpp
+++ b/services/surfaceflinger/LayerBase.cpp
@@ -307,6 +307,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
diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h
index 22bf857..dd1cd05 100644
--- a/services/surfaceflinger/LayerBase.h
+++ b/services/surfaceflinger/LayerBase.h
@@ -35,6 +35,8 @@
#include <pixelflinger/pixelflinger.h>
+#include <hardware/hwcomposer.h>
+
#include "Transform.h"
namespace android {
@@ -108,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().
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 637ae48..a78d9b9 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -52,6 +52,7 @@
#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
@@ -76,6 +77,7 @@ SurfaceFlinger::SurfaceFlinger()
mAccessSurfaceFlinger("android.permission.ACCESS_SURFACE_FLINGER"),
mDump("android.permission.DUMP"),
mVisibleRegionsDirty(false),
+ mHwWorkListDirty(false),
mDeferReleaseConsole(false),
mFreezeDisplay(false),
mFreezeCount(0),
@@ -163,7 +165,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");
}
@@ -199,10 +201,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
@@ -231,7 +233,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);
@@ -265,7 +267,7 @@ status_t SurfaceFlinger::readyToRun()
// start boot animation
property_set("ctl.start", "bootanim");
-
+
return NO_ERROR;
}
@@ -368,6 +370,11 @@ 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())) {
// repaint the framebuffer (if needed)
@@ -443,6 +450,7 @@ void SurfaceFlinger::handleTransaction(uint32_t transactionFlags)
handleTransactionLocked(transactionFlags, ditchedLayers);
mLastTransactionTime = systemTime() - now;
mDebugInTransaction = 0;
+ mHwWorkListDirty = true;
// here the transaction has been committed
}
@@ -450,6 +458,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) {
@@ -653,7 +662,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);
@@ -683,8 +692,8 @@ void SurfaceFlinger::commitTransaction()
void SurfaceFlinger::handlePageFlip()
{
bool visibleRegions = mVisibleRegionsDirty;
- LayerVector& currentLayers = const_cast<LayerVector&>(
- mDrawingState.layersSortedByZ);
+ LayerVector& currentLayers(
+ const_cast<LayerVector&>(mDrawingState.layersSortedByZ));
visibleRegions |= lockPageFlip(currentLayers);
const DisplayHardware& hw = graphicPlane(0).displayHardware();
@@ -707,6 +716,7 @@ void SurfaceFlinger::handlePageFlip()
mWormholeRegion = screenRegion.subtract(opaqueRegion);
mVisibleRegionsDirty = false;
+ mHwWorkListDirty = true;
}
unlockPageFlip(currentLayers);
@@ -737,6 +747,20 @@ 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]);
+ }
+ }
+}
void SurfaceFlinger::handleRepaint()
{
@@ -757,8 +781,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
@@ -801,9 +825,72 @@ 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]);
+ if (cur[i].hints & HWC_HINT_CLEAR_FB) {
+ if (!(layer->needsBlending())) {
+ transparent.orSelf(layer->visibleRegionScreen);
+ }
+ }
+ }
+ err = hwc.prepare();
+ LOGE_IF(err, "HWComposer::prepare failed (%s)", strerror(-err));
+ }
+
+ /*
+ * 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()) {
@@ -1042,7 +1129,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) {
@@ -1086,7 +1173,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))
@@ -1119,14 +1206,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 HAS_PUSH_BUFFERS
if (UNLIKELY(flags & ePushBuffers)) {
layer = createPushBuffersSurface(client, d, w, h, flags);
- } else {
+ } else
+#endif
+ {
normalLayer = createNormalSurface(client, d, w, h, flags, format);
layer = normalLayer;
}
@@ -1145,7 +1235,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;
@@ -1229,7 +1319,7 @@ 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).
@@ -1250,7 +1340,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;
@@ -1263,9 +1353,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.
@@ -1380,7 +1470,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);
@@ -1655,12 +1745,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 {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 8ecfc01..8e286e5 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -296,6 +296,7 @@ private:
void handlePageFlip();
bool lockPageFlip(const LayerVector& currentLayers);
void unlockPageFlip(const LayerVector& currentLayers);
+ void handleWorkList();
void handleRepaint();
void postFramebuffer();
void composeSurfaces(const Region& dirty);
@@ -370,6 +371,7 @@ private:
Region mInvalidRegion;
Region mWormholeRegion;
bool mVisibleRegionsDirty;
+ bool mHwWorkListDirty;
bool mDeferReleaseConsole;
bool mFreezeDisplay;
int32_t mFreezeCount;
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..f6e3a82
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java
@@ -0,0 +1,729 @@
+/*
+ * 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 package name.
+ */
+ private static String sPackageName;
+
+ /**
+ * The interface used to talk to the tested service.
+ */
+ private IAccessibilityManager mManagerService;
+
+ @Override
+ public void setContext(Context context) {
+ super.setContext(context);
+ if (sPackageName == null) {
+ sPackageName = context.getPackageName();
+ }
+ }
+
+ /**
+ * 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 final String sComponentName = new ComponentName(
+ sPackageName,
+ MyFirstMockAccessibilityService.class.getName()
+ ).flattenToShortString();
+
+ /**
+ * 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 final String sComponentName = new ComponentName(
+ sPackageName,
+ MySecondMockAccessibilityService.class.getName()
+ ).flattenToShortString();
+
+ /**
+ * 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;
+ }
+}