diff options
Diffstat (limited to 'services')
63 files changed, 6689 insertions, 2289 deletions
diff --git a/services/audioflinger/AudioPolicyManagerBase.cpp b/services/audioflinger/AudioPolicyManagerBase.cpp index 425ca31..65d9ef7 100644 --- a/services/audioflinger/AudioPolicyManagerBase.cpp +++ b/services/audioflinger/AudioPolicyManagerBase.cpp @@ -1017,8 +1017,8 @@ AudioPolicyManagerBase::AudioPolicyManagerBase(AudioPolicyClientInterface *clien #ifdef AUDIO_POLICY_TEST Thread(false), #endif //AUDIO_POLICY_TEST - mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), mMusicStopTime(0), - mLimitRingtoneVolume(false), mTotalEffectsCpuLoad(0), mTotalEffectsMemory(0) + mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), mMusicStopTime(0), mLimitRingtoneVolume(false), + mLastVoiceVolume(-1.0f), mTotalEffectsCpuLoad(0), mTotalEffectsMemory(0) { mpClientInterface = clientInterface; @@ -1835,29 +1835,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 58209fd..808c679 100644 --- a/services/camera/libcameraservice/CameraService.cpp +++ b/services/camera/libcameraservice/CameraService.cpp @@ -26,6 +26,7 @@ #include <binder/MemoryBase.h> #include <binder/MemoryHeapBase.h> #include <cutils/atomic.h> +#include <cutils/properties.h> #include <hardware/hardware.h> #include <media/AudioSystem.h> #include <media/mediaplayer.h> @@ -320,6 +321,7 @@ CameraService::Client::Client(const sp<CameraService>& cameraService, mPreviewCallbackFlag = FRAME_CALLBACK_FLAG_NOOP; mOrientation = 0; mOrientationChanged = false; + mPlayShutterSound = true; cameraService->setCameraBusy(cameraId); cameraService->loadSound(); LOG1("Client::Client X (pid %d)", callingPid); @@ -337,18 +339,6 @@ CameraService::Client::~Client() { int callingPid = getCallingPid(); LOG1("Client::~Client E (pid %d, this %p)", callingPid, this); - if (mSurface != 0 && !mUseOverlay) { - pthread_t thr; - // We unregister the buffers in a different thread because binder does - // not let us make sychronous transactions in a binder destructor (that - // is, upon our reaching a refcount of zero.) - pthread_create(&thr, - NULL, // attr - unregister_surface, - mSurface.get()); - pthread_join(thr, NULL); - } - // set mClientPid to let disconnet() tear down the hardware mClientPid = callingPid; disconnect(); @@ -466,6 +456,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); @@ -476,8 +471,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(); @@ -487,7 +482,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; } @@ -498,44 +493,28 @@ status_t CameraService::Client::setPreviewDisplay(const sp<ISurface>& surface) { sp<Overlay> dummy; mHardware->setOverlay(dummy); mOverlayRef = 0; - } 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()); @@ -593,16 +572,10 @@ void CameraService::Client::setPreviewCallbackFlag(int callback_flag) { if (checkPidAndHardware() != NO_ERROR) return; mPreviewCallbackFlag = callback_flag; - - // If we don't use overlay, we always need the preview frame for display. - // If we do use overlay, we only need the preview frame if the user - // wants the data. - if (mUseOverlay) { - if(mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ENABLE_MASK) { - enableMsgType(CAMERA_MSG_PREVIEW_FRAME); - } else { - disableMsgType(CAMERA_MSG_PREVIEW_FRAME); - } + if (mPreviewCallbackFlag & FRAME_CALLBACK_FLAG_ENABLE_MASK) { + enableMsgType(CAMERA_MSG_PREVIEW_FRAME); + } else { + disableMsgType(CAMERA_MSG_PREVIEW_FRAME); } } @@ -627,14 +600,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(); @@ -660,16 +633,9 @@ status_t CameraService::Client::startPreviewMode() { if (result != NO_ERROR) return result; result = mHardware->startPreview(); } else { - enableMsgType(CAMERA_MSG_PREVIEW_FRAME); + // XXX: Set the orientation of the ANativeWindow. + mHardware->setPreviewWindow(mPreviewWindow); 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; } @@ -707,13 +673,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(); } @@ -811,6 +774,35 @@ String8 CameraService::Client::getParameters() const { return params; } +// enable shutter sound +status_t CameraService::Client::enableShutterSound(bool enable) { + LOG1("enableShutterSound (pid %d)", getCallingPid()); + + status_t result = checkPidAndHardware(); + if (result != NO_ERROR) return result; + + if (enable) { + mPlayShutterSound = true; + return OK; + } + + // Disabling shutter sound may not be allowed. In that case only + // allow the mediaserver process to disable the sound. + char value[PROPERTY_VALUE_MAX]; + property_get("ro.camera.sound.forced", value, "0"); + if (strcmp(value, "0") != 0) { + // Disabling shutter sound is not allowed. Deny if the current + // process is not mediaserver. + if (getCallingPid() != getpid()) { + LOGE("Failed to disable shutter sound. Permission denied (pid %d)", getCallingPid()); + return PERMISSION_DENIED; + } + } + + mPlayShutterSound = false; + return OK; +} + status_t CameraService::Client::sendCommand(int32_t cmd, int32_t arg1, int32_t arg2) { LOG1("sendCommand (pid %d)", getCallingPid()); int orientation; @@ -844,6 +836,20 @@ status_t CameraService::Client::sendCommand(int32_t cmd, int32_t arg1, int32_t a if (mOverlayRef != 0) mOrientationChanged = true; } return OK; + } else if (cmd == CAMERA_CMD_ENABLE_SHUTTER_SOUND) { + switch (arg1) { + case 0: + enableShutterSound(false); + break; + case 1: + enableShutterSound(true); + break; + default: + return BAD_VALUE; + } + return OK; + } else if (cmd == CAMERA_CMD_PLAY_RECORDING_SOUND) { + mCameraService->playSound(SOUND_RECORDING); } return mHardware->sendCommand(cmd, arg1, arg2); @@ -1004,11 +1010,8 @@ void CameraService::Client::dataCallbackTimestamp(nsecs_t timestamp, // "size" is the width and height of yuv picture for registerBuffer. // If it is NULL, use the picture size from parameters. void CameraService::Client::handleShutter(image_rect_type *size) { - mCameraService->playSound(SOUND_SHUTTER); - - // Screen goes black after the buffer is unregistered. - if (mSurface != 0 && !mUseOverlay) { - mSurface->unregisterBuffers(); + if (mPlayShutterSound) { + mCameraService->playSound(SOUND_SHUTTER); } sp<ICameraClient> c = mCameraClient; @@ -1038,7 +1041,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(); } @@ -1051,12 +1053,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; @@ -1077,9 +1073,7 @@ void CameraService::Client::handlePreviewData(const sp<IMemory>& mem) { mPreviewCallbackFlag &= ~(FRAME_CALLBACK_FLAG_ONE_SHOT_MASK | FRAME_CALLBACK_FLAG_COPY_OUT_MASK | FRAME_CALLBACK_FLAG_ENABLE_MASK); - if (mUseOverlay) { - disableMsgType(CAMERA_MSG_PREVIEW_FRAME); - } + disableMsgType(CAMERA_MSG_PREVIEW_FRAME); } if (c != 0) { @@ -1116,11 +1110,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) { @@ -1278,4 +1267,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 8f0ed75..d57364a 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(); @@ -132,6 +138,9 @@ private: status_t startPreviewMode(); status_t startRecordingMode(); + // internal function used by sendCommand to enable/disable shutter sound. + status_t enableShutterSound(bool enable); + // these are static callback functions static void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2, void* user); static void dataCallback(int32_t msgType, const sp<IMemory>& dataPtr, void* user); @@ -167,10 +176,12 @@ private: int mOrientation; // Current display orientation // True if display orientation has been changed. This is only used in overlay. int mOrientationChanged; + bool mPlayShutterSound; // Ensures atomicity among the public methods mutable Mutex mLock; sp<ISurface> mSurface; + 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 731fb22..5ef3d35 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -426,6 +426,40 @@ class AppWidgetService extends IAppWidgetService.Stub } } + public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + for (int i=0; i<N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + updateAppWidgetInstanceLocked(id, views, true); + } + } + } + + public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + for (int i=0; i<N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + notifyAppWidgetViewDataChangedInstanceLocked(id, viewId); + } + } + } + public void updateAppWidgetProvider(ComponentName provider, RemoteViews views) { synchronized (mAppWidgetIds) { Provider p = lookupProviderLocked(provider); @@ -443,11 +477,17 @@ class AppWidgetService extends IAppWidgetService.Stub } void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) { + updateAppWidgetInstanceLocked(id, views, false); + } + + void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) { // allow for stale appWidgetIds and other badness // lookup also checks that the calling process can access the appWidgetId // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { - id.views = views; + + // We do not want to save this RemoteViews + if (!isPartialUpdate) id.views = views; // is anyone listening? if (id.host.callbacks != null) { @@ -463,6 +503,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); @@ -742,6 +801,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..308c9c0 100644 --- a/services/java/com/android/server/ClipboardService.java +++ b/services/java/com/android/server/ClipboardService.java @@ -16,42 +16,84 @@ package com.android.server; -import android.text.IClipboard; +import android.content.ClipData; +import android.content.ClipDescription; +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 ClipData 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(ClipData 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 ClipData getPrimaryClip() { + synchronized (this) { + return mPrimaryClip; + } + } + + public ClipDescription getPrimaryClipDescription() { + synchronized (this) { + return new ClipDescription(mPrimaryClip); } } - // javadoc from interface - public CharSequence getClipboardText() { + public boolean hasPrimaryClip() { synchronized (this) { - return mClipboard; + return mPrimaryClip != null; + } + } + + public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) { + synchronized (this) { + mPrimaryClipListeners.register(listener); + } + } + + public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) { + synchronized (this) { + mPrimaryClipListeners.unregister(listener); } } - // javadoc from interface public boolean hasClipboardText() { synchronized (this) { - return mClipboard.length() > 0; + if (mPrimaryClip != null) { + CharSequence text = mPrimaryClip.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 a0615ef..6095117 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -26,14 +26,18 @@ import android.net.ConnectivityManager; import android.net.IConnectivityManager; import android.net.MobileDataStateTracker; import android.net.NetworkInfo; +import android.net.LinkProperties; import android.net.NetworkStateTracker; import android.net.NetworkUtils; import android.net.wifi.WifiStateTracker; +import android.net.NetworkUtils; import android.os.Binder; import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; @@ -47,8 +51,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.GregorianCalendar; import java.util.List; import java.net.InetAddress; @@ -68,7 +77,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final String NETWORK_RESTORE_DELAY_PROP_NAME = "android.telephony.apn-restore"; - private Tethering mTethering; private boolean mTetheringConfigValid = false; @@ -85,6 +93,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 @@ -162,6 +172,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final int EVENT_SET_MOBILE_DATA = MAX_NETWORK_STATE_TRACKER_EVENT + 7; + /** + * used internally to clear a wakelock when transitioning + * from one net to another + */ + private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = + MAX_NETWORK_STATE_TRACKER_EVENT + 8; + private Handler mHandler; // list of DeathRecipients used to make sure features are turned off when @@ -171,6 +188,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { private boolean mSystemReady; private Intent mInitialBroadcast; + private PowerManager.WakeLock mNetTransitionWakeLock; + private String mNetTransitionWakeLockCausedBy = ""; + private int mNetTransitionWakeLockSerialNumber; + private int mNetTransitionWakeLockTimeout; + + private InetAddress mDefaultDns; + // used in DBG mode to track inet condition reports private static final int INET_CONDITION_LOG_MAX_SIZE = 15; private ArrayList mInetLog; @@ -210,52 +234,20 @@ public class ConnectivityService extends IConnectivityManager.Stub { } RadioAttributes[] mRadioAttributes; - private static class ConnectivityThread extends Thread { - private Context mContext; - - private ConnectivityThread(Context context) { - super("ConnectivityThread"); - mContext = context; - } - - @Override - public void run() { - Looper.prepare(); - synchronized (this) { - sServiceInstance = new ConnectivityService(mContext); - notifyAll(); - } - Looper.loop(); - } - - public static ConnectivityService getServiceInstance(Context context) { - ConnectivityThread thread = new ConnectivityThread(context); - thread.start(); - - synchronized (thread) { - while (sServiceInstance == null) { - try { - // Wait until sServiceInstance has been initialized. - thread.wait(); - } catch (InterruptedException ignore) { - Slog.e(TAG, - "Unexpected InterruptedException while waiting"+ - " for ConnectivityService thread"); - } - } - } - - return sServiceInstance; + public static synchronized ConnectivityService getInstance(Context context) { + if (sServiceInstance == null) { + sServiceInstance = new ConnectivityService(context); } - } - - public static ConnectivityService getInstance(Context context) { - return ConnectivityThread.getServiceInstance(context); + return sServiceInstance; } private ConnectivityService(Context context) { if (DBG) Slog.v(TAG, "ConnectivityService starting up"); + HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread"); + handlerThread.start(); + mHandler = new MyHandler(handlerThread.getLooper()); + // setup our unique device name String id = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); @@ -264,10 +256,28 @@ public class ConnectivityService extends IConnectivityManager.Stub { SystemProperties.set("net.hostname", name); } + // read our default dns server ip + String dns = Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.DEFAULT_DNS_SERVER); + if (dns == null || dns.length() == 0) { + dns = context.getResources().getString( + com.android.internal.R.string.config_default_dns_server); + } + try { + mDefaultDns = InetAddress.getByName(dns); + } catch (UnknownHostException e) { + Slog.e(TAG, "Error setting defaultDns using " + dns); + } + mContext = context; + + PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + mNetTransitionWakeLockTimeout = mContext.getResources().getInteger( + com.android.internal.R.integer.config_networkTransitionTimeout); + mNetTrackers = new NetworkStateTracker[ ConnectivityManager.MAX_NETWORK_TYPE+1]; - mHandler = new MyHandler(); mNetworkPreference = getPersistedNetworkPreference(); @@ -364,18 +374,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(); @@ -392,7 +405,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); if (DBG) { @@ -528,6 +542,38 @@ public class ConnectivityService extends IConnectivityManager.Stub { return result; } + /** + * Return LinkProperties for the active (i.e., connected) default + * network interface. It is assumed that at most one default network + * is active at a time. If more than one is active, it is indeterminate + * which will be returned. + * @return the ip properties for the active network, or {@code null} if + * none is active + */ + public LinkProperties getActiveLinkProperties() { + enforceAccessPermission(); + for (int type=0; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) { + if (mNetAttributes[type] == null || !mNetAttributes[type].isDefault()) { + continue; + } + NetworkStateTracker t = mNetTrackers[type]; + NetworkInfo info = t.getNetworkInfo(); + if (info.isConnected()) { + return t.getLinkProperties(); + } + } + return null; + } + + public LinkProperties getLinkProperties(int networkType) { + enforceAccessPermission(); + if (ConnectivityManager.isNetworkTypeValid(networkType)) { + NetworkStateTracker t = mNetTrackers[networkType]; + if (t != null) return t.getLinkProperties(); + } + return null; + } + public boolean setRadios(boolean turnOn) { boolean result = true; enforceChangePermission(); @@ -676,14 +722,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { network.reconnect(); return Phone.APN_REQUEST_STARTED; } else { - synchronized(this) { - mFeatureUsers.add(f); - } - mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_RESTORE_DEFAULT_NETWORK, - f), getRestoreDefaultNetworkDelay()); - - return network.startUsingNetworkFeature(feature, - getCallingPid(), getCallingUid()); + return -1; } } return Phone.APN_TYPE_NOT_AVAILABLE; @@ -802,8 +841,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; } } @@ -852,11 +890,37 @@ public class ConnectivityService extends IConnectivityManager.Stub { } return false; } - try { - InetAddress inetAddress = InetAddress.getByAddress(hostAddress); - return tracker.requestRouteToHost(inetAddress); - } catch (UnknownHostException e) { + InetAddress addr = InetAddress.getByAddress(hostAddress); + return addHostRoute(tracker, addr); + } catch (UnknownHostException e) {} + return false; + } + + /** + * Ensure that a network route exists to deliver traffic to the specified + * host via the mobile data network. + * @param hostAddress the IP address of the host to which the route is desired, + * in network byte order. + * TODO - deprecate + * @return {@code true} on success, {@code false} on failure + */ + private boolean addHostRoute(NetworkStateTracker nt, InetAddress hostAddress) { + if (nt.getNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI) { + return false; + } + + LinkProperties p = nt.getLinkProperties(); + if (p == null) return false; + String interfaceName = p.getInterfaceName(); + + if (DBG) { + Slog.d(TAG, "Requested host route to " + hostAddress + "(" + interfaceName + ")"); + } + if (interfaceName != null) { + return NetworkUtils.addHostRoute(interfaceName, hostAddress, null); + } else { + if (DBG) Slog.e(TAG, "addHostRoute failed due to null interface name"); return false; } } @@ -975,6 +1039,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 @@ -1269,9 +1339,17 @@ 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( + EVENT_CLEAR_NET_TRANSITION_WAKELOCK, + mNetTransitionWakeLockSerialNumber, 0), + 1000); } } mActiveDefaultNetwork = type; @@ -1285,36 +1363,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { //reportNetworkCondition(mActiveDefaultNetwork, 100); } thisNet.setTeardownRequested(false); - thisNet.updateNetworkSettings(); + updateNetworkSettings(thisNet); handleConnectivityChange(type); sendConnectedBroadcast(info); } - private void handleScanResultsAvailable(NetworkInfo info) { - int networkType = info.getType(); - if (networkType != ConnectivityManager.TYPE_WIFI) { - if (DBG) Slog.v(TAG, "Got ScanResultsAvailable for " + - info.getTypeName() + " network. Don't know how to handle."); - } - - mNetTrackers[networkType].interpretScanResultsAvailable(); - } - - private void handleNotificationChange(boolean visible, int id, - Notification notification) { - NotificationManager notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - - if (visible) { - notificationManager.notify(id, notification); - } else { - notificationManager.cancel(id); - } - } - /** - * After a change in the connectivity state of any network, We're mainly - * concerned with making sure that the list of DNS servers is setupup + * After a change in the connectivity state of a network. We're mainly + * concerned with making sure that the list of DNS servers is set up * according to which networks are connected, and ensuring that the * right routing table entries exist. */ @@ -1327,19 +1383,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 { - mNetTrackers[netType].removePrivateDnsRoutes(); + removePrivateDnsRoutes(mNetTrackers[netType]); + } + } + } + + private void addPrivateDnsRoutes(NetworkStateTracker nt) { + boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet(); + LinkProperties p = nt.getLinkProperties(); + if (p == null) return; + String interfaceName = p.getInterfaceName(); + + if (DBG) { + 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, null); + } + nt.privateDnsRouteSet(true); + } + } + + private void removePrivateDnsRoutes(NetworkStateTracker nt) { + // TODO - we should do this explicitly but the NetUtils api doesnt + // support this yet - must remove all. No worse than before + LinkProperties p = nt.getLinkProperties(); + if (p == null) return; + String interfaceName = p.getInterfaceName(); + boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet(); + if (interfaceName != null && privateDnsRouteSet) { + if (DBG) { + Slog.d(TAG, "removePrivateDnsRoutes for " + nt.getNetworkInfo().getTypeName() + + " (" + interfaceName + ")"); } + NetworkUtils.removeHostRoutes(interfaceName); + nt.privateDnsRouteSet(false); } } + + private void addDefaultRoute(NetworkStateTracker nt) { + LinkProperties p = nt.getLinkProperties(); + if (p == null) return; + String interfaceName = p.getInterfaceName(); + InetAddress defaultGatewayAddr = p.getGateway(); + + if ((interfaceName != null) && (defaultGatewayAddr != null )) { + if (!NetworkUtils.addDefaultRoute(interfaceName, defaultGatewayAddr) && DBG) { + NetworkInfo networkInfo = nt.getNetworkInfo(); + Slog.d(TAG, "addDefaultRoute for " + networkInfo.getTypeName() + + " (" + interfaceName + "), GatewayAddr=" + defaultGatewayAddr); + } + } + } + + + public void removeDefaultRoute(NetworkStateTracker nt) { + LinkProperties p = nt.getLinkProperties(); + if (p == null) return; + String interfaceName = p.getInterfaceName(); + + if (interfaceName != null) { + if ((NetworkUtils.removeDefaultRoute(interfaceName) >= 0) && DBG) { + NetworkInfo networkInfo = nt.getNetworkInfo(); + 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 { + 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. @@ -1355,12 +1550,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { NetworkStateTracker nt = mNetTrackers[i]; if (nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { + LinkProperties p = nt.getLinkProperties(); + if (p == null) continue; List pids = mNetRequestersPids[i]; for (int j=0; j<pids.size(); j++) { Integer pid = (Integer)pids.get(j); if (pid.intValue() == myPid) { - String[] dnsList = nt.getNameServers(); - writePidDns(dnsList, myPid); + Collection<InetAddress> dnses = p.getDnses(); + writePidDns(dnses, myPid); if (doBump) { bumpDns(); } @@ -1382,12 +1579,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()); } } @@ -1410,16 +1605,24 @@ 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(); + LinkProperties p = nt.getLinkProperties(); + 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 (dnses.size() == 0 && mDefaultDns != null) { + if (DBG) { + Slog.d(TAG, "no dns provided - using " + mDefaultDns.getHostAddress()); + } + SystemProperties.set("net.dns1", mDefaultDns.getHostAddress()); + j++; + } else { + for (InetAddress dns : dnses) { if (DBG) { Slog.d(TAG, "adding dns " + dns + " for " + nt.getNetworkInfo().getTypeName()); } - SystemProperties.set("net.dns" + j++, dns); + SystemProperties.set("net.dns" + j++, dns.getHostAddress()); } } for (int k=j ; k<mNumDnsEntries; k++) { @@ -1432,11 +1635,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() { @@ -1491,6 +1694,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { } pw.println(); + synchronized (this) { + pw.println("NetworkTranstionWakeLock is currently " + + (mNetTransitionWakeLock.isHeld() ? "" : "not ") + "held."); + pw.println("It was last requested for "+mNetTransitionWakeLockCausedBy); + } + pw.println(); + mTethering.dump(fd, pw, args); if (mInetLog != null) { @@ -1504,6 +1714,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { // must be stateless - things change under us. private class MyHandler extends Handler { + public MyHandler(Looper looper) { + super(looper); + } + @Override public void handleMessage(Message msg) { NetworkInfo info; @@ -1565,29 +1779,25 @@ 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 + case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: + String causedBy = null; + synchronized (ConnectivityService.this) { + if (msg.arg1 == mNetTransitionWakeLockSerialNumber && + mNetTransitionWakeLock.isHeld()) { + mNetTransitionWakeLock.release(); + causedBy = mNetTransitionWakeLockCausedBy; + } + } + if (causedBy != null) { + Slog.d(TAG, "NetTransition Wakelock for " + + causedBy + " released by timeout"); + } break; case EVENT_RESTORE_DEFAULT_NETWORK: FeatureUser u = (FeatureUser)msg.obj; @@ -1681,6 +1891,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() { @@ -1709,6 +1928,25 @@ public class ConnectivityService extends IConnectivityManager.Stub { return tetherEnabledInSettings && mTetheringConfigValid; } + // An API NetworkStateTrackers can call when they lose their network. + // This will automatically be cleared after X seconds or a network becomes CONNECTED, + // whichever happens first. The timer is started by the first caller and not + // restarted by subsequent callers. + public void requestNetworkTransitionWakelock(String forWhom) { + enforceConnectivityInternalPermission(); + synchronized (this) { + if (mNetTransitionWakeLock.isHeld()) return; + mNetTransitionWakeLockSerialNumber++; + mNetTransitionWakeLock.acquire(); + mNetTransitionWakeLockCausedBy = forWhom; + } + mHandler.sendMessageDelayed(mHandler.obtainMessage( + EVENT_CLEAR_NET_TRANSITION_WAKELOCK, + mNetTransitionWakeLockSerialNumber, 0), + mNetTransitionWakeLockTimeout); + return; + } + // 100 percent is full good, 0 is full bad. public void reportInetCondition(int networkType, int percentage) { if (DBG) Slog.d(TAG, "reportNetworkCondition(" + networkType + ", " + percentage + ")"); 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/InputManager.java b/services/java/com/android/server/InputManager.java index 29ca9a4..fe306b3 100644 --- a/services/java/com/android/server/InputManager.java +++ b/services/java/com/android/server/InputManager.java @@ -77,6 +77,8 @@ public class InputManager { private static native InputDevice nativeGetInputDevice(int deviceId); private static native void nativeGetInputConfiguration(Configuration configuration); private static native int[] nativeGetInputDeviceIds(); + private static native boolean nativeTransferTouchFocus(InputChannel fromChannel, + InputChannel toChannel); private static native String nativeDump(); // Input event injection constants defined in InputDispatcher.h. @@ -320,6 +322,29 @@ public class InputManager { nativeSetInputDispatchMode(enabled, frozen); } + /** + * Atomically transfers touch focus from one window to another as identified by + * their input channels. It is possible for multiple windows to have + * touch focus if they support split touch dispatch + * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this + * method only transfers touch focus of the specified window without affecting + * other windows that may also have touch focus at the same time. + * @param fromChannel The channel of a window that currently has touch focus. + * @param toChannel The channel of the window that should receive touch focus in + * place of the first. + * @return True if the transfer was successful. False if the window with the + * specified channel did not actually have touch focus at the time of the request. + */ + public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) { + if (fromChannel == null) { + throw new IllegalArgumentException("fromChannel must not be null."); + } + if (toChannel == null) { + throw new IllegalArgumentException("toChannel must not be null."); + } + return nativeTransferTouchFocus(fromChannel, toChannel); + } + public void dump(PrintWriter pw) { String dumpStr = nativeDump(); if (dumpStr != null) { diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 9efc708..675760f 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -61,18 +61,21 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.provider.Settings; import android.provider.Settings.Secure; +import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; import android.util.EventLog; +import android.util.Pair; import android.util.Slog; import android.util.PrintWriterPrinter; import android.util.Printer; import android.view.IWindowManager; import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodSubtype; import java.io.FileDescriptor; import java.io.IOException; @@ -93,6 +96,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final String TAG = "InputManagerService"; static final int MSG_SHOW_IM_PICKER = 1; + static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2; static final int MSG_UNBIND_INPUT = 1000; static final int MSG_BIND_INPUT = 1010; @@ -109,6 +113,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final long TIME_TO_RECONNECT = 10*1000; + private static final int NOT_A_SUBTYPE_ID = -1; + final Context mContext; final Handler mHandler; final SettingsObserver mSettingsObserver; @@ -224,6 +230,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub String mCurId; /** + * The current subtype of the current input method. + */ + private InputMethodSubtype mCurrentSubtype; + + + /** * Set to true if our ServiceConnection is currently actively bound to * a service (whether or not we have gotten its IBinder back yet). */ @@ -292,6 +304,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub AlertDialog mSwitchingDialog; InputMethodInfo[] mIms; CharSequence[] mItems; + int[] mSubtypeIds; class SettingsObserver extends ContentObserver { SettingsObserver(Handler handler) { @@ -299,6 +312,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub ContentResolver resolver = mContext.getContentResolver(); resolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.DEFAULT_INPUT_METHOD), false, this); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this); } @Override public void onChange(boolean selfChange) { @@ -351,9 +366,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!doit) { return true; } - Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD, ""); + resetSelectedInputMethodSubtype(); chooseNewDefaultIMELocked(); return true; } @@ -409,16 +424,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!chooseNewDefaultIMELocked()) { changed = true; curIm = null; - curInputMethodId = ""; Slog.i(TAG, "Unsetting current input method"); Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, - curInputMethodId); + Settings.Secure.DEFAULT_INPUT_METHOD, ""); + resetSelectedInputMethodSubtype(); } } } } - + if (curIm == null) { // We currently don't have a default input method... is // one now available? @@ -509,6 +523,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (defIm != null) { Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD, defIm.getId()); + putSelectedInputMethodSubtype(defIm, NOT_A_SUBTYPE_ID); } } @@ -964,10 +979,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // sync, so we will never have a DEFAULT_INPUT_METHOD that is not // enabled. String id = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD); + Settings.Secure.DEFAULT_INPUT_METHOD); if (id != null && id.length() > 0) { try { - setInputMethodLocked(id); + setInputMethodLocked(id, getSelectedInputMethodSubtypeId(id)); } catch (IllegalArgumentException e) { Slog.w(TAG, "Unknown input method from prefs: " + id, e); mCurMethodId = null; @@ -980,21 +995,44 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - void setInputMethodLocked(String id) { + /* package */ void setInputMethodLocked(String id, int subtypeId) { InputMethodInfo info = mMethodMap.get(id); if (info == null) { throw new IllegalArgumentException("Unknown id: " + id); } if (id.equals(mCurMethodId)) { + if (subtypeId != NOT_A_SUBTYPE_ID) { + InputMethodSubtype subtype = info.getSubtypes().get(subtypeId); + if (subtype != mCurrentSubtype) { + synchronized (mMethodMap) { + if (mCurMethod != null) { + try { + putSelectedInputMethodSubtype(info, subtypeId); + mCurMethod.changeInputMethodSubtype(subtype); + } catch (RemoteException e) { + return; + } + } + } + } + } return; } final long ident = Binder.clearCallingIdentity(); try { mCurMethodId = id; + // Set a subtype to this input method. + // subtypeId the name of a subtype which will be set. + if (putSelectedInputMethodSubtype(info, subtypeId)) { + mCurrentSubtype = info.getSubtypes().get(subtypeId); + } else { + mCurrentSubtype = null; + } + Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, id); + Settings.Secure.DEFAULT_INPUT_METHOD, id); if (ActivityManagerNative.isSystemReady()) { Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED); @@ -1226,7 +1264,22 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + public void showInputMethodSubtypePickerFromClient(IInputMethodClient client) { + synchronized (mMethodMap) { + if (mCurClient == null || client == null + || mCurClient.client.asBinder() != client.asBinder()) { + Slog.w(TAG, "Ignoring showInputSubtypeMethodDialogFromClient of: " + client); + } + + mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER); + } + } + public void setInputMethod(IBinder token, String id) { + setInputMethodWithSubtype(token, id, NOT_A_SUBTYPE_ID); + } + + private void setInputMethodWithSubtype(IBinder token, String id, int subtypeId) { synchronized (mMethodMap) { if (token == null) { if (mContext.checkCallingOrSelfPermission( @@ -1243,7 +1296,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub long ident = Binder.clearCallingIdentity(); try { - setInputMethodLocked(id); + setInputMethodLocked(id, subtypeId); } finally { Binder.restoreCallingIdentity(ident); } @@ -1307,6 +1360,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub showInputMethodMenu(); return true; + case MSG_SHOW_IM_SUBTYPE_PICKER: + showInputMethodSubtypeMenu(); + return true; + // --------------------------------------------------------- case MSG_UNBIND_INPUT: @@ -1417,9 +1474,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub break; } } + InputMethodInfo imi = enabled.get(i); Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, - enabled.get(i).getId()); + Settings.Secure.DEFAULT_INPUT_METHOD, imi.getId()); + putSelectedInputMethodSubtype(imi, NOT_A_SUBTYPE_ID); return true; } @@ -1490,7 +1548,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // ---------------------------------------------------------------------- - void showInputMethodMenu() { + private void showInputMethodMenu() { + showInputMethodMenuInternal(false); + } + + private void showInputMethodSubtypeMenu() { + showInputMethodMenuInternal(true); + } + + private void showInputMethodMenuInternal(boolean showSubtypes) { if (DEBUG) Slog.v(TAG, "Show switching menu"); final Context context = mContext; @@ -1499,42 +1565,78 @@ public class InputMethodManagerService extends IInputMethodManager.Stub String lastInputMethodId = Settings.Secure.getString(context .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); + int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId); if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId); final List<InputMethodInfo> immis = getEnabledInputMethodList(); + ArrayList<Integer> subtypeIds = new ArrayList<Integer>(); if (immis == null) { return; } - + synchronized (mMethodMap) { hideInputMethodMenuLocked(); int N = immis.size(); - final Map<CharSequence, InputMethodInfo> imMap = - new TreeMap<CharSequence, InputMethodInfo>(Collator.getInstance()); + final Map<CharSequence, Pair<InputMethodInfo, Integer>> imMap = + new TreeMap<CharSequence, Pair<InputMethodInfo, Integer>>(Collator.getInstance()); for (int i = 0; i < N; ++i) { InputMethodInfo property = immis.get(i); if (property == null) { continue; } - imMap.put(property.loadLabel(pm), property); + // TODO: Show only enabled subtypes + ArrayList<InputMethodSubtype> subtypes = property.getSubtypes(); + CharSequence label = property.loadLabel(pm); + if (showSubtypes && subtypes.size() > 0) { + for (int j = 0; j < subtypes.size(); ++j) { + InputMethodSubtype subtype = subtypes.get(j); + CharSequence title; + int nameResId = subtype.getNameResId(); + int modeResId = subtype.getModeResId(); + if (nameResId != 0) { + title = pm.getText(property.getPackageName(), nameResId, + property.getServiceInfo().applicationInfo); + } else { + CharSequence language = subtype.getLocale(); + CharSequence mode = modeResId == 0 ? null + : pm.getText(property.getPackageName(), modeResId, + property.getServiceInfo().applicationInfo); + // TODO: Use more friendly Title and UI + title = label + "," + (mode == null ? "" : mode) + "," + + (language == null ? "" : language); + } + imMap.put(title, new Pair<InputMethodInfo, Integer>(property, j)); + } + } else { + imMap.put(label, + new Pair<InputMethodInfo, Integer>(property, NOT_A_SUBTYPE_ID)); + subtypeIds.add(0); + } } N = imMap.size(); mItems = imMap.keySet().toArray(new CharSequence[N]); - mIms = imMap.values().toArray(new InputMethodInfo[N]); - + mIms = new InputMethodInfo[N]; + mSubtypeIds = new int[N]; int checkedItem = 0; for (int i = 0; i < N; ++i) { + Pair<InputMethodInfo, Integer> value = imMap.get(mItems[i]); + mIms[i] = value.first; + mSubtypeIds[i] = value.second; if (mIms[i].getId().equals(lastInputMethodId)) { - checkedItem = i; - break; + int subtypeId = mSubtypeIds[i]; + if ((subtypeId == NOT_A_SUBTYPE_ID) + || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0) + || (subtypeId == lastInputMethodSubtypeId)) { + checkedItem = i; + } } } - + AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() { public void onClick(DialogInterface dialog, int which) { hideInputMethodMenu(); @@ -1559,13 +1661,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub new AlertDialog.OnClickListener() { public void onClick(DialogInterface dialog, int which) { synchronized (mMethodMap) { - if (mIms == null || mIms.length <= which) { + if (mIms == null || mIms.length <= which + || mSubtypeIds == null || mSubtypeIds.length <= which) { return; } InputMethodInfo im = mIms[which]; + int subtypeId = mSubtypeIds[which]; hideInputMethodMenu(); if (im != null) { - setInputMethodLocked(im.getId()); + if ((subtypeId < 0) + || (subtypeId >= im.getSubtypes().size())) { + subtypeId = NOT_A_SUBTYPE_ID; + } + setInputMethodLocked(im.getId(), subtypeId); } } } @@ -1679,6 +1787,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD, firstId != null ? firstId : ""); + resetSelectedInputMethodSubtype(); } // Previous state was enabled. return true; @@ -1698,6 +1807,53 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return false; } + private void resetSelectedInputMethodSubtype() { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID); + } + + private boolean putSelectedInputMethodSubtype(InputMethodInfo imi, int subtypeId) { + ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes(); + if (subtypeId >= 0 && subtypeId < subtypes.size()) { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, + subtypes.get(subtypeId).hashCode()); + return true; + } else { + resetSelectedInputMethodSubtype(); + return false; + } + } + + private int getSelectedInputMethodSubtypeId(String id) { + InputMethodInfo imi = mMethodMap.get(id); + if (imi == null) { + return NOT_A_SUBTYPE_ID; + } + ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes(); + int subtypeId; + try { + subtypeId = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE); + } catch (SettingNotFoundException e) { + return NOT_A_SUBTYPE_ID; + } + for (int i = 0; i < subtypes.size(); ++i) { + InputMethodSubtype ims = subtypes.get(i); + if (subtypeId == ims.hashCode()) { + return i; + } + } + return NOT_A_SUBTYPE_ID; + } + + /** + * @return Return the current subtype of this input method. + */ + public InputMethodSubtype getCurrentInputMethodSubtype() { + return mCurrentSubtype; + } + // ---------------------------------------------------------------------- @Override diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index 643b2f5..aa1bcf7 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -472,10 +472,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); @@ -483,7 +484,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 131dded..32f5e73 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -137,6 +137,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. @@ -397,7 +399,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)); @@ -468,11 +472,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; @@ -981,6 +987,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 7b68d68..cf87a9d 100644 --- a/services/java/com/android/server/NativeDaemonConnector.java +++ b/services/java/com/android/server/NativeDaemonConnector.java @@ -132,11 +132,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)); @@ -219,6 +220,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 33b19d6..05abd99 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -226,10 +226,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/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java new file mode 100644 index 0000000..52f84eb --- /dev/null +++ b/services/java/com/android/server/NetworkTimeUpdateService.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import com.android.internal.telephony.TelephonyIntents; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import android.net.SntpClient; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; +import android.provider.Settings; +import android.util.Log; +import android.util.Slog; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +/** + * Monitors the network time and updates the system time if it is out of sync + * and there hasn't been any NITZ update from the carrier recently. + * If looking up the network time fails for some reason, it tries a few times with a short + * interval and then resets to checking on longer intervals. + * <p> + * If the user enables AUTO_TIME, it will check immediately for the network time, if NITZ wasn't + * available. + * </p> + */ +public class NetworkTimeUpdateService { + + private static final String TAG = "NetworkTimeUpdateService"; + private static final boolean DBG = false; + + private static final int EVENT_AUTO_TIME_CHANGED = 1; + private static final int EVENT_POLL_NETWORK_TIME = 2; + + /** Normal polling frequency */ + private static final long POLLING_INTERVAL_MS = 24L * 60 * 60 * 1000; // 24 hrs + /** Try-again polling interval, in case the network request failed */ + private static final long POLLING_INTERVAL_SHORTER_MS = 60 * 1000L; // 60 seconds + /** Number of times to try again */ + private static final int TRY_AGAIN_TIMES_MAX = 3; + /** How long to wait for the NTP server to respond. */ + private static final int MAX_NTP_FETCH_WAIT_MS = 20 * 1000; + /** If the time difference is greater than this threshold, then update the time. */ + private static final int TIME_ERROR_THRESHOLD_MS = 5 * 1000; + + private static final String ACTION_POLL = + "com.android.server.NetworkTimeUpdateService.action.POLL"; + private static final String PROPERTIES_FILE = "/etc/gps.conf"; + private static int POLL_REQUEST = 0; + + private static final long NOT_SET = -1; + private long mNitzTimeSetTime = NOT_SET; + // TODO: Have a way to look up the timezone we are in + private long mNitzZoneSetTime = NOT_SET; + + private Context mContext; + // NTP lookup is done on this thread and handler + private Handler mHandler; + private HandlerThread mThread; + private AlarmManager mAlarmManager; + private PendingIntent mPendingPollIntent; + private SettingsObserver mSettingsObserver; + // Address of the NTP server + private String mNtpServer; + // The last time that we successfully fetched the NTP time. + private long mLastNtpFetchTime = NOT_SET; + // Keeps track of how many quick attempts were made to fetch NTP time. + // During bootup, the network may not have been up yet, or it's taking time for the + // connection to happen. + private int mTryAgainCounter; + + public NetworkTimeUpdateService(Context context) { + mContext = context; + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + Intent pollIntent = new Intent(ACTION_POLL, null); + mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0); + } + + /** Initialize the receivers and initiate the first NTP request */ + public void systemReady() { + mNtpServer = getNtpServerAddress(); + if (mNtpServer == null) { + Slog.e(TAG, "NTP server address not found, not syncing to NTP time"); + return; + } + + registerForTelephonyIntents(); + registerForAlarms(); + + mThread = new HandlerThread(TAG); + mThread.start(); + mHandler = new MyHandler(mThread.getLooper()); + // Check the network time on the new thread + mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget(); + + mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED); + mSettingsObserver.observe(mContext); + } + + private String getNtpServerAddress() { + String serverAddress = null; + FileInputStream stream = null; + try { + Properties properties = new Properties(); + File file = new File(PROPERTIES_FILE); + stream = new FileInputStream(file); + properties.load(stream); + serverAddress = properties.getProperty("NTP_SERVER", null); + } catch (IOException e) { + Slog.e(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (Exception e) {} + } + } + return serverAddress; + } + + private void registerForTelephonyIntents() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME); + intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE); + mContext.registerReceiver(mNitzReceiver, intentFilter); + } + + private void registerForAlarms() { + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget(); + } + }, new IntentFilter(ACTION_POLL)); + } + + private void onPollNetworkTime(int event) { + // If Automatic time is not set, don't bother. + if (!isAutomaticTimeRequested()) return; + + final long refTime = SystemClock.elapsedRealtime(); + // If NITZ time was received less than POLLING_INTERVAL_MS time ago, + // no need to sync to NTP. + if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < POLLING_INTERVAL_MS) { + resetAlarm(POLLING_INTERVAL_MS); + return; + } + final long currentTime = System.currentTimeMillis(); + if (DBG) Log.d(TAG, "System time = " + currentTime); + // Get the NTP time + if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + POLLING_INTERVAL_MS + || event == EVENT_AUTO_TIME_CHANGED) { + if (DBG) Log.d(TAG, "Before Ntp fetch"); + long ntp = getNtpTime(); + if (DBG) Log.d(TAG, "Ntp = " + ntp); + if (ntp > 0) { + mTryAgainCounter = 0; + mLastNtpFetchTime = SystemClock.elapsedRealtime(); + if (Math.abs(ntp - currentTime) > TIME_ERROR_THRESHOLD_MS) { + // Set the system time + if (DBG) Log.d(TAG, "Ntp time to be set = " + ntp); + // Make sure we don't overflow, since it's going to be converted to an int + if (ntp / 1000 < Integer.MAX_VALUE) { + SystemClock.setCurrentTimeMillis(ntp); + } + } else { + if (DBG) Log.d(TAG, "Ntp time is close enough = " + ntp); + } + } else { + // Try again shortly + mTryAgainCounter++; + if (mTryAgainCounter <= TRY_AGAIN_TIMES_MAX) { + resetAlarm(POLLING_INTERVAL_SHORTER_MS); + } else { + // Try much later + mTryAgainCounter = 0; + resetAlarm(POLLING_INTERVAL_MS); + } + return; + } + } + resetAlarm(POLLING_INTERVAL_MS); + } + + /** + * Cancel old alarm and starts a new one for the specified interval. + * + * @param interval when to trigger the alarm, starting from now. + */ + private void resetAlarm(long interval) { + mAlarmManager.cancel(mPendingPollIntent); + long now = SystemClock.elapsedRealtime(); + long next = now + interval; + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent); + } + + private long getNtpTime() { + SntpClient client = new SntpClient(); + if (client.requestTime(mNtpServer, MAX_NTP_FETCH_WAIT_MS)) { + return client.getNtpTime(); + } else { + return 0; + } + } + + /** + * Checks if the user prefers to automatically set the time. + */ + private boolean isAutomaticTimeRequested() { + return Settings.System.getInt(mContext.getContentResolver(), Settings.System.AUTO_TIME, 0) + != 0; + } + + /** Receiver for Nitz time events */ + private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) { + mNitzTimeSetTime = SystemClock.elapsedRealtime(); + } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) { + mNitzZoneSetTime = SystemClock.elapsedRealtime(); + } + } + }; + + /** Handler to do the network accesses on */ + private class MyHandler extends Handler { + + public MyHandler(Looper l) { + super(l); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case EVENT_AUTO_TIME_CHANGED: + case EVENT_POLL_NETWORK_TIME: + onPollNetworkTime(msg.what); + break; + } + } + } + + /** Observer to watch for changes to the AUTO_TIME setting */ + private static class SettingsObserver extends ContentObserver { + + private int mMsg; + private Handler mHandler; + + SettingsObserver(Handler handler, int msg) { + super(handler); + mHandler = handler; + mMsg = msg; + } + + void observe(Context context) { + ContentResolver resolver = context.getContentResolver(); + resolver.registerContentObserver(Settings.System.getUriFor(Settings.System.AUTO_TIME), + false, this); + } + + @Override + public void onChange(boolean selfChange) { + mHandler.obtainMessage(mMsg).sendToTarget(); + } + } +} diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 518cfd9..5afabbd 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; @@ -285,6 +286,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 72daa64..8f90756 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -9421,7 +9421,8 @@ class PackageManagerService extends IPackageManager.Stub { * Update media status on PackageManager. */ public void updateExternalMediaStatus(final boolean mediaStatus, final boolean reportStatus) { - if (Binder.getCallingUid() != Process.SYSTEM_UID) { + int callingUid = Binder.getCallingUid(); + if (callingUid != 0 && callingUid != Process.SYSTEM_UID) { throw new SecurityException("Media status can only be updated by the system"); } synchronized (mPackages) { diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index 5d32b74..9401ff8 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -2536,7 +2536,8 @@ class PowerManagerService extends IPowerManager.Stub } mKeylightDelay = LONG_KEYLIGHT_DELAY; if (totalDelay < 0) { - mScreenOffDelay = Integer.MAX_VALUE; + // negative number means stay on as long as possible. + mScreenOffDelay = mMaximumScreenOffTimeout; } else if (mKeylightDelay < totalDelay) { // subtract the time that the keylight delay. This will give us the // remainder of the time that we need to sleep to get the accurate 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/ShutdownActivity.java b/services/java/com/android/server/ShutdownActivity.java index 64b9c5d..c9d4d01 100644 --- a/services/java/com/android/server/ShutdownActivity.java +++ b/services/java/com/android/server/ShutdownActivity.java @@ -27,19 +27,26 @@ import com.android.internal.app.ShutdownThread; public class ShutdownActivity extends Activity { private static final String TAG = "ShutdownActivity"; + private boolean mReboot; private boolean mConfirm; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mConfirm = getIntent().getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false); + Intent intent = getIntent(); + mReboot = Intent.ACTION_REBOOT.equals(intent.getAction()); + mConfirm = intent.getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false); Slog.i(TAG, "onCreate(): confirm=" + mConfirm); Handler h = new Handler(); h.post(new Runnable() { public void run() { - ShutdownThread.shutdown(ShutdownActivity.this, mConfirm); + if (mReboot) { + ShutdownThread.reboot(ShutdownActivity.this, null, mConfirm); + } else { + ShutdownThread.shutdown(ShutdownActivity.this, mConfirm); + } } }); } diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java index 4177432..b1baec5 100644 --- a/services/java/com/android/server/StatusBarManagerService.java +++ b/services/java/com/android/server/StatusBarManagerService.java @@ -68,6 +68,10 @@ public class StatusBarManagerService extends IStatusBarService.Stub ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>(); int mDisabled = 0; + Object mLock = new Object(); + // We usually call it lights out mode, but double negatives are annoying + boolean mLightsOn = true; + private class DisableRecord implements IBinder.DeathRecipient { String pkg; int what; @@ -84,6 +88,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); @@ -241,6 +246,56 @@ public class StatusBarManagerService extends IStatusBarService.Stub } } + /** + * This is used for the automatic version of lights-out mode. Only call this from + * the window manager. + * + * @see setLightsOn(boolean) + */ + public void setActiveWindowIsFullscreen(boolean fullscreen) { + // We could get away with a separate permission here, but STATUS_BAR is + // signatureOrSystem which is probably good enough. There is no public API + // for this, so the question is a security issue, not an API compatibility issue. + enforceStatusBar(); + + synchronized (mLock) { + updateLightsOnLocked(!fullscreen); + } + } + + /** + * This is used for the user-controlled version of lights-out mode. Only call this from + * the status bar itself. + * + * We have two different functions here, because I think we're going to want to + * tweak the behavior when the user keeps turning lights-out mode off and the + * app keeps trying to turn it on. For now they can just fight it out. Having + * these two separte inputs will allow us to keep that change local to here. --joeo + */ + public void setLightsOn(boolean lightsOn) { + enforceStatusBarService(); + + synchronized (mLock) { + updateLightsOnLocked(lightsOn); + } + } + + private void updateLightsOnLocked(final boolean lightsOn) { + if (mLightsOn != lightsOn) { + mLightsOn = lightsOn; + mHandler.post(new Runnable() { + public void run() { + if (mBar != null) { + try { + mBar.setLightsOn(lightsOn); + } catch (RemoteException ex) { + } + } + } + }); + } + } + private void enforceStatusBar() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR, "StatusBarManagerService"); @@ -261,7 +316,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub // Callbacks from the status bar service. // ================================================================================ public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList, - List<IBinder> notificationKeys, List<StatusBarNotification> notifications) { + List<IBinder> notificationKeys, List<StatusBarNotification> notifications, + boolean lightsOn[]) { enforceStatusBarService(); Slog.i(TAG, "registerStatusBar bar=" + bar); @@ -275,6 +331,9 @@ public class StatusBarManagerService extends IStatusBarService.Stub notifications.add(e.getValue()); } } + synchronized (mLock) { + lightsOn[0] = mLightsOn; + } } /** @@ -302,6 +361,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 9475005..5ca386b 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -24,26 +24,28 @@ 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.Build; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.StrictMode; +import android.os.SystemClock; +import android.os.SystemProperties; import android.provider.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 +53,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() { @@ -102,6 +101,7 @@ class ServerThread extends Thread { UiModeManagerService uiMode = null; RecognitionManagerService recognition = null; ThrottleService throttle = null; + NetworkTimeUpdateService networkTimeUpdater = null; // Critical services... try { @@ -210,6 +210,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 { @@ -321,6 +322,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)); @@ -328,11 +337,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, @@ -419,6 +423,18 @@ 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 { SipService sipService = SipService.create(context); if (sipService != null) { Slog.i(TAG, "Sip Service"); @@ -427,6 +443,13 @@ class ServerThread extends Thread { } catch (Throwable e) { Slog.e(TAG, "Failure starting SIP Service", e); } + + try { + Slog.i(TAG, "NetworkTimeUpdateService"); + networkTimeUpdater = new NetworkTimeUpdateService(context); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting NetworkTimeUpdate service"); + } } // make sure the ADB_ENABLED setting value matches the secure property value @@ -487,6 +510,8 @@ class ServerThread extends Thread { final InputMethodManagerService immF = imm; final RecognitionManagerService recognitionF = recognition; final LocationManagerService locationF = location; + final CountryDetectorService countryDetectorF = countryDetector; + final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater; // We now tell the activity manager it is okay to run third party // code. It will call back into us once it has gotten to the state @@ -514,7 +539,9 @@ class ServerThread extends Thread { if (wallpaperF != null) wallpaperF.systemReady(); if (immF != null) immF.systemReady(); if (locationF != null) locationF.systemReady(); + if (countryDetectorF != null) countryDetectorF.systemReady(); if (throttleF != null) throttleF.systemReady(); + if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemReady(); } }); @@ -528,37 +555,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; @@ -582,7 +579,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); } @@ -590,7 +587,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..a33b7c2 100644 --- a/services/java/com/android/server/TelephonyRegistry.java +++ b/services/java/com/android/server/TelephonyRegistry.java @@ -19,7 +19,8 @@ package com.android.server; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.net.NetworkUtils; +import android.net.LinkCapabilities; +import android.net.LinkProperties; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -35,6 +36,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 +91,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private String mDataConnectionApn = ""; - private String[] mDataConnectionApnTypes = null; + private ArrayList<String> mConnectedApns; - private String mDataConnectionInterfaceName = ""; + private LinkProperties mDataConnectionLinkProperties; + + private LinkCapabilities mDataConnectionLinkCapabilities; private Bundle mCellLocation = new Bundle(); @@ -121,6 +125,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 +238,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 +262,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 +275,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 +288,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 +301,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 +321,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 +341,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, LinkProperties linkProperties, + LinkCapabilities linkCapabilities, 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); + mDataConnectionLinkProperties = linkProperties; + mDataConnectionLinkCapabilities = linkCapabilities; + 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, linkProperties, linkCapabilities); } - public void notifyDataConnectionFailed(String reason) { + public void notifyDataConnectionFailed(String reason, String apnType) { if (!checkNotifyPermission("notifyDataConnectionFailed()")) { return; } /* - * This is commented out because there is on onDataConnectionFailed callback - * on PhoneStateListener. There should be + * This is commented out because there is no onDataConnectionFailed callback + * in PhoneStateListener. There should be. synchronized (mRecords) { mDataConnectionFailedReason = reason; final int N = mRecords.size(); @@ -395,7 +427,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { } } */ - broadcastDataConnectionFailed(reason); + broadcastDataConnectionFailed(reason, apnType); } public void notifyCellLocation(Bundle cellLocation) { @@ -404,8 +436,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 +447,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 +493,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println(" mDataConnectionPossible=" + mDataConnectionPossible); pw.println(" mDataConnectionReason=" + mDataConnectionReason); pw.println(" mDataConnectionApn=" + mDataConnectionApn); - pw.println(" mDataConnectionInterfaceName=" + mDataConnectionInterfaceName); + pw.println(" mDataConnectionLinkProperties=" + mDataConnectionLinkProperties); + pw.println(" mDataConnectionLinkCapabilities=" + mDataConnectionLinkCapabilities); pw.println(" mCellLocation=" + mCellLocation); pw.println("registrations: count=" + recordCount); - for (int i = 0; i < recordCount; i++) { - Record r = mRecords.get(i); + for (Record r : mRecords) { pw.println(" " + r.pkgForDebug + " 0x" + Integer.toHexString(r.events)); } } @@ -537,7 +568,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { private void broadcastDataConnectionStateChanged(int state, boolean isDataConnectivityPossible, - String reason, String apn, String[] apnTypes, String interfaceName, String gateway) { + String reason, String apn, String apnType, LinkProperties linkProperties, + LinkCapabilities linkCapabilities) { // Note: not reporting to the battery stats service here, because the // status bar takes care of that after taking into account all of the // required info. @@ -550,29 +582,26 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (reason != null) { intent.putExtra(Phone.STATE_CHANGE_REASON_KEY, reason); } - intent.putExtra(Phone.DATA_APN_KEY, apn); - String types = new String(""); - if (apnTypes.length > 0) { - types = apnTypes[0]; - for (int i = 1; i < apnTypes.length; i++) { - types = types+","+apnTypes[i]; + if (linkProperties != null) { + intent.putExtra(Phone.DATA_LINK_PROPERTIES_KEY, linkProperties); + NetworkInterface iface = linkProperties.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); + if (linkCapabilities != null) { + intent.putExtra(Phone.DATA_LINK_CAPABILITIES_KEY, linkCapabilities); } - intent.putExtra(Phone.DATA_GATEWAY_KEY, gatewayAddr); - + intent.putExtra(Phone.DATA_APN_KEY, apn); + intent.putExtra(Phone.DATA_APN_TYPE_KEY, apnType); mContext.sendStickyBroadcast(intent); } - private void broadcastDataConnectionFailed(String reason) { + private void broadcastDataConnectionFailed(String reason, String apnType) { Intent intent = new Intent(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); intent.putExtra(Phone.FAILURE_REASON_KEY, reason); + intent.putExtra(Phone.DATA_APN_TYPE_KEY, apnType); mContext.sendStickyBroadcast(intent); } 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 c047e10..7b2a570 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,70 +28,61 @@ 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.os.WorkSource; 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; @@ -110,15 +91,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; @@ -131,9 +108,7 @@ public class WifiService extends IWifiManager.Stub { private final IBatteryStats mBatteryStats; - private INetworkManagementService nwService; ConnectivityManager mCm; - private WifiWatchdogService mWifiWatchdogService = null; private String[] mWifiRegexs; /** @@ -145,118 +120,94 @@ public class WifiService extends IWifiManager.Stub { */ private static final long DEFAULT_IDLE_MILLIS = 15 * 60 * 1000; /* 15 minutes */ - private static final String WAKELOCK_TAG = "*wifi*"; + /** + * Number of allowed radio frequency channels in various regulatory domains. + * This list is sufficient for 802.11b/g networks (2.4GHz range). + */ + private static int[] sValidRegulatoryChannelCounts = new int[] {11, 13, 14}; + + private static final String ACTION_DEVICE_IDLE = + "com.android.server.WifiManager.action.DEVICE_IDLE"; + + private boolean mIsReceiverRegistered = false; + + NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", ""); + + // Variables relating to the 'available networks' notification /** - * The maximum amount of time to hold the wake lock after a disconnect - * caused by stopping the driver. Establishing an EDGE connection has been - * observed to take about 5 seconds under normal circumstances. This - * provides a bit of extra margin. - * <p> - * See {@link android.provider.Settings.Secure#WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS}. - * This is the default value if a Settings.Secure value is not present. + * The icon to show in the 'available networks' notification. This will also + * be the ID of the Notification given to the NotificationManager. */ - private static final int DEFAULT_WAKELOCK_TIMEOUT = 8000; - - // Wake lock used by driver-stop operation - private static PowerManager.WakeLock sDriverStopWakeLock; - // Wake lock used by other operations - private static PowerManager.WakeLock sWakeLock; - - private static final int MESSAGE_ENABLE_WIFI = 0; - private static final int MESSAGE_DISABLE_WIFI = 1; - private static final int MESSAGE_STOP_WIFI = 2; - private static final int MESSAGE_START_WIFI = 3; - private static final int MESSAGE_RELEASE_WAKELOCK = 4; - private static final int MESSAGE_UPDATE_STATE = 5; - private static final int MESSAGE_START_ACCESS_POINT = 6; - private static final int MESSAGE_STOP_ACCESS_POINT = 7; - private static final int MESSAGE_SET_CHANNELS = 8; - private static final int MESSAGE_ENABLE_NETWORKS = 9; - private static final int MESSAGE_START_SCAN = 10; - - - private final WifiHandler mWifiHandler; - - /* - * Cache of scan results objects (size is somewhat arbitrary) + private static final int ICON_NETWORKS_AVAILABLE = + com.android.internal.R.drawable.stat_notify_wifi_in_range; + /** + * When a notification is shown, we wait this amount before possibly showing it again. */ - private static final int SCAN_RESULT_CACHE_SIZE = 80; - private final LinkedHashMap<String, ScanResult> mScanResultCache; - - /* - * Character buffer used to parse scan results (optimization) + private final long NOTIFICATION_REPEAT_DELAY_MS; + /** + * Whether the user has set the setting to show the 'available networks' notification. */ - private static final int SCAN_RESULT_BUFFER_SIZE = 512; - private boolean mNeedReconfig; - + private boolean mNotificationEnabled; /** - * Temporary for computing UIDS that are responsible for starting WIFI. - * Protected by mWifiStateTracker lock. + * Observes the user setting to keep {@link #mNotificationEnabled} in sync. */ - private final WorkSource mTmpWorkSource = new WorkSource(); - - /* - * Last UID that asked to enable WIFI. + private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver; + /** + * The {@link System#currentTimeMillis()} must be at least this value for us + * to show the notification again. */ - private int mLastEnableUid = Process.myUid(); - - /* - * Last UID that asked to enable WIFI AP. + private long mNotificationRepeatTime; + /** + * The Notification object given to the NotificationManager. */ - private int mLastApEnableUid = Process.myUid(); - - + private Notification mNotification; /** - * Number of allowed radio frequency channels in various regulatory domains. - * This list is sufficient for 802.11b/g networks (2.4GHz range). + * 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 static int[] sValidRegulatoryChannelCounts = new int[] {11, 13, 14}; - - private static final String ACTION_DEVICE_IDLE = - "com.android.server.WifiManager.action.DEVICE_IDLE"; + 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; + + /** + * Temporary for computing UIDS that are responsible for starting WIFI. + * Protected by mWifiStateTracker lock. + */ + private final WorkSource mTmpWorkSource = new WorkSource(); - 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); @@ -271,14 +222,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(); } /** @@ -287,7 +274,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()); @@ -304,7 +291,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) { @@ -324,17 +314,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; } @@ -370,18 +357,13 @@ 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(); } /** @@ -389,9 +371,24 @@ public class WifiService extends IWifiManager.Stub { */ public void startScan(boolean forceActive) { enforceChangePermission(); - if (mWifiHandler == null) return; + mWifiStateMachine.startScan(forceActive); + } - Message.obtain(mWifiHandler, MESSAGE_START_SCAN, forceActive ? 1 : 0, 0).sendToTarget(); + 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"); } /** @@ -400,168 +397,44 @@ public class WifiService extends IWifiManager.Stub { * @return {@code true} if the enable/disable operation was * started or is already in the queue. */ - public boolean setWifiEnabled(boolean enable) { + public synchronized boolean setWifiEnabled(boolean enable) { enforceChangePermission(); - if (mWifiHandler == null) return false; - synchronized (mWifiHandler) { - // caller may not have WAKE_LOCK permission - it's not required here - long ident = Binder.clearCallingIdentity(); - sWakeLock.acquire(); - Binder.restoreCallingIdentity(ident); - - mLastEnableUid = Binder.getCallingUid(); - // set a flag if the user is enabling Wifi while in airplane mode - mAirplaneModeOverwridden = (enable && isAirplaneModeOn() && isAirplaneToggleable()); - sendEnableMessage(enable, true, Binder.getCallingUid()); + if (DBG) { + Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n"); } - return true; - } - - /** - * Enables/disables Wi-Fi synchronously. - * @param enable {@code true} to turn Wi-Fi on, {@code false} to turn it off. - * @param persist {@code true} if the setting should be persisted. - * @param uid The UID of the process making the request. - * @return {@code true} if the operation succeeds (or if the existing state - * is the same as the requested state) - */ - private boolean setWifiEnabledBlocking(boolean enable, boolean persist, int uid) { - final int eventualWifiState = enable ? WIFI_STATE_ENABLED : WIFI_STATE_DISABLED; - final int wifiState = mWifiStateTracker.getWifiState(); - - if (wifiState == eventualWifiState) { - return true; - } - if (enable && isAirplaneModeOn() && !mAirplaneModeOverwridden) { - return false; + // set a flag if the user is enabling Wifi while in airplane mode + if (enable && isAirplaneModeOn() && isAirplaneToggleable()) { + mAirplaneModeOverwridden.set(true); } - /** - * Multiple calls to unregisterReceiver() cause exception and a system crash. - * This can happen if a supplicant is lost (or firmware crash occurs) and user indicates - * disable wifi at the same time. - * Avoid doing a disable when the current Wifi state is UNKNOWN - * TODO: Handle driver load fail and supplicant lost as seperate states - */ - if ((wifiState == WIFI_STATE_UNKNOWN) && !enable) { - return false; + if (enable) { + reportStartWorkSource(); } + mWifiStateMachine.setWifiEnabled(enable); - /** - * Fail Wifi if AP is enabled - * TODO: Deprecate WIFI_STATE_UNKNOWN and rename it - * WIFI_STATE_FAILED + /* + * Caller might not have WRITE_SECURE_SETTINGS, + * only CHANGE_WIFI_STATE is enforced */ - if ((mWifiApState == WIFI_AP_STATE_ENABLED) && enable) { - setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); - return false; - } - - setWifiEnabledState(enable ? WIFI_STATE_ENABLING : WIFI_STATE_DISABLING, uid); + long ident = Binder.clearCallingIdentity(); + persistWifiEnabled(enable); + Binder.restoreCallingIdentity(ident); if (enable) { - if (!mWifiStateTracker.loadDriver()) { - Slog.e(TAG, "Failed to load Wi-Fi driver."); - setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); - return false; + if (!mIsReceiverRegistered) { + registerForBroadcasts(); + mIsReceiverRegistered = true; } - if (!mWifiStateTracker.startSupplicant()) { - mWifiStateTracker.unloadDriver(); - Slog.e(TAG, "Failed to start supplicant daemon."); - setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); - return false; - } - - registerForBroadcasts(); - mWifiStateTracker.startEventLoop(); - - } else { - + } else if (mIsReceiverRegistered){ mContext.unregisterReceiver(mReceiver); - // Remove notification (it will no-op if it isn't visible) - mWifiStateTracker.setNotificationVisible(false, 0, false, 0); - - boolean failedToStopSupplicantOrUnloadDriver = false; - - if (!mWifiStateTracker.stopSupplicant()) { - Slog.e(TAG, "Failed to stop supplicant daemon."); - setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); - failedToStopSupplicantOrUnloadDriver = true; - } - - /** - * Reset connections and disable interface - * before we unload the driver - */ - mWifiStateTracker.resetConnections(true); - - if (!mWifiStateTracker.unloadDriver()) { - Slog.e(TAG, "Failed to unload Wi-Fi driver."); - if (!failedToStopSupplicantOrUnloadDriver) { - setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); - failedToStopSupplicantOrUnloadDriver = true; - } - } - - if (failedToStopSupplicantOrUnloadDriver) { - return false; - } + mIsReceiverRegistered = false; } - // Success! - - if (persist) { - persistWifiEnabled(enable); - } - setWifiEnabledState(eventualWifiState, uid); return true; } - private void setWifiEnabledState(int wifiState, int uid) { - final int previousWifiState = mWifiStateTracker.getWifiState(); - - long ident = Binder.clearCallingIdentity(); - try { - if (wifiState == WIFI_STATE_ENABLED) { - mBatteryStats.noteWifiOn(); - } else if (wifiState == WIFI_STATE_DISABLED) { - mBatteryStats.noteWifiOff(); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(ident); - } - - // Update state - mWifiStateTracker.setWifiState(wifiState); - - // Broadcast - final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - intent.putExtra(WifiManager.EXTRA_WIFI_STATE, wifiState); - intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, previousWifiState); - mContext.sendStickyBroadcast(intent); - } - - private void enforceAccessPermission() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, - "WifiService"); - } - - private void enforceChangePermission() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, - "WifiService"); - - } - - private void enforceMulticastChangePermission() { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, - "WifiService"); - } - /** * see {@link WifiManager#getWifiState()} * @return One of {@link WifiManager#WIFI_STATE_DISABLED}, @@ -572,66 +445,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; @@ -649,7 +515,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; @@ -665,143 +535,27 @@ public class WifiService extends IWifiManager.Stub { } /** - * Enables/disables Wi-Fi AP synchronously. The driver is loaded - * and soft access point configured as a single operation. - * @param enable {@code true} to turn Wi-Fi on, {@code false} to turn it off. - * @param uid The UID of the process making the request. - * @param wifiConfig The WifiConfiguration for AP - * @return {@code true} if the operation succeeds (or if the existing state - * is the same as the requested state) + * see {@link android.net.wifi.WifiManager#disconnect()} */ - private boolean setWifiApEnabledBlocking(boolean enable, - int uid, WifiConfiguration wifiConfig) { - final int eventualWifiApState = enable ? WIFI_AP_STATE_ENABLED : WIFI_AP_STATE_DISABLED; - - if (mWifiApState == eventualWifiApState) { - /* Configuration changed on a running access point */ - if(enable && (wifiConfig != null)) { - try { - nwService.setAccessPoint(wifiConfig, mWifiStateTracker.getInterfaceName(), - SOFTAP_IFACE); - setWifiApConfiguration(wifiConfig); - return true; - } catch(Exception e) { - Slog.e(TAG, "Exception in nwService during AP restart"); - try { - nwService.stopAccessPoint(); - } catch (Exception ee) { - Slog.e(TAG, "Could not stop AP, :" + ee); - } - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD); - return false; - } - } else { - return true; - } - } - - /** - * Fail AP if Wifi is enabled - */ - if ((mWifiStateTracker.getWifiState() == WIFI_STATE_ENABLED) && enable) { - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD); - return false; - } - - setWifiApEnabledState(enable ? WIFI_AP_STATE_ENABLING : - WIFI_AP_STATE_DISABLING, uid, DriverAction.NO_DRIVER_UNLOAD); - - if (enable) { - - /* Use default config if there is no existing config */ - if (wifiConfig == null && ((wifiConfig = getWifiApConfiguration()) == null)) { - wifiConfig = new WifiConfiguration(); - wifiConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default); - wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE); - } - - if (!mWifiStateTracker.loadDriver()) { - Slog.e(TAG, "Failed to load Wi-Fi driver for AP mode"); - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD); - return false; - } - - try { - nwService.startAccessPoint(wifiConfig, mWifiStateTracker.getInterfaceName(), - SOFTAP_IFACE); - } catch(Exception e) { - Slog.e(TAG, "Exception in startAccessPoint()"); - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD); - return false; - } - - setWifiApConfiguration(wifiConfig); - - } else { - - try { - nwService.stopAccessPoint(); - } catch(Exception e) { - Slog.e(TAG, "Exception in stopAccessPoint()"); - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD); - return false; - } - - if (!mWifiStateTracker.unloadDriver()) { - Slog.e(TAG, "Failed to unload Wi-Fi driver for AP mode"); - setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD); - return false; - } - } - - setWifiApEnabledState(eventualWifiApState, uid, DriverAction.NO_DRIVER_UNLOAD); - return true; + public void disconnect() { + enforceChangePermission(); + mWifiStateMachine.disconnectCommand(); } /** - * see {@link WifiManager#getWifiApState()} - * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED}, - * {@link WifiManager#WIFI_AP_STATE_DISABLING}, - * {@link WifiManager#WIFI_AP_STATE_ENABLED}, - * {@link WifiManager#WIFI_AP_STATE_ENABLING}, - * {@link WifiManager#WIFI_AP_STATE_FAILED} + * see {@link android.net.wifi.WifiManager#reconnect()} */ - public int getWifiApEnabledState() { - enforceAccessPermission(); - return mWifiApState; + public void reconnect() { + enforceChangePermission(); + mWifiStateMachine.reconnectCommand(); } - private void setWifiApEnabledState(int wifiAPState, int uid, DriverAction flag) { - final int previousWifiApState = mWifiApState; - - /** - * Unload the driver if going to a failed state - */ - if ((mWifiApState == WIFI_AP_STATE_FAILED) && (flag == DriverAction.DRIVER_UNLOAD)) { - mWifiStateTracker.unloadDriver(); - } - - long ident = Binder.clearCallingIdentity(); - try { - if (wifiAPState == WIFI_AP_STATE_ENABLED) { - mBatteryStats.noteWifiOn(); - } else if (wifiAPState == WIFI_AP_STATE_DISABLED) { - mBatteryStats.noteWifiOff(); - } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(ident); - } - - // Update state - mWifiApState = wifiAPState; - - // Broadcast - final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiAPState); - intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState); - mContext.sendStickyBroadcast(intent); + /** + * see {@link android.net.wifi.WifiManager#reassociate()} + */ + public void reassociate() { + enforceChangePermission(); + mWifiStateMachine.reassociateCommand(); } /** @@ -810,217 +564,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(); } /** @@ -1030,280 +574,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; - } - - 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; + return mWifiStateMachine.syncAddOrUpdateNetwork(config); } - /** + /** * See {@link android.net.wifi.WifiManager#removeNetwork(int)} * @param netId the integer that identifies the network configuration * to the supplicant @@ -1311,8 +585,7 @@ public class WifiService extends IWifiManager.Stub { */ public boolean removeNetwork(int netId) { enforceChangePermission(); - - return mWifiStateTracker.removeNetwork(netId); + return mWifiStateMachine.syncRemoveNetwork(netId); } /** @@ -1324,14 +597,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); } /** @@ -1342,8 +608,7 @@ public class WifiService extends IWifiManager.Stub { */ public boolean disableNetwork(int netId) { enforceChangePermission(); - - return mWifiStateTracker.disableNetwork(netId); + return mWifiStateMachine.syncDisableNetwork(netId); } /** @@ -1356,7 +621,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(); } /** @@ -1366,180 +631,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(); } /** @@ -1554,7 +658,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(); @@ -1576,28 +680,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; } /** @@ -1615,7 +706,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, @@ -1641,7 +732,86 @@ 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); + } + + public void startWpsPbc(String bssid) { + enforceChangePermission(); + mWifiStateMachine.startWpsPbc(bssid); + } + + public void startWpsPin(String bssid, int apPin) { + enforceChangePermission(); + mWifiStateMachine.startWpsPin(bssid, apPin); } private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @@ -1663,17 +833,11 @@ public class WifiService extends IWifiManager.Stub { // Once the screen is on, we are not keeping WIFI running // because of any locks so clear that tracking immediately. reportStartWorkSource(); - mWifiStateTracker.enableRssiPolling(true); - /* DHCP or other temporary failures in the past can prevent - * a disabled network from being connected to, enable on screen on - */ - if (mWifiStateTracker.isAnyNetworkDisabled()) { - sendEnableNetworksMessage(); - } + 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 +845,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"); @@ -1733,7 +897,7 @@ public class WifiService extends IWifiManager.Stub { isBluetoothPlaying = true; } } - mWifiStateTracker.setBluetoothScanMode(isBluetoothPlaying); + mWifiStateMachine.setBluetoothScanMode(isBluetoothPlaying); } else { return; @@ -1772,7 +936,7 @@ public class WifiService extends IWifiManager.Stub { * of {@code 0} isn't really a plugged type, but rather an indication that the * device isn't plugged in at all, there is no bit value corresponding to a * {@code pluggedType} value of {@code 0}. That is why we shift by - * {@code pluggedType — 1} instead of by {@code pluggedType}. + * {@code pluggedType - 1} instead of by {@code pluggedType}. * @param stayAwakeConditions a bit string specifying which "plugged types" should * keep the device (and hence Wi-Fi) awake. * @param pluggedType the type of plug (USB, AC, or none) for which the check is @@ -1785,103 +949,47 @@ 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 reportStartWorkSource() { - synchronized (mWifiStateTracker) { - mTmpWorkSource.clear(); - if (mDeviceIdle) { - for (int i=0; i<mLocks.mList.size(); i++) { - mTmpWorkSource.add(mLocks.mList.get(i).mWorkSource); - } + private synchronized void reportStartWorkSource() { + mTmpWorkSource.clear(); + if (mDeviceIdle) { + for (int i=0; i<mLocks.mList.size(); i++) { + mTmpWorkSource.add(mLocks.mList.get(i).mWorkSource); } - mWifiStateTracker.updateBatteryWorkSourceLocked(mTmpWorkSource); - sWakeLock.setWorkSource(mTmpWorkSource); } + mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource); } - + private void updateWifiState() { - // send a message so it's all serialized - Message.obtain(mWifiHandler, MESSAGE_UPDATE_STATE, 0, 0).sendToTarget(); - } - - private void doUpdateWifiState() { boolean wifiEnabled = getPersistedWifiEnabled(); - boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden; - - boolean lockHeld; - synchronized (mLocks) { - lockHeld = mLocks.hasLocks(); - } - - int strongestLockMode = WifiManager.WIFI_MODE_FULL; + boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden.get(); + boolean lockHeld = mLocks.hasLocks(); + 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) { + reportStartWorkSource(); + 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); } } @@ -1919,97 +1027,6 @@ public class WifiService extends IWifiManager.Stub { Settings.System.AIRPLANE_MODE_ON, 0) == 1; } - /** - * Handler that allows posting to the WifiThread. - */ - private class WifiHandler extends Handler { - public WifiHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - - case MESSAGE_ENABLE_WIFI: - setWifiEnabledBlocking(true, msg.arg1 == 1, msg.arg2); - if (mWifiWatchdogService == null) { - mWifiWatchdogService = new WifiWatchdogService(mContext, mWifiStateTracker); - } - sWakeLock.release(); - break; - - case MESSAGE_START_WIFI: - reportStartWorkSource(); - mWifiStateTracker.setScanOnlyMode(msg.arg1 == WifiManager.WIFI_MODE_SCAN_ONLY); - mWifiStateTracker.restart(); - mWifiStateTracker.setHighPerfMode(msg.arg1 == - WifiManager.WIFI_MODE_FULL_HIGH_PERF); - sWakeLock.release(); - break; - - case MESSAGE_UPDATE_STATE: - doUpdateWifiState(); - break; - - case MESSAGE_DISABLE_WIFI: - // a non-zero msg.arg1 value means the "enabled" setting - // should be persisted - setWifiEnabledBlocking(false, msg.arg1 == 1, msg.arg2); - mWifiWatchdogService = null; - sWakeLock.release(); - break; - - case MESSAGE_STOP_WIFI: - mWifiStateTracker.disconnectAndStop(); - // don't release wakelock - break; - - case MESSAGE_RELEASE_WAKELOCK: - sDriverStopWakeLock.release(); - break; - - case MESSAGE_START_ACCESS_POINT: - setWifiApEnabledBlocking(true, - msg.arg1, - (WifiConfiguration) msg.obj); - sWakeLock.release(); - break; - - case MESSAGE_STOP_ACCESS_POINT: - setWifiApEnabledBlocking(false, - msg.arg1, - (WifiConfiguration) msg.obj); - sWakeLock.release(); - break; - - case MESSAGE_SET_CHANNELS: - setNumAllowedChannelsBlocking(msg.arg1, msg.arg2 == 1); - break; - - case MESSAGE_ENABLE_NETWORKS: - mWifiStateTracker.enableAllNetworks(getConfiguredNetworks()); - break; - - case MESSAGE_START_SCAN: - boolean forceActive = (msg.arg1 == 1); - switch (mWifiStateTracker.getSupplicantState()) { - case DISCONNECTED: - case INACTIVE: - case SCANNING: - case DORMANT: - break; - default: - mWifiStateTracker.setScanResultHandling( - WifiStateTracker.SUPPL_SCAN_HANDLING_LIST_ONLY); - break; - } - mWifiStateTracker.scan(forceActive); - break; - } - } - } - @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) @@ -2019,17 +1036,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) { @@ -2043,33 +1060,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, WorkSource ws) { super(lockMode, tag, binder, ws); @@ -2101,15 +1099,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; } @@ -2147,7 +1141,7 @@ public class WifiService extends IWifiManager.Stub { } void enforceWakeSourcePermission(int uid, int pid) { - if (uid == Process.myUid()) { + if (uid == android.os.Process.myUid()) { return; } mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS, @@ -2156,11 +1150,7 @@ public class WifiService extends IWifiManager.Stub { public boolean acquireWifiLock(IBinder binder, int lockMode, String tag, WorkSource ws) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); - if (lockMode != WifiManager.WIFI_MODE_FULL && - lockMode != WifiManager.WIFI_MODE_SCAN_ONLY && - 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; } if (ws != null) { @@ -2183,10 +1173,6 @@ public class WifiService extends IWifiManager.Stub { case WifiManager.WIFI_MODE_FULL: mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource); break; - case WifiManager.WIFI_MODE_FULL_HIGH_PERF: - /* Treat high power as a full lock for battery stats */ - mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource); - break; case WifiManager.WIFI_MODE_SCAN_ONLY: mBatteryStats.noteScanWifiLockAcquiredFromSource(wifiLock.mWorkSource); break; @@ -2198,10 +1184,6 @@ public class WifiService extends IWifiManager.Stub { case WifiManager.WIFI_MODE_FULL: mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource); break; - case WifiManager.WIFI_MODE_FULL_HIGH_PERF: - /* Treat high power as a full lock for battery stats */ - mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource); - break; case WifiManager.WIFI_MODE_SCAN_ONLY: mBatteryStats.noteScanWifiLockReleasedFromSource(wifiLock.mWorkSource); break; @@ -2220,9 +1202,6 @@ public class WifiService extends IWifiManager.Stub { case WifiManager.WIFI_MODE_FULL: ++mFullLocksAcquired; break; - case WifiManager.WIFI_MODE_FULL_HIGH_PERF: - ++mFullHighPerfLocksAcquired; - break; case WifiManager.WIFI_MODE_SCAN_ONLY: ++mScanLocksAcquired; break; @@ -2235,7 +1214,7 @@ public class WifiService extends IWifiManager.Stub { // Be aggressive about adding new locks into the accounted state... // we want to over-report rather than under-report. reportStartWorkSource(); - + updateWifiState(); return true; } @@ -2291,9 +1270,6 @@ public class WifiService extends IWifiManager.Stub { case WifiManager.WIFI_MODE_FULL: ++mFullLocksReleased; break; - case WifiManager.WIFI_MODE_FULL_HIGH_PERF: - ++mFullHighPerfLocksReleased; - break; case WifiManager.WIFI_MODE_SCAN_ONLY: ++mScanLocksReleased; break; @@ -2365,7 +1341,7 @@ public class WifiService extends IWifiManager.Stub { if (mMulticasters.size() != 0) { return; } else { - mWifiStateTracker.startPacketFiltering(); + mWifiStateMachine.startPacketFiltering(); } } } @@ -2380,7 +1356,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(); @@ -2417,7 +1393,7 @@ public class WifiService extends IWifiManager.Stub { removed.unlinkDeathRecipient(); } if (mMulticasters.size() == 0) { - mWifiStateTracker.startPacketFiltering(); + mWifiStateMachine.startPacketFiltering(); } Long ident = Binder.clearCallingIdentity(); @@ -2436,4 +1412,144 @@ public class WifiService extends IWifiManager.Stub { return (mMulticasters.size() > 0); } } + + private void checkAndSetNotification() { + // If we shouldn't place a notification on available networks, then + // don't bother doing any of the following + if (!mNotificationEnabled) return; + + State state = mNetworkInfo.getState(); + if ((state == NetworkInfo.State.DISCONNECTED) + || (state == NetworkInfo.State.UNKNOWN)) { + // Look for an open network + List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList(); + if (scanResults != null) { + int numOpenNetworks = 0; + for (int i = scanResults.size() - 1; i >= 0; i--) { + ScanResult scanResult = scanResults.get(i); + + if (TextUtils.isEmpty(scanResult.capabilities)) { + numOpenNetworks++; + } + } + + if (numOpenNetworks > 0) { + if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) { + /* + * We've scanned continuously at least + * NUM_SCANS_BEFORE_NOTIFICATION times. The user + * probably does not have a remembered network in range, + * since otherwise supplicant would have tried to + * associate and thus resetting this counter. + */ + setNotificationVisible(true, numOpenNetworks, false, 0); + } + return; + } + } + } + + // No open networks in range, remove the notification + setNotificationVisible(false, 0, false, 0); + } + + /** + * Clears variables related to tracking whether a notification has been + * shown recently and clears the current notification. + */ + private void resetNotification() { + mNotificationRepeatTime = 0; + mNumScansSinceNetworkStateChange = 0; + setNotificationVisible(false, 0, false, 0); + } + + /** + * Display or don't display a notification that there are open Wi-Fi networks. + * @param visible {@code true} if notification should be visible, {@code false} otherwise + * @param numNetworks the number networks seen + * @param force {@code true} to force notification to be shown/not-shown, + * even if it is already shown/not-shown. + * @param delay time in milliseconds after which the notification should be made + * visible or invisible. + */ + private void setNotificationVisible(boolean visible, int numNetworks, boolean force, + int delay) { + + // Since we use auto cancel on the notification, when the + // mNetworksAvailableNotificationShown is true, the notification may + // have actually been canceled. However, when it is false we know + // for sure that it is not being shown (it will not be shown any other + // place than here) + + // If it should be hidden and it is already hidden, then noop + if (!visible && !mNotificationShown && !force) { + return; + } + + NotificationManager notificationManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); + + Message message; + if (visible) { + + // Not enough time has passed to show the notification again + if (System.currentTimeMillis() < mNotificationRepeatTime) { + return; + } + + if (mNotification == null) { + // Cache the Notification object. + mNotification = new Notification(); + mNotification.when = 0; + mNotification.icon = ICON_NETWORKS_AVAILABLE; + mNotification.flags = Notification.FLAG_AUTO_CANCEL; + mNotification.contentIntent = PendingIntent.getActivity(mContext, 0, + new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK), 0); + } + + CharSequence title = mContext.getResources().getQuantityText( + com.android.internal.R.plurals.wifi_available, numNetworks); + CharSequence details = mContext.getResources().getQuantityText( + com.android.internal.R.plurals.wifi_available_detailed, numNetworks); + mNotification.tickerText = title; + mNotification.setLatestEventInfo(mContext, title, details, mNotification.contentIntent); + + mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS; + + notificationManager.notify(ICON_NETWORKS_AVAILABLE, mNotification); + } else { + notificationManager.cancel(ICON_NETWORKS_AVAILABLE); + } + + mNotificationShown = visible; + } + + private class NotificationEnabledSettingObserver extends ContentObserver { + + public NotificationEnabledSettingObserver(Handler handler) { + super(handler); + } + + public void register() { + ContentResolver cr = mContext.getContentResolver(); + cr.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this); + mNotificationEnabled = getValue(); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + + mNotificationEnabled = getValue(); + resetNotification(); + } + + private boolean getValue() { + return Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1; + } + } + + } diff --git a/services/java/com/android/server/WifiStateTracker.java b/services/java/com/android/server/WifiStateTracker.java new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/services/java/com/android/server/WifiStateTracker.java diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java index 445dd03..46d6bef 100644 --- a/services/java/com/android/server/WifiWatchdogService.java +++ b/services/java/com/android/server/WifiWatchdogService.java @@ -27,7 +27,6 @@ import android.net.DhcpInfo; import android.net.wifi.ScanResult; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.net.wifi.WifiStateTracker; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -77,7 +76,6 @@ public class WifiWatchdogService { private Context mContext; private ContentResolver mContentResolver; - private WifiStateTracker mWifiStateTracker; private WifiManager mWifiManager; /** @@ -108,10 +106,9 @@ public class WifiWatchdogService { /** Whether the current AP check should be canceled. */ private boolean mShouldCancel; - WifiWatchdogService(Context context, WifiStateTracker wifiStateTracker) { + WifiWatchdogService(Context context) { mContext = context; mContentResolver = context.getContentResolver(); - mWifiStateTracker = wifiStateTracker; mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); createThread(); @@ -275,12 +272,13 @@ public class WifiWatchdogService { /** * Unregister broadcasts and quit the watchdog thread */ - private void quit() { - unregisterForWifiBroadcasts(); - mContext.getContentResolver().unregisterContentObserver(mContentObserver); - mHandler.removeAllActions(); - mHandler.getLooper().quit(); - } + //TODO: Change back to running WWS when needed +// private void quit() { +// unregisterForWifiBroadcasts(); +// mContext.getContentResolver().unregisterContentObserver(mContentObserver); +// mHandler.removeAllActions(); +// mHandler.getLooper().quit(); +// } /** * Waits for the main watchdog thread to create the handler. @@ -751,7 +749,7 @@ public class WifiWatchdogService { // Black list this "bad" AP, this will cause an attempt to connect to another blacklistAp(ap.bssid); // Initiate an association to an alternate AP - mWifiStateTracker.reassociate(); + mWifiManager.reassociate(); } private void blacklistAp(String bssid) { @@ -762,10 +760,7 @@ public class WifiWatchdogService { // Before taking action, make sure we should not cancel our processing if (shouldCancel()) return; - if (!mWifiStateTracker.addToBlacklist(bssid)) { - // There's a known bug where this method returns failure on success - //Slog.e(TAG, "Blacklisting " + bssid + " failed"); - } + mWifiManager.addToBlacklist(bssid); if (D) { myLogD("Blacklisting " + bssid); @@ -860,10 +855,7 @@ public class WifiWatchdogService { * (and blacklisted them). Clear the blacklist so the AP with best * signal is chosen. */ - if (!mWifiStateTracker.clearBlacklist()) { - // There's a known bug where this method returns failure on success - //Slog.e(TAG, "Clearing blacklist failed"); - } + mWifiManager.clearBlacklist(); if (V) { myLogV("handleSleep: Set state to SLEEP and cleared blacklist"); @@ -934,7 +926,7 @@ public class WifiWatchdogService { * should revert anything done by the watchdog monitoring. */ private void handleReset() { - mWifiStateTracker.clearBlacklist(); + mWifiManager.clearBlacklist(); setIdleState(true); } @@ -1151,7 +1143,7 @@ public class WifiWatchdogService { private void handleWifiStateChanged(int wifiState) { if (wifiState == WifiManager.WIFI_STATE_DISABLED) { - quit(); + onDisconnected(); } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) { onEnabled(); } diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index 7d816c7..7100cc5 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; +import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; import static android.view.WindowManager.LayoutParams.FLAG_SYSTEM_ERROR; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; @@ -158,7 +159,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; @@ -1789,18 +1789,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())) { @@ -1906,7 +1899,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); @@ -1983,14 +1976,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; @@ -3103,8 +3092,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; @@ -4436,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; } } @@ -5569,6 +5560,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(); } @@ -6849,7 +6856,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; } @@ -6953,14 +6960,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, @@ -6982,6 +6987,9 @@ public class WindowManagerService extends IWindowManager.Stub frame.right >= mCompatibleScreenFrame.right && frame.bottom >= mCompatibleScreenFrame.bottom; } else { + if ((mAttrs.flags & FLAG_FULLSCREEN) != 0) { + return true; + } return frame.left <= 0 && frame.top <= 0 && frame.right >= screenWidth && frame.bottom >= screenHeight; @@ -8486,6 +8494,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(); @@ -9274,12 +9287,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); @@ -9680,30 +9687,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) { @@ -9992,8 +9995,6 @@ public class WindowManagerService extends IWindowManager.Stub mFreezeGcPending = now; } - if (DEBUG_FREEZE) Slog.v(TAG, "*** FREEZING DISPLAY", new RuntimeException()); - mDisplayFrozen = true; mInputMonitor.freezeInputDispatchingLw(); @@ -10020,8 +10021,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 3c3006c..328dcd2 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -2445,6 +2445,10 @@ public final class ActivityManagerService extends ActivityManagerNative } if (proc.thread != null) { + if (proc.pid == Process.myPid()) { + Log.w(TAG, "crashApplication: trying to crash self!"); + return; + } long ident = Binder.clearCallingIdentity(); try { proc.thread.scheduleCrash(message); @@ -5904,6 +5908,35 @@ public final class ActivityManagerService extends ActivityManagerNative } } + public void setImmersive(IBinder token, boolean immersive) { + synchronized(this) { + int index = (token != null) ? mMainStack.indexOfTokenLocked(token) : -1; + if (index < 0) { + throw new IllegalArgumentException(); + } + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); + r.immersive = immersive; + } + } + + public boolean isImmersive(IBinder token) { + synchronized (this) { + int index = (token != null) ? mMainStack.indexOfTokenLocked(token) : -1; + if (index < 0) { + throw new IllegalArgumentException(); + } + ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(index); + return r.immersive; + } + } + + public boolean isTopActivityImmersive() { + synchronized (this) { + ActivityRecord r = mMainStack.topRunningActivityLocked(null); + return (r != null) ? r.immersive : false; + } + } + public final void enterSafeMode() { synchronized(this) { // It only makes sense to do this before the system is ready @@ -7150,6 +7183,12 @@ public final class ActivityManagerService extends ActivityManagerNative dumpServicesLocked(fd, pw, args, opti, true); } return; + } else { + // Dumping a single activity? + if (dumpActivity(fd, pw, cmd, args, opti, dumpAll)) { + return; + } + pw.println("Bad activity command: " + cmd); } } @@ -7562,6 +7601,82 @@ public final class ActivityManagerService extends ActivityManagerNative } } + /** + * There are three things that cmd can be: + * - a flattened component name that matched an existing activity + * - the cmd arg isn't the flattened component name of an existing activity: + * dump all activity whose component contains the cmd as a substring + * - A hex number of the ActivityRecord object instance. + */ + protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args, + int opti, boolean dumpAll) { + String[] newArgs; + ComponentName componentName = ComponentName.unflattenFromString(name); + int objectId = 0; + try { + objectId = Integer.parseInt(name, 16); + name = null; + componentName = null; + } catch (RuntimeException e) { + } + newArgs = new String[args.length - opti]; + if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti); + + ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>(); + synchronized (this) { + for (ActivityRecord r1 : (ArrayList<ActivityRecord>)mMainStack.mHistory) { + if (componentName != null) { + if (r1.intent.getComponent().equals(componentName)) { + activities.add(r1); + } + } else if (name != null) { + if (r1.intent.getComponent().flattenToString().contains(name)) { + activities.add(r1); + } + } else if (System.identityHashCode(this) == objectId) { + activities.add(r1); + } + } + } + + if (activities.size() <= 0) { + return false; + } + + for (int i=0; i<activities.size(); i++) { + dumpActivity(fd, pw, activities.get(i), newArgs, dumpAll); + } + return true; + } + + /** + * Invokes IApplicationThread.dumpActivity() on the thread of the specified activity if + * there is a thread associated with the activity. + */ + private void dumpActivity(FileDescriptor fd, PrintWriter pw, ActivityRecord r, String[] args, + boolean dumpAll) { + pw.println(" Activity " + r.intent.getComponent().flattenToString()); + if (dumpAll) { + synchronized (this) { + pw.print(" * "); pw.println(r); + r.dump(pw, " "); + } + pw.println(""); + } + if (r.app != null && r.app.thread != null) { + try { + // flush anything that is already in the PrintWriter since the thread is going + // to write to the file descriptor directly + pw.flush(); + r.app.thread.dumpActivity(fd, r, args); + pw.print("\n"); + pw.flush(); + } catch (RemoteException e) { + pw.println("got a RemoteException while dumping the activity"); + } + } + } + boolean dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll) { boolean needSep = false; @@ -12430,7 +12545,69 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + + public boolean dumpHeap(String process, boolean managed, + String path, ParcelFileDescriptor fd) throws RemoteException { + + try { + synchronized (this) { + // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to + // its own permission (same as profileControl). + if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires permission " + + android.Manifest.permission.SET_ACTIVITY_WATCHER); + } + + if (fd == null) { + throw new IllegalArgumentException("null fd"); + } + + ProcessRecord proc = null; + try { + int pid = Integer.parseInt(process); + synchronized (mPidsSelfLocked) { + proc = mPidsSelfLocked.get(pid); + } + } catch (NumberFormatException e) { + } + + if (proc == null) { + HashMap<String, SparseArray<ProcessRecord>> all + = mProcessNames.getMap(); + SparseArray<ProcessRecord> procs = all.get(process); + if (procs != null && procs.size() > 0) { + proc = procs.valueAt(0); + } + } + + if (proc == null || proc.thread == null) { + throw new IllegalArgumentException("Unknown process: " + process); + } + + boolean isSecure = "1".equals(SystemProperties.get(SYSTEM_SECURE, "0")); + if (isSecure) { + if ((proc.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) { + throw new SecurityException("Process not debuggable: " + proc); + } + } + + proc.thread.dumpHeap(managed, path, fd); + fd = null; + return true; + } + } catch (RemoteException e) { + throw new IllegalStateException("Process disappeared"); + } finally { + if (fd != null) { + try { + fd.close(); + } catch (IOException e) { + } + } + } + } + /** In this method we try to acquire our lock to make sure that we have not deadlocked */ public void monitor() { synchronized (this) { } diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index 9358469..bf4db80 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -105,6 +105,7 @@ class ActivityRecord extends IApplicationToken.Stub { boolean idle; // has the activity gone idle? boolean hasBeenLaunched;// has this activity ever been launched? boolean frozenBeforeDestroy;// has been frozen but not yet destroyed. + boolean immersive; // immersive mode (don't interrupt if possible) String stringName; // for caching of toString(). @@ -161,6 +162,7 @@ class ActivityRecord extends IApplicationToken.Stub { pw.print(prefix); pw.print("keysPaused="); pw.print(keysPaused); pw.print(" inHistory="); pw.print(inHistory); pw.print(" persistent="); pw.print(persistent); + pw.print(" immersive="); pw.print(immersive); pw.print(" launchMode="); pw.println(launchMode); pw.print(prefix); pw.print("fullscreen="); pw.print(fullscreen); pw.print(" visible="); pw.print(visible); @@ -292,6 +294,8 @@ class ActivityRecord extends IApplicationToken.Stub { } else { isHomeActivity = false; } + + immersive = (aInfo.flags & ActivityInfo.FLAG_IMMERSIVE) != 0; } else { realActivity = null; taskAffinity = null; @@ -303,6 +307,7 @@ class ActivityRecord extends IApplicationToken.Stub { packageName = null; fullscreen = true; isHomeActivity = false; + immersive = false; } } diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index 1bc5e4b..bfac346 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,7 +32,6 @@ 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; @@ -54,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 @@ -66,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 @@ -75,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; @@ -87,13 +89,27 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129"; private static final String USB_NETMASK = "255.255.255.0"; - // FYI - the default wifi is 192.168.43.1 and 255.255.255.0 + // USB is 192.168.42.1 and 255.255.255.0 + // Wifi is 192.168.43.1 and 255.255.255.0 + // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1 + // with 255.255.255.0 private String[] mDhcpRange; private static final String DHCP_DEFAULT_RANGE1_START = "192.168.42.2"; private static final String DHCP_DEFAULT_RANGE1_STOP = "192.168.42.254"; private static final String DHCP_DEFAULT_RANGE2_START = "192.168.43.2"; private static final String DHCP_DEFAULT_RANGE2_STOP = "192.168.43.254"; + private static final String DHCP_DEFAULT_RANGE3_START = "192.168.44.2"; + private static final String DHCP_DEFAULT_RANGE3_STOP = "192.168.44.254"; + private static final String DHCP_DEFAULT_RANGE4_START = "192.168.45.2"; + private static final String DHCP_DEFAULT_RANGE4_STOP = "192.168.45.254"; + private static final String DHCP_DEFAULT_RANGE5_START = "192.168.46.2"; + private static final String DHCP_DEFAULT_RANGE5_STOP = "192.168.46.254"; + private static final String DHCP_DEFAULT_RANGE6_START = "192.168.47.2"; + private static final String DHCP_DEFAULT_RANGE6_STOP = "192.168.47.254"; + private static final String DHCP_DEFAULT_RANGE7_START = "192.168.48.2"; + private static final String DHCP_DEFAULT_RANGE7_STOP = "192.168.48.254"; + private String[] mDnsServers; private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8"; @@ -161,11 +177,21 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mDhcpRange = context.getResources().getStringArray( com.android.internal.R.array.config_tether_dhcp_range); if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) { - mDhcpRange = new String[4]; + mDhcpRange = new String[14]; mDhcpRange[0] = DHCP_DEFAULT_RANGE1_START; mDhcpRange[1] = DHCP_DEFAULT_RANGE1_STOP; mDhcpRange[2] = DHCP_DEFAULT_RANGE2_START; mDhcpRange[3] = DHCP_DEFAULT_RANGE2_STOP; + mDhcpRange[4] = DHCP_DEFAULT_RANGE3_START; + mDhcpRange[5] = DHCP_DEFAULT_RANGE3_STOP; + mDhcpRange[6] = DHCP_DEFAULT_RANGE4_START; + mDhcpRange[7] = DHCP_DEFAULT_RANGE4_STOP; + mDhcpRange[8] = DHCP_DEFAULT_RANGE5_START; + mDhcpRange[9] = DHCP_DEFAULT_RANGE5_STOP; + mDhcpRange[10] = DHCP_DEFAULT_RANGE6_START; + mDhcpRange[11] = DHCP_DEFAULT_RANGE6_STOP; + mDhcpRange[12] = DHCP_DEFAULT_RANGE7_START; + mDhcpRange[13] = DHCP_DEFAULT_RANGE7_STOP; } mDunRequired = false; // resample when we turn on @@ -173,6 +199,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); @@ -183,7 +211,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)) { @@ -191,6 +219,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; @@ -225,6 +255,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); @@ -237,29 +273,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); @@ -330,13 +371,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); @@ -345,6 +387,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); } @@ -359,17 +403,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(); } @@ -400,7 +452,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; } @@ -457,7 +509,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; @@ -488,9 +540,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); @@ -515,7 +567,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); @@ -571,6 +623,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return mTetherableWifiRegexs; } + public String[] getTetherableBluetoothRegexs() { + return mTetherableBluetoothRegexs; + } + public String[] getUpstreamIfaceRegexs() { return mUpstreamIfaceRegexs; } @@ -762,7 +818,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: @@ -803,7 +859,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? @@ -855,7 +911,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(); @@ -866,7 +922,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) { @@ -909,7 +965,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); @@ -982,7 +1038,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)) { @@ -1220,8 +1276,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) { @@ -1229,7 +1287,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(); @@ -1241,11 +1299,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(); @@ -1267,7 +1325,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; @@ -1284,19 +1342,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); @@ -1326,7 +1384,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } @Override public boolean processMessage(Message message) { - Log.d(TAG, "TetherModeAliveState.processMessage what=" + message.what); + if (DEBUG) Log.d(TAG, "TetherModeAliveState.processMessage what=" + message.what); boolean retValue = true; switch (message.what) { case CMD_TETHER_MODE_REQUESTED: @@ -1354,8 +1412,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..e9ce3ce --- /dev/null +++ b/services/java/com/android/server/location/ComprehensiveCountryDetector.java @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.location; + +import android.content.Context; +import android.location.Country; +import android.location.CountryListener; +import android.location.Geocoder; +import android.provider.Settings; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Slog; + +import java.util.Locale; +import java.util.Timer; +import java.util.TimerTask; + +/** + * This class is used to detect the country where the user is. The sources of + * country are queried in order of reliability, like + * <ul> + * <li>Mobile network</li> + * <li>Location</li> + * <li>SIM's country</li> + * <li>Phone's locale</li> + * </ul> + * <p> + * Call the {@link #detectCountry()} to get the available country immediately. + * <p> + * To be notified of the future country change, using the + * {@link #setCountryListener(CountryListener)} + * <p> + * Using the {@link #stop()} to stop listening to the country change. + * <p> + * The country information will be refreshed every + * {@link #LOCATION_REFRESH_INTERVAL} once the location based country is used. + * + * @hide + */ +public class ComprehensiveCountryDetector extends CountryDetectorBase { + + private final static String TAG = "ComprehensiveCountryDetector"; + + /** + * The refresh interval when the location based country was used + */ + private final static long LOCATION_REFRESH_INTERVAL = 1000 * 60 * 60 * 24; // 1 day + + protected CountryDetectorBase mLocationBasedCountryDetector; + protected Timer mLocationRefreshTimer; + + private final int mPhoneType; + private Country mCountry; + private TelephonyManager mTelephonyManager; + private Country mCountryFromLocation; + private boolean mStopped = false; + private ServiceState mLastState; + + private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onServiceStateChanged(ServiceState serviceState) { + // TODO: Find out how often we will be notified, if this method is called too + // many times, let's consider querying the network. + Slog.d(TAG, "onServiceStateChanged"); + // We only care the state change + if (mLastState == null || mLastState.getState() != serviceState.getState()) { + detectCountry(true, true); + mLastState = new ServiceState(serviceState); + } + } + }; + + /** + * The listener for receiving the notification from LocationBasedCountryDetector. + */ + private CountryListener mLocationBasedCountryDetectionListener = new CountryListener() { + public void onCountryDetected(Country country) { + mCountryFromLocation = country; + // Don't start the LocationBasedCountryDetector. + detectCountry(true, false); + stopLocationBasedDetector(); + } + }; + + public ComprehensiveCountryDetector(Context context) { + super(context); + mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + mPhoneType = mTelephonyManager.getPhoneType(); + } + + @Override + public Country detectCountry() { + // Don't start the LocationBasedCountryDetector if we have been stopped. + return detectCountry(false, !mStopped); + } + + @Override + public void stop() { + Slog.i(TAG, "Stop the detector."); + cancelLocationRefresh(); + removePhoneStateListener(); + stopLocationBasedDetector(); + mListener = null; + mStopped = true; + } + + /** + * Get the country from different sources in order of the reliability. + */ + private Country getCountry() { + Country result = null; + result = getNetworkBasedCountry(); + if (result == null) { + result = getLastKnownLocationBasedCountry(); + } + if (result == null) { + result = getSimBasedCountry(); + } + if (result == null) { + result = getLocaleCountry(); + } + return result; + } + + /** + * @return the country from the mobile network. + */ + protected Country getNetworkBasedCountry() { + String countryIso = null; + // TODO: The document says the result may be unreliable on CDMA networks. Shall we use + // it on CDMA phone? We may test the Android primarily used countries. + if (mPhoneType == TelephonyManager.PHONE_TYPE_GSM) { + countryIso = mTelephonyManager.getNetworkCountryIso(); + if (!TextUtils.isEmpty(countryIso)) { + return new Country(countryIso, Country.COUNTRY_SOURCE_NETWORK); + } + } + return null; + } + + /** + * @return the cached location based country. + */ + protected Country getLastKnownLocationBasedCountry() { + return mCountryFromLocation; + } + + /** + * @return the country from SIM card + */ + protected Country getSimBasedCountry() { + String countryIso = null; + countryIso = mTelephonyManager.getSimCountryIso(); + if (!TextUtils.isEmpty(countryIso)) { + return new Country(countryIso, Country.COUNTRY_SOURCE_SIM); + } + return null; + } + + /** + * @return the country from the system's locale. + */ + protected Country getLocaleCountry() { + Locale defaultLocale = Locale.getDefault(); + if (defaultLocale != null) { + return new Country(defaultLocale.getCountry(), Country.COUNTRY_SOURCE_LOCALE); + } else { + return null; + } + } + + /** + * @param notifyChange indicates whether the listener should be notified the change of the + * country + * @param startLocationBasedDetection indicates whether the LocationBasedCountryDetector could + * be started if the current country source is less reliable than the location. + * @return the current available UserCountry + */ + private Country detectCountry(boolean notifyChange, boolean startLocationBasedDetection) { + Country country = getCountry(); + runAfterDetectionAsync(mCountry != null ? new Country(mCountry) : mCountry, country, + notifyChange, startLocationBasedDetection); + mCountry = country; + return mCountry; + } + + /** + * Run the tasks in the service's thread. + */ + protected void runAfterDetectionAsync(final Country country, final Country detectedCountry, + final boolean notifyChange, final boolean startLocationBasedDetection) { + mHandler.post(new Runnable() { + public void run() { + runAfterDetection( + country, detectedCountry, notifyChange, startLocationBasedDetection); + } + }); + } + + @Override + public void setCountryListener(CountryListener listener) { + CountryListener prevListener = mListener; + mListener = listener; + if (mListener == null) { + // Stop listening all services + removePhoneStateListener(); + stopLocationBasedDetector(); + cancelLocationRefresh(); + } else if (prevListener == null) { + addPhoneStateListener(); + detectCountry(false, true); + } + } + + void runAfterDetection(final Country country, final Country detectedCountry, + final boolean notifyChange, final boolean startLocationBasedDetection) { + if (notifyChange) { + notifyIfCountryChanged(country, detectedCountry); + } + if (startLocationBasedDetection && (detectedCountry == null + || detectedCountry.getSource() > Country.COUNTRY_SOURCE_LOCATION) + && isAirplaneModeOff() && mListener != null && isGeoCoderImplemented()) { + // Start finding location when the source is less reliable than the + // location and the airplane mode is off (as geocoder will not + // work). + // TODO : Shall we give up starting the detector within a + // period of time? + startLocationBasedDetector(mLocationBasedCountryDetectionListener); + } + if (detectedCountry == null + || detectedCountry.getSource() >= Country.COUNTRY_SOURCE_LOCATION) { + // Schedule the location refresh if the country source is + // not more reliable than the location or no country is + // found. + // TODO: Listen to the preference change of GPS, Wifi etc, + // and start detecting the country. + scheduleLocationRefresh(); + } else { + // Cancel the location refresh once the current source is + // more reliable than the location. + cancelLocationRefresh(); + stopLocationBasedDetector(); + } + } + + /** + * Find the country from LocationProvider. + */ + private synchronized void startLocationBasedDetector(CountryListener listener) { + if (mLocationBasedCountryDetector != null) { + return; + } + mLocationBasedCountryDetector = createLocationBasedCountryDetector(); + mLocationBasedCountryDetector.setCountryListener(listener); + mLocationBasedCountryDetector.detectCountry(); + } + + private synchronized void stopLocationBasedDetector() { + if (mLocationBasedCountryDetector != null) { + mLocationBasedCountryDetector.stop(); + mLocationBasedCountryDetector = null; + } + } + + protected CountryDetectorBase createLocationBasedCountryDetector() { + return new LocationBasedCountryDetector(mContext); + } + + protected boolean isAirplaneModeOff() { + return Settings.System.getInt( + mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) == 0; + } + + /** + * Notify the country change. + */ + private void notifyIfCountryChanged(final Country country, final Country detectedCountry) { + if (detectedCountry != null && mListener != null + && (country == null || !country.equals(detectedCountry))) { + Slog.d(TAG, + "The country was changed from " + country != null ? country.getCountryIso() : + country + " to " + detectedCountry.getCountryIso()); + notifyListener(detectedCountry); + } + } + + /** + * Schedule the next location refresh. We will do nothing if the scheduled task exists. + */ + private synchronized void scheduleLocationRefresh() { + if (mLocationRefreshTimer != null) return; + mLocationRefreshTimer = new Timer(); + mLocationRefreshTimer.schedule(new TimerTask() { + @Override + public void run() { + mLocationRefreshTimer = null; + detectCountry(false, true); + } + }, LOCATION_REFRESH_INTERVAL); + } + + /** + * Cancel the scheduled refresh task if it exists + */ + private synchronized void cancelLocationRefresh() { + if (mLocationRefreshTimer != null) { + mLocationRefreshTimer.cancel(); + mLocationRefreshTimer = null; + } + } + + protected synchronized void addPhoneStateListener() { + if (mPhoneStateListener == null && mPhoneType == TelephonyManager.PHONE_TYPE_GSM) { + mLastState = null; + mPhoneStateListener = new PhoneStateListener() { + @Override + public void onServiceStateChanged(ServiceState serviceState) { + // TODO: Find out how often we will be notified, if this + // method is called too + // many times, let's consider querying the network. + Slog.d(TAG, "onServiceStateChanged"); + // We only care the state change + if (mLastState == null || mLastState.getState() != serviceState.getState()) { + detectCountry(true, true); + mLastState = new ServiceState(serviceState); + } + } + }; + mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); + } + } + + protected synchronized void removePhoneStateListener() { + if (mPhoneStateListener != null) { + mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); + mPhoneStateListener = null; + } + } + + protected boolean isGeoCoderImplemented() { + return Geocoder.isPresent(); + } +} diff --git a/services/java/com/android/server/location/CountryDetectorBase.java b/services/java/com/android/server/location/CountryDetectorBase.java new file mode 100644 index 0000000..8326ef9 --- /dev/null +++ b/services/java/com/android/server/location/CountryDetectorBase.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.location; + +import android.content.Context; +import android.location.Country; +import android.location.CountryListener; +import android.os.Handler; + +/** + * This class defines the methods need to be implemented by the country + * detector. + * <p> + * Calling {@link #detectCountry} to start detecting the country. The country + * could be returned immediately if it is available. + * + * @hide + */ +public abstract class CountryDetectorBase { + protected final Handler mHandler; + protected final Context mContext; + protected CountryListener mListener; + protected Country mDetectedCountry; + + public CountryDetectorBase(Context ctx) { + mContext = ctx; + mHandler = new Handler(); + } + + /** + * Start detecting the country that the user is in. + * + * @return the country if it is available immediately, otherwise null should + * be returned. + */ + public abstract Country detectCountry(); + + /** + * Register a listener to receive the notification when the country is detected or changed. + * <p> + * The previous listener will be replaced if it exists. + */ + public void setCountryListener(CountryListener listener) { + mListener = listener; + } + + /** + * Stop detecting the country. The detector should release all system services and be ready to + * be freed + */ + public abstract void stop(); + + protected void notifyListener(Country country) { + if (mListener != null) { + mListener.onCountryDetected(country); + } + } +} diff --git a/services/java/com/android/server/location/LocationBasedCountryDetector.java b/services/java/com/android/server/location/LocationBasedCountryDetector.java new file mode 100755 index 0000000..139f05d --- /dev/null +++ b/services/java/com/android/server/location/LocationBasedCountryDetector.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.location; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import android.content.Context; +import android.location.Address; +import android.location.Country; +import android.location.Geocoder; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.util.Slog; + +/** + * This class detects which country the user currently is in through the enabled + * location providers and the GeoCoder + * <p> + * Use {@link #detectCountry} to start querying. If the location can not be + * resolved within the given time, the last known location will be used to get + * the user country through the GeoCoder. The IllegalStateException will be + * thrown if there is a ongoing query. + * <p> + * The current query can be stopped by {@link #stop()} + * + * @hide + */ +public class LocationBasedCountryDetector extends CountryDetectorBase { + private final static String TAG = "LocationBasedCountryDetector"; + private final static long QUERY_LOCATION_TIMEOUT = 1000 * 60 * 5; // 5 mins + + /** + * Used for canceling location query + */ + protected Timer mTimer; + + /** + * The thread to query the country from the GeoCoder. + */ + protected Thread mQueryThread; + protected List<LocationListener> mLocationListeners; + + private LocationManager mLocationManager; + private List<String> mEnabledProviders; + + public LocationBasedCountryDetector(Context ctx) { + super(ctx); + mLocationManager = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE); + } + + /** + * @return the ISO 3166-1 two letters country code from the location + */ + protected String getCountryFromLocation(Location location) { + String country = null; + Geocoder geoCoder = new Geocoder(mContext); + try { + List<Address> addresses = geoCoder.getFromLocation( + location.getLatitude(), location.getLongitude(), 1); + if (addresses != null && addresses.size() > 0) { + country = addresses.get(0).getCountryCode(); + } + } catch (IOException e) { + Slog.w(TAG, "Exception occurs when getting country from location"); + } + return country; + } + + /** + * Register the listeners with the location providers + */ + protected void registerEnabledProviders(List<LocationListener> listeners) { + int total = listeners.size(); + for (int i = 0; i< total; i++) { + mLocationManager.requestLocationUpdates( + mEnabledProviders.get(i), 0, 0, listeners.get(i)); + } + } + + /** + * Unregister the listeners with the location providers + */ + protected void unregisterProviders(List<LocationListener> listeners) { + for (LocationListener listener : listeners) { + mLocationManager.removeUpdates(listener); + } + } + + /** + * @return the last known location from all providers + */ + protected Location getLastKnownLocation() { + List<String> providers = mLocationManager.getAllProviders(); + Location bestLocation = null; + for (String provider : providers) { + Location lastKnownLocation = mLocationManager.getLastKnownLocation(provider); + if (lastKnownLocation != null) { + if (bestLocation == null || bestLocation.getTime() < lastKnownLocation.getTime()) { + bestLocation = lastKnownLocation; + } + } + } + return bestLocation; + } + + /** + * @return the timeout for querying the location. + */ + protected long getQueryLocationTimeout() { + return QUERY_LOCATION_TIMEOUT; + } + + /** + * @return the total number of enabled location providers + */ + protected int getTotalEnabledProviders() { + if (mEnabledProviders == null) { + mEnabledProviders = mLocationManager.getProviders(true); + } + return mEnabledProviders.size(); + } + + /** + * Start detecting the country. + * <p> + * Queries the location from all location providers, then starts a thread to query the + * country from GeoCoder. + */ + @Override + public synchronized Country detectCountry() { + if (mLocationListeners != null) { + throw new IllegalStateException(); + } + // Request the location from all enabled providers. + int totalProviders = getTotalEnabledProviders(); + if (totalProviders > 0) { + mLocationListeners = new ArrayList<LocationListener>(totalProviders); + for (int i = 0; i < totalProviders; i++) { + LocationListener listener = new LocationListener () { + public void onLocationChanged(Location location) { + if (location != null) { + LocationBasedCountryDetector.this.stop(); + queryCountryCode(location); + } + } + public void onProviderDisabled(String provider) { + } + public void onProviderEnabled(String provider) { + } + public void onStatusChanged(String provider, int status, Bundle extras) { + } + }; + mLocationListeners.add(listener); + } + registerEnabledProviders(mLocationListeners); + mTimer = new Timer(); + mTimer.schedule(new TimerTask() { + @Override + public void run() { + mTimer = null; + LocationBasedCountryDetector.this.stop(); + // Looks like no provider could provide the location, let's try the last + // known location. + queryCountryCode(getLastKnownLocation()); + } + }, getQueryLocationTimeout()); + } else { + // There is no provider enabled. + queryCountryCode(getLastKnownLocation()); + } + return mDetectedCountry; + } + + /** + * Stop the current query without notifying the listener. + */ + @Override + public synchronized void stop() { + if (mLocationListeners != null) { + unregisterProviders(mLocationListeners); + mLocationListeners = null; + } + if (mTimer != null) { + mTimer.cancel(); + mTimer = null; + } + } + + /** + * Start a new thread to query the country from Geocoder. + */ + private synchronized void queryCountryCode(final Location location) { + if (location == null) { + notifyListener(null); + return; + } + if (mQueryThread != null) return; + mQueryThread = new Thread(new Runnable() { + public void run() { + String countryIso = null; + if (location != null) { + countryIso = getCountryFromLocation(location); + } + if (countryIso != null) { + mDetectedCountry = new Country(countryIso, Country.COUNTRY_SOURCE_LOCATION); + } else { + mDetectedCountry = null; + } + notifyListener(mDetectedCountry); + mQueryThread = null; + } + }); + mQueryThread.start(); + } +} diff --git a/services/jni/Android.mk b/services/jni/Android.mk index 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_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp index 6f52f24..e1e54fc 100644 --- a/services/jni/com_android_server_InputManager.cpp +++ b/services/jni/com_android_server_InputManager.cpp @@ -1286,6 +1286,25 @@ static void android_server_InputManager_nativeGetInputConfiguration(JNIEnv* env, env->SetIntField(configObj, gConfigurationClassInfo.navigation, config.navigation); } +static jboolean android_server_InputManager_nativeTransferTouchFocus(JNIEnv* env, + jclass clazz, jobject fromChannelObj, jobject toChannelObj) { + if (checkInputManagerUnitialized(env)) { + return false; + } + + sp<InputChannel> fromChannel = + android_view_InputChannel_getInputChannel(env, fromChannelObj); + sp<InputChannel> toChannel = + android_view_InputChannel_getInputChannel(env, toChannelObj); + + if (fromChannel == NULL || toChannel == NULL) { + return false; + } + + return gNativeInputManager->getInputManager()->getDispatcher()-> + transferTouchFocus(fromChannel, toChannel); +} + static jstring android_server_InputManager_nativeDump(JNIEnv* env, jclass clazz) { if (checkInputManagerUnitialized(env)) { return NULL; @@ -1334,6 +1353,8 @@ static JNINativeMethod gInputManagerMethods[] = { (void*) android_server_InputManager_nativeGetInputDeviceIds }, { "nativeGetInputConfiguration", "(Landroid/content/res/Configuration;)V", (void*) android_server_InputManager_nativeGetInputConfiguration }, + { "nativeTransferTouchFocus", "(Landroid/view/InputChannel;Landroid/view/InputChannel;)Z", + (void*) android_server_InputManager_nativeTransferTouchFocus }, { "nativeDump", "()Ljava/lang/String;", (void*) android_server_InputManager_nativeDump }, }; 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 0515110..bd348bf 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; } /* @@ -281,6 +292,9 @@ void DisplayHardware::fini() void DisplayHardware::releaseScreen() const { DisplayHardwareBase::releaseScreen(); + if (mHwc->initCheck() == NO_ERROR) { + mHwc->release(); + } } void DisplayHardware::acquireScreen() const @@ -321,7 +335,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 2d7900c..75b55df 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; @@ -110,6 +112,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..ff887e4 --- /dev/null +++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include <utils/Errors.h> +#include <utils/String8.h> + +#include <hardware/hardware.h> + +#include <cutils/log.h> + +#include <EGL/egl.h> + +#include "HWComposer.h" + +namespace android { +// --------------------------------------------------------------------------- + +HWComposer::HWComposer() + : mModule(0), mHwc(0), mList(0), mCapacity(0), + mDpy(EGL_NO_DISPLAY), mSur(EGL_NO_SURFACE) +{ + int err = hw_get_module(HWC_HARDWARE_MODULE_ID, &mModule); + LOGW_IF(err, "%s module not found", HWC_HARDWARE_MODULE_ID); + if (err == 0) { + err = hwc_open(mModule, &mHwc); + LOGE_IF(err, "%s device failed to initialize (%s)", + HWC_HARDWARE_COMPOSER, strerror(-err)); + } +} + +HWComposer::~HWComposer() { + free(mList); + if (mHwc) { + hwc_close(mHwc); + } +} + +status_t HWComposer::initCheck() const { + return mHwc ? NO_ERROR : NO_INIT; +} + +void HWComposer::setFrameBuffer(EGLDisplay dpy, EGLSurface sur) { + mDpy = (hwc_display_t)dpy; + mSur = (hwc_surface_t)sur; +} + +status_t HWComposer::createWorkList(size_t numLayers) { + if (mHwc) { + if (!mList || mCapacity < numLayers) { + free(mList); + size_t size = sizeof(hwc_layer_list) + numLayers*sizeof(hwc_layer_t); + mList = (hwc_layer_list_t*)malloc(size); + mCapacity = numLayers; + } + mList->flags = HWC_GEOMETRY_CHANGED; + mList->numHwLayers = numLayers; + } + return NO_ERROR; +} + +status_t HWComposer::prepare() const { + int err = mHwc->prepare(mHwc, mList); + return (status_t)err; +} + +status_t HWComposer::commit() const { + int err = mHwc->set(mHwc, mDpy, mSur, mList); + mList->flags &= ~HWC_GEOMETRY_CHANGED; + return (status_t)err; +} + +status_t HWComposer::release() const { + int err = mHwc->set(mHwc, NULL, NULL, NULL); + return (status_t)err; +} + +size_t HWComposer::getNumLayers() const { + return mList ? mList->numHwLayers : 0; +} + +hwc_layer_t* HWComposer::getLayers() const { + return mList ? mList->hwLayers : 0; +} + +void HWComposer::dump(String8& result, char* buffer, size_t SIZE) const { + if (mHwc && mList) { + result.append("Hardware Composer state:\n"); + + snprintf(buffer, SIZE, " numHwLayers=%u, flags=%08x\n", + mList->numHwLayers, mList->flags); + result.append(buffer); + + for (size_t i=0 ; i<mList->numHwLayers ; i++) { + const hwc_layer_t& l(mList->hwLayers[i]); + snprintf(buffer, SIZE, " %8s | %08x | %08x | %02x | %04x | [%5d,%5d,%5d,%5d] | [%5d,%5d,%5d,%5d]\n", + l.compositionType ? "OVERLAY" : "FB", + l.hints, l.flags, l.transform, l.blending, + l.sourceCrop.left, l.sourceCrop.top, l.sourceCrop.right, l.sourceCrop.bottom, + l.displayFrame.left, l.displayFrame.top, l.displayFrame.right, l.displayFrame.bottom); + result.append(buffer); + } + } +} + +// --------------------------------------------------------------------------- +}; // namespace android diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h new file mode 100644 index 0000000..5a9e9eb --- /dev/null +++ b/services/surfaceflinger/DisplayHardware/HWComposer.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_SF_HWCOMPOSER_H +#define ANDROID_SF_HWCOMPOSER_H + +#include <stdint.h> +#include <sys/types.h> + +#include <EGL/egl.h> + +#include <hardware/hwcomposer.h> + +namespace android { +// --------------------------------------------------------------------------- + +class String8; + +class HWComposer +{ +public: + + HWComposer(); + ~HWComposer(); + + status_t initCheck() const; + + // tells the HAL what the framebuffer is + void setFrameBuffer(EGLDisplay dpy, EGLSurface sur); + + // create a work list for numLayers layer + status_t createWorkList(size_t numLayers); + + // Asks the HAL what it can do + status_t prepare() const; + + // commits the list + status_t commit() const; + + // release hardware resources + status_t release() const; + + size_t getNumLayers() const; + hwc_layer_t* getLayers() const; + + // for debugging + void dump(String8& out, char* scratch, size_t SIZE) const; + +private: + hw_module_t const* mModule; + hwc_composer_device_t* mHwc; + hwc_layer_list_t* mList; + size_t mCapacity; + hwc_display_t mDpy; + hwc_surface_t mSur; +}; + + +// --------------------------------------------------------------------------- +}; // namespace android + +#endif // ANDROID_SF_HWCOMPOSER_H diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index 695cbfa..1b21a8d 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 @@ -181,6 +182,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()); @@ -310,6 +367,7 @@ sp<GraphicBuffer> Layer::requestBuffer(int index, Mutex::Autolock _l(mLock); // zero means default + const bool fixedSize = reqWidth && reqHeight; if (!reqFormat) reqFormat = mFormat; if (!reqWidth) reqWidth = mWidth; if (!reqHeight) reqHeight = mHeight; @@ -323,6 +381,7 @@ sp<GraphicBuffer> Layer::requestBuffer(int index, mReqWidth = reqWidth; mReqHeight = reqHeight; mReqFormat = reqFormat; + mFixedSize = fixedSize; lcblk->reallocateAllExcept(index); } 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 6fc5010..3d049a7 100644 --- a/services/surfaceflinger/LayerBase.cpp +++ b/services/surfaceflinger/LayerBase.cpp @@ -309,6 +309,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 8cba287..c66dc34 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 2b06f6f..17b98a6 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -53,6 +53,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 @@ -78,12 +79,14 @@ SurfaceFlinger::SurfaceFlinger() mReadFramebuffer("android.permission.READ_FRAME_BUFFER"), mDump("android.permission.DUMP"), mVisibleRegionsDirty(false), + mHwWorkListDirty(false), mDeferReleaseConsole(false), mFreezeDisplay(false), mFreezeCount(0), mFreezeDisplayTime(0), mDebugRegion(0), mDebugBackground(0), + mDebugDisableHWC(0), mDebugInSwapBuffers(0), mLastSwapBufferTime(0), mDebugInTransaction(0), @@ -165,7 +168,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"); } @@ -201,10 +204,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 @@ -233,7 +236,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); @@ -267,7 +270,7 @@ status_t SurfaceFlinger::readyToRun() // start boot animation property_set("ctl.start", "bootanim"); - + return NO_ERROR; } @@ -370,6 +373,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) @@ -384,13 +392,12 @@ bool SurfaceFlinger::threadLoop() logger.log(GraphicLog::SF_COMPOSITION_COMPLETE, index); hw.compositionComplete(); - // release the clients before we flip ('cause flip might block) - logger.log(GraphicLog::SF_UNLOCK_CLIENTS, index); - unlockClients(); - logger.log(GraphicLog::SF_SWAP_BUFFERS, index); postFramebuffer(); + logger.log(GraphicLog::SF_UNLOCK_CLIENTS, index); + unlockClients(); + logger.log(GraphicLog::SF_REPAINT_DONE, index); } else { // pretend we did the post @@ -455,6 +462,7 @@ void SurfaceFlinger::handleTransaction(uint32_t transactionFlags) handleTransactionLocked(transactionFlags, ditchedLayers); mLastTransactionTime = systemTime() - now; mDebugInTransaction = 0; + mHwWorkListDirty = true; // here the transaction has been committed } @@ -462,6 +470,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) { @@ -665,7 +674,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); @@ -695,8 +704,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(); @@ -719,6 +728,7 @@ void SurfaceFlinger::handlePageFlip() mWormholeRegion = screenRegion.subtract(opaqueRegion); mVisibleRegionsDirty = false; + mHwWorkListDirty = true; } unlockPageFlip(currentLayers); @@ -749,6 +759,24 @@ void SurfaceFlinger::unlockPageFlip(const LayerVector& currentLayers) } } +void SurfaceFlinger::handleWorkList() +{ + mHwWorkListDirty = false; + HWComposer& hwc(graphicPlane(0).displayHardware().getHwComposer()); + if (hwc.initCheck() == NO_ERROR) { + const Vector< sp<LayerBase> >& currentLayers(mVisibleLayersSortedByZ); + const size_t count = currentLayers.size(); + hwc.createWorkList(count); + hwc_layer_t* const cur(hwc.getLayers()); + for (size_t i=0 ; cur && i<count ; i++) { + currentLayers[i]->setGeometry(&cur[i]); + if (mDebugDisableHWC) { + cur[i].compositionType = HWC_FRAMEBUFFER; + cur[i].flags |= HWC_SKIP_LAYER; + } + } + } +} void SurfaceFlinger::handleRepaint() { @@ -769,8 +797,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 @@ -813,9 +841,73 @@ 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()) { @@ -1054,7 +1146,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) { @@ -1098,7 +1190,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)) @@ -1131,14 +1223,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; } @@ -1157,7 +1252,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; @@ -1241,7 +1336,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). @@ -1262,7 +1357,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; @@ -1275,9 +1370,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. @@ -1392,7 +1487,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); @@ -1434,6 +1529,13 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) result.append(buffer); } + HWComposer& hwc(hw.getHwComposer()); + snprintf(buffer, SIZE, " h/w composer %s and %s\n", + hwc.initCheck()==NO_ERROR ? "present" : "not present", + mDebugDisableHWC ? "disabled" : "enabled"); + result.append(buffer); + hwc.dump(result, buffer, SIZE); + const GraphicBufferAllocator& alloc(GraphicBufferAllocator::get()); alloc.dump(result); @@ -1507,6 +1609,11 @@ status_t SurfaceFlinger::onTransact( n = data.readInt32(); mDebugBackground = n ? 1 : 0; return NO_ERROR; + case 1008: // toggle use of hw composer + n = data.readInt32(); + mDebugDisableHWC = n ? 1 : 0; + mHwWorkListDirty = true; + // fall-through... case 1004:{ // repaint everything Mutex::Autolock _l(mStateLock); const DisplayHardware& hw(graphicPlane(0).displayHardware()); @@ -1819,12 +1926,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 f09fdbc..6e9ecbd 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -301,6 +301,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); @@ -376,6 +377,7 @@ private: Region mInvalidRegion; Region mWormholeRegion; bool mVisibleRegionsDirty; + bool mHwWorkListDirty; bool mDeferReleaseConsole; bool mFreezeDisplay; int32_t mFreezeCount; @@ -386,6 +388,7 @@ private: // don't use a lock for these, we don't care int mDebugRegion; int mDebugBackground; + int mDebugDisableHWC; volatile nsecs_t mDebugInSwapBuffers; nsecs_t mLastSwapBufferTime; volatile nsecs_t mDebugInTransaction; diff --git a/services/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; + } +} |