summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt1
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java2
-rw-r--r--core/java/android/net/ConnectivityManager.java10
-rw-r--r--core/java/android/view/HardwareRenderer.java14
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java2
-rwxr-xr-xcore/res/res/values/attrs.xml1
-rw-r--r--docs/downloads/README9
-rw-r--r--docs/downloads/design/Android_Design_Color_Swatches_20120229.zipbin0 -> 2255 bytes
-rw-r--r--docs/downloads/design/Android_Design_Downloads_20120229.zipbin0 -> 34136660 bytes
-rw-r--r--docs/downloads/design/Android_Design_Fireworks_Stencil_20120229.pngbin0 -> 3301154 bytes
-rw-r--r--docs/downloads/design/Android_Design_Holo_Widgets_20120302.zipbin0 -> 1100021 bytes
-rw-r--r--docs/downloads/design/Android_Design_Icons_20120229.zipbin0 -> 2324251 bytes
-rw-r--r--docs/downloads/design/Android_Design_OmniGraffle_Stencil_20120229.grafflebin0 -> 26314580 bytes
-rw-r--r--docs/downloads/design/Roboto_Hinted_20111129.zipbin0 -> 1505810 bytes
-rw-r--r--docs/downloads/design/Roboto_Specimen_Book_20111129.pdfbin0 -> 317985 bytes
-rw-r--r--docs/downloads/training/ActivityLifecycle.zipbin0 -> 251212 bytes
-rw-r--r--docs/downloads/training/BitmapFun.zipbin0 -> 315657 bytes
-rw-r--r--docs/downloads/training/CustomView.zipbin0 -> 40248 bytes
-rw-r--r--docs/downloads/training/DeviceManagement.zipbin0 -> 19978 bytes
-rw-r--r--docs/downloads/training/EffectiveNavigation.zipbin0 -> 29897 bytes
-rw-r--r--docs/downloads/training/FragmentBasics.zipbin0 -> 283814 bytes
-rw-r--r--docs/downloads/training/LocationAware.zipbin0 -> 261408 bytes
-rw-r--r--docs/downloads/training/MobileAds.zipbin0 -> 42183 bytes
-rw-r--r--docs/downloads/training/NetworkUsage.zipbin0 -> 24270 bytes
-rw-r--r--docs/downloads/training/NewsReader.zipbin0 -> 334913 bytes
-rw-r--r--docs/downloads/training/OpenGLES.zipbin0 -> 27980 bytes
-rw-r--r--docs/downloads/training/PhotoIntentActivity.zipbin0 -> 15261 bytes
-rw-r--r--docs/downloads/training/TabCompat.zipbin0 -> 579116 bytes
-rw-r--r--docs/html/about/start.jd4
-rw-r--r--docs/html/distribute/googleplay/promote/brand.jd4
-rw-r--r--docs/html/guide/topics/providers/content-provider-basics.jd10
-rw-r--r--docs/html/tools/samples/index.jd15
-rw-r--r--docs/html/training/basics/fragments/communicating.jd2
-rw-r--r--graphics/java/android/graphics/Bitmap.java10
-rw-r--r--libs/hwui/Caches.cpp15
-rw-r--r--libs/hwui/Caches.h2
-rw-r--r--libs/hwui/LayerRenderer.cpp10
-rw-r--r--libs/hwui/OpenGLRenderer.cpp76
-rw-r--r--libs/hwui/Patch.cpp4
-rw-r--r--libs/hwui/Properties.h14
-rw-r--r--services/java/com/android/server/ConnectivityService.java3
-rw-r--r--tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java14
-rw-r--r--tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/Vignette.java46
-rw-r--r--tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/vignette_approx.rsh60
-rw-r--r--tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/vignette_approx_full.rs21
-rw-r--r--tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/vignette_approx_relaxed.rs22
-rw-r--r--wifi/java/android/net/wifi/WifiNative.java8
-rw-r--r--wifi/java/android/net/wifi/WifiStateMachine.java40
-rw-r--r--wifi/java/android/net/wifi/WifiWatchdogStateMachine.java1116
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> &lt;service android:name=".MyAccessibilityService"
- * android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE&gt;
+ * android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"&gt;
* &lt;intent-filter&gt;
* &lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;
* &lt;/intent-filter&gt;
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
new file mode 100644
index 000000000000..81b65228ce02
--- /dev/null
+++ b/docs/downloads/design/Android_Design_Color_Swatches_20120229.zip
Binary files differ
diff --git a/docs/downloads/design/Android_Design_Downloads_20120229.zip b/docs/downloads/design/Android_Design_Downloads_20120229.zip
new file mode 100644
index 000000000000..7bbd85f7b317
--- /dev/null
+++ b/docs/downloads/design/Android_Design_Downloads_20120229.zip
Binary files differ
diff --git a/docs/downloads/design/Android_Design_Fireworks_Stencil_20120229.png b/docs/downloads/design/Android_Design_Fireworks_Stencil_20120229.png
new file mode 100644
index 000000000000..51363799882c
--- /dev/null
+++ b/docs/downloads/design/Android_Design_Fireworks_Stencil_20120229.png
Binary files differ
diff --git a/docs/downloads/design/Android_Design_Holo_Widgets_20120302.zip b/docs/downloads/design/Android_Design_Holo_Widgets_20120302.zip
new file mode 100644
index 000000000000..90c45c6ea781
--- /dev/null
+++ b/docs/downloads/design/Android_Design_Holo_Widgets_20120302.zip
Binary files differ
diff --git a/docs/downloads/design/Android_Design_Icons_20120229.zip b/docs/downloads/design/Android_Design_Icons_20120229.zip
new file mode 100644
index 000000000000..c2728f3fb259
--- /dev/null
+++ b/docs/downloads/design/Android_Design_Icons_20120229.zip
Binary files differ
diff --git a/docs/downloads/design/Android_Design_OmniGraffle_Stencil_20120229.graffle b/docs/downloads/design/Android_Design_OmniGraffle_Stencil_20120229.graffle
new file mode 100644
index 000000000000..9e418d38f0fd
--- /dev/null
+++ b/docs/downloads/design/Android_Design_OmniGraffle_Stencil_20120229.graffle
Binary files differ
diff --git a/docs/downloads/design/Roboto_Hinted_20111129.zip b/docs/downloads/design/Roboto_Hinted_20111129.zip
new file mode 100644
index 000000000000..abf4942a5dad
--- /dev/null
+++ b/docs/downloads/design/Roboto_Hinted_20111129.zip
Binary files differ
diff --git a/docs/downloads/design/Roboto_Specimen_Book_20111129.pdf b/docs/downloads/design/Roboto_Specimen_Book_20111129.pdf
new file mode 100644
index 000000000000..594a3661f48f
--- /dev/null
+++ b/docs/downloads/design/Roboto_Specimen_Book_20111129.pdf
Binary files differ
diff --git a/docs/downloads/training/ActivityLifecycle.zip b/docs/downloads/training/ActivityLifecycle.zip
new file mode 100644
index 000000000000..1cbed448b2bb
--- /dev/null
+++ b/docs/downloads/training/ActivityLifecycle.zip
Binary files differ
diff --git a/docs/downloads/training/BitmapFun.zip b/docs/downloads/training/BitmapFun.zip
new file mode 100644
index 000000000000..e7e71f9f85a0
--- /dev/null
+++ b/docs/downloads/training/BitmapFun.zip
Binary files differ
diff --git a/docs/downloads/training/CustomView.zip b/docs/downloads/training/CustomView.zip
new file mode 100644
index 000000000000..d7ae8a2268c7
--- /dev/null
+++ b/docs/downloads/training/CustomView.zip
Binary files differ
diff --git a/docs/downloads/training/DeviceManagement.zip b/docs/downloads/training/DeviceManagement.zip
new file mode 100644
index 000000000000..9f7ec694af47
--- /dev/null
+++ b/docs/downloads/training/DeviceManagement.zip
Binary files differ
diff --git a/docs/downloads/training/EffectiveNavigation.zip b/docs/downloads/training/EffectiveNavigation.zip
new file mode 100644
index 000000000000..f21af451f39e
--- /dev/null
+++ b/docs/downloads/training/EffectiveNavigation.zip
Binary files differ
diff --git a/docs/downloads/training/FragmentBasics.zip b/docs/downloads/training/FragmentBasics.zip
new file mode 100644
index 000000000000..b5b19d03ba5a
--- /dev/null
+++ b/docs/downloads/training/FragmentBasics.zip
Binary files differ
diff --git a/docs/downloads/training/LocationAware.zip b/docs/downloads/training/LocationAware.zip
new file mode 100644
index 000000000000..8ed97cbff32a
--- /dev/null
+++ b/docs/downloads/training/LocationAware.zip
Binary files differ
diff --git a/docs/downloads/training/MobileAds.zip b/docs/downloads/training/MobileAds.zip
new file mode 100644
index 000000000000..468e4ee72511
--- /dev/null
+++ b/docs/downloads/training/MobileAds.zip
Binary files differ
diff --git a/docs/downloads/training/NetworkUsage.zip b/docs/downloads/training/NetworkUsage.zip
new file mode 100644
index 000000000000..8c7fbef9fb49
--- /dev/null
+++ b/docs/downloads/training/NetworkUsage.zip
Binary files differ
diff --git a/docs/downloads/training/NewsReader.zip b/docs/downloads/training/NewsReader.zip
new file mode 100644
index 000000000000..7dda41c7c273
--- /dev/null
+++ b/docs/downloads/training/NewsReader.zip
Binary files differ
diff --git a/docs/downloads/training/OpenGLES.zip b/docs/downloads/training/OpenGLES.zip
new file mode 100644
index 000000000000..862ae1f87984
--- /dev/null
+++ b/docs/downloads/training/OpenGLES.zip
Binary files differ
diff --git a/docs/downloads/training/PhotoIntentActivity.zip b/docs/downloads/training/PhotoIntentActivity.zip
new file mode 100644
index 000000000000..9fcc0e19a7c8
--- /dev/null
+++ b/docs/downloads/training/PhotoIntentActivity.zip
Binary files differ
diff --git a/docs/downloads/training/TabCompat.zip b/docs/downloads/training/TabCompat.zip
new file mode 100644
index 000000000000..b70b44212581
--- /dev/null
+++ b/docs/downloads/training/TabCompat.zip
Binary files differ
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">&lt;provider&gt;</a>}
+ attribute of the <a href="{@docRoot}guide/topics/manifest/provider-element.html">
+ {@code &lt;provider&gt;}</a>
element, as well as the
- {@code <a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html">
- &lt;grant-uri-permission&gt;</a>} child element of the
- {@code <a href="{@docRoot}guide/topics/manifest/provider-element.html">&lt;provider&gt;</a>}
+ <a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html">{@code
+ &lt;grant-uri-permission&gt;}</a> child element of the
+ <a href="{@docRoot}guide/topics/manifest/provider-element.html">{@code &lt;provider&gt;}</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>&lt;sdk&gt;</em>/platforms/&lt;android-version>/samples/</code>
+<code>&lt;sdk&gt;/samples/android-&lt;version>/</code>
</p>
+<p>The {@code &lt;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;
+ }
+ }
}