diff options
49 files changed, 1099 insertions, 436 deletions
diff --git a/api/current.txt b/api/current.txt index 8c44b269347b..f59420571666 100644 --- a/api/current.txt +++ b/api/current.txt @@ -12402,6 +12402,7 @@ package android.net { field public static final java.lang.String EXTRA_EXTRA_INFO = "extraInfo"; field public static final java.lang.String EXTRA_IS_FAILOVER = "isFailover"; field public static final deprecated java.lang.String EXTRA_NETWORK_INFO = "networkInfo"; + field public static final java.lang.String EXTRA_NETWORK_TYPE = "networkType"; field public static final java.lang.String EXTRA_NO_CONNECTIVITY = "noConnectivity"; field public static final java.lang.String EXTRA_OTHER_NETWORK_INFO = "otherNetwork"; field public static final java.lang.String EXTRA_REASON = "reason"; diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index b6001ebeedd2..81ee1920e4a8 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -66,7 +66,7 @@ import com.android.internal.os.HandlerCaller; * accessibility service. Following is an example declaration: * </p> * <pre> <service android:name=".MyAccessibilityService" - * android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE> + * android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> * <intent-filter> * <action android:name="android.accessibilityservice.AccessibilityService" /> * </intent-filter> diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index fa1ff8521288..d30ef04628b9 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -89,11 +89,21 @@ public class ConnectivityManager { * should always obtain network information through * {@link #getActiveNetworkInfo()} or * {@link #getAllNetworkInfo()}. + * @see #EXTRA_NETWORK_TYPE */ @Deprecated public static final String EXTRA_NETWORK_INFO = "networkInfo"; /** + * Network type which triggered a {@link #CONNECTIVITY_ACTION} broadcast. + * Can be used with {@link #getNetworkInfo(int)} to get {@link NetworkInfo} + * state based on the calling application. + * + * @see android.content.Intent#getIntExtra(String, int) + */ + public static final String EXTRA_NETWORK_TYPE = "networkType"; + + /** * The lookup key for a boolean that indicates whether a connect event * is for a network to which the connectivity manager was failing over * following a disconnect on another network. diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 8236cd7cf3ae..188fede7ddd3 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -135,7 +135,19 @@ public abstract class HardwareRenderer { * @hide */ public static final String DEBUG_DIRTY_REGIONS_PROPERTY = "debug.hwui.show_dirty_regions"; - + + /** + * Turn on to flash hardware layers when they update. + * + * Possible values: + * "true", to enable hardware layers updates debugging + * "false", to disable hardware layers updates debugging + * + * @hide + */ + public static final String DEBUG_SHOW_LAYERS_UPDATES_PROPERTY = + "debug.hwui.show_layers_updates"; + /** * A process can set this flag to false to prevent the use of hardware * rendering. diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index 8ff39d69d8cb..d85e816da135 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -847,7 +847,7 @@ public class ActionBarView extends AbsActionBarView { int contentWidth = MeasureSpec.getSize(widthMeasureSpec); - int maxHeight = mContentHeight > 0 ? + int maxHeight = mContentHeight >= 0 ? mContentHeight : MeasureSpec.getSize(heightMeasureSpec); final int verticalPadding = getPaddingTop() + getPaddingBottom(); diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 6386bcb74f10..5d8d3974c28d 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -5502,6 +5502,7 @@ </attr> <!-- Options affecting how the action bar is displayed. --> <attr name="displayOptions"> + <flag name="none" value="0" /> <flag name="useLogo" value="0x1" /> <flag name="showHome" value="0x2" /> <flag name="homeAsUp" value="0x4" /> diff --git a/docs/downloads/README b/docs/downloads/README new file mode 100644 index 000000000000..0e7d7902df52 --- /dev/null +++ b/docs/downloads/README @@ -0,0 +1,9 @@ +Files in this directory are not hosted on developer.android.com. + +This directory serves as a "master" repository for various +downloadable files. This provides us consistent version control +for downloadables that are associated with documentation. + +Once saved here, the files must be uploaded to a separate +hosting service where they're accessible by users, such as +Google Cloud Storage.
\ No newline at end of file diff --git a/docs/downloads/design/Android_Design_Color_Swatches_20120229.zip b/docs/downloads/design/Android_Design_Color_Swatches_20120229.zip Binary files differnew file mode 100644 index 000000000000..81b65228ce02 --- /dev/null +++ b/docs/downloads/design/Android_Design_Color_Swatches_20120229.zip diff --git a/docs/downloads/design/Android_Design_Downloads_20120229.zip b/docs/downloads/design/Android_Design_Downloads_20120229.zip Binary files differnew file mode 100644 index 000000000000..7bbd85f7b317 --- /dev/null +++ b/docs/downloads/design/Android_Design_Downloads_20120229.zip diff --git a/docs/downloads/design/Android_Design_Fireworks_Stencil_20120229.png b/docs/downloads/design/Android_Design_Fireworks_Stencil_20120229.png Binary files differnew file mode 100644 index 000000000000..51363799882c --- /dev/null +++ b/docs/downloads/design/Android_Design_Fireworks_Stencil_20120229.png diff --git a/docs/downloads/design/Android_Design_Holo_Widgets_20120302.zip b/docs/downloads/design/Android_Design_Holo_Widgets_20120302.zip Binary files differnew file mode 100644 index 000000000000..90c45c6ea781 --- /dev/null +++ b/docs/downloads/design/Android_Design_Holo_Widgets_20120302.zip diff --git a/docs/downloads/design/Android_Design_Icons_20120229.zip b/docs/downloads/design/Android_Design_Icons_20120229.zip Binary files differnew file mode 100644 index 000000000000..c2728f3fb259 --- /dev/null +++ b/docs/downloads/design/Android_Design_Icons_20120229.zip diff --git a/docs/downloads/design/Android_Design_OmniGraffle_Stencil_20120229.graffle b/docs/downloads/design/Android_Design_OmniGraffle_Stencil_20120229.graffle Binary files differnew file mode 100644 index 000000000000..9e418d38f0fd --- /dev/null +++ b/docs/downloads/design/Android_Design_OmniGraffle_Stencil_20120229.graffle diff --git a/docs/downloads/design/Roboto_Hinted_20111129.zip b/docs/downloads/design/Roboto_Hinted_20111129.zip Binary files differnew file mode 100644 index 000000000000..abf4942a5dad --- /dev/null +++ b/docs/downloads/design/Roboto_Hinted_20111129.zip diff --git a/docs/downloads/design/Roboto_Specimen_Book_20111129.pdf b/docs/downloads/design/Roboto_Specimen_Book_20111129.pdf Binary files differnew file mode 100644 index 000000000000..594a3661f48f --- /dev/null +++ b/docs/downloads/design/Roboto_Specimen_Book_20111129.pdf diff --git a/docs/downloads/training/ActivityLifecycle.zip b/docs/downloads/training/ActivityLifecycle.zip Binary files differnew file mode 100644 index 000000000000..1cbed448b2bb --- /dev/null +++ b/docs/downloads/training/ActivityLifecycle.zip diff --git a/docs/downloads/training/BitmapFun.zip b/docs/downloads/training/BitmapFun.zip Binary files differnew file mode 100644 index 000000000000..e7e71f9f85a0 --- /dev/null +++ b/docs/downloads/training/BitmapFun.zip diff --git a/docs/downloads/training/CustomView.zip b/docs/downloads/training/CustomView.zip Binary files differnew file mode 100644 index 000000000000..d7ae8a2268c7 --- /dev/null +++ b/docs/downloads/training/CustomView.zip diff --git a/docs/downloads/training/DeviceManagement.zip b/docs/downloads/training/DeviceManagement.zip Binary files differnew file mode 100644 index 000000000000..9f7ec694af47 --- /dev/null +++ b/docs/downloads/training/DeviceManagement.zip diff --git a/docs/downloads/training/EffectiveNavigation.zip b/docs/downloads/training/EffectiveNavigation.zip Binary files differnew file mode 100644 index 000000000000..f21af451f39e --- /dev/null +++ b/docs/downloads/training/EffectiveNavigation.zip diff --git a/docs/downloads/training/FragmentBasics.zip b/docs/downloads/training/FragmentBasics.zip Binary files differnew file mode 100644 index 000000000000..b5b19d03ba5a --- /dev/null +++ b/docs/downloads/training/FragmentBasics.zip diff --git a/docs/downloads/training/LocationAware.zip b/docs/downloads/training/LocationAware.zip Binary files differnew file mode 100644 index 000000000000..8ed97cbff32a --- /dev/null +++ b/docs/downloads/training/LocationAware.zip diff --git a/docs/downloads/training/MobileAds.zip b/docs/downloads/training/MobileAds.zip Binary files differnew file mode 100644 index 000000000000..468e4ee72511 --- /dev/null +++ b/docs/downloads/training/MobileAds.zip diff --git a/docs/downloads/training/NetworkUsage.zip b/docs/downloads/training/NetworkUsage.zip Binary files differnew file mode 100644 index 000000000000..8c7fbef9fb49 --- /dev/null +++ b/docs/downloads/training/NetworkUsage.zip diff --git a/docs/downloads/training/NewsReader.zip b/docs/downloads/training/NewsReader.zip Binary files differnew file mode 100644 index 000000000000..7dda41c7c273 --- /dev/null +++ b/docs/downloads/training/NewsReader.zip diff --git a/docs/downloads/training/OpenGLES.zip b/docs/downloads/training/OpenGLES.zip Binary files differnew file mode 100644 index 000000000000..862ae1f87984 --- /dev/null +++ b/docs/downloads/training/OpenGLES.zip diff --git a/docs/downloads/training/PhotoIntentActivity.zip b/docs/downloads/training/PhotoIntentActivity.zip Binary files differnew file mode 100644 index 000000000000..9fcc0e19a7c8 --- /dev/null +++ b/docs/downloads/training/PhotoIntentActivity.zip diff --git a/docs/downloads/training/TabCompat.zip b/docs/downloads/training/TabCompat.zip Binary files differnew file mode 100644 index 000000000000..b70b44212581 --- /dev/null +++ b/docs/downloads/training/TabCompat.zip diff --git a/docs/html/about/start.jd b/docs/html/about/start.jd index af8344d0bb72..fbe70e3de215 100644 --- a/docs/html/about/start.jd +++ b/docs/html/about/start.jd @@ -29,9 +29,9 @@ h2.green+hr{background:#99CC00} <p>Before you write a single line of code, you need to design the user interface and make it fit the Android user experience. Although you may know what a user will <em>do</em> with your app, you should pause to focus on how a user will <em>interact</em> with it. Your design should be sleek, -simple, powereful, and tailored to the Android experience.</p> +simple, powerful, and tailored to the Android experience.</p> -<p>So whether your a one-man shop or a large team, you should study the <a +<p>So whether you're a one-man shop or a large team, you should study the <a href="{@docRoot}design/index.html">Design</a> guidelines first.</p> </div> diff --git a/docs/html/distribute/googleplay/promote/brand.jd b/docs/html/distribute/googleplay/promote/brand.jd index 8d049030b675..76ed61961445 100644 --- a/docs/html/distribute/googleplay/promote/brand.jd +++ b/docs/html/distribute/googleplay/promote/brand.jd @@ -150,7 +150,7 @@ any way you want, provided that you follow the guidelines described below.</p> <span style="margin-left:1em;">http://play.google.com/store/search?q=<em>yourCompanyName</em></span> </li> <li>A list of products published by you, for example,<br /> - <span style="margin-left:1em;">http://play.google.com/store/search?q=<em>publisherName</em>M/span> + <span style="margin-left:1em;">http://play.google.com/store/search?q=<em>publisherName</em></span> </li> <li>A specific app product details page within Google Play, for example,<br /> <span style="margin-left:1em;">http://play.google.com/store/apps/details?id=<em>packageName</em></span> @@ -171,4 +171,4 @@ any way you want, provided that you follow the guidelines described below.</p> <h2>Other Brands</h2> <p>Any other brands or icons depicted on this site are <em>not</em> are the property of their -repective owners and usage is reserved. You must seek the developer for appropriate permission to use them.</p> +respective owners and usage is reserved. You must seek the developer for appropriate permission to use them.</p> diff --git a/docs/html/guide/topics/providers/content-provider-basics.jd b/docs/html/guide/topics/providers/content-provider-basics.jd index f5e480595fc5..8c47ad72906d 100644 --- a/docs/html/guide/topics/providers/content-provider-basics.jd +++ b/docs/html/guide/topics/providers/content-provider-basics.jd @@ -1030,12 +1030,12 @@ mRowsDeleted = getContentResolver().delete( A provider defines URI permissions for content URIs in its manifest, using the <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn"> android:grantUriPermission</a></code> - attribute of the - {@code <a href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a>} + attribute of the <a href="{@docRoot}guide/topics/manifest/provider-element.html"> + {@code <provider>}</a> element, as well as the - {@code <a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html"> - <grant-uri-permission></a>} child element of the - {@code <a href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a>} + <a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html">{@code + <grant-uri-permission>}</a> child element of the + <a href="{@docRoot}guide/topics/manifest/provider-element.html">{@code <provider>}</a> element. The URI permissions mechanism is explained in more detail in the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a> guide, in the section "URI Permissions". diff --git a/docs/html/tools/samples/index.jd b/docs/html/tools/samples/index.jd index 5c0e8db777eb..ed416e6a82db 100644 --- a/docs/html/tools/samples/index.jd +++ b/docs/html/tools/samples/index.jd @@ -3,7 +3,8 @@ page.title=Samples @jd:body <p>To help you understand some fundamental Android APIs and coding practices, a variety of sample -code is available from the Android SDK Manager.</p> +code is available from the Android SDK Manager. Each version of the Android platform available +from the SDK Manager offers its own set of sample apps.</p> <p>To download the samples:</p> <ol> @@ -18,14 +19,14 @@ Android SDK, then execute {@code android sdk}.</ul> <li>Select and download <em>Samples for SDK</em>.</li> </ol> -<p>When the download is complete, you can find the samples sources at this location:</p> +<p>When the download is complete, you can find the source code for all samples at this location:</p> <p style="margin-left:2em"> -<code><em><sdk></em>/platforms/<android-version>/samples/</code> +<code><sdk>/samples/android-<version>/</code> </p> +<p>The {@code <version>} number corresponds to the platform's + <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">API level</a>.</p> + <p>You can easily create new Android projects with the downloaded samples, modify them -if you'd like, and then run them on an emulator or device. </p> -<!-- -<p>Below are summaries for several of the available samples.</p> --->
\ No newline at end of file +if you'd like, and then run them on an emulator or device.</p>
\ No newline at end of file diff --git a/docs/html/training/basics/fragments/communicating.jd b/docs/html/training/basics/fragments/communicating.jd index 3ac987372ec7..eb9b3689fc8b 100644 --- a/docs/html/training/basics/fragments/communicating.jd +++ b/docs/html/training/basics/fragments/communicating.jd @@ -108,7 +108,7 @@ public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{ ... - public void onArticleSelected(Uri articleUri) { + public void onArticleSelected(int position) { // The user selected the headline of an article from the HeadlinesFragment // Do something here to display that article } diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 128adcbf53a2..6ba57809f3e6 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -881,15 +881,17 @@ public final class Bitmap implements Parcelable { * <code>(128, 128, 0, 0)</code>.</p> * * <p>This method always returns false if {@link #getConfig()} is - * {@link Bitmap.Config#ALPHA_8} or {@link Bitmap.Config#RGB_565}.</p> + * {@link Bitmap.Config#RGB_565}.</p> + * + * <p>This method only returns true if {@link #hasAlpha()} returns true. + * A bitmap with no alpha channel can be used both as a pre-multiplied and + * as a non pre-multiplied bitmap.</p> * * @return true if the underlying pixels have been pre-multiplied, false * otherwise */ public final boolean isPremultiplied() { - final Config config = getConfig(); - //noinspection deprecation - return config == Config.ARGB_8888 || config == Config.ARGB_4444; + return getConfig() != Config.RGB_565 && hasAlpha(); } /** Returns the bitmap's width */ diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index aa2bc3f67419..0ed4888ac7d3 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -52,13 +52,10 @@ Caches::Caches(): Singleton<Caches>(), mInitialized(false) { initFont(); initExtensions(); initConstraints(); + initProperties(); mDebugLevel = readDebugLevel(); ALOGD("Enabling debug mode %d", mDebugLevel); - -#if RENDER_LAYERS_AS_REGIONS - INIT_LOGD("Layers will be composited as regions"); -#endif } void Caches::init() { @@ -126,6 +123,16 @@ void Caches::initConstraints() { glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); } +void Caches::initProperties() { + char property[PROPERTY_VALUE_MAX]; + if (property_get(PROPERTY_DEBUG_LAYERS_UPDATES, property, NULL) > 0) { + INIT_LOGD(" Layers updates debug enabled: %s", property); + debugLayersUpdates = !strcmp(property, "true"); + } else { + debugLayersUpdates = false; + } +} + void Caches::terminate() { if (!mInitialized) return; diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h index ad1098c9b550..f4f56d6e594b 100644 --- a/libs/hwui/Caches.h +++ b/libs/hwui/Caches.h @@ -236,6 +236,7 @@ public: // Misc GLint maxTextureSize; + bool debugLayersUpdates; TextureCache textureCache; LayerCache layerCache; @@ -267,6 +268,7 @@ private: void initFont(); void initExtensions(); void initConstraints(); + void initProperties(); static void eventMarkNull(GLsizei length, const GLchar* marker) { } static void startMarkNull(GLsizei length, const GLchar* marker) { } diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp index 41a5f0d4ba84..f81640b22b37 100644 --- a/libs/hwui/LayerRenderer.cpp +++ b/libs/hwui/LayerRenderer.cpp @@ -45,7 +45,6 @@ int LayerRenderer::prepareDirty(float left, float top, float right, float bottom const float width = mLayer->layer.getWidth(); const float height = mLayer->layer.getHeight(); -#if RENDER_LAYERS_AS_REGIONS Rect dirty(left, top, right, bottom); if (dirty.isEmpty() || (dirty.left <= 0 && dirty.top <= 0 && dirty.right >= width && dirty.bottom >= height)) { @@ -58,9 +57,6 @@ int LayerRenderer::prepareDirty(float left, float top, float right, float bottom } return OpenGLRenderer::prepareDirty(dirty.left, dirty.top, dirty.right, dirty.bottom, opaque); -#else - return OpenGLRenderer::prepareDirty(0.0f, 0.0f, width, height, opaque); -#endif } void LayerRenderer::finish() { @@ -87,14 +83,10 @@ bool LayerRenderer::hasLayer() { } Region* LayerRenderer::getRegion() { -#if RENDER_LAYERS_AS_REGIONS if (getSnapshot()->flags & Snapshot::kFlagFboTarget) { return OpenGLRenderer::getRegion(); } return &mLayer->region; -#else - return OpenGLRenderer::getRegion(); -#endif } // TODO: This implementation is flawed and can generate T-junctions @@ -105,7 +97,6 @@ Region* LayerRenderer::getRegion() { // In practice, T-junctions do not appear often so this has yet // to be fixed. void LayerRenderer::generateMesh() { -#if RENDER_LAYERS_AS_REGIONS if (mLayer->region.isRect() || mLayer->region.isEmpty()) { if (mLayer->mesh) { delete mLayer->mesh; @@ -172,7 +163,6 @@ void LayerRenderer::generateMesh() { indices[index + 5] = quad + 3; // bottom-right } } -#endif } /////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index a1da87868701..580d6b58bd43 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -321,13 +321,11 @@ status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { Rect clip(*mSnapshot->clipRect); clip.snapToPixelBoundaries(); -#if RENDER_LAYERS_AS_REGIONS // Since we don't know what the functor will draw, let's dirty // tne entire clip region if (hasLayer()) { dirtyLayerUnchecked(clip, getRegion()); } -#endif DrawGlInfo info; info.clipLeft = clip.left; @@ -595,10 +593,8 @@ bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, sp<Snapshot> sna GLuint previousFbo) { layer->setFbo(mCaches.fboCache.get()); -#if RENDER_LAYERS_AS_REGIONS snapshot->region = &snapshot->layer->region; snapshot->flags |= Snapshot::kFlagFboTarget; -#endif Rect clip(bounds); snapshot->transform->mapRect(clip); @@ -826,7 +822,6 @@ void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap) } void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { -#if RENDER_LAYERS_AS_REGIONS if (layer->region.isRect()) { layer->setRegionAsRect(); @@ -907,9 +902,6 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { layer->region.clear(); } -#else - composeLayerRect(layer, rect); -#endif } void OpenGLRenderer::drawRegionRects(const Region& region) { @@ -940,27 +932,22 @@ void OpenGLRenderer::drawRegionRects(const Region& region) { void OpenGLRenderer::dirtyLayer(const float left, const float top, const float right, const float bottom, const mat4 transform) { -#if RENDER_LAYERS_AS_REGIONS if (hasLayer()) { Rect bounds(left, top, right, bottom); transform.mapRect(bounds); dirtyLayerUnchecked(bounds, getRegion()); } -#endif } void OpenGLRenderer::dirtyLayer(const float left, const float top, const float right, const float bottom) { -#if RENDER_LAYERS_AS_REGIONS if (hasLayer()) { Rect bounds(left, top, right, bottom); dirtyLayerUnchecked(bounds, getRegion()); } -#endif } void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) { -#if RENDER_LAYERS_AS_REGIONS if (bounds.intersect(*mSnapshot->clipRect)) { bounds.snapToPixelBoundaries(); android::Rect dirty(bounds.left, bounds.top, bounds.right, bounds.bottom); @@ -968,7 +955,6 @@ void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) { region->orSelf(dirty); } } -#endif } void OpenGLRenderer::clearLayerRegions() { @@ -1585,11 +1571,7 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes float right = FLT_MIN; float bottom = FLT_MIN; -#if RENDER_LAYERS_AS_REGIONS const bool hasActiveLayer = hasLayer(); -#else - const bool hasActiveLayer = false; -#endif // TODO: Support the colors array TextureVertex mesh[count]; @@ -1620,7 +1602,6 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes TextureVertex::set(vertex++, vertices[cx], vertices[cy], u2, v1); TextureVertex::set(vertex++, vertices[dx], vertices[dy], u2, v2); -#if RENDER_LAYERS_AS_REGIONS if (hasActiveLayer) { // TODO: This could be optimized to avoid unnecessary ops left = fminf(left, fminf(vertices[ax], fminf(vertices[bx], vertices[cx]))); @@ -1628,15 +1609,12 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes right = fmaxf(right, fmaxf(vertices[ax], fmaxf(vertices[bx], vertices[cx]))); bottom = fmaxf(bottom, fmaxf(vertices[ay], fmaxf(vertices[by], vertices[cy]))); } -#endif } } -#if RENDER_LAYERS_AS_REGIONS if (hasActiveLayer) { dirtyLayer(left, top, right, bottom, *mSnapshot->transform); } -#endif drawTextureMesh(0.0f, 0.0f, 1.0f, 1.0f, texture->id, alpha / 255.0f, mode, texture->blend, &mesh[0].position[0], &mesh[0].texture[0], @@ -1734,7 +1712,6 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const if (CC_LIKELY(mesh && mesh->verticesCount > 0)) { const bool pureTranslate = mSnapshot->transform->isPureTranslate(); -#if RENDER_LAYERS_AS_REGIONS // Mark the current layer dirty where we are going to draw the patch if (hasLayer() && mesh->hasEmptyQuads) { const float offsetX = left + mSnapshot->transform->getTranslateX(); @@ -1752,7 +1729,6 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const } } } -#endif if (CC_LIKELY(pureTranslate)) { const float x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f); @@ -2400,22 +2376,16 @@ status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count const Rect* clip = pureTranslate ? mSnapshot->clipRect : &mSnapshot->getLocalClip(); Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); -#if RENDER_LAYERS_AS_REGIONS const bool hasActiveLayer = hasLayer(); -#else - const bool hasActiveLayer = false; -#endif if (fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y, positions, hasActiveLayer ? &bounds : NULL)) { -#if RENDER_LAYERS_AS_REGIONS if (hasActiveLayer) { if (!pureTranslate) { mSnapshot->transform->mapRect(bounds); } dirtyLayerUnchecked(bounds, getRegion()); } -#endif } return DrawGlInfo::kStatusDrew; @@ -2501,11 +2471,7 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, const Rect* clip = pureTranslate ? mSnapshot->clipRect : &mSnapshot->getLocalClip(); Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); -#if RENDER_LAYERS_AS_REGIONS const bool hasActiveLayer = hasLayer(); -#else - const bool hasActiveLayer = false; -#endif bool status; if (paint->getTextAlign() != SkPaint::kLeft_Align) { @@ -2517,15 +2483,12 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, status = fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y, positions, hasActiveLayer ? &bounds : NULL); } - if (status) { -#if RENDER_LAYERS_AS_REGIONS - if (hasActiveLayer) { - if (!pureTranslate) { - mSnapshot->transform->mapRect(bounds); - } - dirtyLayerUnchecked(bounds, getRegion()); + + if (status && hasActiveLayer) { + if (!pureTranslate) { + mSnapshot->transform->mapRect(bounds); } -#endif + dirtyLayerUnchecked(bounds, getRegion()); } drawTextDecorations(text, bytesCount, length, oldX, oldY, paint); @@ -2568,20 +2531,14 @@ status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int co const Rect* clip = &mSnapshot->getLocalClip(); Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); -#if RENDER_LAYERS_AS_REGIONS const bool hasActiveLayer = hasLayer(); -#else - const bool hasActiveLayer = false; -#endif if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path, hOffset, vOffset, hasActiveLayer ? &bounds : NULL)) { -#if RENDER_LAYERS_AS_REGIONS if (hasActiveLayer) { mSnapshot->transform->mapRect(bounds); dirtyLayerUnchecked(bounds, getRegion()); } -#endif } return DrawGlInfo::kStatusDrew; @@ -2610,6 +2567,8 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* pain return DrawGlInfo::kStatusDone; } + bool debugLayerUpdate = false; + if (layer->deferredUpdateScheduled && layer->renderer && layer->displayList) { OpenGLRenderer* renderer = layer->renderer; Rect& dirty = layer->dirtyRect; @@ -2625,6 +2584,8 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* pain layer->deferredUpdateScheduled = false; layer->renderer = NULL; layer->displayList = NULL; + + debugLayerUpdate = mCaches.debugLayersUpdates; } mCaches.activeTexture(0); @@ -2635,13 +2596,11 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* pain layer->setAlpha(alpha, mode); -#if RENDER_LAYERS_AS_REGIONS if (CC_LIKELY(!layer->region.isEmpty())) { if (layer->region.isRect()) { composeLayerRect(layer, layer->regionRect); } else if (layer->mesh) { const float a = alpha / 255.0f; - const Rect& rect = layer->layer; setupDraw(); setupDrawWithTexture(); @@ -2653,12 +2612,12 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* pain setupDrawColorFilterUniforms(); setupDrawTexture(layer->getTexture()); if (CC_LIKELY(mSnapshot->transform->isPureTranslate())) { - x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f); - y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f); + int tx = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f); + int ty = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f); layer->setFilter(GL_NEAREST); - setupDrawModelViewTranslate(x, y, - x + layer->layer.getWidth(), y + layer->layer.getHeight(), true); + setupDrawModelViewTranslate(tx, ty, + tx + layer->layer.getWidth(), ty + layer->layer.getHeight(), true); } else { layer->setFilter(GL_LINEAR); setupDrawModelViewTranslate(x, y, @@ -2675,11 +2634,12 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* pain drawRegionRects(layer->region); #endif } + + if (debugLayerUpdate) { + drawColorRect(x, y, x + layer->layer.getWidth(), y + layer->layer.getHeight(), + 0x7f00ff00, SkXfermode::kSrcOver_Mode); + } } -#else - const Rect r(x, y, x + layer->layer.getWidth(), y + layer->layer.getHeight()); - composeLayerRect(layer, r); -#endif return DrawGlInfo::kStatusDrew; } diff --git a/libs/hwui/Patch.cpp b/libs/hwui/Patch.cpp index 27f530c48fa6..6971d8a25314 100644 --- a/libs/hwui/Patch.cpp +++ b/libs/hwui/Patch.cpp @@ -108,9 +108,7 @@ bool Patch::matches(const int32_t* xDivs, const int32_t* yDivs, const uint32_t c void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, float left, float top, float right, float bottom) { -#if RENDER_LAYERS_AS_REGIONS if (hasEmptyQuads) quads.clear(); -#endif // Reset the vertices count here, we will count exactly how many // vertices we actually need when generating the quads @@ -278,13 +276,11 @@ void Patch::generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, f return; } -#if RENDER_LAYERS_AS_REGIONS // Record all non empty quads if (hasEmptyQuads) { Rect bounds(x1, y1, x2, y2); quads.add(bounds); } -#endif // Left triangle TextureVertex::set(vertex++, x1, y1, u1, v1); diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 3f3b39a1c349..6b6dc9e73a14 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -25,9 +25,6 @@ * the OpenGLRenderer. */ -// If turned on, layers drawn inside FBOs are optimized with regions -#define RENDER_LAYERS_AS_REGIONS 1 - // If turned on, text is interpreted as glyphs instead of UTF-16 #define RENDER_TEXT_AS_GLYPHS 1 @@ -43,9 +40,10 @@ #define STENCIL_BUFFER_SIZE 0 /** - * Debug level for app developers. + * Debug level for app developers. The value is a numeric value defined + * by the DebugLevel enum below. */ -#define PROPERTY_DEBUG "hwui.debug_level" +#define PROPERTY_DEBUG "debug.hwui.level" /** * Debug levels. Debug levels are used as flags. @@ -57,6 +55,12 @@ enum DebugLevel { kDebugMoreCaches = kDebugMemory | kDebugCaches }; +/** + * Used to enable/disbale layers update debugging. The accepted values are + * "true" and "false". The default value is "false". + */ +#define PROPERTY_DEBUG_LAYERS_UPDATES "debug.hwui.show_layers_updates" + // These properties are defined in mega-bytes #define PROPERTY_TEXTURE_CACHE_SIZE "ro.hwui.texture_cache_size" #define PROPERTY_LAYER_CACHE_SIZE "ro.hwui.layer_cache_size" diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index ad6342449387..dc0cb758ca5e 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -1612,6 +1612,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); if (info.isFailover()) { intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); info.setFailover(false); @@ -1738,6 +1739,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private Intent makeGeneralIntent(NetworkInfo info, String bcastType) { Intent intent = new Intent(bcastType); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); if (info.isFailover()) { intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); info.setFailover(false); @@ -1788,6 +1790,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); if (getActiveNetworkInfo() == null) { intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); } diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java index 9ed8fa93aeba..d9cbf8134b13 100644 --- a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java +++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java @@ -150,10 +150,16 @@ public class ImageProcessingActivity extends Activity mTest = new Fisheye(true); break; case 9: - mTest = new Vignette(false); + mTest = new Vignette(false, false); break; case 10: - mTest = new Vignette(true); + mTest = new Vignette(false, true); + break; + case 11: + mTest = new Vignette(true, false); + break; + case 12: + mTest = new Vignette(true, true); break; } @@ -167,7 +173,7 @@ public class ImageProcessingActivity extends Activity } void setupTests() { - mTestNames = new String[11]; + mTestNames = new String[13]; mTestNames[0] = "Levels Vec3 Relaxed"; mTestNames[1] = "Levels Vec4 Relaxed"; mTestNames[2] = "Levels Vec3 Full"; @@ -179,6 +185,8 @@ public class ImageProcessingActivity extends Activity mTestNames[8] = "Fisheye Relaxed"; mTestNames[9] = "Vignette Full"; mTestNames[10] = "Vignette Relaxed"; + mTestNames[11] = "Vignette Approximate Full"; + mTestNames[12] = "Vignette Approximate Relaxed"; mTestSpinner.setAdapter(new ArrayAdapter<String>( this, R.layout.spinner_layout, mTestNames)); } diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/Vignette.java b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/Vignette.java index 927d7d533c14..18d11039a178 100644 --- a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/Vignette.java +++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/Vignette.java @@ -26,6 +26,9 @@ import android.widget.TextView; public class Vignette extends TestBase { private ScriptC_vignette_full mScript_full = null; private ScriptC_vignette_relaxed mScript_relaxed = null; + private ScriptC_vignette_approx_full mScript_approx_full = null; + private ScriptC_vignette_approx_relaxed mScript_approx_relaxed = null; + private final boolean approx; private final boolean relaxed; private float center_x = 0.5f; private float center_y = 0.5f; @@ -33,7 +36,8 @@ public class Vignette extends TestBase { private float shade = 0.5f; private float slope = 20.0f; - public Vignette(boolean relaxed) { + public Vignette(boolean approx, boolean relaxed) { + this.approx = approx; this.relaxed = relaxed; } @@ -90,7 +94,18 @@ public class Vignette extends TestBase { } private void do_init() { - if (relaxed) + if (approx) { + if (relaxed) + mScript_approx_relaxed.invoke_init_vignette( + mInPixelsAllocation.getType().getX(), + mInPixelsAllocation.getType().getY(), center_x, + center_y, scale, shade, slope); + else + mScript_approx_full.invoke_init_vignette( + mInPixelsAllocation.getType().getX(), + mInPixelsAllocation.getType().getY(), center_x, + center_y, scale, shade, slope); + } else if (relaxed) mScript_relaxed.invoke_init_vignette( mInPixelsAllocation.getType().getX(), mInPixelsAllocation.getType().getY(), center_x, center_y, @@ -103,21 +118,36 @@ public class Vignette extends TestBase { } public void createTest(android.content.res.Resources res) { - if (relaxed) { + if (approx) { + if (relaxed) + mScript_approx_relaxed = new ScriptC_vignette_approx_relaxed( + mRS, res, R.raw.vignette_approx_relaxed); + else + mScript_approx_full = new ScriptC_vignette_approx_full( + mRS, res, R.raw.vignette_approx_full); + } else if (relaxed) mScript_relaxed = new ScriptC_vignette_relaxed(mRS, res, R.raw.vignette_relaxed); - } else { + else mScript_full = new ScriptC_vignette_full(mRS, res, R.raw.vignette_full); - } do_init(); } public void runTest() { - if (relaxed) - mScript_relaxed.forEach_root(mInPixelsAllocation, mOutPixelsAllocation); + if (approx) { + if (relaxed) + mScript_approx_relaxed.forEach_root(mInPixelsAllocation, + mOutPixelsAllocation); + else + mScript_approx_full.forEach_root(mInPixelsAllocation, + mOutPixelsAllocation); + } else if (relaxed) + mScript_relaxed.forEach_root(mInPixelsAllocation, + mOutPixelsAllocation); else - mScript_full.forEach_root(mInPixelsAllocation, mOutPixelsAllocation); + mScript_full.forEach_root(mInPixelsAllocation, + mOutPixelsAllocation); } } diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/vignette_approx.rsh b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/vignette_approx.rsh new file mode 100644 index 000000000000..19d0117bac7a --- /dev/null +++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/vignette_approx.rsh @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +static float2 neg_center, axis_scale, inv_dimensions; +static float sloped_neg_range, sloped_inv_max_dist, shade, opp_shade; + +void init_vignette(uint32_t dim_x, uint32_t dim_y, float center_x, float center_y, + float desired_scale, float desired_shade, float desired_slope) { + + neg_center.x = -center_x; + neg_center.y = -center_y; + inv_dimensions.x = 1.f / (float)dim_x; + inv_dimensions.y = 1.f / (float)dim_y; + + axis_scale = (float2)1.f; + if (dim_x > dim_y) + axis_scale.y = (float)dim_y / (float)dim_x; + else + axis_scale.x = (float)dim_x / (float)dim_y; + + const float max_dist = 0.5 * length(axis_scale); + sloped_inv_max_dist = desired_slope * 1.f/max_dist; + + // Range needs to be between 1.3 to 0.6. When scale is zero then range is + // 1.3 which means no vignette at all because the luminousity difference is + // less than 1/256. Expect input scale to be between 0.0 and 1.0. + const float neg_range = 0.7*sqrt(desired_scale) - 1.3; + sloped_neg_range = exp(neg_range * desired_slope); + + shade = desired_shade; + opp_shade = 1.f - desired_shade; +} + +void root(const uchar4 *in, uchar4 *out, uint32_t x, uint32_t y) { + // Convert x and y to floating point coordinates with center as origin + const float4 fin = convert_float4(*in); + const float2 inCoord = {(float)x, (float)y}; + const float2 coord = mad(inCoord, inv_dimensions, neg_center); + const float sloped_dist_ratio = approx_length(axis_scale * coord) * sloped_inv_max_dist; + // TODO: add approx_exp once implemented + const float lumen = opp_shade + shade * approx_recip(1.f + sloped_neg_range * exp(sloped_dist_ratio)); + float4 fout; + fout.rgb = fin.rgb * lumen; + fout.w = fin.w; + *out = convert_uchar4(fout); +} + diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/vignette_approx_full.rs b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/vignette_approx_full.rs new file mode 100644 index 000000000000..c83c6e1a37c1 --- /dev/null +++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/vignette_approx_full.rs @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma version(1) +#pragma rs java_package_name(com.android.rs.image) + +#include "vignette_approx.rsh" + diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/vignette_approx_relaxed.rs b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/vignette_approx_relaxed.rs new file mode 100644 index 000000000000..912061282e52 --- /dev/null +++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/vignette_approx_relaxed.rs @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma version(1) +#pragma rs java_package_name(com.android.rs.image) +#pragma rs_fp_relaxed + +#include "vignette_approx.rsh" + diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java index 4bf1ca35403d..84c565b0a4ef 100644 --- a/wifi/java/android/net/wifi/WifiNative.java +++ b/wifi/java/android/net/wifi/WifiNative.java @@ -368,6 +368,14 @@ public class WifiNative { return doStringCommand("SIGNAL_POLL"); } + /** Example outout: + * TXGOOD=396 + * TXBAD=1 + */ + public String pktcntPoll() { + return doStringCommand("PKTCNT_POLL"); + } + public boolean startWpsPbc(String bssid) { if (TextUtils.isEmpty(bssid)) { return doBooleanCommand("WPS_PBC"); diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index 473d12cd3820..c4807598c468 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -51,6 +51,7 @@ import android.net.LinkProperties; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkUtils; +import android.net.wifi.WifiWatchdogStateMachine.RssiPktcntStat; import android.net.wifi.WpsResult.Status; import android.net.wifi.p2p.WifiP2pManager; import android.net.wifi.p2p.WifiP2pService; @@ -1163,7 +1164,7 @@ public class WifiStateMachine extends StateMachine { case CMD_RSSI_POLL: case CMD_DELAYED_STOP_DRIVER: case WifiMonitor.SCAN_RESULTS_EVENT: - case WifiWatchdogStateMachine.RSSI_FETCH: + case WifiWatchdogStateMachine.RSSI_PKTCNT_FETCH: return false; default: return true; @@ -1514,6 +1515,30 @@ public class WifiStateMachine extends StateMachine { } } + /* + * Fetch TX packet counters on current connection + */ + private void fetchPktcntNative(RssiPktcntStat stat) { + String pktcntPoll = mWifiNative.pktcntPoll(); + + if (pktcntPoll != null) { + String[] lines = pktcntPoll.split("\n"); + for (String line : lines) { + String[] prop = line.split("="); + if (prop.length < 2) continue; + try { + if (prop[0].equals("TXGOOD")) { + stat.txgood = Integer.parseInt(prop[1]); + } else if (prop[0].equals("TXBAD")) { + stat.txbad = Integer.parseInt(prop[1]); + } + } catch (NumberFormatException e) { + //Ignore + } + } + } + } + private void configureLinkProperties() { if (mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) { mLinkProperties = mWifiConfigStore.getLinkProperties(mLastNetworkId); @@ -1922,8 +1947,8 @@ public class WifiStateMachine extends StateMachine { replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED, WifiManager.BUSY); break; - case WifiWatchdogStateMachine.RSSI_FETCH: - replyToMessage(message, WifiWatchdogStateMachine.RSSI_FETCH_FAILED); + case WifiWatchdogStateMachine.RSSI_PKTCNT_FETCH: + replyToMessage(message, WifiWatchdogStateMachine.RSSI_PKTCNT_FETCH_FAILED); break; default: loge("Error! unhandled message" + message); @@ -3126,10 +3151,13 @@ public class WifiStateMachine extends StateMachine { mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS); } break; - case WifiWatchdogStateMachine.RSSI_FETCH: + case WifiWatchdogStateMachine.RSSI_PKTCNT_FETCH: + RssiPktcntStat stat = (RssiPktcntStat) message.obj; fetchRssiAndLinkSpeedNative(); - replyToMessage(message, WifiWatchdogStateMachine.RSSI_FETCH_SUCCEEDED, - mWifiInfo.getRssi()); + stat.rssi = mWifiInfo.getRssi(); + fetchPktcntNative(stat); + replyToMessage(message, WifiWatchdogStateMachine.RSSI_PKTCNT_FETCH_SUCCEEDED, + stat); break; default: return NOT_HANDLED; diff --git a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java index 4018c74e87e6..7b4d1135222e 100644 --- a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java +++ b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java @@ -26,19 +26,16 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.database.ContentObserver; -import android.net.arp.ArpPeer; import android.net.ConnectivityManager; -import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkInfo; -import android.net.RouteInfo; import android.net.Uri; import android.os.Message; import android.os.SystemClock; -import android.os.SystemProperties; import android.provider.Settings; import android.provider.Settings.Secure; import android.util.Log; +import android.util.LruCache; import com.android.internal.R; import com.android.internal.util.AsyncChannel; @@ -49,40 +46,36 @@ import com.android.internal.util.StateMachine; import java.io.IOException; import java.io.PrintWriter; import java.net.HttpURLConnection; -import java.net.InetAddress; -import java.net.SocketException; import java.net.URL; +import java.text.DecimalFormat; /** - * WifiWatchdogStateMachine monitors the connection to a Wi-Fi - * network. After the framework notifies that it has connected to an - * acccess point and is waiting for link to be verified, the watchdog - * takes over and verifies if the link is good by doing ARP pings to - * the gateway using {@link ArpPeer}. - * - * Upon successful verification, the watchdog notifies and continues - * to monitor the link afterwards when the RSSI level falls below - * a certain threshold. - - * When Wi-fi connects at L2 layer, the beacons from access point reach - * the device and it can maintain a connection, but the application - * connectivity can be flaky (due to bigger packet size exchange). - * - * We now monitor the quality of the last hop on - * Wi-Fi using signal strength and ARP connectivity as indicators - * to decide if the link is good enough to switch to Wi-Fi as the uplink. - * - * ARP pings are useful for link validation but can still get through - * when the application traffic fails to go through and are thus not - * the best indicator of real packet loss since they are tiny packets - * (28 bytes) and have a much low chance of packet corruption than the - * regular data packets. - * - * When signal strength and ARP are used together, it ends up working well in tests. - * The goal is to switch to Wi-Fi after validating ARP transfer - * and RSSI and then switching out of Wi-Fi when we hit a low - * signal strength threshold and then waiting until the signal strength - * improves and validating ARP transfer. + * WifiWatchdogStateMachine monitors the connection to a WiFi network. When WiFi + * connects at L2 layer, the beacons from access point reach the device and it + * can maintain a connection, but the application connectivity can be flaky (due + * to bigger packet size exchange). + * <p> + * We now monitor the quality of the last hop on WiFi using packet loss ratio as + * an indicator to decide if the link is good enough to switch to Wi-Fi as the + * uplink. + * <p> + * When WiFi is connected, the WiFi watchdog keeps sampling the RSSI and the + * instant packet loss, and record it as per-AP loss-to-rssi statistics. When + * the instant packet loss is higher than a threshold, the WiFi watchdog sends a + * poor link notification to avoid WiFi connection temporarily. + * <p> + * While WiFi is being avoided, the WiFi watchdog keep watching the RSSI to + * bring the WiFi connection back. Once the RSSI is high enough to achieve a + * lower packet loss, a good link detection is sent such that the WiFi + * connection become available again. + * <p> + * BSSID roaming has been taken into account. When user is moving across + * multiple APs, the WiFi watchdog will detect that and keep watching the + * currently connected AP. + * <p> + * Power impact should be minimal since much of the measurement relies on + * passive statistics already being tracked at the driver and the polling is + * done when screen is turned on and the RSSI is in a certain range. * * @hide */ @@ -91,92 +84,243 @@ public class WifiWatchdogStateMachine extends StateMachine { /* STOPSHIP: Keep this configurable for debugging until ship */ private static boolean DBG = false; private static final String TAG = "WifiWatchdogStateMachine"; - private static final String WALLED_GARDEN_NOTIFICATION_ID = "WifiWatchdog.walledgarden"; - /* RSSI Levels as used by notification icon - Level 4 -55 <= RSSI - Level 3 -66 <= RSSI < -55 - Level 2 -77 <= RSSI < -67 - Level 1 -88 <= RSSI < -78 - Level 0 RSSI < -88 */ - - /* Wi-fi connection is monitored actively below this - threshold */ - private static final int RSSI_LEVEL_MONITOR = 0; - /* Rssi threshold is at level 0 (-88dBm) */ - private static final int RSSI_MONITOR_THRESHOLD = -88; - /* Number of times RSSI is measured to be low before being avoided */ - private static final int RSSI_MONITOR_COUNT = 5; - private int mRssiMonitorCount = 0; - - /* Avoid flapping. The interval is changed over time as long as we continue to avoid - * under the max interval after which we reset the interval again */ - private static final int MIN_INTERVAL_AVOID_BSSID_MS[] = {0, 30 * 1000, 60 * 1000, - 5 * 60 * 1000, 30 * 60 * 1000}; - /* Index into the interval array MIN_INTERVAL_AVOID_BSSID_MS */ - private int mMinIntervalArrayIndex = 0; - - private long mLastBssidAvoidedTime; + private static final int BASE = Protocol.BASE_WIFI_WATCHDOG; - private int mCurrentSignalLevel; + /* Internal events */ + private static final int EVENT_WATCHDOG_TOGGLED = BASE + 1; + private static final int EVENT_NETWORK_STATE_CHANGE = BASE + 2; + private static final int EVENT_RSSI_CHANGE = BASE + 3; + private static final int EVENT_SUPPLICANT_STATE_CHANGE = BASE + 4; + private static final int EVENT_WIFI_RADIO_STATE_CHANGE = BASE + 5; + private static final int EVENT_WATCHDOG_SETTINGS_CHANGE = BASE + 6; + private static final int EVENT_BSSID_CHANGE = BASE + 7; + private static final int EVENT_SCREEN_ON = BASE + 8; + private static final int EVENT_SCREEN_OFF = BASE + 9; - private static final long DEFAULT_ARP_CHECK_INTERVAL_MS = 2 * 60 * 1000; - private static final long DEFAULT_RSSI_FETCH_INTERVAL_MS = 1000; - private static final long DEFAULT_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000; + /* Internal messages */ + private static final int CMD_DELAYED_WALLED_GARDEN_CHECK = BASE + 11; + private static final int CMD_RSSI_FETCH = BASE + 12; - private static final int DEFAULT_NUM_ARP_PINGS = 5; - private static final int DEFAULT_MIN_ARP_RESPONSES = 1; + /* Notifications from/to WifiStateMachine */ + static final int POOR_LINK_DETECTED = BASE + 21; + static final int GOOD_LINK_DETECTED = BASE + 22; + static final int RSSI_PKTCNT_FETCH = BASE + 23; + static final int RSSI_PKTCNT_FETCH_SUCCEEDED = BASE + 24; + static final int RSSI_PKTCNT_FETCH_FAILED = BASE + 25; + + /* + * RSSI levels as used by notification icon + * Level 4 -55 <= RSSI + * Level 3 -66 <= RSSI < -55 + * Level 2 -77 <= RSSI < -67 + * Level 1 -88 <= RSSI < -78 + * Level 0 RSSI < -88 + */ - private static final int DEFAULT_ARP_PING_TIMEOUT_MS = 100; + /** + * WiFi link statistics is monitored and recorded actively below this threshold. + * <p> + * Larger threshold is more adaptive but increases sampling cost. + */ + private static final int LINK_MONITOR_LEVEL_THRESHOLD = 4; - // See http://go/clientsdns for usage approval - private static final String DEFAULT_WALLED_GARDEN_URL = - "http://clients3.google.com/generate_204"; - private static final int WALLED_GARDEN_SOCKET_TIMEOUT_MS = 10000; + /** + * Remember packet loss statistics of how many BSSIDs. + * <p> + * Larger size is usually better but requires more space. + */ + private static final int BSSID_STAT_CACHE_SIZE = 20; - /* Some carrier apps might have support captive portal handling. Add some delay to allow - app authentication to be done before our test. - TODO: This should go away once we provide an API to apps to disable walled garden test - for certain SSIDs + /** + * RSSI range of a BSSID statistics. + * Within the range, (RSSI -> packet loss %) mappings are stored. + * <p> + * Larger range is usually better but requires more space. */ - private static final int WALLED_GARDEN_START_DELAY_MS = 3000; + private static final int BSSID_STAT_RANGE_LOW_DBM = -105; - private static final int BASE = Protocol.BASE_WIFI_WATCHDOG; + /** + * See {@link #BSSID_STAT_RANGE_LOW_DBM}. + */ + private static final int BSSID_STAT_RANGE_HIGH_DBM = -45; /** - * Indicates the enable setting of WWS may have changed + * How many consecutive empty data point to trigger a empty-cache detection. + * In this case, a preset/default loss value (function on RSSI) is used. + * <p> + * In normal uses, some RSSI values may never be seen due to channel randomness. + * However, the size of such empty RSSI chunk in normal use is generally 1~2. */ - private static final int EVENT_WATCHDOG_TOGGLED = BASE + 1; + private static final int BSSID_STAT_EMPTY_COUNT = 3; /** - * Indicates the wifi network state has changed. Passed w/ original intent - * which has a non-null networkInfo object + * Sample interval for packet loss statistics, in msec. + * <p> + * Smaller interval is more accurate but increases sampling cost (battery consumption). */ - private static final int EVENT_NETWORK_STATE_CHANGE = BASE + 2; - /* Passed with RSSI information */ - private static final int EVENT_RSSI_CHANGE = BASE + 3; - private static final int EVENT_WIFI_RADIO_STATE_CHANGE = BASE + 5; - private static final int EVENT_WATCHDOG_SETTINGS_CHANGE = BASE + 6; + private static final long LINK_SAMPLING_INTERVAL_MS = 1 * 1000; - /* Internal messages */ - private static final int CMD_ARP_CHECK = BASE + 11; - private static final int CMD_DELAYED_WALLED_GARDEN_CHECK = BASE + 12; - private static final int CMD_RSSI_FETCH = BASE + 13; + /** + * Coefficients (alpha) for moving average for packet loss tracking. + * Must be within (0.0, 1.0). + * <p> + * Equivalent number of samples: N = 2 / alpha - 1 . + * We want the historic loss to base on more data points to be statistically reliable. + * We want the current instant loss to base on less data points to be responsive. + */ + private static final double EXP_COEFFICIENT_RECORD = 0.1; - /* Notifications to WifiStateMachine */ - static final int POOR_LINK_DETECTED = BASE + 21; - static final int GOOD_LINK_DETECTED = BASE + 22; - static final int RSSI_FETCH = BASE + 23; - static final int RSSI_FETCH_SUCCEEDED = BASE + 24; - static final int RSSI_FETCH_FAILED = BASE + 25; + /** + * See {@link #EXP_COEFFICIENT_RECORD}. + */ + private static final double EXP_COEFFICIENT_MONITOR = 0.5; + + /** + * Thresholds for sending good/poor link notifications, in packet loss %. + * Good threshold must be smaller than poor threshold. + * Use smaller poor threshold to avoid WiFi more aggressively. + * Use smaller good threshold to bring back WiFi more conservatively. + * <p> + * When approaching the boundary, loss ratio jumps significantly within a few dBs. + * 50% loss threshold is a good balance between accuracy and reponsiveness. + * <=10% good threshold is a safe value to avoid jumping back to WiFi too easily. + */ + private static final double POOR_LINK_LOSS_THRESHOLD = 0.5; + + /** + * See {@link #POOR_LINK_LOSS_THRESHOLD}. + */ + private static final double GOOD_LINK_LOSS_THRESHOLD = 0.1; + + /** + * Number of samples to confirm before sending a poor link notification. + * Response time = confirm_count * sample_interval . + * <p> + * A smaller threshold improves response speed but may suffer from randomness. + * According to experiments, 3~5 are good values to achieve a balance. + * These parameters should be tuned along with {@link #LINK_SAMPLING_INTERVAL_MS}. + */ + private static final int POOR_LINK_SAMPLE_COUNT = 3; + + /** + * Minimum volume (converted from pkt/sec) to detect a poor link, to avoid randomness. + * <p> + * According to experiments, 1pkt/sec is too sensitive but 3pkt/sec is slightly unresponsive. + */ + private static final double POOR_LINK_MIN_VOLUME = 2.0 * LINK_SAMPLING_INTERVAL_MS / 1000.0; + + /** + * When a poor link is detected, we scan over this range (based on current + * poor link RSSI) for a target RSSI that satisfies a target packet loss. + * Refer to {@link #GOOD_LINK_TARGET}. + * <p> + * We want range_min not too small to avoid jumping back to WiFi too easily. + */ + private static final int GOOD_LINK_RSSI_RANGE_MIN = 3; + + /** + * See {@link #GOOD_LINK_RSSI_RANGE_MIN}. + */ + private static final int GOOD_LINK_RSSI_RANGE_MAX = 20; + + /** + * Adaptive good link target to avoid flapping. + * When a poor link is detected, a good link target is calculated as follows: + * <p> + * targetRSSI = min{ rssi | loss(rssi) < GOOD_LINK_LOSS_THRESHOLD } + rssi_adj[i], + * where rssi is in the above GOOD_LINK_RSSI_RANGE. + * targetCount = sample_count[i] . + * <p> + * While WiFi is being avoided, we keep monitoring its signal strength. + * Good link notification is sent when we see current RSSI >= targetRSSI + * for targetCount consecutive times. + * <p> + * Index i is incremented each time after a poor link detection. + * Index i is decreased to at most k if the last poor link was at lease reduce_time[k] ago. + * <p> + * Intuitively, larger index i makes it more difficult to get back to WiFi, avoiding flapping. + * In experiments, (+9 dB / 30 counts) makes it quite difficult to achieve. + * Avoid using it unless flapping is really bad (say, last poor link is only 1min ago). + */ + private static final GoodLinkTarget[] GOOD_LINK_TARGET = { + /* rssi_adj, sample_count, reduce_time */ + new GoodLinkTarget( 0, 3, 30 * 60000 ), + new GoodLinkTarget( 3, 5, 5 * 60000 ), + new GoodLinkTarget( 6, 10, 1 * 60000 ), + new GoodLinkTarget( 9, 30, 0 * 60000 ), + }; + + /** + * The max time to avoid a BSSID, to prevent avoiding forever. + * If current RSSI is at least min_rssi[i], the max avoidance time is at most max_time[i] + * <p> + * It is unusual to experience high packet loss at high RSSI. Something unusual must be + * happening (e.g. strong interference). For higher signal strengths, we set the avoidance + * time to be low to allow for quick turn around from temporary interference. + * <p> + * See {@link BssidStatistics#poorLinkDetected}. + */ + private static final MaxAvoidTime[] MAX_AVOID_TIME = { + /* max_time, min_rssi */ + new MaxAvoidTime( 30 * 60000, -200 ), + new MaxAvoidTime( 5 * 60000, -70 ), + new MaxAvoidTime( 0 * 60000, -55 ), + }; + + + private static final String WALLED_GARDEN_NOTIFICATION_ID = "WifiWatchdog.walledgarden"; + + private static final long DEFAULT_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000; + /** + * See http://go/clientsdns for usage approval + */ + private static final String DEFAULT_WALLED_GARDEN_URL = + "http://clients3.google.com/generate_204"; + private static final int WALLED_GARDEN_SOCKET_TIMEOUT_MS = 10000; + + /** + * Some carrier apps might have support captive portal handling. Add some + * delay to allow app authentication to be done before our test. TODO: This + * should go away once we provide an API to apps to disable walled garden + * test for certain SSIDs + */ + private static final int WALLED_GARDEN_START_DELAY_MS = 3000; + + + /* Framework related */ private Context mContext; private ContentResolver mContentResolver; private WifiManager mWifiManager; private IntentFilter mIntentFilter; private BroadcastReceiver mBroadcastReceiver; - private AsyncChannel mWsmChannel = new AsyncChannel();; + private AsyncChannel mWsmChannel = new AsyncChannel(); + private WifiInfo mWifiInfo; + private LinkProperties mLinkProperties; + + /* System settingss related */ + private static boolean sWifiOnly = false; + private boolean mPoorNetworkDetectionEnabled; + private long mWalledGardenIntervalMs; + private boolean mWalledGardenTestEnabled; + private String mWalledGardenUrl; + /* Wall garden detection related */ + private long mLastWalledGardenCheckTime = 0; + private boolean mWalledGardenNotificationShown; + + /* Poor link detection related */ + private LruCache<String, BssidStatistics> mBssidCache = + new LruCache<String, BssidStatistics>(BSSID_STAT_CACHE_SIZE); + private int mRssiFetchToken = 0; + private int mCurrentSignalLevel; + private BssidStatistics mCurrentBssid; + private VolumeWeightedEMA mCurrentLoss; + private boolean mIsScreenOn = true; + private static double sPresetLoss[]; + + /* WiFi watchdog state machine related */ private DefaultState mDefaultState = new DefaultState(); private WatchdogDisabledState mWatchdogDisabledState = new WatchdogDisabledState(); private WatchdogEnabledState mWatchdogEnabledState = new WatchdogEnabledState(); @@ -184,33 +328,10 @@ public class WifiWatchdogStateMachine extends StateMachine { private VerifyingLinkState mVerifyingLinkState = new VerifyingLinkState(); private ConnectedState mConnectedState = new ConnectedState(); private WalledGardenCheckState mWalledGardenCheckState = new WalledGardenCheckState(); - /* Online and watching link connectivity */ private OnlineWatchState mOnlineWatchState = new OnlineWatchState(); - /* RSSI level is below RSSI_LEVEL_MONITOR and needs close monitoring */ - private RssiMonitoringState mRssiMonitoringState = new RssiMonitoringState(); - /* Online and doing nothing */ + private LinkMonitoringState mLinkMonitoringState = new LinkMonitoringState(); private OnlineState mOnlineState = new OnlineState(); - private int mArpToken = 0; - private long mArpCheckIntervalMs; - private int mRssiFetchToken = 0; - private long mRssiFetchIntervalMs; - private long mWalledGardenIntervalMs; - private int mNumArpPings; - private int mMinArpResponses; - private int mArpPingTimeoutMs; - private boolean mPoorNetworkDetectionEnabled; - private boolean mWalledGardenTestEnabled; - private String mWalledGardenUrl; - - private WifiInfo mWifiInfo; - private LinkProperties mLinkProperties; - - private long mLastWalledGardenCheckTime = 0; - - private static boolean sWifiOnly = false; - private boolean mWalledGardenNotificationShown; - /** * STATE MAP * Default @@ -231,7 +352,7 @@ public class WifiWatchdogStateMachine extends StateMachine { setupNetworkReceiver(); - // The content observer to listen needs a handler + // the content observer to listen needs a handler registerForSettingsChanges(); registerForWatchdogToggle(); addState(mDefaultState); @@ -242,7 +363,7 @@ public class WifiWatchdogStateMachine extends StateMachine { addState(mConnectedState, mWatchdogEnabledState); addState(mWalledGardenCheckState, mConnectedState); addState(mOnlineWatchState, mConnectedState); - addState(mRssiMonitoringState, mOnlineWatchState); + addState(mLinkMonitoringState, mConnectedState); addState(mOnlineState, mConnectedState); if (isWatchdogEnabled()) { @@ -266,9 +387,9 @@ public class WifiWatchdogStateMachine extends StateMachine { // watchdog in an enabled state putSettingsBoolean(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON, true); - // Disable poor network avoidance, but keep watchdog active for walled garden detection + // disable poor network avoidance, but keep watchdog active for walled garden detection if (sWifiOnly) { - log("Disabling poor network avoidance for wi-fi only device"); + logd("Disabling poor network avoidance for wi-fi only device"); putSettingsBoolean(contentResolver, Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, false); } @@ -283,15 +404,20 @@ public class WifiWatchdogStateMachine extends StateMachine { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { - sendMessage(EVENT_NETWORK_STATE_CHANGE, intent); - } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { + if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { obtainMessage(EVENT_RSSI_CHANGE, intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200), 0).sendToTarget(); + } else if (action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) { + sendMessage(EVENT_SUPPLICANT_STATE_CHANGE, intent); + } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + sendMessage(EVENT_NETWORK_STATE_CHANGE, intent); + } else if (action.equals(Intent.ACTION_SCREEN_ON)) { + sendMessage(EVENT_SCREEN_ON); + } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { + sendMessage(EVENT_SCREEN_OFF); } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { - sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE, - intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, - WifiManager.WIFI_STATE_UNKNOWN)); + sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE,intent.getIntExtra( + WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN)); } } }; @@ -300,6 +426,9 @@ public class WifiWatchdogStateMachine extends StateMachine { mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); + mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); + mIntentFilter.addAction(Intent.ACTION_SCREEN_ON); + mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF); mContext.registerReceiver(mBroadcastReceiver, mIntentFilter); } @@ -331,22 +460,9 @@ public class WifiWatchdogStateMachine extends StateMachine { }; mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor( - Settings.Secure.WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS), - false, contentObserver); - mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS), false, contentObserver); mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_NUM_ARP_PINGS), - false, contentObserver); - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MIN_ARP_RESPONSES), - false, contentObserver); - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS), - false, contentObserver); - mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED), false, contentObserver); mContext.getContentResolver().registerContentObserver( @@ -372,12 +488,10 @@ public class WifiWatchdogStateMachine extends StateMachine { urlConnection.setReadTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS); urlConnection.setUseCaches(false); urlConnection.getInputStream(); - // We got a valid response, but not from the real google + // we got a valid response, but not from the real google return urlConnection.getResponseCode() != 204; } catch (IOException e) { - if (DBG) { - log("Walled garden check - probably not a portal: exception " + e); - } + if (DBG) logd("Walled garden check - probably not a portal: exception " + e); return false; } finally { if (urlConnection != null) { @@ -392,12 +506,7 @@ public class WifiWatchdogStateMachine extends StateMachine { pw.println("mWifiInfo: [" + mWifiInfo + "]"); pw.println("mLinkProperties: [" + mLinkProperties + "]"); pw.println("mCurrentSignalLevel: [" + mCurrentSignalLevel + "]"); - pw.println("mArpCheckIntervalMs: [" + mArpCheckIntervalMs+ "]"); - pw.println("mRssiFetchIntervalMs: [" + mRssiFetchIntervalMs + "]"); pw.println("mWalledGardenIntervalMs: [" + mWalledGardenIntervalMs + "]"); - pw.println("mNumArpPings: [" + mNumArpPings + "]"); - pw.println("mMinArpResponses: [" + mMinArpResponses + "]"); - pw.println("mArpPingTimeoutMs: [" + mArpPingTimeoutMs + "]"); pw.println("mPoorNetworkDetectionEnabled: [" + mPoorNetworkDetectionEnabled + "]"); pw.println("mWalledGardenTestEnabled: [" + mWalledGardenTestEnabled + "]"); pw.println("mWalledGardenUrl: [" + mWalledGardenUrl + "]"); @@ -405,28 +514,13 @@ public class WifiWatchdogStateMachine extends StateMachine { private boolean isWatchdogEnabled() { boolean ret = getSettingsBoolean(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, true); - if (DBG) log("watchdog enabled " + ret); + if (DBG) logd("Watchdog enabled " + ret); return ret; } private void updateSettings() { - if (DBG) log("Updating secure settings"); - - mArpCheckIntervalMs = Secure.getLong(mContentResolver, - Secure.WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS, - DEFAULT_ARP_CHECK_INTERVAL_MS); - mRssiFetchIntervalMs = Secure.getLong(mContentResolver, - Secure.WIFI_WATCHDOG_RSSI_FETCH_INTERVAL_MS, - DEFAULT_RSSI_FETCH_INTERVAL_MS); - mNumArpPings = Secure.getInt(mContentResolver, - Secure.WIFI_WATCHDOG_NUM_ARP_PINGS, - DEFAULT_NUM_ARP_PINGS); - mMinArpResponses = Secure.getInt(mContentResolver, - Secure.WIFI_WATCHDOG_MIN_ARP_RESPONSES, - DEFAULT_MIN_ARP_RESPONSES); - mArpPingTimeoutMs = Secure.getInt(mContentResolver, - Secure.WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS, - DEFAULT_ARP_PING_TIMEOUT_MS); + if (DBG) logd("Updating secure settings"); + mPoorNetworkDetectionEnabled = getSettingsBoolean(mContentResolver, Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, true); mWalledGardenTestEnabled = getSettingsBoolean(mContentResolver, @@ -440,7 +534,7 @@ public class WifiWatchdogStateMachine extends StateMachine { } private void setWalledGardenNotificationVisible(boolean visible) { - // If it should be hidden and it is already hidden, then noop + // if it should be hidden and it is already hidden, then noop if (!visible && !mWalledGardenNotificationShown) { return; } @@ -472,10 +566,13 @@ public class WifiWatchdogStateMachine extends StateMachine { mWalledGardenNotificationShown = visible; } + /** + * Default state, guard for unhandled messages. + */ class DefaultState extends State { @Override public void enter() { - if (DBG) log(getName() + "\n"); + if (DBG) logd(getName()); } @Override @@ -483,34 +580,42 @@ public class WifiWatchdogStateMachine extends StateMachine { switch (msg.what) { case EVENT_WATCHDOG_SETTINGS_CHANGE: updateSettings(); - if (DBG) { - log("Updating wifi-watchdog secure settings"); - } + if (DBG) logd("Updating wifi-watchdog secure settings"); break; case EVENT_RSSI_CHANGE: mCurrentSignalLevel = calculateSignalLevel(msg.arg1); break; case EVENT_WIFI_RADIO_STATE_CHANGE: case EVENT_NETWORK_STATE_CHANGE: - case CMD_ARP_CHECK: + case EVENT_SUPPLICANT_STATE_CHANGE: + case EVENT_BSSID_CHANGE: case CMD_DELAYED_WALLED_GARDEN_CHECK: case CMD_RSSI_FETCH: - case RSSI_FETCH_SUCCEEDED: - case RSSI_FETCH_FAILED: - //ignore + case RSSI_PKTCNT_FETCH_SUCCEEDED: + case RSSI_PKTCNT_FETCH_FAILED: + // ignore + break; + case EVENT_SCREEN_ON: + mIsScreenOn = true; + break; + case EVENT_SCREEN_OFF: + mIsScreenOn = false; break; default: - log("Unhandled message " + msg + " in state " + getCurrentState().getName()); + loge("Unhandled message " + msg + " in state " + getCurrentState().getName()); break; } return HANDLED; } } + /** + * WiFi watchdog is disabled by the setting. + */ class WatchdogDisabledState extends State { @Override public void enter() { - if (DBG) log(getName() + "\n"); + if (DBG) logd(getName()); } @Override @@ -527,8 +632,8 @@ public class WifiWatchdogStateMachine extends StateMachine { switch (networkInfo.getDetailedState()) { case VERIFYING_POOR_LINK: - if (DBG) log("Watchdog disabled, verify link"); - mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + if (DBG) logd("Watchdog disabled, verify link"); + sendLinkStatusNotification(true); break; default: break; @@ -539,41 +644,46 @@ public class WifiWatchdogStateMachine extends StateMachine { } } + /** + * WiFi watchdog is enabled by the setting. + */ class WatchdogEnabledState extends State { @Override public void enter() { - if (DBG) log("WifiWatchdogService enabled"); + if (DBG) logd(getName()); } @Override public boolean processMessage(Message msg) { + Intent intent; switch (msg.what) { case EVENT_WATCHDOG_TOGGLED: if (!isWatchdogEnabled()) transitionTo(mWatchdogDisabledState); break; + case EVENT_NETWORK_STATE_CHANGE: - Intent intent = (Intent) msg.obj; - NetworkInfo networkInfo = (NetworkInfo) - intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + intent = (Intent) msg.obj; + NetworkInfo networkInfo = + (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + if (DBG) logd("Network state change " + networkInfo.getDetailedState()); - if (DBG) log("network state change " + networkInfo.getDetailedState()); + mWifiInfo = (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO); + updateCurrentBssid(mWifiInfo != null ? mWifiInfo.getBSSID() : null); switch (networkInfo.getDetailedState()) { case VERIFYING_POOR_LINK: mLinkProperties = (LinkProperties) intent.getParcelableExtra( WifiManager.EXTRA_LINK_PROPERTIES); - mWifiInfo = (WifiInfo) intent.getParcelableExtra( - WifiManager.EXTRA_WIFI_INFO); if (mPoorNetworkDetectionEnabled) { if (mWifiInfo == null) { - log("Ignoring link verification, mWifiInfo is NULL"); - mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + if (DBG) logd("Ignoring link verification, mWifiInfo is NULL"); + sendLinkStatusNotification(true); } else { transitionTo(mVerifyingLinkState); } } else { - mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + sendLinkStatusNotification(true); } break; case CONNECTED: @@ -588,12 +698,22 @@ public class WifiWatchdogStateMachine extends StateMachine { break; } break; + + case EVENT_SUPPLICANT_STATE_CHANGE: + intent = (Intent) msg.obj; + SupplicantState supplicantState = (SupplicantState) intent.getParcelableExtra( + WifiManager.EXTRA_NEW_STATE); + if (supplicantState == SupplicantState.COMPLETED) { + mWifiInfo = mWifiManager.getConnectionInfo(); + updateCurrentBssid(mWifiInfo.getBSSID()); + } + break; + case EVENT_WIFI_RADIO_STATE_CHANGE: - if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) { - if (DBG) log("WifiStateDisabling -- Resetting WatchdogState"); + if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) transitionTo(mNotConnectedState); - } break; + default: return NOT_HANDLED; } @@ -601,36 +721,31 @@ public class WifiWatchdogStateMachine extends StateMachine { setWalledGardenNotificationVisible(false); return HANDLED; } - - @Override - public void exit() { - if (DBG) log("WifiWatchdogService disabled"); - } } + /** + * WiFi is disconnected. + */ class NotConnectedState extends State { @Override public void enter() { - if (DBG) log(getName() + "\n"); + if (DBG) logd(getName()); } } + /** + * WiFi is connected, but waiting for good link detection message. + */ class VerifyingLinkState extends State { + + private int mSampleCount; + @Override public void enter() { - if (DBG) log(getName() + "\n"); - //Treat entry as an rssi change - handleRssiChange(); - } - - private void handleRssiChange() { - if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) { - //stay here - if (DBG) log("enter VerifyingLinkState, stay level: " + mCurrentSignalLevel); - } else { - if (DBG) log("enter VerifyingLinkState, arp check level: " + mCurrentSignalLevel); - sendMessage(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0)); - } + if (DBG) logd(getName()); + mSampleCount = 0; + mCurrentBssid.newLinkDetected(); + sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0)); } @Override @@ -639,27 +754,51 @@ public class WifiWatchdogStateMachine extends StateMachine { case EVENT_WATCHDOG_SETTINGS_CHANGE: updateSettings(); if (!mPoorNetworkDetectionEnabled) { - mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + sendLinkStatusNotification(true); } break; - case EVENT_RSSI_CHANGE: - mCurrentSignalLevel = calculateSignalLevel(msg.arg1); - handleRssiChange(); + + case EVENT_BSSID_CHANGE: + transitionTo(mVerifyingLinkState); break; - case CMD_ARP_CHECK: - if (msg.arg1 == mArpToken) { - boolean success = ArpPeer.doArp(mWifiInfo.getMacAddress(), mLinkProperties, - mArpPingTimeoutMs, mNumArpPings, mMinArpResponses); - if (success) { - if (DBG) log("Notify link is good " + mCurrentSignalLevel); - mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + + case CMD_RSSI_FETCH: + if (msg.arg1 == mRssiFetchToken) { + mWsmChannel.sendMessage(RSSI_PKTCNT_FETCH, new RssiPktcntStat()); + sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0), + LINK_SAMPLING_INTERVAL_MS); + } + break; + + case RSSI_PKTCNT_FETCH_SUCCEEDED: + RssiPktcntStat stat = (RssiPktcntStat) msg.obj; + int rssi = stat.rssi; + if (DBG) logd("Fetch RSSI succeed, rssi=" + rssi); + + long time = mCurrentBssid.mBssidAvoidTimeMax - SystemClock.elapsedRealtime(); + if (time <= 0) { + // max avoidance time is met + if (DBG) logd("Max avoid time elapsed"); + sendLinkStatusNotification(true); + } else { + if (rssi >= mCurrentBssid.mGoodLinkTargetRssi) { + if (++mSampleCount >= mCurrentBssid.mGoodLinkTargetCount) { + // link is good again + if (DBG) logd("Good link detected, rssi=" + rssi); + mCurrentBssid.mBssidAvoidTimeMax = 0; + sendLinkStatusNotification(true); + } } else { - if (DBG) log("Continue ARP check, rssi level: " + mCurrentSignalLevel); - sendMessageDelayed(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0), - mArpCheckIntervalMs); + mSampleCount = 0; + if (DBG) logd("Link is still poor, time left=" + time); } } break; + + case RSSI_PKTCNT_FETCH_FAILED: + if (DBG) logd("RSSI_FETCH_FAILED"); + break; + default: return NOT_HANDLED; } @@ -667,19 +806,23 @@ public class WifiWatchdogStateMachine extends StateMachine { } } + /** + * WiFi is connected and link is verified. + */ class ConnectedState extends State { @Override public void enter() { - if (DBG) log(getName() + "\n"); + if (DBG) logd(getName()); } + @Override public boolean processMessage(Message msg) { switch (msg.what) { case EVENT_WATCHDOG_SETTINGS_CHANGE: updateSettings(); - //STOPSHIP: Remove this at ship + // STOPSHIP: Remove this at ship + logd("Updated secure settings and turned debug on"); DBG = true; - if (DBG) log("Updated secure settings and turned debug on"); if (mPoorNetworkDetectionEnabled) { transitionTo(mOnlineWatchState); @@ -692,11 +835,14 @@ public class WifiWatchdogStateMachine extends StateMachine { } } + /** + * Checking for wall garden. + */ class WalledGardenCheckState extends State { private int mWalledGardenToken = 0; @Override public void enter() { - if (DBG) log(getName() + "\n"); + if (DBG) logd(getName()); sendMessageDelayed(obtainMessage(CMD_DELAYED_WALLED_GARDEN_CHECK, ++mWalledGardenToken, 0), WALLED_GARDEN_START_DELAY_MS); } @@ -708,7 +854,7 @@ public class WifiWatchdogStateMachine extends StateMachine { if (msg.arg1 == mWalledGardenToken) { mLastWalledGardenCheckTime = SystemClock.elapsedRealtime(); if (isWalledGardenConnection()) { - if (DBG) log("Walled garden detected"); + if (DBG) logd("Walled garden detected"); setWalledGardenNotificationVisible(true); } transitionTo(mOnlineWatchState); @@ -721,11 +867,15 @@ public class WifiWatchdogStateMachine extends StateMachine { } } + /** + * RSSI is high enough and don't need link monitoring. + */ class OnlineWatchState extends State { + @Override public void enter() { - if (DBG) log(getName() + "\n"); + if (DBG) logd(getName()); if (mPoorNetworkDetectionEnabled) { - //Treat entry as an rssi change + // treat entry as an rssi change handleRssiChange(); } else { transitionTo(mOnlineState); @@ -733,10 +883,10 @@ public class WifiWatchdogStateMachine extends StateMachine { } private void handleRssiChange() { - if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) { - transitionTo(mRssiMonitoringState); + if (mCurrentSignalLevel <= LINK_MONITOR_LEVEL_THRESHOLD) { + transitionTo(mLinkMonitoringState); } else { - //stay here + // stay here } } @@ -745,16 +895,7 @@ public class WifiWatchdogStateMachine extends StateMachine { switch (msg.what) { case EVENT_RSSI_CHANGE: mCurrentSignalLevel = calculateSignalLevel(msg.arg1); - //Ready to avoid bssid again ? - long time = android.os.SystemClock.elapsedRealtime(); - if (time - mLastBssidAvoidedTime > MIN_INTERVAL_AVOID_BSSID_MS[ - mMinIntervalArrayIndex]) { - handleRssiChange(); - } else { - if (DBG) log("Early to avoid " + mWifiInfo + " time: " + time + - " last avoided: " + mLastBssidAvoidedTime + - " mMinIntervalArrayIndex: " + mMinIntervalArrayIndex); - } + handleRssiChange(); break; default: return NOT_HANDLED; @@ -763,48 +904,110 @@ public class WifiWatchdogStateMachine extends StateMachine { } } - class RssiMonitoringState extends State { + /** + * Keep sampling the link and monitor any poor link situation. + */ + class LinkMonitoringState extends State { + + private int mSampleCount; + + private int mLastRssi; + private int mLastTxGood; + private int mLastTxBad; + + @Override public void enter() { - if (DBG) log(getName() + "\n"); + if (DBG) logd(getName()); + mSampleCount = 0; + mCurrentLoss = new VolumeWeightedEMA(EXP_COEFFICIENT_MONITOR); sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0)); } + @Override public boolean processMessage(Message msg) { switch (msg.what) { case EVENT_RSSI_CHANGE: mCurrentSignalLevel = calculateSignalLevel(msg.arg1); - if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) { - //stay here; + if (mCurrentSignalLevel <= LINK_MONITOR_LEVEL_THRESHOLD) { + // stay here; } else { - //We dont need frequent RSSI monitoring any more + // we don't need frequent RSSI monitoring any more transitionTo(mOnlineWatchState); } break; + + case EVENT_BSSID_CHANGE: + transitionTo(mLinkMonitoringState); + break; + case CMD_RSSI_FETCH: - if (msg.arg1 == mRssiFetchToken) { - mWsmChannel.sendMessage(RSSI_FETCH); + if (!mIsScreenOn) { + transitionTo(mOnlineState); + } else if (msg.arg1 == mRssiFetchToken) { + mWsmChannel.sendMessage(RSSI_PKTCNT_FETCH, new RssiPktcntStat()); sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0), - mRssiFetchIntervalMs); + LINK_SAMPLING_INTERVAL_MS); } break; - case RSSI_FETCH_SUCCEEDED: - int rssi = msg.arg1; - if (DBG) log("RSSI_FETCH_SUCCEEDED: " + rssi); - if (msg.arg1 < RSSI_MONITOR_THRESHOLD) { - mRssiMonitorCount++; - } else { - mRssiMonitorCount = 0; - } - if (mRssiMonitorCount > RSSI_MONITOR_COUNT) { - sendPoorLinkDetected(); - ++mRssiFetchToken; + case RSSI_PKTCNT_FETCH_SUCCEEDED: + RssiPktcntStat stat = (RssiPktcntStat) msg.obj; + int rssi = stat.rssi; + int mrssi = (mLastRssi + rssi) / 2; + int txbad = stat.txbad; + int txgood = stat.txgood; + if (DBG) logd("Fetch RSSI succeed, rssi=" + rssi + " mrssi=" + mrssi + " txbad=" + + txbad + " txgood=" + txgood); + + // skip the first data point as we want incremental values + long now = SystemClock.elapsedRealtime(); + if (now - mCurrentBssid.mLastTimeSample < LINK_SAMPLING_INTERVAL_MS * 2) { + + // update packet loss statistics + int dbad = txbad - mLastTxBad; + int dgood = txgood - mLastTxGood; + int dtotal = dbad + dgood; + + if (dtotal > 0) { + // calculate packet loss in the last sampling interval + double loss = ((double) dbad) / ((double) dtotal); + + mCurrentLoss.update(loss, dtotal); + + if (DBG) { + DecimalFormat df = new DecimalFormat("#.##"); + logd("Incremental loss=" + dbad + "/" + dtotal + " Current loss=" + + df.format(mCurrentLoss.mValue * 100) + "% volume=" + + df.format(mCurrentLoss.mVolume)); + } + + mCurrentBssid.updateLoss(mrssi, loss, dtotal); + + // check for high packet loss and send poor link notification + if (mCurrentLoss.mValue > POOR_LINK_LOSS_THRESHOLD + && mCurrentLoss.mVolume > POOR_LINK_MIN_VOLUME) { + if (++mSampleCount >= POOR_LINK_SAMPLE_COUNT) + if (mCurrentBssid.poorLinkDetected(rssi)) { + sendLinkStatusNotification(false); + ++mRssiFetchToken; + } + } else { + mSampleCount = 0; + } + } } + + mCurrentBssid.mLastTimeSample = now; + mLastTxBad = txbad; + mLastTxGood = txgood; + mLastRssi = rssi; break; - case RSSI_FETCH_FAILED: - //can happen if we are waiting to get a disconnect notification - if (DBG) log("RSSI_FETCH_FAILED"); + + case RSSI_PKTCNT_FETCH_FAILED: + // can happen if we are waiting to get a disconnect notification + if (DBG) logd("RSSI_FETCH_FAILED"); break; + default: return NOT_HANDLED; } @@ -812,19 +1015,33 @@ public class WifiWatchdogStateMachine extends StateMachine { } } - /* Child state of ConnectedState indicating that we are online - * and there is nothing to do + /** + * Child state of ConnectedState indicating that we are online and there is nothing to do. */ class OnlineState extends State { @Override public void enter() { - if (DBG) log(getName() + "\n"); + if (DBG) logd(getName()); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case EVENT_SCREEN_ON: + mIsScreenOn = true; + if (mPoorNetworkDetectionEnabled) + transitionTo(mOnlineWatchState); + break; + default: + return NOT_HANDLED; + } + return HANDLED; } } private boolean shouldCheckWalledGarden() { if (!mWalledGardenTestEnabled) { - if (DBG) log("Skipping walled garden check - disabled"); + if (DBG) logd("Skipping walled garden check - disabled"); return false; } @@ -832,40 +1049,57 @@ public class WifiWatchdogStateMachine extends StateMachine { - SystemClock.elapsedRealtime(); if (mLastWalledGardenCheckTime != 0 && waitTime > 0) { - if (DBG) { - log("Skipping walled garden check - wait " + - waitTime + " ms."); - } + if (DBG) logd("Skipping walled garden check - wait " + waitTime + " ms."); return false; } return true; } + private void updateCurrentBssid(String bssid) { + if (DBG) logd("Update current BSSID to " + (bssid != null ? bssid : "null")); + + // if currently not connected, then set current BSSID to null + if (bssid == null) { + if (mCurrentBssid == null) return; + mCurrentBssid = null; + if (DBG) logd("BSSID changed"); + sendMessage(EVENT_BSSID_CHANGE); + return; + } + + // if it is already the current BSSID, then done + if (mCurrentBssid != null && bssid.equals(mCurrentBssid.mBssid)) return; + + // search for the new BSSID in the cache, add to cache if not found + mCurrentBssid = mBssidCache.get(bssid); + if (mCurrentBssid == null) { + mCurrentBssid = new BssidStatistics(bssid); + mBssidCache.put(bssid, mCurrentBssid); + } + + // send BSSID change notification + if (DBG) logd("BSSID changed"); + sendMessage(EVENT_BSSID_CHANGE); + } + private int calculateSignalLevel(int rssi) { - int signalLevel = WifiManager.calculateSignalLevel(rssi, - WifiManager.RSSI_LEVELS); - if (DBG) log("RSSI current: " + mCurrentSignalLevel + "new: " + rssi + ", " + signalLevel); + int signalLevel = WifiManager.calculateSignalLevel(rssi, WifiManager.RSSI_LEVELS); + if (DBG) + logd("RSSI current: " + mCurrentSignalLevel + " new: " + rssi + ", " + signalLevel); return signalLevel; } - private void sendPoorLinkDetected() { - if (DBG) log("send POOR_LINK_DETECTED " + mWifiInfo); - mWsmChannel.sendMessage(POOR_LINK_DETECTED); - - long time = android.os.SystemClock.elapsedRealtime(); - if (time - mLastBssidAvoidedTime > MIN_INTERVAL_AVOID_BSSID_MS[ - MIN_INTERVAL_AVOID_BSSID_MS.length - 1]) { - mMinIntervalArrayIndex = 1; - if (DBG) log("set mMinIntervalArrayIndex to 1"); + private void sendLinkStatusNotification(boolean isGood) { + if (DBG) logd("########################################"); + if (isGood) { + mWsmChannel.sendMessage(GOOD_LINK_DETECTED); + mCurrentBssid.mLastTimeGood = SystemClock.elapsedRealtime(); + logd("Good link notification is sent"); } else { - - if (mMinIntervalArrayIndex < MIN_INTERVAL_AVOID_BSSID_MS.length - 1) { - mMinIntervalArrayIndex++; - } - if (DBG) log("mMinIntervalArrayIndex: " + mMinIntervalArrayIndex); + mWsmChannel.sendMessage(POOR_LINK_DETECTED); + mCurrentBssid.mLastTimePoor = SystemClock.elapsedRealtime(); + logd("Poor link notification is sent"); } - - mLastBssidAvoidedTime = android.os.SystemClock.elapsedRealtime(); } /** @@ -884,30 +1118,28 @@ public class WifiWatchdogStateMachine extends StateMachine { } /** - * Convenience function for retrieving a single secure settings value - * as a boolean. Note that internally setting values are always - * stored as strings; this function converts the string to a boolean - * for you. The default value will be returned if the setting is - * not defined or not a valid boolean. + * Convenience function for retrieving a single secure settings value as a + * boolean. Note that internally setting values are always stored as + * strings; this function converts the string to a boolean for you. The + * default value will be returned if the setting is not defined or not a + * valid boolean. * * @param cr The ContentResolver to access. * @param name The name of the setting to retrieve. * @param def Value to return if the setting is not defined. - * - * @return The setting's current value, or 'def' if it is not defined - * or not a valid boolean. + * @return The setting's current value, or 'def' if it is not defined or not + * a valid boolean. */ private static boolean getSettingsBoolean(ContentResolver cr, String name, boolean def) { return Settings.Secure.getInt(cr, name, def ? 1 : 0) == 1; } /** - * Convenience function for updating a single settings value as an - * integer. This will either create a new entry in the table if the - * given name does not exist, or modify the value of the existing row - * with that name. Note that internally setting values are always - * stored as strings, so this function converts the given value to a - * string before storing it. + * Convenience function for updating a single settings value as an integer. + * This will either create a new entry in the table if the given name does + * not exist, or modify the value of the existing row with that name. Note + * that internally setting values are always stored as strings, so this + * function converts the given value to a string before storing it. * * @param cr The ContentResolver to access. * @param name The name of the setting to modify. @@ -918,11 +1150,267 @@ public class WifiWatchdogStateMachine extends StateMachine { return Settings.Secure.putInt(cr, name, value ? 1 : 0); } - private static void log(String s) { + private static void logd(String s) { Log.d(TAG, s); } private static void loge(String s) { Log.e(TAG, s); } + + /** + * Bundle of RSSI and packet count information + */ + public class RssiPktcntStat { + public int rssi; + public int txgood; + public int txbad; + } + + /** + * Bundle of good link count parameters + */ + private static class GoodLinkTarget { + public final int RSSI_ADJ_DBM; + public final int SAMPLE_COUNT; + public final int REDUCE_TIME_MS; + public GoodLinkTarget(int adj, int count, int time) { + RSSI_ADJ_DBM = adj; + SAMPLE_COUNT = count; + REDUCE_TIME_MS = time; + } + } + + /** + * Bundle of max avoidance time parameters + */ + private static class MaxAvoidTime { + public final int TIME_MS; + public final int MIN_RSSI_DBM; + public MaxAvoidTime(int time, int rssi) { + TIME_MS = time; + MIN_RSSI_DBM = rssi; + } + } + + /** + * Volume-weighted Exponential Moving Average (V-EMA) + * - volume-weighted: each update has its own weight (number of packets) + * - exponential: O(1) time and O(1) space for both update and query + * - moving average: reflect most recent results and expire old ones + */ + private class VolumeWeightedEMA { + private double mValue; + private double mVolume; + private double mProduct; + private final double mAlpha; + + public VolumeWeightedEMA(double coefficient) { + mValue = 0.0; + mVolume = 0.0; + mProduct = 0.0; + mAlpha = coefficient; + } + + public void update(double newValue, int newVolume) { + if (newVolume <= 0) return; + // core update formulas + double newProduct = newValue * newVolume; + mProduct = mAlpha * newProduct + (1 - mAlpha) * mProduct; + mVolume = mAlpha * newVolume + (1 - mAlpha) * mVolume; + mValue = mProduct / mVolume; + } + } + + /** + * Record (RSSI -> pakce loss %) mappings of one BSSID + */ + private class BssidStatistics { + + /* MAC address of this BSSID */ + private final String mBssid; + + /* RSSI -> packet loss % mappings */ + private VolumeWeightedEMA[] mEntries; + private int mRssiBase; + private int mEntriesSize; + + /* Target to send good link notification, set when poor link is detected */ + private int mGoodLinkTargetRssi; + private int mGoodLinkTargetCount; + + /* Index of GOOD_LINK_TARGET array */ + private int mGoodLinkTargetIndex; + + /* Timestamps of some last events */ + private long mLastTimeSample; + private long mLastTimeGood; + private long mLastTimePoor; + + /* Max time to avoid this BSSID */ + private long mBssidAvoidTimeMax; + + /** + * Constructor + * + * @param bssid is the address of this BSSID + */ + public BssidStatistics(String bssid) { + this.mBssid = bssid; + mRssiBase = BSSID_STAT_RANGE_LOW_DBM; + mEntriesSize = BSSID_STAT_RANGE_HIGH_DBM - BSSID_STAT_RANGE_LOW_DBM + 1; + mEntries = new VolumeWeightedEMA[mEntriesSize]; + for (int i = 0; i < mEntriesSize; i++) + mEntries[i] = new VolumeWeightedEMA(EXP_COEFFICIENT_RECORD); + } + + /** + * Update this BSSID cache + * + * @param rssi is the RSSI + * @param value is the new instant loss value at this RSSI + * @param volume is the volume for this single update + */ + public void updateLoss(int rssi, double value, int volume) { + if (volume <= 0) return; + int index = rssi - mRssiBase; + if (index < 0 || index >= mEntriesSize) return; + mEntries[index].update(value, volume); + if (DBG) { + DecimalFormat df = new DecimalFormat("#.##"); + logd("Cache updated: loss[" + rssi + "]=" + df.format(mEntries[index].mValue * 100) + + "% volume=" + df.format(mEntries[index].mVolume)); + } + } + + /** + * Get preset loss if the cache has insufficient data, observed from experiments. + * + * @param rssi is the input RSSI + * @return preset loss of the given RSSI + */ + public double presetLoss(int rssi) { + if (rssi <= -90) return 1.0; + if (rssi > 0) return 0.0; + + if (sPresetLoss == null) { + // pre-calculate all preset losses only once, then reuse them + final int size = 90; + sPresetLoss = new double[size]; + for (int i = 0; i < size; i++) sPresetLoss[i] = 1.0 / Math.pow(90 - i, 1.5); + } + return sPresetLoss[-rssi]; + } + + /** + * A poor link is detected, calculate a target RSSI to bring WiFi back. + * + * @param rssi is the current RSSI + * @return true iff the current BSSID should be avoided + */ + public boolean poorLinkDetected(int rssi) { + if (DBG) logd("Poor link detected, rssi=" + rssi); + + long now = SystemClock.elapsedRealtime(); + long lastGood = now - mLastTimeGood; + long lastPoor = now - mLastTimePoor; + + // reduce the difficulty of good link target if last avoidance was long time ago + while (mGoodLinkTargetIndex > 0 + && lastPoor >= GOOD_LINK_TARGET[mGoodLinkTargetIndex - 1].REDUCE_TIME_MS) + mGoodLinkTargetIndex--; + mGoodLinkTargetCount = GOOD_LINK_TARGET[mGoodLinkTargetIndex].SAMPLE_COUNT; + + // scan for a target RSSI at which the link is good + int from = rssi + GOOD_LINK_RSSI_RANGE_MIN; + int to = rssi + GOOD_LINK_RSSI_RANGE_MAX; + mGoodLinkTargetRssi = findRssiTarget(from, to, GOOD_LINK_LOSS_THRESHOLD); + mGoodLinkTargetRssi += GOOD_LINK_TARGET[mGoodLinkTargetIndex].RSSI_ADJ_DBM; + if (mGoodLinkTargetIndex < GOOD_LINK_TARGET.length - 1) mGoodLinkTargetIndex++; + + // calculate max avoidance time to prevent avoiding forever + int p = 0, pmax = MAX_AVOID_TIME.length - 1; + while (p < pmax && rssi >= MAX_AVOID_TIME[p + 1].MIN_RSSI_DBM) p++; + long avoidMax = MAX_AVOID_TIME[p].TIME_MS; + + // don't avoid if max avoidance time is 0 (RSSI is super high) + if (avoidMax <= 0) return false; + + // set max avoidance time, send poor link notification + mBssidAvoidTimeMax = now + avoidMax; + + if (DBG) logd("goodRssi=" + mGoodLinkTargetRssi + " goodCount=" + mGoodLinkTargetCount + + " lastGood=" + lastGood + " lastPoor=" + lastPoor + " avoidMax=" + avoidMax); + + return true; + } + + /** + * A new BSSID is connected, recalculate target RSSI threshold + */ + public void newLinkDetected() { + // if this BSSID is currently being avoided, the reuse those values + if (mBssidAvoidTimeMax > 0) { + if (DBG) logd("Previous avoidance still in effect, rssi=" + mGoodLinkTargetRssi + + " count=" + mGoodLinkTargetCount); + return; + } + + // calculate a new RSSI threshold for new link verifying + int from = BSSID_STAT_RANGE_LOW_DBM; + int to = BSSID_STAT_RANGE_HIGH_DBM; + mGoodLinkTargetRssi = findRssiTarget(from, to, GOOD_LINK_LOSS_THRESHOLD); + mGoodLinkTargetCount = 1; + mBssidAvoidTimeMax = SystemClock.elapsedRealtime() + MAX_AVOID_TIME[0].TIME_MS; + if (DBG) logd("New link verifying target set, rssi=" + mGoodLinkTargetRssi + " count=" + + mGoodLinkTargetCount); + } + + /** + * Return the first RSSI within the range where loss[rssi] < threshold + * + * @param from start scanning from this RSSI + * @param to stop scanning at this RSSI + * @param threshold target threshold for scanning + * @return target RSSI + */ + public int findRssiTarget(int from, int to, double threshold) { + from -= mRssiBase; + to -= mRssiBase; + int emptyCount = 0; + int d = from < to ? 1 : -1; + for (int i = from; i != to; i += d) + // don't use a data point if it volume is too small (statistically unreliable) + if (i >= 0 && i < mEntriesSize && mEntries[i].mVolume > 1.0) { + emptyCount = 0; + if (mEntries[i].mValue < threshold) { + // scan target found + int rssi = mRssiBase + i; + if (DBG) { + DecimalFormat df = new DecimalFormat("#.##"); + logd("Scan target found: rssi=" + rssi + " threshold=" + + df.format(threshold * 100) + "% value=" + + df.format(mEntries[i].mValue * 100) + "% volume=" + + df.format(mEntries[i].mVolume)); + } + return rssi; + } + } else if (++emptyCount >= BSSID_STAT_EMPTY_COUNT) { + // cache has insufficient data around this RSSI, use preset loss instead + int rssi = mRssiBase + i; + double lossPreset = presetLoss(rssi); + if (lossPreset < threshold) { + if (DBG) { + DecimalFormat df = new DecimalFormat("#.##"); + logd("Scan target found: rssi=" + rssi + " threshold=" + + df.format(threshold * 100) + "% value=" + + df.format(lossPreset * 100) + "% volume=preset"); + } + return rssi; + } + } + + return mRssiBase + to; + } + } } |