Merge "Import revised translations."
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index d25de97..12a4dbb 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -235,7 +235,7 @@
PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1);
PropertyValuesHolder pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1);
PropertyValuesHolder pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1);
- defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder(this,
+ defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder((Object)null,
pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY);
defaultChangeIn.setDuration(DEFAULT_DURATION);
defaultChangeIn.setStartDelay(mChangingAppearingDelay);
@@ -244,11 +244,11 @@
defaultChangeOut.setStartDelay(mChangingDisappearingDelay);
defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator);
- defaultFadeIn = ObjectAnimator.ofFloat(this, "alpha", 0f, 1f);
+ defaultFadeIn = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
defaultFadeIn.setDuration(DEFAULT_DURATION);
defaultFadeIn.setStartDelay(mAppearingDelay);
defaultFadeIn.setInterpolator(mAppearingInterpolator);
- defaultFadeOut = ObjectAnimator.ofFloat(this, "alpha", 1f, 0f);
+ defaultFadeOut = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
defaultFadeOut.setDuration(DEFAULT_DURATION);
defaultFadeOut.setStartDelay(mDisappearingDelay);
defaultFadeOut.setInterpolator(mDisappearingInterpolator);
diff --git a/core/java/android/preference/SeekBarPreference.java b/core/java/android/preference/SeekBarPreference.java
index b8919c2..7133d3a 100644
--- a/core/java/android/preference/SeekBarPreference.java
+++ b/core/java/android/preference/SeekBarPreference.java
@@ -77,6 +77,11 @@
}
@Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ return a.getInt(index, 0);
+ }
+
+ @Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() != KeyEvent.ACTION_UP) {
if (keyCode == KeyEvent.KEYCODE_PLUS
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 761007f..d584acd 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1783,6 +1783,20 @@
}
/**
+ * @hide
+ */
+ public void setProperty(String key, String value) {
+ mWebView.nativeSetProperty(key, value);
+ }
+
+ /**
+ * @hide
+ */
+ public String getProperty(String key) {
+ return mWebView.nativeGetProperty(key);
+ }
+
+ /**
* Transfer messages from the queue to the new WebCoreThread. Called from
* WebCore thread.
*/
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 3ae10fe..b22c57b0 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -9289,4 +9289,6 @@
*/
private native boolean nativeScrollLayer(int layer, int newX, int newY);
private native int nativeGetBackgroundColor();
+ native void nativeSetProperty(String key, String value);
+ native String nativeGetProperty(String key);
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index ecbd997..5a5fae4 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6586,11 +6586,11 @@
scrollx = left;
}
}
- } else if (a == Layout.Alignment.ALIGN_LEFT) {
- scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
- } else { // a == Layout.Alignment.ALIGN_RIGHT
+ } else if (a == Layout.Alignment.ALIGN_RIGHT) {
int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
scrollx = right - hspace;
+ } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
+ scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line));
}
if (ht < vspace) {
diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java
index b754d94..69b80d9 100644
--- a/core/java/com/android/internal/util/Protocol.java
+++ b/core/java/com/android/internal/util/Protocol.java
@@ -40,6 +40,7 @@
/** Non system protocols */
public static final int BASE_WIFI = 0x00020000;
+ public static final int BASE_WIFI_WATCHDOG = 0x00021000;
public static final int BASE_DHCP = 0x00030000;
public static final int BASE_DATA_CONNECTION = 0x00040000;
public static final int BASE_DATA_CONNECTION_AC = 0x00041000;
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 58e7c8d..06dc083 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -145,7 +145,6 @@
android_server_Watchdog.cpp \
android_ddm_DdmHandleNativeHeap.cpp \
com_android_internal_os_ZygoteInit.cpp \
- com_android_internal_graphics_NativeUtils.cpp \
android_backup_BackupDataInput.cpp \
android_backup_BackupDataOutput.cpp \
android_backup_FileBackupHelperBase.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 23c6da7..08431cd 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -115,7 +115,6 @@
extern int register_android_graphics_SurfaceTexture(JNIEnv* env);
extern int register_android_graphics_Xfermode(JNIEnv* env);
extern int register_android_graphics_PixelFormat(JNIEnv* env);
-extern int register_com_android_internal_graphics_NativeUtils(JNIEnv *env);
extern int register_android_view_Display(JNIEnv* env);
extern int register_android_view_GLES20Canvas(JNIEnv* env);
extern int register_android_view_Surface(JNIEnv* env);
@@ -1147,7 +1146,6 @@
REG_JNI(register_android_graphics_Typeface),
REG_JNI(register_android_graphics_Xfermode),
REG_JNI(register_android_graphics_YuvImage),
- REG_JNI(register_com_android_internal_graphics_NativeUtils),
REG_JNI(register_android_database_CursorWindow),
REG_JNI(register_android_database_SQLiteCompiledSql),
diff --git a/core/jni/com_android_internal_graphics_NativeUtils.cpp b/core/jni/com_android_internal_graphics_NativeUtils.cpp
deleted file mode 100644
index 9cc43606..0000000
--- a/core/jni/com_android_internal_graphics_NativeUtils.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "AWT"
-
-#include "jni.h"
-#include "JNIHelp.h"
-#include "GraphicsJNI.h"
-#include <android_runtime/AndroidRuntime.h>
-
-#include "SkCanvas.h"
-#include "SkDevice.h"
-#include "SkPicture.h"
-#include "SkTemplates.h"
-
-namespace android
-{
-
-static jboolean scrollRect(JNIEnv* env, jobject graphics2D, jobject canvas, jobject rect, int dx, int dy) {
- if (canvas == NULL) {
- jniThrowNullPointerException(env, NULL);
- return false;
- }
-
- SkIRect src, *srcPtr = NULL;
- if (NULL != rect) {
- GraphicsJNI::jrect_to_irect(env, rect, &src);
- srcPtr = &src;
- }
- SkCanvas* c = GraphicsJNI::getNativeCanvas(env, canvas);
- const SkBitmap& bitmap = c->getDevice()->accessBitmap(true);
- return bitmap.scrollRect(srcPtr, dx, dy, NULL);
-}
-
-static JNINativeMethod method_table[] = {
- { "nativeScrollRect",
- "(Landroid/graphics/Canvas;Landroid/graphics/Rect;II)Z",
- (void*)scrollRect}
-};
-
-int register_com_android_internal_graphics_NativeUtils(JNIEnv *env) {
- return AndroidRuntime::registerNativeMethods(
- env, "com/android/internal/graphics/NativeUtils",
- method_table, NELEM(method_table));
-}
-
-}
diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml
new file mode 100644
index 0000000..c0d9153
--- /dev/null
+++ b/data/fonts/fallback_fonts.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Fallback Fonts
+
+ This file specifies the fonts, and the priority order, that will be searched for any
+ glyphs not handled by the default fonts specified in /system/etc/system_fonts.xml.
+ Each entry consists of a family tag and a list of files (file names) which support that
+ family. The fonts for each family are listed in the order of the styles that they
+ handle (the order is: regular, bold, italic, and bold-italic). The order in which the
+ families are listed in this file represents the order in which these fallback fonts
+ will be searched for glyphs that are not supported by the default system fonts (which are
+ found in /system/etc/system_fonts.xml).
+
+ Note that there is not nameset for fallback fonts, unlike the fonts specified in
+ system_fonts.xml. The ability to support specific names in fallback fonts may be supported
+ in the future. For now, the lack of files entries here is an indicator to the system that
+ these are fallback fonts, instead of default named system fonts.
+
+ There is another optional file in /vendor/etc/fallback_fonts.xml. That file can be used to
+ provide references to other font families that should be used in addition to the default
+ fallback fonts. That file can also specify the order in which the fallback fonts should be
+ searched, to ensure that a vendor-provided font will be used before another fallback font
+ which happens to handle the same glyph.
+-->
+<familyset>
+ <family>
+ <fileset>
+ <file>DroidSansArabic.ttf</file>
+ </fileset>
+ </family>
+ <family>
+ <fileset>
+ <file>DroidSansHebrew-Regular.ttf</file>
+ <file>DroidSansHebrew-Bold.ttf</file>
+ </fileset>
+ </family>
+ <family>
+ <fileset>
+ <file>DroidSansThai.ttf</file>
+ </fileset>
+ </family>
+ <family>
+ <fileset>
+ <file>DroidSansFallback.ttf</file>
+ </fileset>
+ </family>
+</familyset>
diff --git a/data/fonts/fonts.mk b/data/fonts/fonts.mk
index d222c0b..57a1bab 100644
--- a/data/fonts/fonts.mk
+++ b/data/fonts/fonts.mk
@@ -30,4 +30,6 @@
frameworks/base/data/fonts/DroidSansFallback.ttf:system/fonts/DroidSansFallback.ttf \
frameworks/base/data/fonts/AndroidClock.ttf:system/fonts/AndroidClock.ttf \
frameworks/base/data/fonts/AndroidClock_Highlight.ttf:system/fonts/AndroidClock_Highlight.ttf \
- frameworks/base/data/fonts/AndroidClock_Solid.ttf:system/fonts/AndroidClock_Solid.ttf
+ frameworks/base/data/fonts/AndroidClock_Solid.ttf:system/fonts/AndroidClock_Solid.ttf \
+ frameworks/base/data/fonts/system_fonts.xml:system/etc/system_fonts.xml \
+ frameworks/base/data/fonts/fallback_fonts.xml:system/etc/fallback_fonts.xml
diff --git a/data/fonts/system_fonts.xml b/data/fonts/system_fonts.xml
new file mode 100644
index 0000000..8d8d020
--- /dev/null
+++ b/data/fonts/system_fonts.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ System Fonts
+
+ This file lists the font families that will be used by default for all supported glyphs.
+ Each entry consists of a family, various names that are supported by that family, and
+ up to four font files. The font files are listed in the order of the styles which they
+ support: regular, bold, italic and bold-italic. If less than four styles are listed, then
+ the styles with no associated font file will be supported by the other font files listed.
+
+ The first family is also the default font, which handles font request that have not specified
+ specific font names.
+
+ Any glyph that is not handled by the system fonts will cause a search of the fallback fonts.
+ The default fallback fonts are specified in the file /system/etc/fallback_fonts.xml, and there
+ is an optional file which may be supplied by vendors to specify other fallback fonts to use
+ in /vendor/etc/fallback_fonts.xml.
+-->
+<familyset>
+
+ <family>
+ <nameset>
+ <name>sans-serif</name>
+ <name>arial</name>
+ <name>helvetica</name>
+ <name>tahoma</name>
+ <name>verdana</name>
+ </nameset>
+ <fileset>
+ <file>DroidSans.ttf</file>
+ <file>DroidSans-Bold.ttf</file>
+ </fileset>
+ </family>
+
+ <family>
+ <nameset>
+ <name>serif</name>
+ <name>times</name>
+ <name>times new roman</name>
+ <name>palatino</name>
+ <name>georgia</name>
+ <name>baskerville</name>
+ <name>goudy</name>
+ <name>fantasy</name>
+ <name>cursive</name>
+ <name>ITC Stone Serif</name>
+ </nameset>
+ <fileset>
+ <file>DroidSerif-Regular.ttf</file>
+ <file>DroidSerif-Bold.ttf</file>
+ <file>DroidSerif-Italic.ttf</file>
+ <file>DroidSerif-BoldItalic.ttf</file>
+ </fileset>
+ </family>
+
+ <family>
+ <nameset>
+ <name>monospace</name>
+ <name>courier</name>
+ <name>courier new</name>
+ <name>monaco</name>
+ </nameset>
+ <fileset>
+ <file>DroidSansMono.ttf</file>
+ </fileset>
+ </family>
+
+</familyset>
diff --git a/data/fonts/vendor_fonts.xml b/data/fonts/vendor_fonts.xml
new file mode 100644
index 0000000..fe51fd27
--- /dev/null
+++ b/data/fonts/vendor_fonts.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Vendor-provided fallback fonts
+
+ This file can be edited to add references to fonts that are not installed or referenced in the
+ default system. The file should then be placed in /vendor/etc/fallback_fonts.xml.
+
+ For example, vendors might want to build configurations for locales that are
+ better served by fonts which either handle glyphs not supported in the default fonts or which
+ handle these glyphs differently than the default fallback fonts.
+ Each entry in this list is a "family", which consists of a list of "files"
+ (the filenames for that family). The files objects are
+ provided in the order of the styles supported for that family: regular, bold, italic, and
+ bold-italic. Only providing one font means that all styles will be rendered with that font.
+ Providing two means that these two fonts will render regular and bold fonts (italics will
+ be mapped to these two fonts).
+
+ There is also an optional "order" attribute on the Family tag. This specifies the index at
+ which that family of fonts should be inserted in the fallback font list, where the
+ default fallback fonts on the system (in /system/etc/fallback_fonts.xml) start at index 0.
+ If no 'order' attribute is supplied, that family will be inserted either at the end of the
+ current fallback list (if no order was supplied for any previous family in this file) or
+ after the previous family (if there was an order specified previously). Typically, vendors
+ may want to supply an order for the first family that puts this set of fonts at the appropriate
+ place in the overall fallback fonts. The order of this list determines which fallback font
+ will be used to support any glyphs that are not handled by the default system fonts.
+
+ The sample configuration below is an example of how one might provide two families of fonts
+ that get inserted at the first and second (0 and 1) position in the overall fallback fonts.
+
+ See /system/etc/system_fonts.xml and /system/etc/fallback_fonts.xml for more information
+ and to understand the order in which the default system fonts are loaded and structured for
+ lookup.
+-->
+
+<!-- Sample fallback font additions to the default fallback list. These fonts will be added
+ to the top two positions of the fallback list, since the first has an order of 0. -->
+<!--
+<familyset>
+ <family order="0">
+ <fileset>
+ <file>MyFont.ttf</file>
+ </fileset>
+ </family>
+ <family>
+ <fileset>
+ <file>MyOtherFont.ttf</file>
+ </fileset>
+ </family>
+</familyset>
+-->
\ No newline at end of file
diff --git a/graphics/java/com/android/internal/graphics/NativeUtils.java b/graphics/java/com/android/internal/graphics/NativeUtils.java
deleted file mode 100644
index c91b7d9..0000000
--- a/graphics/java/com/android/internal/graphics/NativeUtils.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.graphics;
-
-import android.graphics.Canvas;
-import android.graphics.Rect;
-
-public final class NativeUtils {
- /**
- * This class is uninstantiable.
- */
- private NativeUtils() {
- // This space intentionally left blank.
- }
-
- /**
- * Scroll a rectangular portion of a canvas.
- *
- * @param canvas the canvas to manipulate
- * @param src the source rectangle
- * @param dx horizontal offset
- * @param dy vertical offset
- */
- public static native boolean nativeScrollRect(Canvas canvas, Rect src,
- int dx, int dy);
-}
diff --git a/include/cpustats/CentralTendencyStatistics.h b/include/cpustats/CentralTendencyStatistics.h
new file mode 100644
index 0000000..21b6981
--- /dev/null
+++ b/include/cpustats/CentralTendencyStatistics.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _CENTRAL_TENDENCY_STATISTICS_H
+#define _CENTRAL_TENDENCY_STATISTICS_H
+
+#include <math.h>
+
+// Not multithread safe
+class CentralTendencyStatistics {
+
+public:
+
+ CentralTendencyStatistics() :
+ mMean(NAN), mMedian(NAN), mMinimum(INFINITY), mMaximum(-INFINITY), mN(0), mM2(0),
+ mVariance(NAN), mVarianceKnownForN(0), mStddev(NAN), mStddevKnownForN(0) { }
+
+ ~CentralTendencyStatistics() { }
+
+ // add x to the set of samples
+ void sample(double x);
+
+ // return the arithmetic mean of all samples so far
+ double mean() const { return mMean; }
+
+ // return the minimum of all samples so far
+ double minimum() const { return mMinimum; }
+
+ // return the maximum of all samples so far
+ double maximum() const { return mMaximum; }
+
+ // return the variance of all samples so far
+ double variance() const;
+
+ // return the standard deviation of all samples so far
+ double stddev() const;
+
+ // return the number of samples added so far
+ unsigned n() const { return mN; }
+
+ // reset the set of samples to be empty
+ void reset();
+
+private:
+ double mMean;
+ double mMedian;
+ double mMinimum;
+ double mMaximum;
+ unsigned mN; // number of samples so far
+ double mM2;
+
+ // cached variance, and n at time of caching
+ mutable double mVariance;
+ mutable unsigned mVarianceKnownForN;
+
+ // cached standard deviation, and n at time of caching
+ mutable double mStddev;
+ mutable unsigned mStddevKnownForN;
+
+};
+
+#endif // _CENTRAL_TENDENCY_STATISTICS_H
diff --git a/include/cpustats/README.txt b/include/cpustats/README.txt
new file mode 100644
index 0000000..14439f0
--- /dev/null
+++ b/include/cpustats/README.txt
@@ -0,0 +1,6 @@
+This is a static library of CPU usage statistics, originally written
+for audio but most are not actually specific to audio.
+
+Requirements to be here:
+ * should be related to CPU usage statistics
+ * should be portable to host; avoid Android OS dependencies without a conditional
diff --git a/include/cpustats/ThreadCpuUsage.h b/include/cpustats/ThreadCpuUsage.h
new file mode 100644
index 0000000..24012a4
--- /dev/null
+++ b/include/cpustats/ThreadCpuUsage.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _THREAD_CPU_USAGE_H
+#define _THREAD_CPU_USAGE_H
+
+#include <cpustats/CentralTendencyStatistics.h>
+
+// Track CPU usage for the current thread, and maintain statistics on
+// the CPU usage. Units are in per-thread CPU ns, as reported by
+// clock_gettime(CLOCK_THREAD_CPUTIME_ID). Simple usage: for cyclic
+// threads where you want to measure the execution time of the whole
+// cycle, just call sampleAndEnable() at the start of each cycle.
+// Then call statistics() to get the results, and resetStatistics()
+// to start a new set of measurements.
+// For acyclic threads, or for cyclic threads where you want to measure
+// only part of each cycle, call enable(), disable(), and/or setEnabled()
+// to demarcate the region(s) of interest, and then call sample() periodically.
+// This class is not thread-safe for concurrent calls from multiple threads;
+// the methods of this class may only be called by the current thread
+// which constructed the object.
+
+class ThreadCpuUsage
+{
+
+public:
+ ThreadCpuUsage() :
+ mIsEnabled(false),
+ mWasEverEnabled(false),
+ mAccumulator(0),
+ // mPreviousTs
+ // mMonotonicTs
+ mMonotonicKnown(false)
+ // mStatistics
+ { }
+
+ ~ThreadCpuUsage() { }
+
+ // Return whether currently tracking CPU usage by current thread
+ bool isEnabled() { return mIsEnabled; }
+
+ // Enable tracking of CPU usage by current thread;
+ // any CPU used from this point forward will be tracked.
+ // Returns the previous enabled status.
+ bool enable() { return setEnabled(true); }
+
+ // Disable tracking of CPU usage by current thread;
+ // any CPU used from this point forward will be ignored.
+ // Returns the previous enabled status.
+ bool disable() { return setEnabled(false); }
+
+ // Set the enabled status and return the previous enabled status.
+ // This method is intended to be used for safe nested enable/disabling.
+ bool setEnabled(bool isEnabled);
+
+ // Add a sample point for central tendency statistics, and also
+ // enable tracking if needed. If tracking has never been enabled, then
+ // enables tracking but does not add a sample (it is not possible to add
+ // a sample the first time because no previous). Otherwise if tracking is
+ // enabled, then adds a sample for tracked CPU ns since the previous
+ // sample, or since the first call to sampleAndEnable(), enable(), or
+ // setEnabled(true). If there was a previous sample but tracking is
+ // now disabled, then adds a sample for the tracked CPU ns accumulated
+ // up until the most recent disable(), resets this accumulator, and then
+ // enables tracking. Calling this method rather than enable() followed
+ // by sample() avoids a race condition for the first sample.
+ void sampleAndEnable();
+
+ // Add a sample point for central tendency statistics, but do not
+ // change the tracking enabled status. If tracking has either never been
+ // enabled, or has never been enabled since the last sample, then log a warning
+ // and don't add sample. Otherwise, adds a sample for tracked CPU ns since
+ // the previous sample or since the first call to sampleAndEnable(),
+ // enable(), or setEnabled(true) if no previous sample.
+ void sample();
+
+ // Return the elapsed delta wall clock ns since initial enable or statistics reset,
+ // as reported by clock_gettime(CLOCK_MONOTONIC).
+ long long elapsed() const;
+
+ // Reset statistics and elapsed. Has no effect on tracking or accumulator.
+ void resetStatistics();
+
+ // Return a const reference to the central tendency statistics.
+ // Note that only the const methods can be called on this object.
+ const CentralTendencyStatistics& statistics() const {
+ return mStatistics;
+ }
+
+private:
+ bool mIsEnabled; // whether tracking is currently enabled
+ bool mWasEverEnabled; // whether tracking was ever enabled
+ long long mAccumulator; // accumulated thread CPU time since last sample, in ns
+ struct timespec mPreviousTs; // most recent thread CPU time, valid only if mIsEnabled is true
+ struct timespec mMonotonicTs; // most recent monotonic time
+ bool mMonotonicKnown; // whether mMonotonicTs has been set
+ CentralTendencyStatistics mStatistics;
+};
+
+#endif // _THREAD_CPU_USAGE_H
diff --git a/libs/cpustats/Android.mk b/libs/cpustats/Android.mk
new file mode 100644
index 0000000..5c1074d
--- /dev/null
+++ b/libs/cpustats/Android.mk
@@ -0,0 +1,21 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ CentralTendencyStatistics.cpp \
+ ThreadCpuUsage.cpp
+
+LOCAL_MODULE := libcpustats
+
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ CentralTendencyStatistics.cpp \
+ ThreadCpuUsage.cpp
+
+LOCAL_MODULE := libcpustats
+
+include $(BUILD_HOST_STATIC_LIBRARY)
diff --git a/libs/cpustats/CentralTendencyStatistics.cpp b/libs/cpustats/CentralTendencyStatistics.cpp
new file mode 100644
index 0000000..42ab62b
--- /dev/null
+++ b/libs/cpustats/CentralTendencyStatistics.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+
+#include <cpustats/CentralTendencyStatistics.h>
+
+void CentralTendencyStatistics::sample(double x)
+{
+ // update min and max
+ if (x < mMinimum)
+ mMinimum = x;
+ if (x > mMaximum)
+ mMaximum = x;
+ // Knuth
+ if (mN == 0) {
+ mMean = 0;
+ }
+ ++mN;
+ double delta = x - mMean;
+ mMean += delta / mN;
+ mM2 += delta * (x - mMean);
+}
+
+void CentralTendencyStatistics::reset()
+{
+ mMean = NAN;
+ mMedian = NAN;
+ mMinimum = INFINITY;
+ mMaximum = -INFINITY;
+ mN = 0;
+ mM2 = 0;
+ mVariance = NAN;
+ mVarianceKnownForN = 0;
+ mStddev = NAN;
+ mStddevKnownForN = 0;
+}
+
+double CentralTendencyStatistics::variance() const
+{
+ double variance;
+ if (mVarianceKnownForN != mN) {
+ if (mN > 1) {
+ // double variance_n = M2/n;
+ variance = mM2 / (mN - 1);
+ } else {
+ variance = NAN;
+ }
+ mVariance = variance;
+ mVarianceKnownForN = mN;
+ } else {
+ variance = mVariance;
+ }
+ return variance;
+}
+
+double CentralTendencyStatistics::stddev() const
+{
+ double stddev;
+ if (mStddevKnownForN != mN) {
+ stddev = sqrt(variance());
+ mStddev = stddev;
+ mStddevKnownForN = mN;
+ } else {
+ stddev = mStddev;
+ }
+ return stddev;
+}
diff --git a/libs/cpustats/ThreadCpuUsage.cpp b/libs/cpustats/ThreadCpuUsage.cpp
new file mode 100644
index 0000000..4bfbdf3
--- /dev/null
+++ b/libs/cpustats/ThreadCpuUsage.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <time.h>
+
+#include <utils/Log.h>
+
+#include <cpustats/ThreadCpuUsage.h>
+
+bool ThreadCpuUsage::setEnabled(bool isEnabled)
+{
+ bool wasEnabled = mIsEnabled;
+ // only do something if there is a change
+ if (isEnabled != wasEnabled) {
+ int rc;
+ // enabling
+ if (isEnabled) {
+ rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &mPreviousTs);
+ if (rc) {
+ LOGE("clock_gettime(CLOCK_THREAD_CPUTIME_ID) errno=%d", errno);
+ isEnabled = false;
+ } else {
+ mWasEverEnabled = true;
+ // record wall clock time at first enable
+ if (!mMonotonicKnown) {
+ rc = clock_gettime(CLOCK_MONOTONIC, &mMonotonicTs);
+ if (rc) {
+ LOGE("clock_gettime(CLOCK_MONOTONIC) errno=%d", errno);
+ } else {
+ mMonotonicKnown = true;
+ }
+ }
+ }
+ // disabling
+ } else {
+ struct timespec ts;
+ rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
+ if (rc) {
+ LOGE("clock_gettime(CLOCK_THREAD_CPUTIME_ID) errno=%d", errno);
+ } else {
+ long long delta = (ts.tv_sec - mPreviousTs.tv_sec) * 1000000000LL +
+ (ts.tv_nsec - mPreviousTs.tv_nsec);
+ mAccumulator += delta;
+#if 0
+ mPreviousTs = ts;
+#endif
+ }
+ }
+ mIsEnabled = isEnabled;
+ }
+ return wasEnabled;
+}
+
+void ThreadCpuUsage::sampleAndEnable()
+{
+ bool wasEverEnabled = mWasEverEnabled;
+ if (enable()) {
+ // already enabled, so add a new sample relative to previous
+ sample();
+ } else if (wasEverEnabled) {
+ // was disabled, but add sample for accumulated time while enabled
+ mStatistics.sample((double) mAccumulator);
+ mAccumulator = 0;
+ }
+}
+
+void ThreadCpuUsage::sample()
+{
+ if (mWasEverEnabled) {
+ if (mIsEnabled) {
+ struct timespec ts;
+ int rc;
+ rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
+ if (rc) {
+ LOGE("clock_gettime(CLOCK_THREAD_CPUTIME_ID) errno=%d", errno);
+ } else {
+ long long delta = (ts.tv_sec - mPreviousTs.tv_sec) * 1000000000LL +
+ (ts.tv_nsec - mPreviousTs.tv_nsec);
+ mAccumulator += delta;
+ mPreviousTs = ts;
+ }
+ } else {
+ mWasEverEnabled = false;
+ }
+ mStatistics.sample((double) mAccumulator);
+ mAccumulator = 0;
+ } else {
+ LOGW("Can't add sample because measurements have never been enabled");
+ }
+}
+
+long long ThreadCpuUsage::elapsed() const
+{
+ long long elapsed;
+ if (mMonotonicKnown) {
+ struct timespec ts;
+ int rc;
+ rc = clock_gettime(CLOCK_MONOTONIC, &ts);
+ if (rc) {
+ LOGE("clock_gettime(CLOCK_MONOTONIC) errno=%d", errno);
+ elapsed = 0;
+ } else {
+ // mMonotonicTs is updated only at first enable and resetStatistics
+ elapsed = (ts.tv_sec - mMonotonicTs.tv_sec) * 1000000000LL +
+ (ts.tv_nsec - mMonotonicTs.tv_nsec);
+ }
+ } else {
+ LOGW("Can't compute elapsed time because measurements have never been enabled");
+ elapsed = 0;
+ }
+ return elapsed;
+}
+
+void ThreadCpuUsage::resetStatistics()
+{
+ mStatistics.reset();
+ if (mMonotonicKnown) {
+ int rc;
+ rc = clock_gettime(CLOCK_MONOTONIC, &mMonotonicTs);
+ if (rc) {
+ LOGE("clock_gettime(CLOCK_MONOTONIC) errno=%d", errno);
+ mMonotonicKnown = false;
+ }
+ }
+}
diff --git a/libs/gui/tests/SurfaceTexture_test.cpp b/libs/gui/tests/SurfaceTexture_test.cpp
index 88433fb..9abe89d 100644
--- a/libs/gui/tests/SurfaceTexture_test.cpp
+++ b/libs/gui/tests/SurfaceTexture_test.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#define LOG_TAG "SurfaceTexture_test"
//#define LOG_NDEBUG 0
#include <gtest/gtest.h>
@@ -379,6 +380,13 @@
ASSERT_NE(-1, mTexMatrixHandle);
}
+ virtual void TearDown() {
+ mANW.clear();
+ mSTC.clear();
+ mST.clear();
+ GLTest::TearDown();
+ }
+
// drawTexture draws the SurfaceTexture over the entire GL viewport.
void drawTexture() {
const GLfloat triangleVertices[] = {
@@ -1089,13 +1097,21 @@
// synchronously from SurfaceTexture::queueBuffer.
class FrameCondition : public SurfaceTexture::FrameAvailableListener {
public:
+ FrameCondition():
+ mFrameAvailable(false),
+ mFrameFinished(false) {
+ }
+
// waitForFrame waits for the next frame to arrive. This should be
// called from the consumer thread once for every frame expected by the
// test.
void waitForFrame() {
- LOGV("+waitForFrame");
Mutex::Autolock lock(mMutex);
- status_t result = mFrameAvailableCondition.wait(mMutex);
+ LOGV("+waitForFrame");
+ while (!mFrameAvailable) {
+ mFrameAvailableCondition.wait(mMutex);
+ }
+ mFrameAvailable = false;
LOGV("-waitForFrame");
}
@@ -1103,22 +1119,30 @@
// on to produce the next frame. This should be called by the consumer
// thread once for every frame expected by the test.
void finishFrame() {
- LOGV("+finishFrame");
Mutex::Autolock lock(mMutex);
+ LOGV("+finishFrame");
+ mFrameFinished = true;
mFrameFinishCondition.signal();
LOGV("-finishFrame");
}
// This should be called by SurfaceTexture on the producer thread.
virtual void onFrameAvailable() {
- LOGV("+onFrameAvailable");
Mutex::Autolock lock(mMutex);
+ LOGV("+onFrameAvailable");
+ mFrameAvailable = true;
mFrameAvailableCondition.signal();
- mFrameFinishCondition.wait(mMutex);
+ while (!mFrameFinished) {
+ mFrameFinishCondition.wait(mMutex);
+ }
+ mFrameFinished = false;
LOGV("-onFrameAvailable");
}
protected:
+ bool mFrameAvailable;
+ bool mFrameFinished;
+
Mutex mMutex;
Condition mFrameAvailableCondition;
Condition mFrameFinishCondition;
@@ -1164,6 +1188,7 @@
}
mProducerThread.clear();
mFC.clear();
+ SurfaceTextureGLTest::TearDown();
}
void runProducerThread(const sp<ProducerThread> producerThread) {
diff --git a/media/libstagefright/XINGSeeker.cpp b/media/libstagefright/XINGSeeker.cpp
index 0d0d6c2..2091381 100644
--- a/media/libstagefright/XINGSeeker.cpp
+++ b/media/libstagefright/XINGSeeker.cpp
@@ -24,8 +24,8 @@
static bool parse_xing_header(
const sp<DataSource> &source, off64_t first_frame_pos,
int32_t *frame_number = NULL, int32_t *byte_number = NULL,
- char *table_of_contents = NULL, int32_t *quality_indicator = NULL,
- int64_t *duration = NULL);
+ unsigned char *table_of_contents = NULL,
+ int32_t *quality_indicator = NULL, int64_t *duration = NULL);
// static
sp<XINGSeeker> XINGSeeker::CreateFromSource(
@@ -94,7 +94,7 @@
static bool parse_xing_header(
const sp<DataSource> &source, off64_t first_frame_pos,
int32_t *frame_number, int32_t *byte_number,
- char *table_of_contents, int32_t *quality_indicator,
+ unsigned char *table_of_contents, int32_t *quality_indicator,
int64_t *duration) {
if (frame_number) {
*frame_number = 0;
diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp
index 8ecc17c..ca61b3d 100644
--- a/media/libstagefright/httplive/LiveSession.cpp
+++ b/media/libstagefright/httplive/LiveSession.cpp
@@ -36,11 +36,10 @@
#include <ctype.h>
#include <openssl/aes.h>
+#include <openssl/md5.h>
namespace android {
-const int64_t LiveSession::kMaxPlaylistAgeUs = 15000000ll;
-
LiveSession::LiveSession(uint32_t flags, bool uidValid, uid_t uid)
: mFlags(flags),
mUIDValid(uidValid),
@@ -59,7 +58,8 @@
mDurationUs(-1),
mSeekDone(false),
mDisconnectPending(false),
- mMonitorQueueGeneration(0) {
+ mMonitorQueueGeneration(0),
+ mRefreshState(INITIAL_MINIMUM_RELOAD_DELAY) {
if (mUIDValid) {
mHTTPDataSource->setUID(mUID);
}
@@ -175,7 +175,8 @@
mMasterURL = url;
- sp<M3UParser> playlist = fetchPlaylist(url.c_str());
+ bool dummy;
+ sp<M3UParser> playlist = fetchPlaylist(url.c_str(), &dummy);
if (playlist == NULL) {
LOGE("unable to fetch master playlist '%s'.", url.c_str());
@@ -289,7 +290,9 @@
return OK;
}
-sp<M3UParser> LiveSession::fetchPlaylist(const char *url) {
+sp<M3UParser> LiveSession::fetchPlaylist(const char *url, bool *unchanged) {
+ *unchanged = false;
+
sp<ABuffer> buffer;
status_t err = fetchFile(url, &buffer);
@@ -297,6 +300,38 @@
return NULL;
}
+ // MD5 functionality is not available on the simulator, treat all
+ // playlists as changed.
+
+#if defined(HAVE_ANDROID_OS)
+ uint8_t hash[16];
+
+ MD5_CTX m;
+ MD5_Init(&m);
+ MD5_Update(&m, buffer->data(), buffer->size());
+
+ MD5_Final(hash, &m);
+
+ if (mPlaylist != NULL && !memcmp(hash, mPlaylistHash, 16)) {
+ // playlist unchanged
+
+ if (mRefreshState != THIRD_UNCHANGED_RELOAD_ATTEMPT) {
+ mRefreshState = (RefreshState)(mRefreshState + 1);
+ }
+
+ *unchanged = true;
+
+ LOGV("Playlist unchanged, refresh state is now %d",
+ (int)mRefreshState);
+
+ return NULL;
+ }
+
+ memcpy(mPlaylistHash, hash, sizeof(hash));
+
+ mRefreshState = INITIAL_MINIMUM_RELOAD_DELAY;
+#endif
+
sp<M3UParser> playlist =
new M3UParser(url, buffer->data(), buffer->size());
@@ -384,6 +419,63 @@
return index;
}
+bool LiveSession::timeToRefreshPlaylist(int64_t nowUs) const {
+ if (mPlaylist == NULL) {
+ CHECK_EQ((int)mRefreshState, (int)INITIAL_MINIMUM_RELOAD_DELAY);
+ return true;
+ }
+
+ int32_t targetDurationSecs;
+ CHECK(mPlaylist->meta()->findInt32("target-duration", &targetDurationSecs));
+
+ int64_t targetDurationUs = targetDurationSecs * 1000000ll;
+
+ int64_t minPlaylistAgeUs;
+
+ switch (mRefreshState) {
+ case INITIAL_MINIMUM_RELOAD_DELAY:
+ {
+ size_t n = mPlaylist->size();
+ if (n > 0) {
+ sp<AMessage> itemMeta;
+ CHECK(mPlaylist->itemAt(n - 1, NULL /* uri */, &itemMeta));
+
+ int64_t itemDurationUs;
+ CHECK(itemMeta->findInt64("durationUs", &itemDurationUs));
+
+ minPlaylistAgeUs = itemDurationUs;
+ break;
+ }
+
+ // fall through
+ }
+
+ case FIRST_UNCHANGED_RELOAD_ATTEMPT:
+ {
+ minPlaylistAgeUs = targetDurationUs / 2;
+ break;
+ }
+
+ case SECOND_UNCHANGED_RELOAD_ATTEMPT:
+ {
+ minPlaylistAgeUs = (targetDurationUs * 3) / 2;
+ break;
+ }
+
+ case THIRD_UNCHANGED_RELOAD_ATTEMPT:
+ {
+ minPlaylistAgeUs = targetDurationUs * 3;
+ break;
+ }
+
+ default:
+ TRESPASS();
+ break;
+ }
+
+ return mLastPlaylistFetchTimeUs + minPlaylistAgeUs <= nowUs;
+}
+
void LiveSession::onDownloadNext() {
size_t bandwidthIndex = getBandwidthIndex();
@@ -392,8 +484,7 @@
if (mLastPlaylistFetchTimeUs < 0
|| (ssize_t)bandwidthIndex != mPrevBandwidthIndex
- || (!mPlaylist->isComplete()
- && mLastPlaylistFetchTimeUs + kMaxPlaylistAgeUs <= nowUs)) {
+ || (!mPlaylist->isComplete() && timeToRefreshPlaylist(nowUs))) {
AString url;
if (mBandwidthItems.size() > 0) {
url = mBandwidthItems.editItemAt(bandwidthIndex).mURI;
@@ -403,11 +494,19 @@
bool firstTime = (mPlaylist == NULL);
- mPlaylist = fetchPlaylist(url.c_str());
- if (mPlaylist == NULL) {
- LOGE("failed to load playlist at url '%s'", url.c_str());
- mDataSource->queueEOS(ERROR_IO);
- return;
+ bool unchanged;
+ sp<M3UParser> playlist = fetchPlaylist(url.c_str(), &unchanged);
+ if (playlist == NULL) {
+ if (unchanged) {
+ // We succeeded in fetching the playlist, but it was
+ // unchanged from the last time we tried.
+ } else {
+ LOGE("failed to load playlist at url '%s'", url.c_str());
+ mDataSource->queueEOS(ERROR_IO);
+ return;
+ }
+ } else {
+ mPlaylist = playlist;
}
if (firstTime) {
diff --git a/media/libstagefright/include/LiveSession.h b/media/libstagefright/include/LiveSession.h
index 188ef5e..116ed0e 100644
--- a/media/libstagefright/include/LiveSession.h
+++ b/media/libstagefright/include/LiveSession.h
@@ -62,8 +62,6 @@
kMaxNumRetries = 5,
};
- static const int64_t kMaxPlaylistAgeUs;
-
enum {
kWhatConnect = 'conn',
kWhatDisconnect = 'disc',
@@ -106,6 +104,16 @@
int32_t mMonitorQueueGeneration;
+ enum RefreshState {
+ INITIAL_MINIMUM_RELOAD_DELAY,
+ FIRST_UNCHANGED_RELOAD_ATTEMPT,
+ SECOND_UNCHANGED_RELOAD_ATTEMPT,
+ THIRD_UNCHANGED_RELOAD_ATTEMPT
+ };
+ RefreshState mRefreshState;
+
+ uint8_t mPlaylistHash[16];
+
void onConnect(const sp<AMessage> &msg);
void onDisconnect();
void onDownloadNext();
@@ -113,7 +121,7 @@
void onSeek(const sp<AMessage> &msg);
status_t fetchFile(const char *url, sp<ABuffer> *out);
- sp<M3UParser> fetchPlaylist(const char *url);
+ sp<M3UParser> fetchPlaylist(const char *url, bool *unchanged);
size_t getBandwidthIndex();
status_t decryptBuffer(
@@ -121,6 +129,8 @@
void postMonitorQueue(int64_t delayUs = 0);
+ bool timeToRefreshPlaylist(int64_t nowUs) const;
+
static int SortByBandwidth(const BandwidthItem *, const BandwidthItem *);
DISALLOW_EVIL_CONSTRUCTORS(LiveSession);
diff --git a/media/libstagefright/include/XINGSeeker.h b/media/libstagefright/include/XINGSeeker.h
index d5a484e..ec5bd9b 100644
--- a/media/libstagefright/include/XINGSeeker.h
+++ b/media/libstagefright/include/XINGSeeker.h
@@ -37,7 +37,7 @@
int32_t mSizeBytes;
// TOC entries in XING header. Skip the first one since it's always 0.
- char mTableOfContents[99];
+ unsigned char mTableOfContents[99];
XINGSeeker();
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPropertiesTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPropertiesTest.java
index 59783e5..4d517db 100755
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPropertiesTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPropertiesTest.java
@@ -91,8 +91,12 @@
assertEquals("AudioType Mismatch ", audioCodecType, mvi.getAudioType());
assertEquals("Audio Sampling " + mvi.getAudioSamplingFrequency(),
audioSamplingFrequency, mvi.getAudioSamplingFrequency());
- assertEquals("Audio Channels " + mvi.getAudioChannels(), audioChannel,
- mvi.getAudioChannels());
+ // PV SW AAC codec always returns number of channels as Stereo.
+ // So we do not assert for number of audio channels for AAC_LC
+ if ( audioCodecType != MediaProperties.ACODEC_AAC_LC ) {
+ assertEquals("Audio Channels " + mvi.getAudioChannels(), audioChannel,
+ mvi.getAudioChannels());
+ }
}
protected void validateAudioProperties(int audioCodecType, int duration,
@@ -103,8 +107,12 @@
duration, aT.getDuration(), 10));
assertEquals("Audio Sampling " + aT.getAudioSamplingFrequency(),
audioSamplingFrequency, aT.getAudioSamplingFrequency());
- assertEquals("Audio Channels " + aT.getAudioChannels(), audioChannel,
- aT.getAudioChannels());
+ // PV SW AAC codec always returns number of channels as Stereo.
+ // So we do not assert for number of audio channels for AAC_LC
+ if ( audioCodecType != MediaProperties.ACODEC_AAC_LC ) {
+ assertEquals("Audio Channels " + aT.getAudioChannels(), audioChannel,
+ aT.getAudioChannels());
+ }
}
protected void validateImageProperties(int aspectRatio, int fileType,
diff --git a/packages/VpnDialogs/res/layout/confirm.xml b/packages/VpnDialogs/res/layout/confirm.xml
index 5ab6ee2..11a247a 100644
--- a/packages/VpnDialogs/res/layout/confirm.xml
+++ b/packages/VpnDialogs/res/layout/confirm.xml
@@ -18,7 +18,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:padding="5mm">
+ android:padding="3mm">
<ImageView android:id="@+id/icon"
android:layout_width="@android:dimen/app_icon_size"
diff --git a/packages/VpnDialogs/res/layout/manage.xml b/packages/VpnDialogs/res/layout/manage.xml
index 330b8e3..3dcbb46 100644
--- a/packages/VpnDialogs/res/layout/manage.xml
+++ b/packages/VpnDialogs/res/layout/manage.xml
@@ -18,6 +18,7 @@
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:padding="3mm"
android:stretchColumns="0,1">
<TableRow>
diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk
index 6bb1f56..a0407b9 100644
--- a/services/audioflinger/Android.mk
+++ b/services/audioflinger/Android.mk
@@ -24,6 +24,7 @@
libdl
LOCAL_STATIC_LIBRARIES := \
+ libcpustats \
libmedia_helper
LOCAL_MODULE:= libaudioflinger
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index daf94f2..86d4cc3 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -52,6 +52,9 @@
#include <media/EffectsFactoryApi.h>
#include <audio_effects/effect_visualizer.h>
+#include <cpustats/ThreadCpuUsage.h>
+// #define DEBUG_CPU_USAGE 10 // log statistics every n wall clock seconds
+
// ----------------------------------------------------------------------------
@@ -1529,9 +1532,40 @@
uint32_t idleSleepTime = idleSleepTimeUs();
uint32_t sleepTime = idleSleepTime;
Vector< sp<EffectChain> > effectChains;
+#ifdef DEBUG_CPU_USAGE
+ ThreadCpuUsage cpu;
+ const CentralTendencyStatistics& stats = cpu.statistics();
+#endif
while (!exitPending())
{
+#ifdef DEBUG_CPU_USAGE
+ cpu.sampleAndEnable();
+ unsigned n = stats.n();
+ // cpu.elapsed() is expensive, so don't call it every loop
+ if ((n & 127) == 1) {
+ long long elapsed = cpu.elapsed();
+ if (elapsed >= DEBUG_CPU_USAGE * 1000000000LL) {
+ double perLoop = elapsed / (double) n;
+ double perLoop100 = perLoop * 0.01;
+ double mean = stats.mean();
+ double stddev = stats.stddev();
+ double minimum = stats.minimum();
+ double maximum = stats.maximum();
+ cpu.resetStatistics();
+ LOGI("CPU usage over past %.1f secs (%u mixer loops at %.1f mean ms per loop):\n us per mix loop: mean=%.0f stddev=%.0f min=%.0f max=%.0f\n %% of wall: mean=%.1f stddev=%.1f min=%.1f max=%.1f",
+ elapsed * .000000001, n, perLoop * .000001,
+ mean * .001,
+ stddev * .001,
+ minimum * .001,
+ maximum * .001,
+ mean / perLoop100,
+ stddev / perLoop100,
+ minimum / perLoop100,
+ maximum / perLoop100);
+ }
+ }
+#endif
processConfigEvents();
mixerStatus = MIXER_IDLE;
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 168b894..e9e66cb 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -87,8 +87,10 @@
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@@ -109,6 +111,8 @@
// Name and current contents version of the full-backup manifest file
static final String BACKUP_MANIFEST_FILENAME = "_manifest";
static final int BACKUP_MANIFEST_VERSION = 1;
+ static final String BACKUP_FILE_HEADER_MAGIC = "ANDROID BACKUP\n";
+ static final int BACKUP_FILE_VERSION = 1;
// How often we perform a backup pass. Privileged external callers can
// trigger an immediate pass.
@@ -1791,16 +1795,42 @@
}
}
- // Set up the compression stage
FileOutputStream ofstream = new FileOutputStream(mOutputFile.getFileDescriptor());
+
+ // Set up the compression stage
Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
DeflaterOutputStream out = new DeflaterOutputStream(ofstream, deflater, true);
- // !!! TODO: if using encryption, set up the encryption stage
- // and emit the tar header stating the password salt.
-
PackageInfo pkg = null;
try {
+
+ // !!! TODO: if using encryption, set up the encryption stage
+ // and emit the tar header stating the password salt.
+
+ // Write the global file header. All strings are UTF-8 encoded; lines end
+ // with a '\n' byte. Actual backup data begins immediately following the
+ // final '\n'.
+ //
+ // line 1: "ANDROID BACKUP"
+ // line 2: backup file format version, currently "1"
+ // line 3: compressed? "0" if not compressed, "1" if compressed.
+ // line 4: encryption salt? "-" if not encrypted, otherwise this
+ // line contains the encryption salt with which the user-
+ // supplied password is to be expanded, in hexadecimal.
+ StringBuffer headerbuf = new StringBuffer(256);
+ // !!! TODO: programmatically build the compressed / encryption salt fields
+ headerbuf.append(BACKUP_FILE_HEADER_MAGIC);
+ headerbuf.append("1\n1\n-\n");
+
+ try {
+ byte[] header = headerbuf.toString().getBytes("UTF-8");
+ ofstream.write(header);
+ } catch (Exception e) {
+ // Should never happen!
+ Slog.e(TAG, "Unable to emit archive header", e);
+ return;
+ }
+
// Now back up the app data via the agent mechanism
int N = packagesToBackup.size();
for (int i = 0; i < N; i++) {
@@ -2176,7 +2206,46 @@
mBytes = 0;
byte[] buffer = new byte[32 * 1024];
FileInputStream rawInStream = new FileInputStream(mInputFile.getFileDescriptor());
- InflaterInputStream in = new InflaterInputStream(rawInStream);
+
+ // First, parse out the unencrypted/uncompressed header
+ boolean compressed = false;
+ boolean encrypted = false;
+ final InputStream in;
+
+ boolean okay = false;
+ final int headerLen = BACKUP_FILE_HEADER_MAGIC.length();
+ byte[] streamHeader = new byte[headerLen];
+ try {
+ int got;
+ if ((got = rawInStream.read(streamHeader, 0, headerLen)) == headerLen) {
+ byte[] magicBytes = BACKUP_FILE_HEADER_MAGIC.getBytes("UTF-8");
+ if (Arrays.equals(magicBytes, streamHeader)) {
+ // okay, header looks good. now parse out the rest of the fields.
+ String s = readHeaderLine(rawInStream);
+ if (Integer.parseInt(s) == BACKUP_FILE_VERSION) {
+ // okay, it's a version we recognize
+ s = readHeaderLine(rawInStream);
+ compressed = (Integer.parseInt(s) != 0);
+ s = readHeaderLine(rawInStream);
+ if (!s.startsWith("-")) {
+ encrypted = true;
+ // TODO: parse out the salt here and process with the user pw
+ }
+ okay = true;
+ } else Slog.e(TAG, "Wrong header version: " + s);
+ } else Slog.e(TAG, "Didn't read the right header magic");
+ } else Slog.e(TAG, "Only read " + got + " bytes of header");
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Can't parse restore data header");
+ }
+
+ if (!okay) {
+ Slog.e(TAG, "Invalid restore data; aborting.");
+ return;
+ }
+
+ // okay, use the right stream layer based on compression
+ in = (compressed) ? new InflaterInputStream(rawInStream) : rawInStream;
boolean didRestore;
do {
@@ -2184,6 +2253,8 @@
} while (didRestore);
if (DEBUG) Slog.v(TAG, "Done consuming input tarfile, total bytes=" + mBytes);
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to read restore input");
} finally {
tearDownPipes();
tearDownAgent(mTargetApp);
@@ -2207,6 +2278,16 @@
}
}
+ String readHeaderLine(InputStream in) throws IOException {
+ int c;
+ StringBuffer buffer = new StringBuffer(80);
+ while ((c = in.read()) >= 0) {
+ if (c == '\n') break; // consume and discard the newlines
+ buffer.append((char)c);
+ }
+ return buffer.toString();
+ }
+
boolean restoreOneFile(InputStream instream, byte[] buffer) {
FileMetadata info;
try {
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 5f0922e..7112553 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -35,8 +35,8 @@
import android.net.wifi.WifiManager;
import android.net.wifi.WifiStateMachine;
import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiWatchdogStateMachine;
import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WifiWatchdogService;
import android.net.wifi.WpsConfiguration;
import android.net.wifi.WpsResult;
import android.net.ConnectivityManager;
@@ -343,7 +343,7 @@
* Protected by mWifiStateTracker lock.
*/
private final WorkSource mTmpWorkSource = new WorkSource();
- private WifiWatchdogService mWifiWatchdogService;
+ private WifiWatchdogStateMachine mWifiWatchdogStateMachine;
WifiService(Context context) {
mContext = context;
@@ -434,8 +434,9 @@
(wifiEnabled ? "enabled" : "disabled"));
setWifiEnabled(wifiEnabled);
- //TODO: as part of WWS refactor, create only when needed
- mWifiWatchdogService = new WifiWatchdogService(mContext);
+ mWifiWatchdogStateMachine = WifiWatchdogStateMachine.
+ makeWifiWatchdogStateMachine(mContext);
+
}
private boolean testAndClearWifiSavedState() {
@@ -1162,8 +1163,8 @@
mLocks.dump(pw);
pw.println();
- pw.println("WifiWatchdogService dump");
- mWifiWatchdogService.dump(pw);
+ pw.println("WifiWatchdogStateMachine dump");
+ mWifiWatchdogStateMachine.dump(pw);
}
private class WifiLock extends DeathRecipient {
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index b0881a4..680814c 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1551,8 +1551,18 @@
* Dump SurfaceFlinger global state
*/
- snprintf(buffer, SIZE, "SurfaceFlinger global state\n");
+ snprintf(buffer, SIZE, "SurfaceFlinger global state:\n");
result.append(buffer);
+
+ const GLExtensions& extensions(GLExtensions::getInstance());
+ snprintf(buffer, SIZE, "GLES: %s, %s, %s\n",
+ extensions.getVendor(),
+ extensions.getRenderer(),
+ extensions.getVersion());
+ result.append(buffer);
+ snprintf(buffer, SIZE, "EXTS: %s\n", extensions.getExtension());
+ result.append(buffer);
+
mWormholeRegion.dump(result, "WormholeRegion");
const DisplayHardware& hw(graphicPlane(0).displayHardware());
snprintf(buffer, SIZE,
diff --git a/tests/BiDiTests/res/layout/basic.xml b/tests/BiDiTests/res/layout/basic.xml
index d438b2c..c1ebd20 100644
--- a/tests/BiDiTests/res/layout/basic.xml
+++ b/tests/BiDiTests/res/layout/basic.xml
@@ -40,7 +40,7 @@
android:textSize="32dip"
android:text="@string/textview_text"
/>
-
+
<EditText android:id="@+id/edittext"
android:layout_height="wrap_content"
android:layout_width="match_parent"
@@ -51,4 +51,32 @@
</LinearLayout>
+ <LinearLayout
+ android:layout_width="600dip"
+ android:layout_height="128dip"
+ android:layout_gravity="center_vertical"
+ android:orientation="horizontal"
+ style="@android:style/Widget.Holo.Spinner"
+ >
+ <LinearLayout
+ android:layout_width="0dip"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
+ android:orientation="vertical"
+ >
+ <TextView
+ android:id="@+id/spinner_line_1"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:textSize="16dip"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:gravity="left|center_vertical"
+ android:text="@string/button_text"
+ />
+ </LinearLayout>
+ </LinearLayout>
+
</FrameLayout>
\ No newline at end of file
diff --git a/wifi/java/android/net/wifi/WifiWatchdogService.java b/wifi/java/android/net/wifi/WifiWatchdogService.java
deleted file mode 100644
index bce4b3a..0000000
--- a/wifi/java/android/net/wifi/WifiWatchdogService.java
+++ /dev/null
@@ -1,765 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.ConnectivityManager;
-import android.net.DnsPinger;
-import android.net.NetworkInfo;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Slog;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PrintWriter;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Scanner;
-
-/**
- * {@link WifiWatchdogService} monitors the initial connection to a Wi-Fi
- * network with multiple access points. After the framework successfully
- * connects to an access point, the watchdog verifies connectivity by 'pinging'
- * the configured DNS server using {@link DnsPinger}.
- * <p>
- * On DNS check failure, the BSSID is blacklisted if it is reasonably likely
- * that another AP might have internet access; otherwise the SSID is disabled.
- * <p>
- * On DNS success, the WatchdogService initiates a walled garden check via an
- * http get. A browser windows is activated if a walled garden is detected.
- *
- * @hide
- */
-public class WifiWatchdogService {
-
- private static final String WWS_TAG = "WifiWatchdogService";
-
- private static final boolean VDBG = true;
- private static final boolean DBG = true;
-
- // Used for verbose logging
- private String mDNSCheckLogStr;
-
- private Context mContext;
- private ContentResolver mContentResolver;
- private WifiManager mWifiManager;
-
- private WifiWatchdogHandler mHandler;
-
- private DnsPinger mDnsPinger;
-
- private IntentFilter mIntentFilter;
- private BroadcastReceiver mBroadcastReceiver;
- private boolean mBroadcastsEnabled;
-
- private static final int WIFI_SIGNAL_LEVELS = 4;
-
- /**
- * Low signal is defined as less than or equal to cut off
- */
- private static final int LOW_SIGNAL_CUTOFF = 0;
-
- private static final long MIN_LOW_SIGNAL_CHECK_INTERVAL = 2 * 60 * 1000;
- private static final long MIN_SINGLE_DNS_CHECK_INTERVAL = 10 * 60 * 1000;
- private static final long MIN_WALLED_GARDEN_INTERVAL = 15 * 60 * 1000;
-
- private static final int MAX_CHECKS_PER_SSID = 9;
- private static final int NUM_DNS_PINGS = 7;
- private static double MIN_RESPONSE_RATE = 0.50;
-
- // TODO : Adjust multiple DNS downward to 250 on repeated failure
- // private static final int MULTI_DNS_PING_TIMEOUT_MS = 250;
-
- private static final int DNS_PING_TIMEOUT_MS = 800;
- private static final long DNS_PING_INTERVAL = 250;
-
- private static final long BLACKLIST_FOLLOWUP_INTERVAL = 15 * 1000;
-
- private Status mStatus = new Status();
-
- private static class Status {
- String bssid = "";
- String ssid = "";
-
- HashSet<String> allBssids = new HashSet<String>();
- int numFullDNSchecks = 0;
-
- long lastSingleCheckTime = -24 * 60 * 60 * 1000;
- long lastWalledGardenCheckTime = -24 * 60 * 60 * 1000;
-
- WatchdogState state = WatchdogState.INACTIVE;
-
- // Info for dns check
- int dnsCheckTries = 0;
- int dnsCheckSuccesses = 0;
-
- public int signal = -200;
-
- }
-
- private enum WatchdogState {
- /**
- * Full DNS check in progress
- */
- DNS_FULL_CHECK,
-
- /**
- * Walled Garden detected, will pop up browser next round.
- */
- WALLED_GARDEN_DETECTED,
-
- /**
- * DNS failed, will blacklist/disable AP next round
- */
- DNS_CHECK_FAILURE,
-
- /**
- * Online or displaying walled garden auth page
- */
- CHECKS_COMPLETE,
-
- /**
- * Watchdog idle, network has been blacklisted or received disconnect
- * msg
- */
- INACTIVE,
-
- BLACKLISTED_AP
- }
-
- public WifiWatchdogService(Context context) {
- mContext = context;
- mContentResolver = context.getContentResolver();
- mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- mDnsPinger = new DnsPinger("WifiWatchdogServer.DnsPinger", context,
- ConnectivityManager.TYPE_WIFI);
-
- HandlerThread handlerThread = new HandlerThread("WifiWatchdogServiceThread");
- handlerThread.start();
- mHandler = new WifiWatchdogHandler(handlerThread.getLooper());
-
- setupNetworkReceiver();
-
- // The content observer to listen needs a handler, which createThread
- // creates
- registerForSettingsChanges();
-
- // Start things off
- if (isWatchdogEnabled()) {
- mHandler.sendEmptyMessage(WifiWatchdogHandler.MESSAGE_CONTEXT_EVENT);
- }
- }
-
- /**
- *
- */
- private void setupNetworkReceiver() {
- mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
- mHandler.sendMessage(mHandler.obtainMessage(
- WifiWatchdogHandler.MESSAGE_NETWORK_EVENT,
- intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO)
- ));
- } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
- mHandler.sendEmptyMessage(WifiWatchdogHandler.RSSI_CHANGE_EVENT);
- } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
- mHandler.sendEmptyMessage(WifiWatchdogHandler.SCAN_RESULTS_AVAILABLE);
- } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
- mHandler.sendMessage(mHandler.obtainMessage(
- WifiWatchdogHandler.WIFI_STATE_CHANGE,
- intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 4)));
- }
- }
- };
-
- mIntentFilter = new IntentFilter();
- mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
- mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
- mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
- mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
- }
-
- /**
- * Observes the watchdog on/off setting, and takes action when changed.
- */
- private void registerForSettingsChanges() {
- ContentObserver contentObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- mHandler.sendEmptyMessage((WifiWatchdogHandler.MESSAGE_CONTEXT_EVENT));
- }
- };
-
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON),
- false, contentObserver);
- }
-
- private void handleNewConnection() {
- WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
- String newSsid = wifiInfo.getSSID();
- String newBssid = wifiInfo.getBSSID();
-
- if (VDBG) {
- Slog.v(WWS_TAG, String.format("handleConnected:: old (%s, %s) ==> new (%s, %s)",
- mStatus.ssid, mStatus.bssid, newSsid, newBssid));
- }
-
- if (TextUtils.isEmpty(newSsid) || TextUtils.isEmpty(newBssid)) {
- return;
- }
-
- if (!TextUtils.equals(mStatus.ssid, newSsid)) {
- mStatus = new Status();
- mStatus.ssid = newSsid;
- }
-
- mStatus.bssid = newBssid;
- mStatus.allBssids.add(newBssid);
- mStatus.signal = WifiManager.calculateSignalLevel(wifiInfo.getRssi(), WIFI_SIGNAL_LEVELS);
-
- initDnsFullCheck();
- }
-
- public void updateRssi() {
- WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
- if (!TextUtils.equals(mStatus.ssid, wifiInfo.getSSID()) ||
- !TextUtils.equals(mStatus.bssid, wifiInfo.getBSSID())) {
- return;
- }
-
- mStatus.signal = WifiManager.calculateSignalLevel(wifiInfo.getRssi(), WIFI_SIGNAL_LEVELS);
- }
-
- /**
- * Single step in state machine
- */
- private void handleStateStep() {
- // Slog.v(WWS_TAG, "handleStateStep:: " + mStatus.state);
-
- switch (mStatus.state) {
- case DNS_FULL_CHECK:
- if (VDBG) {
- Slog.v(WWS_TAG, "DNS_FULL_CHECK: " + mDNSCheckLogStr);
- }
-
- long pingResponseTime = mDnsPinger.pingDns(mDnsPinger.getDns(),
- DNS_PING_TIMEOUT_MS);
-
- mStatus.dnsCheckTries++;
- if (pingResponseTime >= 0)
- mStatus.dnsCheckSuccesses++;
-
- if (DBG) {
- if (pingResponseTime >= 0) {
- mDNSCheckLogStr += " | " + pingResponseTime;
- } else {
- mDNSCheckLogStr += " | " + "x";
- }
- }
-
- switch (currentDnsCheckStatus()) {
- case SUCCESS:
- if (DBG) {
- Slog.d(WWS_TAG, mDNSCheckLogStr + " -- Success");
- }
- doWalledGardenCheck();
- break;
- case FAILURE:
- if (DBG) {
- Slog.d(WWS_TAG, mDNSCheckLogStr + " -- Failure");
- }
- mStatus.state = WatchdogState.DNS_CHECK_FAILURE;
- break;
- case INCOMPLETE:
- // Taking no action
- break;
- }
- break;
- case DNS_CHECK_FAILURE:
- WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
- if (!mStatus.ssid.equals(wifiInfo.getSSID()) ||
- !mStatus.bssid.equals(wifiInfo.getBSSID())) {
- Slog.i(WWS_TAG, "handleState DNS_CHECK_FAILURE:: network has changed!");
- mStatus.state = WatchdogState.INACTIVE;
- break;
- }
-
- if (mStatus.numFullDNSchecks >= mStatus.allBssids.size() ||
- mStatus.numFullDNSchecks >= MAX_CHECKS_PER_SSID) {
- disableAP(wifiInfo);
- } else {
- blacklistAP();
- }
- break;
- case WALLED_GARDEN_DETECTED:
- popUpBrowser();
- mStatus.state = WatchdogState.CHECKS_COMPLETE;
- break;
- case BLACKLISTED_AP:
- WifiInfo wifiInfo2 = mWifiManager.getConnectionInfo();
- if (wifiInfo2.getSupplicantState() != SupplicantState.COMPLETED) {
- Slog.d(WWS_TAG,
- "handleState::BlacklistedAP - offline, but didn't get disconnect!");
- mStatus.state = WatchdogState.INACTIVE;
- break;
- }
- if (mStatus.bssid.equals(wifiInfo2.getBSSID())) {
- Slog.d(WWS_TAG, "handleState::BlacklistedAP - connected to same bssid");
- if (!handleSingleDnsCheck()) {
- disableAP(wifiInfo2);
- break;
- }
- }
-
- Slog.d(WWS_TAG, "handleState::BlacklistedAP - Simiulating a new connection");
- handleNewConnection();
- break;
- }
- }
-
- private void doWalledGardenCheck() {
- if (!isWalledGardenTestEnabled()) {
- if (VDBG)
- Slog.v(WWS_TAG, "Skipping walled garden check - disabled");
- mStatus.state = WatchdogState.CHECKS_COMPLETE;
- return;
- }
- long waitTime = waitTime(MIN_WALLED_GARDEN_INTERVAL,
- mStatus.lastWalledGardenCheckTime);
- if (waitTime > 0) {
- if (VDBG) {
- Slog.v(WWS_TAG, "Skipping walled garden check - wait " +
- waitTime + " ms.");
- }
- mStatus.state = WatchdogState.CHECKS_COMPLETE;
- return;
- }
-
- mStatus.lastWalledGardenCheckTime = SystemClock.elapsedRealtime();
- if (isWalledGardenConnection()) {
- if (DBG)
- Slog.d(WWS_TAG,
- "Walled garden test complete - walled garden detected");
- mStatus.state = WatchdogState.WALLED_GARDEN_DETECTED;
- } else {
- if (DBG)
- Slog.d(WWS_TAG, "Walled garden test complete - online");
- mStatus.state = WatchdogState.CHECKS_COMPLETE;
- }
- }
-
- private boolean handleSingleDnsCheck() {
- mStatus.lastSingleCheckTime = SystemClock.elapsedRealtime();
- long responseTime = mDnsPinger.pingDns(mDnsPinger.getDns(),
- DNS_PING_TIMEOUT_MS);
- if (DBG) {
- Slog.d(WWS_TAG, "Ran a single DNS ping. Response time: " + responseTime);
- }
- if (responseTime < 0) {
- return false;
- }
- return true;
-
- }
-
- /**
- * @return Delay in MS before next single DNS check can proceed.
- */
- private long timeToNextScheduledDNSCheck() {
- if (mStatus.signal > LOW_SIGNAL_CUTOFF) {
- return waitTime(MIN_SINGLE_DNS_CHECK_INTERVAL, mStatus.lastSingleCheckTime);
- } else {
- return waitTime(MIN_LOW_SIGNAL_CHECK_INTERVAL, mStatus.lastSingleCheckTime);
- }
- }
-
- /**
- * Helper to return wait time left given a min interval and last run
- *
- * @param interval minimum wait interval
- * @param lastTime last time action was performed in
- * SystemClock.elapsedRealtime()
- * @return non negative time to wait
- */
- private static long waitTime(long interval, long lastTime) {
- long wait = interval + lastTime - SystemClock.elapsedRealtime();
- return wait > 0 ? wait : 0;
- }
-
- private void popUpBrowser() {
- Uri uri = Uri.parse("http://www.google.com");
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
- Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(intent);
- }
-
- private void disableAP(WifiInfo info) {
- // TODO : Unban networks if they had low signal ?
- Slog.i(WWS_TAG, String.format("Disabling current SSID, %s [bssid %s]. " +
- "numChecks %d, numAPs %d", mStatus.ssid, mStatus.bssid,
- mStatus.numFullDNSchecks, mStatus.allBssids.size()));
- mWifiManager.disableNetwork(info.getNetworkId());
- mStatus.state = WatchdogState.INACTIVE;
- }
-
- private void blacklistAP() {
- Slog.i(WWS_TAG, String.format("Blacklisting current BSSID %s [ssid %s]. " +
- "numChecks %d, numAPs %d", mStatus.bssid, mStatus.ssid,
- mStatus.numFullDNSchecks, mStatus.allBssids.size()));
-
- mWifiManager.addToBlacklist(mStatus.bssid);
- mWifiManager.reassociate();
- mStatus.state = WatchdogState.BLACKLISTED_AP;
- }
-
- /**
- * Checks the scan for new BBIDs using current mSsid
- */
- private void updateBssids() {
- String curSsid = mStatus.ssid;
- HashSet<String> bssids = mStatus.allBssids;
- List<ScanResult> results = mWifiManager.getScanResults();
- int oldNumBssids = bssids.size();
-
- if (results == null) {
- if (VDBG) {
- Slog.v(WWS_TAG, "updateBssids: Got null scan results!");
- }
- return;
- }
-
- for (ScanResult result : results) {
- if (result != null && curSsid.equals(result.SSID))
- bssids.add(result.BSSID);
- }
-
- // if (VDBG && bssids.size() - oldNumBssids > 0) {
- // Slog.v(WWS_TAG,
- // String.format("updateBssids:: Found %d new APs (total %d) on SSID %s",
- // bssids.size() - oldNumBssids, bssids.size(), curSsid));
- // }
- }
-
- enum DnsCheckStatus {
- SUCCESS,
- FAILURE,
- INCOMPLETE
- }
-
- /**
- * Computes the current results of the dns check, ends early if outcome is
- * assured.
- */
- private DnsCheckStatus currentDnsCheckStatus() {
- /**
- * After a full ping count, if we have more responses than this cutoff,
- * the outcome is success; else it is 'failure'.
- */
- double pingResponseCutoff = MIN_RESPONSE_RATE * NUM_DNS_PINGS;
- int remainingChecks = NUM_DNS_PINGS - mStatus.dnsCheckTries;
-
- /**
- * Our final success count will be at least this big, so we're
- * guaranteed to succeed.
- */
- if (mStatus.dnsCheckSuccesses >= pingResponseCutoff) {
- return DnsCheckStatus.SUCCESS;
- }
-
- /**
- * Our final count will be at most the current count plus the remaining
- * pings - we're guaranteed to fail.
- */
- if (remainingChecks + mStatus.dnsCheckSuccesses < pingResponseCutoff) {
- return DnsCheckStatus.FAILURE;
- }
-
- return DnsCheckStatus.INCOMPLETE;
- }
-
- private void initDnsFullCheck() {
- if (DBG) {
- Slog.d(WWS_TAG, "Starting DNS pings at " + SystemClock.elapsedRealtime());
- }
- mStatus.numFullDNSchecks++;
- mStatus.dnsCheckSuccesses = 0;
- mStatus.dnsCheckTries = 0;
- mStatus.state = WatchdogState.DNS_FULL_CHECK;
-
- if (DBG) {
- mDNSCheckLogStr = String.format("Dns Check %d. Pinging %s on ssid [%s]: ",
- mStatus.numFullDNSchecks, mDnsPinger.getDns(),
- mStatus.ssid);
- }
- }
-
- /**
- * DNS based detection techniques do not work at all hotspots. The one sure
- * way to check a walled garden is to see if a URL fetch on a known address
- * fetches the data we expect
- */
- private boolean isWalledGardenConnection() {
- InputStream in = null;
- HttpURLConnection urlConnection = null;
- try {
- URL url = new URL(getWalledGardenUrl());
- urlConnection = (HttpURLConnection) url.openConnection();
- in = new BufferedInputStream(urlConnection.getInputStream());
- Scanner scanner = new Scanner(in);
- if (scanner.findInLine(getWalledGardenPattern()) != null) {
- return false;
- } else {
- return true;
- }
- } catch (IOException e) {
- return false;
- } finally {
- if (in != null) {
- try {
- in.close();
- } catch (IOException e) {
- }
- }
- if (urlConnection != null)
- urlConnection.disconnect();
- }
- }
-
- /**
- * There is little logic inside this class, instead methods of the form
- * "handle___" are called in the main {@link WifiWatchdogService}.
- */
- private class WifiWatchdogHandler extends Handler {
- /**
- * Major network event, object is NetworkInfo
- */
- static final int MESSAGE_NETWORK_EVENT = 1;
- /**
- * Change in settings, no object
- */
- static final int MESSAGE_CONTEXT_EVENT = 2;
-
- /**
- * Change in signal strength
- */
- static final int RSSI_CHANGE_EVENT = 3;
- static final int SCAN_RESULTS_AVAILABLE = 4;
-
- static final int WIFI_STATE_CHANGE = 5;
-
- /**
- * Single step of state machine. One DNS check, or one WalledGarden
- * check, or one external action. We separate out external actions to
- * increase chance of detecting that a check failure is caused by change
- * in network status. Messages should have an arg1 which to sync status
- * messages.
- */
- static final int CHECK_SEQUENCE_STEP = 10;
- static final int SINGLE_DNS_CHECK = 11;
-
- /**
- * @param looper
- */
- public WifiWatchdogHandler(Looper looper) {
- super(looper);
- }
-
- boolean singleCheckQueued = false;
- long queuedSingleDnsCheckArrival;
-
- /**
- * Sends a singleDnsCheck message with shortest time - guards against
- * multiple.
- */
- private boolean queueSingleDnsCheck() {
- long delay = timeToNextScheduledDNSCheck();
- long newArrival = delay + SystemClock.elapsedRealtime();
- if (singleCheckQueued && queuedSingleDnsCheckArrival <= newArrival)
- return true;
- queuedSingleDnsCheckArrival = newArrival;
- singleCheckQueued = true;
- removeMessages(SINGLE_DNS_CHECK);
- return sendMessageDelayed(obtainMessage(SINGLE_DNS_CHECK), delay);
- }
-
- boolean checkSequenceQueued = false;
- long queuedCheckSequenceArrival;
-
- /**
- * Sends a state_machine_step message if the delay requested is lower
- * than the current delay.
- */
- private boolean sendCheckSequenceStep(long delay) {
- long newArrival = delay + SystemClock.elapsedRealtime();
- if (checkSequenceQueued && queuedCheckSequenceArrival <= newArrival)
- return true;
- queuedCheckSequenceArrival = newArrival;
- checkSequenceQueued = true;
- removeMessages(CHECK_SEQUENCE_STEP);
- return sendMessageDelayed(obtainMessage(CHECK_SEQUENCE_STEP), delay);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case CHECK_SEQUENCE_STEP:
- checkSequenceQueued = false;
- handleStateStep();
- if (mStatus.state == WatchdogState.CHECKS_COMPLETE) {
- queueSingleDnsCheck();
- } else if (mStatus.state == WatchdogState.DNS_FULL_CHECK) {
- sendCheckSequenceStep(DNS_PING_INTERVAL);
- } else if (mStatus.state == WatchdogState.BLACKLISTED_AP) {
- sendCheckSequenceStep(BLACKLIST_FOLLOWUP_INTERVAL);
- } else if (mStatus.state != WatchdogState.INACTIVE) {
- sendCheckSequenceStep(0);
- }
- return;
- case MESSAGE_NETWORK_EVENT:
- if (!mBroadcastsEnabled) {
- Slog.e(WWS_TAG,
- "MessageNetworkEvent - WatchdogService not enabled... returning");
- return;
- }
- NetworkInfo info = (NetworkInfo) msg.obj;
- switch (info.getState()) {
- case DISCONNECTED:
- mStatus.state = WatchdogState.INACTIVE;
- return;
- case CONNECTED:
- handleNewConnection();
- sendCheckSequenceStep(0);
- }
- return;
- case SINGLE_DNS_CHECK:
- singleCheckQueued = false;
- if (mStatus.state != WatchdogState.CHECKS_COMPLETE) {
- Slog.d(WWS_TAG, "Single check returning, curState: " + mStatus.state);
- break;
- }
-
- if (!handleSingleDnsCheck()) {
- initDnsFullCheck();
- sendCheckSequenceStep(0);
- } else {
- queueSingleDnsCheck();
- }
-
- break;
- case RSSI_CHANGE_EVENT:
- updateRssi();
- if (mStatus.state == WatchdogState.CHECKS_COMPLETE)
- queueSingleDnsCheck();
- break;
- case SCAN_RESULTS_AVAILABLE:
- updateBssids();
- break;
- case WIFI_STATE_CHANGE:
- if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) {
- Slog.i(WWS_TAG, "WifiStateDisabling -- Resetting WatchdogState");
- mStatus = new Status();
- }
- break;
- case MESSAGE_CONTEXT_EVENT:
- if (isWatchdogEnabled() && !mBroadcastsEnabled) {
- mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
- mBroadcastsEnabled = true;
- Slog.i(WWS_TAG, "WifiWatchdogService enabled");
- } else if (!isWatchdogEnabled() && mBroadcastsEnabled) {
- mContext.unregisterReceiver(mBroadcastReceiver);
- removeMessages(SINGLE_DNS_CHECK);
- removeMessages(CHECK_SEQUENCE_STEP);
- mBroadcastsEnabled = false;
- Slog.i(WWS_TAG, "WifiWatchdogService disabled");
- }
- break;
- }
- }
- }
-
- public void dump(PrintWriter pw) {
- pw.print("WatchdogStatus: ");
- pw.print("State " + mStatus.state);
- pw.println(", network [" + mStatus.ssid + ", " + mStatus.bssid + "]");
- pw.print("checkCount " + mStatus.numFullDNSchecks);
- pw.println(", bssids: " + mStatus.allBssids);
- pw.print(", hasCheckMessages? " +
- mHandler.hasMessages(WifiWatchdogHandler.CHECK_SEQUENCE_STEP));
- pw.println(" hasSingleCheckMessages? " +
- mHandler.hasMessages(WifiWatchdogHandler.SINGLE_DNS_CHECK));
- pw.println("DNS check log str: " + mDNSCheckLogStr);
- pw.println("lastSingleCheck: " + mStatus.lastSingleCheckTime);
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED
- */
- private Boolean isWalledGardenTestEnabled() {
- return Settings.Secure.getInt(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, 1) == 1;
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_URL
- */
- private String getWalledGardenUrl() {
- String url = Settings.Secure.getString(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL);
- if (TextUtils.isEmpty(url))
- return "http://www.google.com/";
- return url;
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_PATTERN
- */
- private String getWalledGardenPattern() {
- String pattern = Settings.Secure.getString(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN);
- if (TextUtils.isEmpty(pattern))
- return "<title>.*Google.*</title>";
- return pattern;
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON
- */
- private boolean isWatchdogEnabled() {
- return Settings.Secure.getInt(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1;
- }
-}
diff --git a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
new file mode 100644
index 0000000..0eb73b7
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
@@ -0,0 +1,825 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.ConnectivityManager;
+import android.net.DnsPinger;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.os.Message;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Scanner;
+
+/**
+ * {@link WifiWatchdogStateMachine} monitors the initial connection to a Wi-Fi
+ * network with multiple access points. After the framework successfully
+ * connects to an access point, the watchdog verifies connectivity by 'pinging'
+ * the configured DNS server using {@link DnsPinger}.
+ * <p>
+ * On DNS check failure, the BSSID is blacklisted if it is reasonably likely
+ * that another AP might have internet access; otherwise the SSID is disabled.
+ * <p>
+ * On DNS success, the WatchdogService initiates a walled garden check via an
+ * http get. A browser window is activated if a walled garden is detected.
+ *
+ * @hide
+ */
+public class WifiWatchdogStateMachine extends StateMachine {
+
+ private static final boolean VDBG = false;
+ private static final boolean DBG = true;
+ private static final String WWSM_TAG = "WifiWatchdogStateMachine";
+
+ private static final int WIFI_SIGNAL_LEVELS = 4;
+ /**
+ * Low signal is defined as less than or equal to cut off
+ */
+ private static final int LOW_SIGNAL_CUTOFF = 1;
+
+ private static final long MIN_LOW_SIGNAL_CHECK_INTERVAL_MS = 2 * 60 * 1000;
+ private static final long MIN_SINGLE_DNS_CHECK_INTERVAL_MS = 10 * 60 * 1000;
+ private static final long MIN_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000;
+
+ private static final int MAX_CHECKS_PER_SSID = 7;
+ private static final int NUM_DNS_PINGS = 5;
+ private static final double MIN_DNS_RESPONSE_RATE = 0.50;
+
+ private static final int DNS_PING_TIMEOUT_MS = 800;
+ private static final long DNS_PING_INTERVAL_MS = 100;
+
+ private static final long BLACKLIST_FOLLOWUP_INTERVAL_MS = 15 * 1000;
+
+ private static final int BASE = Protocol.BASE_WIFI_WATCHDOG;
+
+ /**
+ * Indicates the enable setting of WWS may have changed
+ */
+ private static final int EVENT_WATCHDOG_TOGGLED = BASE + 1;
+
+ /**
+ * Indicates the wifi network state has changed. Passed w/ original intent
+ * which has a non-null networkInfo object
+ */
+ private static final int EVENT_NETWORK_STATE_CHANGE = BASE + 2;
+ /**
+ * Indicates the signal has changed. Passed with arg1
+ * {@link #mNetEventCounter} and arg2 [raw signal strength]
+ */
+ private static final int EVENT_RSSI_CHANGE = BASE + 3;
+ private static final int EVENT_SCAN_RESULTS_AVAILABLE = BASE + 4;
+ private static final int EVENT_WIFI_RADIO_STATE_CHANGE = BASE + 5;
+
+ private static final int MESSAGE_CHECK_STEP = BASE + 100;
+ private static final int MESSAGE_HANDLE_WALLED_GARDEN = BASE + 101;
+ private static final int MESSAGE_HANDLE_BAD_AP = BASE + 102;
+ /**
+ * arg1 == mOnlineWatchState.checkCount
+ */
+ private static final int MESSAGE_SINGLE_DNS_CHECK = BASE + 103;
+ private static final int MESSAGE_NETWORK_FOLLOWUP = BASE + 104;
+
+ private Context mContext;
+ private ContentResolver mContentResolver;
+ private WifiManager mWifiManager;
+ private DnsPinger mDnsPinger;
+ private IntentFilter mIntentFilter;
+ private BroadcastReceiver mBroadcastReceiver;
+
+ private DefaultState mDefaultState = new DefaultState();
+ private WatchdogDisabledState mWatchdogDisabledState = new WatchdogDisabledState();
+ private WatchdogEnabledState mWatchdogEnabledState = new WatchdogEnabledState();
+ private NotConnectedState mNotConnectedState = new NotConnectedState();
+ private ConnectedState mConnectedState = new ConnectedState();
+ private DnsCheckingState mDnsCheckingState = new DnsCheckingState();
+ private OnlineWatchState mOnlineWatchState = new OnlineWatchState();
+ private DnsCheckFailureState mDnsCheckFailureState = new DnsCheckFailureState();
+ private WalledGardenState mWalledGardenState = new WalledGardenState();
+ private BlacklistedApState mBlacklistedApState = new BlacklistedApState();
+
+ /**
+ * The {@link WifiInfo} object passed to WWSM on network broadcasts
+ */
+ private WifiInfo mInitialConnInfo;
+ private int mNetEventCounter = 0;
+
+ /**
+ * Currently maintained but not used, TODO
+ */
+ private HashSet<String> mBssids = new HashSet<String>();
+ private int mNumFullDNSchecks = 0;
+
+ private Long mLastWalledGardenCheckTime = null;
+
+ /**
+ * This is set by the blacklisted state and reset when connected to a new AP.
+ * It triggers a disableNetwork call if a DNS check fails.
+ */
+ public boolean mDisableAPNextFailure = false;
+
+ /**
+ * STATE MAP
+ * Default
+ * / \
+ * Disabled Enabled
+ * / \
+ * Disconnected Connected
+ * /---------\
+ * (all other states)
+ */
+ private WifiWatchdogStateMachine(Context context) {
+ super(WWSM_TAG);
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ mDnsPinger = new DnsPinger("WifiWatchdogServer.DnsPinger", context,
+ ConnectivityManager.TYPE_WIFI);
+
+ setupNetworkReceiver();
+
+ // The content observer to listen needs a handler
+ registerForSettingsChanges();
+ addState(mDefaultState);
+ addState(mWatchdogDisabledState, mDefaultState);
+ addState(mWatchdogEnabledState, mDefaultState);
+ addState(mNotConnectedState, mWatchdogEnabledState);
+ addState(mConnectedState, mWatchdogEnabledState);
+ addState(mDnsCheckingState, mConnectedState);
+ addState(mDnsCheckFailureState, mConnectedState);
+ addState(mWalledGardenState, mConnectedState);
+ addState(mBlacklistedApState, mConnectedState);
+ addState(mOnlineWatchState, mConnectedState);
+
+ setInitialState(mWatchdogDisabledState);
+ }
+
+ public static WifiWatchdogStateMachine makeWifiWatchdogStateMachine(Context context) {
+ WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context);
+ wwsm.start();
+ wwsm.sendMessage(EVENT_WATCHDOG_TOGGLED);
+ return wwsm;
+ }
+
+ /**
+ *
+ */
+ private void setupNetworkReceiver() {
+ mBroadcastReceiver = new BroadcastReceiver() {
+ @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)) {
+ obtainMessage(EVENT_RSSI_CHANGE, mNetEventCounter,
+ intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200)).sendToTarget();
+ } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+ sendMessage(EVENT_SCAN_RESULTS_AVAILABLE);
+ } 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));
+ }
+ }
+ };
+
+ mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
+ mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ }
+
+ /**
+ * Observes the watchdog on/off setting, and takes action when changed.
+ */
+ private void registerForSettingsChanges() {
+ ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ sendMessage(EVENT_WATCHDOG_TOGGLED);
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON),
+ false, contentObserver);
+ }
+
+ /**
+ * DNS based detection techniques do not work at all hotspots. The one sure
+ * way to check a walled garden is to see if a URL fetch on a known address
+ * fetches the data we expect
+ */
+ private boolean isWalledGardenConnection() {
+ InputStream in = null;
+ HttpURLConnection urlConnection = null;
+ try {
+ URL url = new URL(getWalledGardenUrl());
+ urlConnection = (HttpURLConnection) url.openConnection();
+ in = new BufferedInputStream(urlConnection.getInputStream());
+ Scanner scanner = new Scanner(in);
+ if (scanner.findInLine(getWalledGardenPattern()) != null) {
+ return false;
+ } else {
+ return true;
+ }
+ } catch (IOException e) {
+ return false;
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ }
+ }
+ if (urlConnection != null)
+ urlConnection.disconnect();
+ }
+ }
+
+ private boolean rssiStrengthAboveCutoff(int rssi) {
+ return WifiManager.calculateSignalLevel(rssi, WIFI_SIGNAL_LEVELS) > LOW_SIGNAL_CUTOFF;
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.print("WatchdogStatus: ");
+ pw.print("State " + getCurrentState());
+ pw.println(", network [" + mInitialConnInfo + "]");
+ pw.print("checkCount " + mNumFullDNSchecks);
+ pw.println(", bssids: " + mBssids);
+ pw.println("lastSingleCheck: " + mOnlineWatchState.lastCheckTime);
+ }
+
+ /**
+ * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED
+ */
+ private Boolean isWalledGardenTestEnabled() {
+ return Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, 1) == 1;
+ }
+
+ /**
+ * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_URL
+ */
+ private String getWalledGardenUrl() {
+ String url = Settings.Secure.getString(mContentResolver,
+ Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL);
+ if (TextUtils.isEmpty(url))
+ return "http://www.google.com/";
+ return url;
+ }
+
+ /**
+ * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_PATTERN
+ */
+ private String getWalledGardenPattern() {
+ String pattern = Settings.Secure.getString(mContentResolver,
+ Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN);
+ if (TextUtils.isEmpty(pattern))
+ return "<title>.*Google.*</title>";
+ return pattern;
+ }
+
+ /**
+ * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON
+ */
+ private boolean isWatchdogEnabled() {
+ return Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1;
+ }
+
+
+ /**
+ * Helper to return wait time left given a min interval and last run
+ *
+ * @param interval minimum wait interval
+ * @param lastTime last time action was performed in
+ * SystemClock.elapsedRealtime(). Null if never.
+ * @return non negative time to wait
+ */
+ private static long waitTime(long interval, Long lastTime) {
+ if (lastTime == null)
+ return 0;
+ long wait = interval + lastTime - SystemClock.elapsedRealtime();
+ return wait > 0 ? wait : 0;
+ }
+
+ private static String wifiInfoToStr(WifiInfo wifiInfo) {
+ if (wifiInfo == null)
+ return "null";
+ return "(" + wifiInfo.getSSID() + ", " + wifiInfo.getBSSID() + ")";
+ }
+
+ /**
+ *
+ */
+ private void resetWatchdogState() {
+ mInitialConnInfo = null;
+ mDisableAPNextFailure = false;
+ mLastWalledGardenCheckTime = null;
+ mNumFullDNSchecks = 0;
+ mBssids.clear();
+ }
+
+ private void popUpBrowser() {
+ Uri uri = Uri.parse("http://www.google.com");
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ }
+
+ private void sendCheckStepMessage(long delay) {
+ sendMessageDelayed(obtainMessage(MESSAGE_CHECK_STEP, mNetEventCounter, 0), delay);
+ }
+
+ class DefaultState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ if (VDBG) {
+ Slog.v(WWSM_TAG, "Caught message " + msg.what + " in state " +
+ getCurrentState().getName());
+ }
+ return HANDLED;
+ }
+ }
+
+ class WatchdogDisabledState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_WATCHDOG_TOGGLED:
+ if (isWatchdogEnabled())
+ transitionTo(mNotConnectedState);
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ class WatchdogEnabledState extends State {
+ @Override
+ public void enter() {
+ resetWatchdogState();
+ mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
+ Slog.i(WWSM_TAG, "WifiWatchdogService enabled");
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_WATCHDOG_TOGGLED:
+ if (!isWatchdogEnabled())
+ transitionTo(mWatchdogDisabledState);
+ return HANDLED;
+ case EVENT_NETWORK_STATE_CHANGE:
+ Intent stateChangeIntent = (Intent) msg.obj;
+ NetworkInfo networkInfo = (NetworkInfo)
+ stateChangeIntent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+
+ switch (networkInfo.getState()) {
+ case CONNECTED:
+ // WifiInfo wifiInfo = (WifiInfo)
+ // stateChangeIntent
+ // .getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
+ // TODO : Replace with above code when API is changed
+ WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+ if (wifiInfo == null) {
+ Slog.e(WWSM_TAG, "Connected --> WifiInfo object null!");
+ return HANDLED;
+ }
+
+ if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) {
+ Slog.e(WWSM_TAG, "Received wifiInfo object with null elts: "
+ + wifiInfoToStr(wifiInfo));
+ return HANDLED;
+ }
+
+ initConnection(wifiInfo);
+ transitionTo(mDnsCheckingState);
+ mNetEventCounter++;
+ return HANDLED;
+ case DISCONNECTED:
+ case DISCONNECTING:
+ mNetEventCounter++;
+ transitionTo(mNotConnectedState);
+ return HANDLED;
+ }
+ return HANDLED;
+ case EVENT_WIFI_RADIO_STATE_CHANGE:
+ if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) {
+ Slog.i(WWSM_TAG, "WifiStateDisabling -- Resetting WatchdogState");
+ resetWatchdogState();
+ mNetEventCounter++;
+ transitionTo(mNotConnectedState);
+ }
+ return HANDLED;
+ }
+
+ return NOT_HANDLED;
+ }
+
+ /**
+ * @param wifiInfo Info object with non-null ssid and bssid
+ */
+ private void initConnection(WifiInfo wifiInfo) {
+ if (VDBG) {
+ Slog.v(WWSM_TAG, "Connected:: old " + wifiInfoToStr(mInitialConnInfo) +
+ " ==> new " + wifiInfoToStr(wifiInfo));
+ }
+
+ if (mInitialConnInfo == null || !wifiInfo.getSSID().equals(mInitialConnInfo.getSSID())) {
+ resetWatchdogState();
+ } else if (!wifiInfo.getBSSID().equals(mInitialConnInfo.getBSSID())) {
+ mDisableAPNextFailure = false;
+ }
+ mInitialConnInfo = wifiInfo;
+ }
+
+ @Override
+ public void exit() {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ Slog.i(WWSM_TAG, "WifiWatchdogService disabled");
+ }
+ }
+
+ class NotConnectedState extends State {
+ }
+
+ class ConnectedState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_SCAN_RESULTS_AVAILABLE:
+ String curSsid = mInitialConnInfo.getSSID();
+ List<ScanResult> results = mWifiManager.getScanResults();
+ int oldNumBssids = mBssids.size();
+
+ if (results == null) {
+ if (DBG) {
+ Slog.d(WWSM_TAG, "updateBssids: Got null scan results!");
+ }
+ return HANDLED;
+ }
+
+ for (ScanResult result : results) {
+ if (result == null || result.SSID == null) {
+ if (VDBG) {
+ Slog.v(WWSM_TAG, "Received invalid scan result: " + result);
+ }
+ continue;
+ }
+ if (curSsid.equals(result.SSID))
+ mBssids.add(result.BSSID);
+ }
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+
+ }
+
+ class DnsCheckingState extends State {
+ int dnsCheckTries = 0;
+ int dnsCheckSuccesses = 0;
+ String dnsCheckLogStr = "";
+
+ @Override
+ public void enter() {
+ mNumFullDNSchecks++;
+ dnsCheckSuccesses = 0;
+ dnsCheckTries = 0;
+ if (DBG) {
+ Slog.d(WWSM_TAG, "Starting DNS pings at " + SystemClock.elapsedRealtime());
+ dnsCheckLogStr = String.format("Dns Check %d. Pinging %s on ssid [%s]: ",
+ mNumFullDNSchecks, mDnsPinger.getDns(), mInitialConnInfo.getSSID());
+ }
+
+ sendCheckStepMessage(0);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (msg.what != MESSAGE_CHECK_STEP) {
+ return NOT_HANDLED;
+ }
+ if (msg.arg1 != mNetEventCounter) {
+ Slog.d(WWSM_TAG, "Check step out of sync, ignoring...");
+ return HANDLED;
+ }
+
+ long pingResponseTime = mDnsPinger.pingDns(mDnsPinger.getDns(),
+ DNS_PING_TIMEOUT_MS);
+
+ dnsCheckTries++;
+ if (pingResponseTime >= 0)
+ dnsCheckSuccesses++;
+
+ if (DBG) {
+ if (pingResponseTime >= 0) {
+ dnsCheckLogStr += "|" + pingResponseTime;
+ } else {
+ dnsCheckLogStr += "|x";
+ }
+ }
+
+ if (VDBG) {
+ Slog.v(WWSM_TAG, dnsCheckLogStr);
+ }
+
+ /**
+ * After a full ping count, if we have more responses than this
+ * cutoff, the outcome is success; else it is 'failure'.
+ */
+ double pingResponseCutoff = MIN_DNS_RESPONSE_RATE * NUM_DNS_PINGS;
+ int remainingChecks = NUM_DNS_PINGS - dnsCheckTries;
+
+ /**
+ * Our final success count will be at least this big, so we're
+ * guaranteed to succeed.
+ */
+ if (dnsCheckSuccesses >= pingResponseCutoff) {
+ // DNS CHECKS OK, NOW WALLED GARDEN
+ if (DBG) {
+ Slog.d(WWSM_TAG, dnsCheckLogStr + "| SUCCESS");
+ }
+
+ if (!shouldCheckWalledGarden()) {
+ transitionTo(mOnlineWatchState);
+ return HANDLED;
+ }
+
+ mLastWalledGardenCheckTime = SystemClock.elapsedRealtime();
+ if (isWalledGardenConnection()) {
+ if (DBG)
+ Slog.d(WWSM_TAG,
+ "Walled garden test complete - walled garden detected");
+ transitionTo(mWalledGardenState);
+ } else {
+ if (DBG)
+ Slog.d(WWSM_TAG, "Walled garden test complete - online");
+ transitionTo(mOnlineWatchState);
+ }
+ return HANDLED;
+ }
+
+ /**
+ * Our final count will be at most the current count plus the
+ * remaining pings - we're guaranteed to fail.
+ */
+ if (remainingChecks + dnsCheckSuccesses < pingResponseCutoff) {
+ if (DBG) {
+ Slog.d(WWSM_TAG, dnsCheckLogStr + "| FAILURE");
+ }
+ transitionTo(mDnsCheckFailureState);
+ return HANDLED;
+ }
+
+ // Still in dns check step
+ sendCheckStepMessage(DNS_PING_INTERVAL_MS);
+ return HANDLED;
+ }
+
+ private boolean shouldCheckWalledGarden() {
+ if (!isWalledGardenTestEnabled()) {
+ if (VDBG)
+ Slog.v(WWSM_TAG, "Skipping walled garden check - disabled");
+ return false;
+ }
+ long waitTime = waitTime(MIN_WALLED_GARDEN_INTERVAL_MS,
+ mLastWalledGardenCheckTime);
+ if (waitTime > 0) {
+ if (DBG) {
+ Slog.d(WWSM_TAG, "Skipping walled garden check - wait " +
+ waitTime + " ms.");
+ }
+ return false;
+ }
+ return true;
+ }
+
+ }
+
+ class OnlineWatchState extends State {
+ /**
+ * Signals a short-wait message is enqueued for the current 'guard' counter
+ */
+ boolean unstableSignalChecks = false;
+
+ /**
+ * The signal is unstable. We should enqueue a short-wait check, if one is enqueued
+ * already
+ */
+ boolean signalUnstable = false;
+
+ /**
+ * A monotonic counter to ensure that at most one check message will be processed from any
+ * set of check messages currently enqueued. Avoids duplicate checks when a low-signal
+ * event is observed.
+ */
+ int checkGuard = 0;
+ Long lastCheckTime = null;
+
+ @Override
+ public void enter() {
+ lastCheckTime = SystemClock.elapsedRealtime();
+ signalUnstable = false;
+ checkGuard++;
+ unstableSignalChecks = false;
+ triggerSingleDnsCheck();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_RSSI_CHANGE:
+ if (msg.arg1 != mNetEventCounter) {
+ if (DBG) {
+ Slog.d(WWSM_TAG, "Rssi change message out of sync, ignoring");
+ }
+ return HANDLED;
+ }
+ int newRssi = msg.arg2;
+ signalUnstable = !rssiStrengthAboveCutoff(newRssi);
+ if (VDBG) {
+ Slog.v(WWSM_TAG, "OnlineWatchState:: new rssi " + newRssi + " --> level " +
+ WifiManager.calculateSignalLevel(newRssi, WIFI_SIGNAL_LEVELS));
+ }
+
+ if (signalUnstable && !unstableSignalChecks) {
+ if (VDBG) {
+ Slog.v(WWSM_TAG, "Sending triggered check msg");
+ }
+ triggerSingleDnsCheck();
+ }
+ return HANDLED;
+ case MESSAGE_SINGLE_DNS_CHECK:
+ if (msg.arg1 != checkGuard) {
+ if (VDBG) {
+ Slog.v(WWSM_TAG, "Single check msg out of sync, ignoring.");
+ }
+ return HANDLED;
+ }
+ lastCheckTime = SystemClock.elapsedRealtime();
+ long responseTime = mDnsPinger.pingDns(mDnsPinger.getDns(),
+ DNS_PING_TIMEOUT_MS);
+ if (responseTime >= 0) {
+ if (VDBG) {
+ Slog.v(WWSM_TAG, "Ran a single DNS ping. Response time: "
+ + responseTime);
+ }
+
+ checkGuard++;
+ unstableSignalChecks = false;
+ triggerSingleDnsCheck();
+ } else {
+ if (DBG) {
+ Slog.d(WWSM_TAG, "Single dns ping failure. Starting full checks.");
+ }
+ transitionTo(mDnsCheckingState);
+ }
+ return HANDLED;
+ }
+ return NOT_HANDLED;
+ }
+
+ /**
+ * Times a dns check with an interval based on {@link #curSignalStable}
+ */
+ private void triggerSingleDnsCheck() {
+ long waitInterval;
+ if (signalUnstable) {
+ waitInterval = MIN_LOW_SIGNAL_CHECK_INTERVAL_MS;
+ unstableSignalChecks = true;
+ } else {
+ waitInterval = MIN_SINGLE_DNS_CHECK_INTERVAL_MS;
+ }
+ sendMessageDelayed(obtainMessage(MESSAGE_SINGLE_DNS_CHECK, checkGuard, 0),
+ waitTime(waitInterval, lastCheckTime));
+ }
+ }
+
+ class DnsCheckFailureState extends State {
+ @Override
+ public void enter() {
+ obtainMessage(MESSAGE_HANDLE_BAD_AP, mNetEventCounter, 0).sendToTarget();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (msg.what != MESSAGE_HANDLE_BAD_AP) {
+ return NOT_HANDLED;
+ }
+
+ if (msg.arg1 != mNetEventCounter) {
+ if (VDBG) {
+ Slog.v(WWSM_TAG, "Msg out of sync, ignoring...");
+ }
+ return HANDLED;
+ }
+
+ if (mDisableAPNextFailure || mNumFullDNSchecks >= MAX_CHECKS_PER_SSID) {
+ // TODO : Unban networks if they had low signal ?
+ Slog.i(WWSM_TAG, "Disabling current SSID " + wifiInfoToStr(mInitialConnInfo)
+ + ". " +
+ "numChecks " + mNumFullDNSchecks + ", numAPs " + mBssids.size());
+ mWifiManager.disableNetwork(mInitialConnInfo.getNetworkId());
+ transitionTo(mNotConnectedState);
+ } else {
+ Slog.i(WWSM_TAG, "Blacklisting current BSSID. " + wifiInfoToStr(mInitialConnInfo) +
+ "numChecks " + mNumFullDNSchecks + ", numAPs " + mBssids.size());
+
+ mWifiManager.addToBlacklist(mInitialConnInfo.getBSSID());
+ mWifiManager.reassociate();
+ transitionTo(mBlacklistedApState);
+ }
+ return HANDLED;
+ }
+ }
+
+ class WalledGardenState extends State {
+ @Override
+ public void enter() {
+ obtainMessage(MESSAGE_HANDLE_WALLED_GARDEN, mNetEventCounter, 0).sendToTarget();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (msg.what != MESSAGE_HANDLE_WALLED_GARDEN) {
+ return NOT_HANDLED;
+ }
+
+ if (msg.arg1 != mNetEventCounter) {
+ if (VDBG) {
+ Slog.v(WWSM_TAG, "WalledGardenState::Msg out of sync, ignoring...");
+ }
+ return HANDLED;
+ }
+ popUpBrowser();
+ transitionTo(mOnlineWatchState);
+ return HANDLED;
+ }
+ }
+
+ class BlacklistedApState extends State {
+ @Override
+ public void enter() {
+ mDisableAPNextFailure = true;
+ sendMessageDelayed(obtainMessage(MESSAGE_NETWORK_FOLLOWUP, mNetEventCounter, 0),
+ BLACKLIST_FOLLOWUP_INTERVAL_MS);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (msg.what != MESSAGE_NETWORK_FOLLOWUP) {
+ return NOT_HANDLED;
+ }
+
+ if (msg.arg1 != mNetEventCounter) {
+ if (VDBG) {
+ Slog.v(WWSM_TAG, "BlacklistedApState::Msg out of sync, ignoring...");
+ }
+ return HANDLED;
+ }
+
+ transitionTo(mDnsCheckingState);
+ return HANDLED;
+ }
+ }
+}