Merge "System server: Add Bluetooth to native processes" into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index e1569f1..e91d017 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6073,6 +6073,7 @@
field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
field public static final int KEYGUARD_DISABLE_FINGERPRINT = 32; // 0x20
+ field public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 64; // 0x40
field public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 2; // 0x2
field public static final int KEYGUARD_DISABLE_SECURE_NOTIFICATIONS = 4; // 0x4
field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
diff --git a/api/system-current.txt b/api/system-current.txt
index 55f8b6f..8781663 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6240,6 +6240,7 @@
field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
field public static final int KEYGUARD_DISABLE_FINGERPRINT = 32; // 0x20
+ field public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 64; // 0x40
field public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 2; // 0x2
field public static final int KEYGUARD_DISABLE_SECURE_NOTIFICATIONS = 4; // 0x4
field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
diff --git a/api/test-current.txt b/api/test-current.txt
index 1376ef1..410eb88 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6078,6 +6078,7 @@
field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
field public static final int KEYGUARD_DISABLE_FINGERPRINT = 32; // 0x20
+ field public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 64; // 0x40
field public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 2; // 0x2
field public static final int KEYGUARD_DISABLE_SECURE_NOTIFICATIONS = 4; // 0x4
field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e54edf2..cfffe34 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4183,12 +4183,10 @@
// window is being added.
r.mPendingRemoveWindow = r.window;
r.mPendingRemoveWindowManager = wm;
- if (r.mPreserveWindow) {
- // We can only keep the part of the view hierarchy that we control,
- // everything else must be removed, because it might not be able to
- // behave properly when activity is relaunching.
- r.window.clearContentView();
- }
+ // We can only keep the part of the view hierarchy that we control,
+ // everything else must be removed, because it might not be able to
+ // behave properly when activity is relaunching.
+ r.window.clearContentView();
} else {
wm.removeViewImmediate(v);
}
@@ -4196,6 +4194,13 @@
if (wtoken != null && r.mPendingRemoveWindow == null) {
WindowManagerGlobal.getInstance().closeAll(wtoken,
r.activity.getClass().getName(), "Activity");
+ } else if (r.mPendingRemoveWindow != null) {
+ // We're preserving only one window, others should be closed so app views
+ // will be detached before the final tear down. It should be done now because
+ // some components (e.g. WebView) rely on detach callbacks to perform receiver
+ // unregister and other cleanup.
+ WindowManagerGlobal.getInstance().closeAllExceptView(token, v,
+ r.activity.getClass().getName(), "Activity");
}
r.activity.mDecor = null;
}
@@ -5205,10 +5210,6 @@
} else {
Log.e(TAG, "Unable to setupGraphicsSupport due to missing code-cache directory");
}
-
- // Add the lib dir path to hardware renderer so that vulkan layers
- // can be searched for within that directory.
- ThreadedRenderer.setLibDir(data.info.getLibDir());
}
// Install the Network Security Config Provider. This must happen before the application
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
index d89e186..c869944 100644
--- a/core/java/android/app/ApplicationLoaders.java
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -65,6 +65,8 @@
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ setupVulkanLayerPath(pathClassloader, librarySearchPath);
+
mLoaders.put(zip, pathClassloader);
return pathClassloader;
}
@@ -76,6 +78,8 @@
}
}
+ private static native void setupVulkanLayerPath(ClassLoader classLoader, String librarySearchPath);
+
/**
* Adds a new path the classpath of the given loader.
* @throws IllegalStateException if the provided class loader is not a {@link PathClassLoader}.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 451acf3..f78ed1e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2571,6 +2571,11 @@
public static final int KEYGUARD_DISABLE_FINGERPRINT = 1 << 5;
/**
+ * Disable text entry into notifications on secure keyguard screens (e.g. PIN/Pattern/Password).
+ */
+ public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 1 << 6;
+
+ /**
* Disable all current and future keyguard customizations.
*/
public static final int KEYGUARD_DISABLE_FEATURES_ALL = 0x7fffffff;
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
index 0d25f80..7ed827c 100644
--- a/core/java/android/content/ContentValues.java
+++ b/core/java/android/content/ContentValues.java
@@ -230,11 +230,12 @@
}
/**
- * Gets a value. Valid value types are {@link String}, {@link Boolean}, and
- * {@link Number} implementations.
+ * Gets a value. Valid value types are {@link String}, {@link Boolean},
+ * {@link Number}, and {@code byte[]} implementations.
*
* @param key the value to get
- * @return the data for the value
+ * @return the data for the value, or {@code null} if the value is missing or if {@code null}
+ * was previously added with the given {@code key}
*/
public Object get(String key) {
return mValues.get(key);
@@ -255,7 +256,7 @@
* Gets a value and converts it to a Long.
*
* @param key the value to get
- * @return the Long value, or null if the value is missing or cannot be converted
+ * @return the Long value, or {@code null} if the value is missing or cannot be converted
*/
public Long getAsLong(String key) {
Object value = mValues.get(key);
@@ -280,7 +281,7 @@
* Gets a value and converts it to an Integer.
*
* @param key the value to get
- * @return the Integer value, or null if the value is missing or cannot be converted
+ * @return the Integer value, or {@code null} if the value is missing or cannot be converted
*/
public Integer getAsInteger(String key) {
Object value = mValues.get(key);
@@ -305,7 +306,7 @@
* Gets a value and converts it to a Short.
*
* @param key the value to get
- * @return the Short value, or null if the value is missing or cannot be converted
+ * @return the Short value, or {@code null} if the value is missing or cannot be converted
*/
public Short getAsShort(String key) {
Object value = mValues.get(key);
@@ -330,7 +331,7 @@
* Gets a value and converts it to a Byte.
*
* @param key the value to get
- * @return the Byte value, or null if the value is missing or cannot be converted
+ * @return the Byte value, or {@code null} if the value is missing or cannot be converted
*/
public Byte getAsByte(String key) {
Object value = mValues.get(key);
@@ -355,7 +356,7 @@
* Gets a value and converts it to a Double.
*
* @param key the value to get
- * @return the Double value, or null if the value is missing or cannot be converted
+ * @return the Double value, or {@code null} if the value is missing or cannot be converted
*/
public Double getAsDouble(String key) {
Object value = mValues.get(key);
@@ -380,7 +381,7 @@
* Gets a value and converts it to a Float.
*
* @param key the value to get
- * @return the Float value, or null if the value is missing or cannot be converted
+ * @return the Float value, or {@code null} if the value is missing or cannot be converted
*/
public Float getAsFloat(String key) {
Object value = mValues.get(key);
@@ -405,7 +406,7 @@
* Gets a value and converts it to a Boolean.
*
* @param key the value to get
- * @return the Boolean value, or null if the value is missing or cannot be converted
+ * @return the Boolean value, or {@code null} if the value is missing or cannot be converted
*/
public Boolean getAsBoolean(String key) {
Object value = mValues.get(key);
@@ -428,7 +429,8 @@
* any other types to byte arrays.
*
* @param key the value to get
- * @return the byte[] value, or null is the value is missing or not a byte[]
+ * @return the {@code byte[]} value, or {@code null} is the value is missing or not a
+ * {@code byte[]}
*/
public byte[] getAsByteArray(String key) {
Object value = mValues.get(key);
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 50e7356..0f64b92 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1431,10 +1431,9 @@
* row. The keys should be the column names and the values the
* column values
* @param conflictAlgorithm for insert conflict resolver
- * @return the row ID of the newly inserted row
- * OR the primary key of the existing row if the input param 'conflictAlgorithm' =
- * {@link #CONFLICT_IGNORE}
- * OR -1 if any error
+ * @return the row ID of the newly inserted row OR <code>-1</code> if either the
+ * input parameter <code>conflictAlgorithm</code> = {@link #CONFLICT_IGNORE}
+ * or an error occurred.
*/
public long insertWithOnConflict(String table, String nullColumnHack,
ContentValues initialValues, int conflictAlgorithm) {
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 1cd2fb0..e650d95 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -252,17 +252,6 @@
}
/**
- * Sets the library directory to use as a search path for vulkan layers.
- *
- * @param libDir A directory that contains vulkan layers
- *
- * @hide
- */
- public static void setLibDir(String libDir) {
- ThreadedRenderer.setupVulkanLayerPath(libDir);
- }
-
- /**
* Creates a hardware renderer using OpenGL.
*
* @param translucent True if the surface is translucent, false otherwise
@@ -980,7 +969,6 @@
}
static native void setupShadersDiskCache(String cacheFile);
- static native void setupVulkanLayerPath(String layerPath);
private static native void nSetAtlas(long nativeProxy, GraphicBuffer buffer, long[] map);
private static native void nSetProcessStatsBuffer(long nativeProxy, int fd);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4e7d1914..77884f6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8740,7 +8740,7 @@
* @param direction The direction of the focus
*/
public void addFocusables(ArrayList<View> views, @FocusDirection int direction) {
- addFocusables(views, direction, FOCUSABLES_TOUCH_MODE);
+ addFocusables(views, direction, isInTouchMode() ? FOCUSABLES_TOUCH_MODE : FOCUSABLES_ALL);
}
/**
@@ -8768,7 +8768,7 @@
return;
}
if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
- && isInTouchMode() && !isFocusableInTouchMode()) {
+ && !isFocusableInTouchMode()) {
return;
}
views.add(this);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 9b74fc0..fe24230 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1803,7 +1803,21 @@
*/
public final void setSurfaceInsets(View view, boolean manual, boolean preservePrevious) {
final int surfaceInset = (int) Math.ceil(view.getZ() * 2);
- surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
+ // Partial workaround for b/28318973. Every inset change causes a freeform window
+ // to jump a little for a few frames. If we never allow surface insets to decrease,
+ // they will stabilize quickly (often from the very beginning, as most windows start
+ // as focused).
+ // TODO(b/22668382) to fix this properly.
+ if (surfaceInset == 0) {
+ // OK to have 0 (this is the case for non-freeform windows).
+ surfaceInsets.set(0, 0, 0, 0);
+ } else {
+ surfaceInsets.set(
+ Math.max(surfaceInset, surfaceInsets.left),
+ Math.max(surfaceInset, surfaceInsets.top),
+ Math.max(surfaceInset, surfaceInsets.right),
+ Math.max(surfaceInset, surfaceInsets.bottom));
+ }
hasManualSurfaceInsets = manual;
preservePreviousSurfaceInsets = preservePrevious;
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 887cc3a..11734d3 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -391,17 +391,34 @@
}
}
+ /**
+ * Remove all roots with specified token.
+ *
+ * @param token app or window token.
+ * @param who name of caller, used in logs.
+ * @param what type of caller, used in logs.
+ */
public void closeAll(IBinder token, String who, String what) {
+ closeAllExceptView(token, null /* view */, who, what);
+ }
+
+ /**
+ * Remove all roots with specified token, except maybe one view.
+ *
+ * @param token app or window token.
+ * @param view view that should be should be preserved along with it's root.
+ * Pass null if everything should be removed.
+ * @param who name of caller, used in logs.
+ * @param what type of caller, used in logs.
+ */
+ public void closeAllExceptView(IBinder token, View view, String who, String what) {
synchronized (mLock) {
int count = mViews.size();
- //Log.i("foo", "Closing all windows of " + token);
for (int i = 0; i < count; i++) {
- //Log.i("foo", "@ " + i + " token " + mParams[i].token
- // + " view " + mRoots[i].getView());
- if (token == null || mParams.get(i).token == token) {
+ if ((view == null || mViews.get(i) != view)
+ && (token == null || mParams.get(i).token == token)) {
ViewRootImpl root = mRoots.get(i);
- //Log.i("foo", "Force closing " + root);
if (who != null) {
WindowLeaked leak = new WindowLeaked(
what + " " + who + " has leaked window "
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 2b7afea..5554182 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -549,6 +549,7 @@
private static boolean startSystemServer(String abiList, String socketName)
throws MethodAndArgsCaller, RuntimeException {
long capabilities = posixCapabilitiesAsBits(
+ OsConstants.CAP_IPC_LOCK,
OsConstants.CAP_KILL,
OsConstants.CAP_NET_ADMIN,
OsConstants.CAP_NET_BIND_SERVICE,
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 986a5fd..dd66b24 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -34,6 +34,7 @@
com_google_android_gles_jni_EGLImpl.cpp \
com_google_android_gles_jni_GLImpl.cpp.arm \
android_app_Activity.cpp \
+ android_app_ApplicationLoaders.cpp \
android_app_NativeActivity.cpp \
android_app_admin_SecurityLog.cpp \
android_opengl_EGL14.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index c2273d6..315887d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -181,6 +181,7 @@
extern int register_android_app_backup_FullBackup(JNIEnv *env);
extern int register_android_app_Activity(JNIEnv *env);
extern int register_android_app_ActivityThread(JNIEnv *env);
+extern int register_android_app_ApplicationLoaders(JNIEnv *env);
extern int register_android_app_NativeActivity(JNIEnv *env);
extern int register_android_media_RemoteDisplay(JNIEnv *env);
extern int register_android_util_jar_StrictJarFile(JNIEnv* env);
@@ -1389,6 +1390,7 @@
REG_JNI(register_android_app_backup_FullBackup),
REG_JNI(register_android_app_Activity),
REG_JNI(register_android_app_ActivityThread),
+ REG_JNI(register_android_app_ApplicationLoaders),
REG_JNI(register_android_app_NativeActivity),
REG_JNI(register_android_util_jar_StrictJarFile),
REG_JNI(register_android_view_InputChannel),
diff --git a/core/jni/android_app_ApplicationLoaders.cpp b/core/jni/android_app_ApplicationLoaders.cpp
new file mode 100644
index 0000000..24ee959
--- /dev/null
+++ b/core/jni/android_app_ApplicationLoaders.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2016 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 <nativehelper/ScopedUtfChars.h>
+#include <nativeloader/native_loader.h>
+#include <vulkan/vulkan_loader_data.h>
+
+#include "core_jni_helpers.h"
+
+static void setupVulkanLayerPath_native(JNIEnv* env, jobject clazz,
+ jobject classLoader, jstring librarySearchPath) {
+ ScopedUtfChars layerPathChars(env, librarySearchPath);
+ vulkan::LoaderData& loader_data = vulkan::LoaderData::GetInstance();
+ loader_data.layer_path = layerPathChars.c_str();
+ loader_data.app_namespace = android::FindNamespaceByClassLoader(env, classLoader);
+}
+
+static const JNINativeMethod g_methods[] = {
+ { "setupVulkanLayerPath", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V",
+ reinterpret_cast<void*>(setupVulkanLayerPath_native) },
+};
+
+static const char* const kApplicationLoadersName = "android/app/ApplicationLoaders";
+
+namespace android
+{
+
+int register_android_app_ApplicationLoaders(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, kApplicationLoadersName, g_methods, NELEM(g_methods));
+}
+
+} // namespace android
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 650a0fc..5b4bfe9 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -28,7 +28,6 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <EGL/egl_cache.h>
-#include <vulkan/vulkan_loader_data.h>
#include <utils/Looper.h>
#include <utils/RefBase.h>
@@ -50,7 +49,6 @@
#include <renderthread/RenderProxy.h>
#include <renderthread/RenderTask.h>
#include <renderthread/RenderThread.h>
-#include <Vector.h>
namespace android {
@@ -718,18 +716,6 @@
}
// ----------------------------------------------------------------------------
-// Layers
-// ----------------------------------------------------------------------------
-
-static void android_view_ThreadedRenderer_setupVulkanLayerPath(JNIEnv* env, jobject clazz,
- jstring layerPath) {
-
- const char* layerArray = env->GetStringUTFChars(layerPath, NULL);
- vulkan::LoaderData::GetInstance().layer_path = layerArray;
- env->ReleaseStringUTFChars(layerPath, layerArray);
-}
-
-// ----------------------------------------------------------------------------
// JNI Glue
// ----------------------------------------------------------------------------
@@ -771,8 +757,6 @@
{ "nDumpProfileData", "([BLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileData },
{ "setupShadersDiskCache", "(Ljava/lang/String;)V",
(void*) android_view_ThreadedRenderer_setupShadersDiskCache },
- { "setupVulkanLayerPath", "(Ljava/lang/String;)V",
- (void*) android_view_ThreadedRenderer_setupVulkanLayerPath },
{ "nAddRenderNode", "(JJZ)V", (void*) android_view_ThreadedRenderer_addRenderNode},
{ "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
{ "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2a83d88..e64d905 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2476,4 +2476,9 @@
<!-- True if the device supports persisting security logs across reboots. -->
<bool name="config_supportPreRebootSecurityLogs">false</bool>
+
+ <!-- Default files to pin via Pinner Service -->
+ <string-array translatable="false" name="config_defaultPinnerServiceFiles">
+ </string-array>
+
</resources>
diff --git a/core/res/res/values/styles_holo.xml b/core/res/res/values/styles_holo.xml
index fdf9e31..8ca12ae 100644
--- a/core/res/res/values/styles_holo.xml
+++ b/core/res/res/values/styles_holo.xml
@@ -1172,7 +1172,8 @@
<style name="Widget.Holo.Light.FastScroll" parent="Widget.Holo.FastScroll" />
- <style name="Widget.Holo.SuggestionItem" parent="TextAppearance.Holo.Medium">
+ <style name="Widget.Holo.SuggestionItem">
+ <item name="textAppearance">@android:style/TextAppearance.Holo.Medium</item>
<item name="background">@color/white</item>
<item name="drawablePadding">8dip</item>
<item name="gravity">start|center_vertical</item>
@@ -1188,9 +1189,9 @@
<item name="textColor">@color/black</item>
</style>
- <style name="TextAppearance.Holo.SuggestionHighlight" parent="TextAppearance.SuggestionHighlight" />
+ <style name="TextAppearance.Holo.SuggestionHighlight" parent="@android:style/TextAppearance.SuggestionHighlight" />
- <style name="Widget.Holo.SuggestionButton" parent="Widget.Holo.SuggestionItem">
+ <style name="Widget.Holo.SuggestionButton" parent="@android:style/Widget.Holo.SuggestionItem">
<item name="background">#E9E9E9</item>
<item name="textColor">@color/black</item>
</style>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 6752e3c..3ed8daa 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -1001,7 +1001,8 @@
<item name="contentDescription">@string/media_route_button_content_description</item>
</style>
- <style name="Widget.Material.SuggestionItem" parent="@android:style/TextAppearance.Material.Body1">
+ <style name="Widget.Material.SuggestionItem">
+ <item name="textAppearance">@android:style/TextAppearance.Material.Body1</item>
<item name="textColor">?attr/textColorSecondary</item>
<item name="drawablePadding">8dip</item>
<item name="gravity">start|center_vertical</item>
@@ -1016,11 +1017,12 @@
<item name="textSize">14sp</item>
</style>
- <style name="TextAppearance.Material.TextSuggestionHighlight" parent="Widget.Material.SuggestionItem">
+ <style name="TextAppearance.Material.TextSuggestionHighlight">
<item name="textColor">?attr/textColorPrimary</item>
</style>
- <style name="Widget.Material.SuggestionButton" parent="@android:style/TextAppearance.Material.Button">
+ <style name="Widget.Material.SuggestionButton">
+ <item name="textAppearance">@android:style/TextAppearance.Material.Button</item>
<item name="textColor">?attr/colorAccent</item>
<item name="drawablePadding">8dip</item>
<item name="gravity">start|center_vertical</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7ed293f..ac36a2f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2587,4 +2587,8 @@
<java-symbol type="string" name="config_tvRemoteServicePackage" />
<java-symbol type="bool" name="config_supportPreRebootSecurityLogs" />
+
+ <!-- Pinner Service -->
+ <java-symbol type="array" name="config_defaultPinnerServiceFiles" />
+
</resources>
diff --git a/docs/html/develop/index.jd b/docs/html/develop/index.jd
index de38f3d..6835beb 100644
--- a/docs/html/develop/index.jd
+++ b/docs/html/develop/index.jd
@@ -9,7 +9,7 @@
excludeFromSuggestions=true
@jd:body
-<section class="dac-expand dac-hero dac-section-light" style="background:#FFE57F">
+<section class="dac-expand dac-hero dac-section-light">
<div class="wrap">
<div class="cols dac-hero-content">
<div class="col-1of2 col-push-1of2 dac-hero-figure">
@@ -19,14 +19,14 @@
frameborder="0" allowfullscreen=""
style="float: right;"></iframe>
-->
- <a href="{@docRoot}sdk/index.html">
+ <a href="{@docRoot}studio/index.html">
<img class="dac-hero-image" src="{@docRoot}images/tools/studio/studio-feature-instant-run_2x.png" />
</a>
</div>
<div class="col-1of2 col-pull-1of2">
<h1 class="dac-hero-title">
- <a style="color:inherit" href="{@docRoot}sdk/index.html">
- Android Studio 2.0,<br>now available!</a></h1>
+ <a style="color:inherit" href="{@docRoot}studio/index.html">
+ Android Studio 2.1,<br>now available!</a></h1>
<p class="dac-hero-description">
The latest version of Android Studio is the biggest update yet.
It includes new features like <strong>Instant Run</strong>, which
@@ -35,7 +35,7 @@
</p>
<div class="cols">
<div class="col-1of2">
- <a class="dac-hero-cta" href="{@docRoot}sdk/index.html">
+ <a class="dac-hero-cta" href="{@docRoot}studio/index.html">
<span class="dac-sprite dac-auto-chevron"></span>
Get Android Studio
</a><br>
diff --git a/docs/html/distribute/essentials/quality/billions.jd b/docs/html/distribute/essentials/quality/billions.jd
index 58f15a3..7042143 100644
--- a/docs/html/distribute/essentials/quality/billions.jd
+++ b/docs/html/distribute/essentials/quality/billions.jd
@@ -585,6 +585,16 @@
Implementing a Preferences Activity</a>.</li>
</ul>
+
+
+<h3 class="rel-resources clearfloat">Related resources</h3>
+<div class="resource-widget resource-flow-layout col-13"
+ data-query="collection:distribute/essentials/billionsquality/cost"
+ data-sortOrder="-timestamp"
+ data-cardSizes="6x3"
+ data-maxResults="6"></div>
+
+
<!-- consumption -->
<div class="headerLine">
<h2 id="consumption">Battery Consumption</h2>
diff --git a/docs/html/jd_collections.js b/docs/html/jd_collections.js
index cf6fb97..04e7a3d 100644
--- a/docs/html/jd_collections.js
+++ b/docs/html/jd_collections.js
@@ -375,7 +375,7 @@
"distribute/essentials/quality/tv.html",
"distribute/essentials/quality/wear.html",
"distribute/essentials/quality/auto.html",
- "https://developers.google.com/edu/guidelines"
+ "distribute/essentials/quality/billions.html"
]
},
"distribute/essentials/zhcn": {
@@ -506,7 +506,7 @@
"distribute/essentials/quality/wear.html",
"distribute/essentials/quality/tv.html",
"distribute/essentials/quality/auto.html",
- "https://developers.google.com/edu/guidelines"
+ "distribute/essentials/quality/billions.html"
]
},
"distribute/essentials/tools": {
@@ -1007,6 +1007,44 @@
"google/play/filters.html"
]
},
+ "distribute/essentials/billionsquality/connectivity": {
+ "title": "",
+ "resources": [
+ "training/basics/network-ops/managing.html",
+ "training/monitoring-device-state/connectivity-monitoring.html",
+ "guide/topics/providers/content-providers.html"
+ ]
+ },
+ "distribute/essentials/billionsquality/capability": {
+ "title": "",
+ "resources": [
+ "guide/practices/screens_support.html",
+ "training/multiscreen/screendensities.html",
+ "training/articles/memory.html"
+ ]
+ },
+ "distribute/essentials/billionsquality/cost": {
+ "title": "",
+ "resources": [
+ "https://medium.com/@wkalicinski/smallerapk-part-6-image-optimization-zopfli-webp-4c462955647d#.23hlddo3x",
+ "training/basics/network-ops/managing.html"
+ ]
+ },
+ "distribute/essentials/billionsquality/consumption": {
+ "title": "",
+ "resources": [
+ "training/efficient-downloads/efficient-network-access.html",
+ "training/monitoring-device-state/index.html"
+ ]
+ },
+ "distribute/essentials/billionsquality/content": {
+ "title": "",
+ "resources": [
+ "training/material/animations.html#Touch",
+ "training/articles/perf-anr.html",
+ "training/improving-layouts/index.html"
+ ]
+ },
"distribute/essentials/tabletguidelines": {
"title": "",
"resources": [
diff --git a/docs/html/jd_extras.js b/docs/html/jd_extras.js
index d176883..685394f 100644
--- a/docs/html/jd_extras.js
+++ b/docs/html/jd_extras.js
@@ -32,6 +32,17 @@
"type":"medium"
},
{
+ "title":"SmallerAPK, Part 6: Image optimization, Zopfli & WebP",
+ "category":"",
+ "summary":"Series of posts on minimizing your APK size.",
+ "url":"https://medium.com/@wkalicinski/smallerapk-part-6-image-optimization-zopfli-webp-4c462955647d#.23hlddo3x",
+ "group":"",
+ "keywords": [],
+ "tags": [],
+ "image":"https://cdn-images-1.medium.com/max/2000/1*chMiA9mGa_FBUOoesHHk3Q.png",
+ "type":"medium"
+ },
+ {
"title":"Measure your app’s user acquisition channels",
"titleFriendly":"",
"summary":"Get details on how to use the Developer Console User Acquisitions reports to discover where your users come from.",
@@ -999,6 +1010,19 @@
"lang": "en",
"group": "",
"tags": [],
+ "url": "training/material/animations.html#Touch",
+ "timestamp": 1194884220000,
+ "image": null,
+ "title": "Customize Touch Feedback",
+ "summary": "Provide visual confirmation when users interact with your UI.",
+ "keywords": [],
+ "type": "develop",
+ "category": "guide"
+ },
+ {
+ "lang": "en",
+ "group": "",
+ "tags": [],
"url": "guide/topics/manifest/uses-feature-element.html#testing",
"timestamp": 1194884220000,
"image": null,
diff --git a/docs/html/jd_extras_en.js b/docs/html/jd_extras_en.js
index 61eecc0..b351aa5 100644
--- a/docs/html/jd_extras_en.js
+++ b/docs/html/jd_extras_en.js
@@ -657,7 +657,54 @@
"image":"https://i1.ytimg.com/vi/K2dodTXARqc/maxresdefault.jpg",
"type":"video"
},
-
+ {
+ "title":"Instant Run: An Android Tool Time Deep Dive",
+ "category":"",
+ "summary":"Instant Run is an Android Studio feature that significantly reduces the time for building and deploying incremental code changes during your coding / testing / debugging lifecycle.",
+ "url":"https://www.youtube.com/watch?v=StqAZ1OQbqA&list=PLWz5rJ2EKKc_w6fodMGrA1_tsI3pqPbqa",
+ "group":"",
+ "keywords": ["studio", "tools"],
+ "tags": [
+ ],
+ "image":"https://i1.ytimg.com/vi/StqAZ1OQbqA/maxresdefault.jpg",
+ "type":"youtube"
+ },
+ {
+ "title":"What’s New in Android Studio 2.1",
+ "category":"",
+ "summary":"Android Studio 2.1 is required to try out new features and APIs of the Android N developer preview including the new Jack compiler and Java 8 language support. It also includes performance improvements to Instant Run, and a number of bug fixes and stability improvements.",
+ "url":"https://www.youtube.com/watch?v=ZOz_yr8Yxq8&list=PLWz5rJ2EKKc_w6fodMGrA1_tsI3pqPbqa",
+ "group":"",
+ "keywords": ["studio", "tools"],
+ "tags": [
+ ],
+ "image":"https://i1.ytimg.com/vi/ZOz_yr8Yxq8/maxresdefault.jpg",
+ "type":"youtube"
+ },
+ {
+ "title":"10 Things You Didn’t Know You Could Do",
+ "category":"",
+ "summary":"This Android Tool Time pro-tip roundup with Reto Meier shows off expert Android Studio tips designed to help you write less code, and make every keystroke count",
+ "url":"https://www.youtube.com/watch?v=eOV2owswDkE&list=PLWz5rJ2EKKc_w6fodMGrA1_tsI3pqPbqa",
+ "group":"",
+ "keywords": ["studio", "tools"],
+ "tags": [
+ ],
+ "image":"https://i1.ytimg.com/vi/eOV2owswDkE/maxresdefault.jpg",
+ "type":"youtube"
+ },
+ {
+ "title":"Live Templates in Android Studio",
+ "category":"",
+ "summary":"Android Tool Time Protip: Use and create your own Live Templates in Android Studio to write more code with less keystrokes using Live Templates to insert common, templatized code snippets.",
+ "url":"https://www.youtube.com/watch?v=4rI4tTd7-J8&list=PLWz5rJ2EKKc_w6fodMGrA1_tsI3pqPbqa",
+ "group":"",
+ "keywords": ["studio", "tools"],
+ "tags": [
+ ],
+ "image":"https://i1.ytimg.com/vi/4rI4tTd7-J8/maxresdefault.jpg",
+ "type":"youtube"
+ },
{
"title":"Google Play Services 7.5",
"category":"",
diff --git a/docs/html/preview/api-overview.jd b/docs/html/preview/api-overview.jd
index 110418a..5d93198 100644
--- a/docs/html/preview/api-overview.jd
+++ b/docs/html/preview/api-overview.jd
@@ -24,6 +24,7 @@
<li><a href="#number-blocking">Number-blocking</a></li>
<li><a href="#call_screening">Call screening</a></li>
<li><a href="#multi-locale_languages">Locales and languages</a></li>
+ <li><a href="#emoji">New Emojis</a></li>
<li><a href="#icu4">ICU4J APIs in Android</a></li>
<li><a href="#gles_32">OpenGL ES 3.2 API</a></li>
<li><a href="#android_tv_recording">Android TV recording</a></li>
@@ -37,7 +38,6 @@
<li><a href="#scoped_directory_access">Scoped directory access</a></li>
<li><a href="#print_svc">Print service enhancements</a></li>
<li><a href="#virtual_files">Virtual Files</a></li>
- <li><a href="#emoji">New Emojis</a></li>
</ol>
</div>
</div>
@@ -479,6 +479,49 @@
should follow, see <a href="{@docRoot}preview/features/multilingual-support.html"
>Multilingual Support</a>.</p>
+
+<h2 id="emoji">New Emojis</h2>
+
+<p>
+ Android N introduces additional emojis and emoji-related features including
+ skin tone emojis and support for variation
+ selectors. If your app supports emojis,
+ follow the guidelines below to take advantage of these emoji-related features.
+</p>
+
+<ul>
+ <li>
+ <strong>Check that a device contains an emoji before inserting it.</strong>
+ To check which emojis are present in the
+ system font, use the {@link android.graphics.Paint#hasGlyph(String)} method.
+ </li>
+ <li>
+ <strong>Check that an emoji supports variation selectors.</strong>
+ Variation selectors allow you to
+ present certain emojis in color or in black-and-white.
+ On mobile devices, apps should represent emojis in color rather than black-and-white. However,
+ if your app displays emojis inline with text, then it should use the black-and-white variation.
+ To determine whether an emoji has a variation, use the variation selector.
+ For a complete list of characters with variations, review the
+ <em>emoji variation sequences</em> section of the
+ <a class="external-link"
+ href="http://www.unicode.org/Public/9.0.0/ucd/StandardizedVariants-9.0.0d1.txt">
+ Unicode documentation on variations</a>.
+ </li>
+ <li>
+ <strong>Check that an emoji supports skin tone.</strong> Android N allows users to modify the
+ rendered skin tone of emojis to their preference. Keyboard apps should provide visual
+ indications for emojis that have multiple skin tones and should allow users to
+ select the skin tone that they prefer. To determine which system emojis have
+ skin tone modifiers, use the {@link android.graphics.Paint#hasGlyph(String)}
+ method. You can determine which emojis use skin tones by reading the
+ <a class="external-link"
+ href="http://unicode.org/emoji/charts/full-emoji-list.html">
+ Unicode documentation</a>.
+ </li>
+</ul>
+
+
<h2 id="icu4">ICU4J APIs in Android</h2>
<p>
@@ -733,19 +776,21 @@
<p>
Android N introduces APK Signature Scheme v2, a new app-signing scheme that
- offers faster app install times and better protection against unauthorized
- alterations to APK files. Android Studio 2.2 and Gradle provide built-in
- support for APK Signature Scheme v2.
+ offers faster app install times and more protection against unauthorized
+ alterations to APK files. By default, Android Studio 2.2 and the Android
+ Plugin for Gradle 2.2 sign your app using both APK Signature Scheme v2 and
+ the traditional signing scheme, which uses JAR signing.
</p>
<p>
- Although we recommend applying APK Signature Scheme v2 to your app, the new
- scheme is not mandatory. If your app doesn't build properly when using the
- APK Signature Scheme v2, you can use the traditional signing scheme—which
- uses JAR signing—instead. To use the traditional scheme, open the
- module-level <code>build.gradle</code> file and add the
- <code>v2SigningEnabled</code> parameter to your release signing
- configuration, setting this parameter's value to <code>false</code>:
+ Although we recommend applying APK Signature Scheme v2 to your app, this new
+ scheme is not mandatory. If your app doesn't build properly when using APK
+ Signature Scheme v2, you can disable the new scheme. The disabling process
+ causes Android Studio 2.2 and the Android Plugin for Gradle 2.2 to sign your
+ app using only the traditional signing scheme. To sign with only the
+ traditional scheme, open the module-level <code>build.gradle</code> file, then
+ add the line <code>v2SigningEnabled false</code> to your release signing
+ configuration:
</p>
<pre>
@@ -764,12 +809,18 @@
}
</pre>
+<p class="caution"><strong>Caution: </strong> If you sign your app using APK
+ Signature Scheme v2 and make further changes to the app, the app's signature
+ is invalidated. For this reason, use tools such as <code>zipalign</code>
+ before signing your app using APK Signature Scheme v2, not after.
+</p>
+
<p>
- For more information, see the following guides, which describe how to <a href=
- "{@docRoot}studio/tools/publishing/app-signing.html#release-mode"> sign an app
- in Android Studio</a> and how to <a href=
+ For more information, read the Android Studio documents that describe how to
+ <a href="{@docRoot}studio/tools/publishing/app-signing.html#release-mode">
+ sign an app</a> in Android Studio and how to <a href=
"{@docRoot}studio/tools/building/configuring-gradle.html#signing"> configure
- the Gradle build file for signing apps</a>.
+ the build file for signing apps</a> using the Android Plugin for Gradle.
</p>
<h2 id="scoped_directory_access">Scoped directory access</h2>
@@ -916,41 +967,3 @@
<a href="{@docRoot}guide/topics/providers/document-provider.html">Storage
Access Frameworks guide</a>.
</p>
-
-<h2 id="emoji">New Emojis</h2>
-
-<p>
- Android N introduces new emojis, including skin tone emojis, support
- for variation
- selectors, and other improvements. For a good user experience, observe the
- following guidelines for using the new emojis and emoji features in your apps.
-</p>
-
-<ul>
- <li>
- <strong>New emojis</strong>: To check which emojis are present in the
- system font, use the {@link android.graphics.Paint#hasGlyph(String)} method
- and the dynamic layout in the emoji picker to place the glyphs.
- </li>
- <li>
- <strong>Variation selectors</strong>: Variation selectors allow certain
- emojis to be represented in color or in text presentation, which is used in
- documents. For mobile devices, emojis should use their color representation.
- To determine whether an emoji has a variation, use the variation selector.
- You can view the complete list of characters with variations in the
- <em>emoji variation sequences</em> section of the
- <a href="http://www.unicode.org/Public/9.0.0/ucd/StandardizedVariants-9.0.0d1.txt">
- Unicode documentation on variations</a>.
- </li>
- <li>
- <strong>Skin tone modifiers</strong>: In Android N, users can modify the
- rendered skin tone of emojis. This allows users to customize the presentation
- of emojis to their preference. Keyboard apps should provide visual
- indications for emojis that have multiple skin tones and should allow users to
- select the skin tone that they prefer. To determine which system emojis have
- skin tone modifiers, use the {@link android.graphics.Paint#hasGlyph(String)}
- method. You can determine which emojis use skin tones by reading the
- <a href="http://unicode.org/emoji/charts/full-emoji-list.html">
- Unicode documentation</a>.
- </li>
-</ul>
\ No newline at end of file
diff --git a/docs/html/preview/features/data-saver.jd b/docs/html/preview/features/data-saver.jd
index d5cdf27..c4cab18 100644
--- a/docs/html/preview/features/data-saver.jd
+++ b/docs/html/preview/features/data-saver.jd
@@ -14,7 +14,7 @@
<a href="#status">Checking Data Saver Preferences</a>
<ol>
<li>
- <a href="#request-whitelist">Requesting Whitelist Permissions</a>
+ <a href="#request-whitelist">Requesting whitelist permissions</a>
</li>
</ol>
</li>
@@ -138,15 +138,17 @@
If your app needs to use data in the background, it can request whitelist
permissions by sending a
<code>Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS</code>
- (<code>"android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS"</code>)
- intent with a <code>package:<your-app-id></code> URI.
+ intent containing a URI of your app's package name: for example,
+ <code>package:MY_APP_ID</code>.
</p>
<p>
- Sending the intent and URI launches the <strong>Settings</strong> app, and
- displays your app's <strong>App Data Usage</strong> page to the user. The
- user can then decide whether to enable background data for your app.
- It is good practice to prompt the user before sending this intent.
+ Sending the intent and URI launches the <strong>Settings</strong> app and
+ displays data usage settings for your app. The user can then decide whether
+ to enable background data for your app. Before you send this intent, it is
+ good practice to first ask the user if they want to launch the
+ <strong>Settings</strong> app for the purpose of enabling background data
+ usage.
</p>
<h2 id="monitor-changes">
@@ -156,9 +158,8 @@
<p>
Apps can monitor changes to Data Saver preferences by creating a {@link
android.content.BroadcastReceiver} to listen for {@code
- ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED} ({@code
- "android.net.conn.RESTRICT_BACKGROUND_CHANGED"}) and dynamically registering
- the receiver with {@link android.content.Context#registerReceiver
+ ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED} and dynamically
+ registering the receiver with {@link android.content.Context#registerReceiver
Context.registerReceiver()}. When an app receives this broadcast, it should
<a href="#status">check if the new Data Saver preferences affect its
permissions</a> by calling {@code
diff --git a/docs/html/preview/features/notification-updates.jd b/docs/html/preview/features/notification-updates.jd
index b8dfbaa..c405360 100644
--- a/docs/html/preview/features/notification-updates.jd
+++ b/docs/html/preview/features/notification-updates.jd
@@ -204,6 +204,16 @@
android.support.v4.app.RemoteInput}, you can update the reply history
using the {@code setRemoteInputHistory()} method.
</p>
+
+<p>
+ The notification must be either updated or cancelled after the app has
+ received remote input. When the user replies to a remote update
+ using Direct Reply,
+ do not cancel the notification. Instead, update the notification to display the user's reply. You can update the notification using a
+ <code>MessagingStyle</code>, or you can append the user's reply to the remote
+ input history.
+</p>
+
<h2 id="bundle">Bundled Notifications</h2>
<p>Android N provides developers with a new way to represent
@@ -364,7 +374,7 @@
</pre>
-<h2 id="style">Message Style</h2>
+<h2 id="style">Messaging Style</h2>
<p>
Android N introduces a new API for customizing the style of a notification.
Using the <code>MessageStyle</code> class, you can change several of the
@@ -380,10 +390,9 @@
<pre>
Notification notification = new Notification.Builder()
.setStyle(new Notification.MessagingStyle("Me")
- .setConversationTitle("Team lunch")
- .addMessage("Hi", timestamp1, null) // Pass in null for user.
- .addMessage("What's up?", timestamp2, "Coworker")
- .addMessage("Not much", timestamp3, null)
- .addMessage("How about lunch?", timestamp4, "Coworker")
- .setAllow());
+ .setConversationTitle("Team lunch")
+ .addMessage("Hi", timestamp1, null) // Pass in null for user.
+ .addMessage("What's up?", timestamp2, "Coworker")
+ .addMessage("Not much", timestamp3, null)
+ .addMessage("How about lunch?", timestamp4, "Coworker"));
</pre>
\ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 596e5a8..f49594c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -46,6 +46,7 @@
import android.os.DropBoxManager;
import android.os.Environment;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
@@ -199,6 +200,12 @@
@GuardedBy("mLock")
private SettingsRegistry mSettingsRegistry;
+ @GuardedBy("mLock")
+ private HandlerThread mHandlerThread;
+
+ @GuardedBy("mLock")
+ private Handler mBackgroundHandler;
+
// We have to call in the user manager with no lock held,
private volatile UserManager mUserManager;
@@ -244,6 +251,10 @@
synchronized (mLock) {
mUserManager = UserManager.get(getContext());
mPackageManager = AppGlobals.getPackageManager();
+ mHandlerThread = new HandlerThread(LOG_TAG,
+ Process.THREAD_PRIORITY_BACKGROUND);
+ mHandlerThread.start();
+ mBackgroundHandler = new Handler(mHandlerThread.getLooper());
mSettingsRegistry = new SettingsRegistry();
}
registerBroadcastReceivers();
@@ -1669,7 +1680,7 @@
if (mSettingsStates.get(key) == null) {
final int maxBytesPerPackage = getMaxBytesPerPackageForType(getTypeFromKey(key));
SettingsState settingsState = new SettingsState(mLock, getSettingsFile(key), key,
- maxBytesPerPackage);
+ maxBytesPerPackage, mBackgroundHandler);
mSettingsStates.put(key, settingsState);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 2de0618c..1e02747 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -95,7 +95,7 @@
private final Object mLock;
- private final Handler mHandler = new MyHandler();
+ private final Handler mHandler;
@GuardedBy("mLock")
private final ArrayMap<String, Setting> mSettings = new ArrayMap<>();
@@ -134,13 +134,15 @@
@GuardedBy("mLock")
private long mNextId;
- public SettingsState(Object lock, File file, int key, int maxBytesPerAppPackage) {
+ public SettingsState(Object lock, File file, int key, int maxBytesPerAppPackage,
+ Handler handler) {
// It is important that we use the same lock as the settings provider
// to ensure multiple mutations on this state are atomicaly persisted
// as the async persistence should be blocked while we make changes.
mLock = lock;
mStatePersistFile = file;
mKey = key;
+ mHandler = handler;
if (maxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_LIMITED) {
mMaxBytesPerAppPackage = maxBytesPerAppPackage;
mPackageToMemoryUsage = new ArrayMap<>();
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 3f9ffa1..53c2958 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -15,6 +15,7 @@
*/
package com.android.providers.settings;
+import android.os.Handler;
import android.test.AndroidTestCase;
import android.util.Xml;
@@ -126,7 +127,7 @@
final Object lock = new Object();
final SettingsState ssWriter = new SettingsState(lock, file, 1,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED);
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, new Handler());
ssWriter.setVersionLocked(SettingsState.SETTINGS_VERSOIN_NEW_ENCODING);
ssWriter.insertSettingLocked("k1", "\u0000", "package");
@@ -138,7 +139,7 @@
}
final SettingsState ssReader = new SettingsState(lock, file, 1,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED);
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, new Handler());
synchronized (lock) {
assertEquals("\u0000", ssReader.getSettingLocked("k1").getValue());
assertEquals("abc", ssReader.getSettingLocked("k2").getValue());
@@ -165,7 +166,7 @@
os.close();
final SettingsState ss = new SettingsState(lock, file, 1,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED);
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, new Handler());
synchronized (lock) {
SettingsState.Setting s;
s = ss.getSettingLocked("k0");
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index a5f3e77..0f356e0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -107,6 +107,7 @@
private boolean mFinishedOnStartup;
private boolean mIgnoreAltTabRelease;
private boolean mIsVisible;
+ private boolean mReceivedNewIntent;
// Top level views
private RecentsView mRecentsView;
@@ -120,6 +121,9 @@
private int mFocusTimerDuration;
private DozeTrigger mIterateTrigger;
private final UserInteractionEvent mUserInteractionEvent = new UserInteractionEvent();
+ private final Runnable mSendEnterWindowAnimationCompleteRunnable = () -> {
+ EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
+ };
/**
* A common Runnable to finish Recents by launching Home with an animation depending on the
@@ -342,6 +346,7 @@
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
+ mReceivedNewIntent = true;
// Reload the stack view
reloadStackView();
@@ -364,7 +369,7 @@
RecentsActivityLaunchState launchState = config.getLaunchState();
if (!loadPlan.hasTasks()) {
loader.preloadTasks(loadPlan, launchState.launchedToTaskId,
- launchState.launchedFromHome);
+ !launchState.launchedFromHome);
}
RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
@@ -419,7 +424,16 @@
@Override
public void onEnterAnimationComplete() {
super.onEnterAnimationComplete();
- EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
+
+ // Workaround for b/28705801, on first docking, we may receive the enter animation callback
+ // before the first layout, so in such cases, send the event on the next frame after all
+ // the views are laid out and attached (and registered to the EventBus).
+ mHandler.removeCallbacks(mSendEnterWindowAnimationCompleteRunnable);
+ if (!mReceivedNewIntent) {
+ mHandler.post(mSendEnterWindowAnimationCompleteRunnable);
+ } else {
+ mSendEnterWindowAnimationCompleteRunnable.run();
+ }
}
@Override
@@ -453,7 +467,8 @@
RecentsActivityLaunchState launchState = config.getLaunchState();
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
- loader.preloadTasks(loadPlan, -1 /* runningTaskId */, false /* isHomeStackVisible */);
+ loader.preloadTasks(loadPlan, -1 /* runningTaskId */,
+ false /* includeFrontMostExcludedTask */);
RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
@@ -477,6 +492,7 @@
// Notify that recents is now hidden
mIsVisible = false;
+ mReceivedNewIntent = false;
EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false));
MetricsLogger.hidden(this, MetricsEvent.OVERVIEW_ACTIVITY);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 297dec9..611f9f2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -112,7 +112,7 @@
// Load the next task only if we aren't svelte
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, -1, true /* isHomeStackVisible */);
+ loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
// This callback is made when a new activity is launched and the old one is paused
// so ignore the current activity and try and preload the thumbnail for the
@@ -189,7 +189,7 @@
// We can use a new plan since the caches will be the same.
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, -1, true /* isHomeStackVisible */);
+ loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
launchOpts.numVisibleTasks = loader.getIconCacheSize();
launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
@@ -366,8 +366,8 @@
ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
RecentsTaskLoader loader = Recents.getTaskLoader();
sInstanceLoadPlan = loader.createLoadPlan(mContext);
- sInstanceLoadPlan.preloadRawTasks(isHomeStackVisible.value);
- loader.preloadTasks(sInstanceLoadPlan, runningTask.id, isHomeStackVisible.value);
+ sInstanceLoadPlan.preloadRawTasks(!isHomeStackVisible.value);
+ loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value);
TaskStack stack = sInstanceLoadPlan.getTaskStack();
if (stack.getTaskCount() > 0) {
// Only preload the icon (but not the thumbnail since it may not have been taken for
@@ -401,7 +401,7 @@
SystemServicesProxy ssp = Recents.getSystemServices();
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, -1, true /* isHomeStackVisible */);
+ loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
TaskStack focusedStack = plan.getTaskStack();
// Return early if there are no tasks in the focused stack
@@ -453,7 +453,7 @@
SystemServicesProxy ssp = Recents.getSystemServices();
RecentsTaskLoader loader = Recents.getTaskLoader();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, -1, true /* isHomeStackVisible */);
+ loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
TaskStack focusedStack = plan.getTaskStack();
// Return early if there are no tasks in the focused stack
@@ -818,7 +818,7 @@
sInstanceLoadPlan = loader.createLoadPlan(mContext);
}
if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
- loader.preloadTasks(sInstanceLoadPlan, runningTask.id, isHomeStackVisible);
+ loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible);
}
TaskStack stack = sInstanceLoadPlan.getTaskStack();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 08b52d9..62c1945 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -253,11 +253,12 @@
/**
* Returns a list of the recents tasks.
*
- * @param isHomeStackVisible whether or not the home stack is currently visible. If it is
- * visible, then we ignore all excluded tasks (even the first one).
+ * @param includeFrontMostExcludedTask if set, will ensure that the front most excluded task
+ * will be visible, otherwise no excluded tasks will be
+ * visible.
*/
public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId,
- boolean isHomeStackVisible, ArraySet<Integer> quietProfileIds) {
+ boolean includeFrontMostExcludedTask, ArraySet<Integer> quietProfileIds) {
if (mAm == null) return null;
// If we are mocking, then create some recent tasks
@@ -295,13 +296,16 @@
// Remove home/recents/excluded tasks
int minNumTasksToQuery = 10;
int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks);
- List<ActivityManager.RecentTaskInfo> tasks = mAm.getRecentTasksForUser(numTasksToQuery,
- ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
+ int flags = ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK |
ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS |
ActivityManager.RECENT_IGNORE_UNAVAILABLE |
- ActivityManager.RECENT_INCLUDE_PROFILES |
- ActivityManager.RECENT_WITH_EXCLUDED, userId);
+ ActivityManager.RECENT_INCLUDE_PROFILES;
+ if (includeFrontMostExcludedTask) {
+ flags |= ActivityManager.RECENT_WITH_EXCLUDED;
+ }
+ List<ActivityManager.RecentTaskInfo> tasks = mAm.getRecentTasksForUser(numTasksToQuery,
+ flags, userId);
// Break early if we can't get a valid set of tasks
if (tasks == null) {
@@ -316,18 +320,20 @@
// NOTE: The order of these checks happens in the expected order of the traversal of the
// tasks
- // Check the first non-recents task, include this task even if it is marked as excluded
- // from recents if we are currently in the app. In other words, only remove excluded
- // tasks if it is not the first active task, and not in the blacklist.
+ // Remove the task if it is blacklisted
+ if (sRecentsBlacklist.contains(t.realActivity.getClassName())) {
+ iter.remove();
+ }
+
+ // Remove the task if it is marked as excluded, unless it is the first most task and we
+ // are requested to include it
boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
== Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
- boolean isBlackListed = sRecentsBlacklist.contains(t.realActivity.getClassName());
- // Filter out recent tasks from managed profiles which are in quiet mode.
isExcluded |= quietProfileIds.contains(t.userId);
- if (isBlackListed || (isExcluded && (isHomeStackVisible || !isFirstValidTask))) {
+ if (isExcluded && (!isFirstValidTask || !includeFrontMostExcludedTask)) {
iter.remove();
- continue;
}
+
isFirstValidTask = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 251ad71..1278b73 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -100,12 +100,12 @@
* An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
* to most-recent order.
*/
- public synchronized void preloadRawTasks(boolean isHomeStackVisible) {
+ public synchronized void preloadRawTasks(boolean includeFrontMostExcludedTask) {
int currentUserId = UserHandle.USER_CURRENT;
updateCurrentQuietProfilesCache(currentUserId);
SystemServicesProxy ssp = Recents.getSystemServices();
mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
- currentUserId, isHomeStackVisible, mCurrentQuietProfiles);
+ currentUserId, includeFrontMostExcludedTask, mCurrentQuietProfiles);
// Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
Collections.reverse(mRawTasks);
@@ -121,11 +121,11 @@
* - least-recent to most-recent freeform tasks
*/
public synchronized void preloadPlan(RecentsTaskLoader loader, int runningTaskId,
- boolean isHomeStackVisible) {
+ boolean includeFrontMostExcludedTask) {
Resources res = mContext.getResources();
ArrayList<Task> allTasks = new ArrayList<>();
if (mRawTasks == null) {
- preloadRawTasks(isHomeStackVisible);
+ preloadRawTasks(includeFrontMostExcludedTask);
}
SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 9460b64..ca59831 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -334,8 +334,8 @@
/** Preloads recents tasks using the specified plan to store the output. */
public void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
- boolean isHomeStackVisible) {
- plan.preloadPlan(this, runningTaskId, isHomeStackVisible);
+ boolean includeFrontMostExcludedTask) {
+ plan.preloadPlan(this, runningTaskId, includeFrontMostExcludedTask);
}
/** Begins loading the heavy task data according to the specified options. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
index acebf42..3a17d22 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
@@ -180,7 +180,7 @@
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
if (!plan.hasTasks()) {
- loader.preloadTasks(plan, -1, launchState.launchedFromHome);
+ loader.preloadTasks(plan, -1, !launchState.launchedFromHome);
}
mLaunchedFromHome = launchState.launchedFromHome;
TaskStack stack = plan.getTaskStack();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
index 6f7bd41..fca8d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
@@ -63,7 +63,7 @@
sInstanceLoadPlan = loader.createLoadPlan(mContext);
}
if (mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
- loader.preloadTasks(sInstanceLoadPlan, runningTask.id, isHomeStackVisible);
+ loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible);
}
TaskStack stack = sInstanceLoadPlan.getTaskStack();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 3d0de1c..1a197b6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -1391,8 +1391,9 @@
updateLayoutAlgorithm(true /* boundScroll */);
// Animate all the tasks into place
- relayoutTaskViews(new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
- Interpolators.FAST_OUT_SLOW_IN));
+ relayoutTaskViews(mAwaitingFirstLayout
+ ? AnimationProps.IMMEDIATE
+ : new AnimationProps(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN));
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index dc5957d..593e170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -2246,9 +2246,12 @@
Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
0,
mCurrentUserId) != 0;
+ final boolean remoteInputDpm = (dpmFlags
+ & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0;
+
setShowLockscreenNotifications(show && allowedByDpm);
- setLockScreenAllowRemoteInput(remoteInput);
+ setLockScreenAllowRemoteInput(remoteInput && remoteInputDpm);
}
protected abstract void setAreThereNotifications();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index d2326d2..270b6ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -459,7 +459,8 @@
@Override
public String toString() {
- String result = " summary:\n " + summary.notification;
+ String result = " summary:\n "
+ + (summary != null ? summary.notification : "null");
result += "\n children size: " + children.size();
for (NotificationData.Entry child : children) {
result += "\n " + child.notification;
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
new file mode 100644
index 0000000..d48aeed
--- /dev/null
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.util.EventLog;
+import android.util.Slog;
+import android.os.Binder;
+import android.os.Build;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructStat;
+
+import java.util.ArrayList;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * <p>PinnerService pins important files for key processes in memory.</p>
+ * <p>Files to pin are specified in the config_defaultPinnerServiceFiles
+ * overlay. </p>
+ */
+public final class PinnerService extends SystemService {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "PinnerService";
+
+ private final Context mContext;
+ private final ArrayList<String> mPinnedFiles = new ArrayList<String>();
+
+ private BinderService mBinderService;
+
+
+ public PinnerService(Context context) {
+ super(context);
+
+ mContext = context;
+
+ }
+
+ @Override
+ public void onStart() {
+ Slog.e(TAG, "Starting PinnerService");
+
+ mBinderService = new BinderService();
+ publishBinderService("pinner", mBinderService);
+
+ // Files to pin come from the overlay and can be specified per-device config
+ // Continue trying to pin remaining files even if there is a failure
+ String[] filesToPin = mContext.getResources().getStringArray(com.android.internal.R.array.config_defaultPinnerServiceFiles);
+ for (int i = 0; i < filesToPin.length; i++){
+ boolean success = pinFile(filesToPin[i], 0, 0);
+ if (success == true) {
+ mPinnedFiles.add(filesToPin[i]);
+ Slog.i(TAG, "Pinned file = " + filesToPin[i]);
+ } else {
+ Slog.e(TAG, "Failed to pin file = " + filesToPin[i]);
+ }
+ }
+ }
+
+ // mlock length bytes of fileToPin in memory, starting at offset
+ // length == 0 means pin from offset to end of file
+ private boolean pinFile(String fileToPin, long offset, long length) {
+ FileDescriptor fd = new FileDescriptor();
+ try {
+ fd = Os.open(fileToPin, OsConstants.O_RDONLY | OsConstants.O_CLOEXEC | OsConstants.O_NOFOLLOW, OsConstants.O_RDONLY);
+
+ StructStat sb = Os.fstat(fd);
+
+ if (offset + length > sb.st_size) {
+ Os.close(fd);
+ return false;
+ }
+
+ if (length == 0) {
+ length = sb.st_size - offset;
+ }
+
+ long address = Os.mmap(0, length, OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, fd, offset);
+ Os.close(fd);
+
+ Os.mlock(address, length);
+
+ return true;
+ } catch (ErrnoException e) {
+ Slog.e(TAG, "Failed to pin file " + fileToPin + " with error " + e.getMessage());
+ if(fd.valid()) {
+ try { Os.close(fd); }
+ catch (ErrnoException eClose) {Slog.e(TAG, "Failed to close fd, error = " + eClose.getMessage());}
+ }
+ return false;
+ }
+ }
+
+
+ private final class BinderService extends Binder {
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+ pw.println("Pinned Files:");
+ for (int i = 0; i < mPinnedFiles.size(); i++) {
+ pw.println(mPinnedFiles.get(i));
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 29e2e44..53c5a6d 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -868,7 +868,7 @@
return false;
}
if (this.userid == UserHandle.USER_ALL) return true;
- if (this.userid == UserHandle.USER_SYSTEM) return true;
+ if (this.isSystem) return true;
if (nid == UserHandle.USER_ALL || nid == this.userid) return true;
return supportsProfiles() && mUserProfiles.isCurrentProfile(nid);
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index a234241..abbb5f4 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -88,6 +88,11 @@
// case do not clear allDrawn until the animation completes.
boolean deferClearAllDrawn;
+ // These are to track the app's real drawing status if there were no saved surfaces.
+ boolean allDrawnExcludingSaved;
+ int numInterestingWindowsExcludingSaved;
+ int numDrawnWindowsExclusingSaved;
+
// Is this window's surface needed? This is almost like hidden, except
// it will sometimes be true a little earlier: when the token has
// been shown, but is still waiting for its app transition to execute
@@ -411,6 +416,39 @@
}
}
+ /**
+ * Whether the app has some window that is invisible in layout, but
+ * animating with saved surface.
+ */
+ boolean isAnimatingInvisibleWithSavedSurface() {
+ for (int i = allAppWindows.size() - 1; i >= 0; i--) {
+ final WindowState w = allAppWindows.get(i);
+ if (w.isAnimatingInvisibleWithSavedSurface()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Hide all window surfaces that's still invisible in layout but animating
+ * with a saved surface, and mark them destroying.
+ */
+ void stopUsingSavedSurfaceLocked() {
+ for (int i = allAppWindows.size() - 1; i >= 0; i--) {
+ final WindowState w = allAppWindows.get(i);
+ if (w.isAnimatingInvisibleWithSavedSurface()) {
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.d(TAG,
+ "stopUsingSavedSurfaceLocked: " + w);
+ w.clearAnimatingWithSavedSurface();
+ w.mDestroying = true;
+ w.mWinAnimator.hide("stopUsingSavedSurfaceLocked");
+ w.mWinAnimator.mWallpaperControllerLocked.hideWallpapers(w);
+ }
+ }
+ destroySurfaces();
+ }
+
void restoreSavedSurfaces() {
if (!canRestoreSurfaces()) {
clearVisibleBeforeClientHidden();
@@ -456,6 +494,7 @@
void clearAllDrawn() {
allDrawn = false;
deferClearAllDrawn = false;
+ allDrawnExcludingSaved = false;
}
@Override
@@ -582,10 +621,7 @@
w.mSkipEnterAnimationForSeamlessReplacement = !candidate.mAnimateReplacingWindow;
// if we got a replacement window, reset the timeout to give drawing more time
- service.mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT);
- service.mH.sendMessageDelayed(
- service.mH.obtainMessage(H.WINDOW_REPLACEMENT_TIMEOUT, this),
- WINDOW_REPLACEMENT_TIMEOUT_DURATION);
+ service.scheduleReplacingWindowTimeouts(this);
}
}
allAppWindows.add(w);
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 6e1ff06..5b2ce07 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -123,6 +123,7 @@
private final Rect mTouchRegion = new Rect();
private boolean mAnimatingForIme;
private boolean mAdjustedForIme;
+ private int mImeHeight;
private WindowState mDelayedImeWin;
private boolean mAdjustedForDivider;
private float mDividerAnimationStart;
@@ -296,8 +297,9 @@
void setAdjustedForIme(
boolean adjustedForIme, boolean adjustedForDivider,
- boolean animate, WindowState imeWin) {
- if (mAdjustedForIme != adjustedForIme || mAdjustedForDivider != adjustedForDivider) {
+ boolean animate, WindowState imeWin, int imeHeight) {
+ if (mAdjustedForIme != adjustedForIme || (adjustedForIme && mImeHeight != imeHeight)
+ || mAdjustedForDivider != adjustedForDivider) {
if (animate) {
startImeAdjustAnimation(adjustedForIme, adjustedForDivider, imeWin);
} else {
@@ -305,10 +307,15 @@
notifyAdjustedForImeChanged(adjustedForIme || adjustedForDivider, 0 /* duration */);
}
mAdjustedForIme = adjustedForIme;
+ mImeHeight = imeHeight;
mAdjustedForDivider = adjustedForDivider;
}
}
+ int getImeHeightAdjustedFor() {
+ return mImeHeight;
+ }
+
void positionDockedStackedDivider(Rect frame) {
TaskStack stack = mDisplayContent.getDockedStackLocked();
if (stack == null) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 58468f6..7426329 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -849,10 +849,10 @@
*
* @param imeWin The IME window.
*/
- void setAdjustedForIme(WindowState imeWin) {
+ void setAdjustedForIme(WindowState imeWin, boolean forceUpdate) {
mImeWin = imeWin;
mImeGoingAway = false;
- if (!mAdjustedForIme) {
+ if (!mAdjustedForIme || forceUpdate) {
mAdjustedForIme = true;
mAdjustImeAmount = 0f;
mAdjustDividerAmount = 0f;
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index eae7838..be060d2 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -788,6 +788,7 @@
removeReplacedWindowsLocked();
}
+ mService.stopUsingSavedSurfaceLocked();
mService.destroyPreservedSurfaceLocked();
mService.mWindowPlacerLocked.destroyPendingSurfaces();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 196b38a..8fa9efb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -415,6 +415,19 @@
final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<>();
/**
+ * List of window tokens that have finished drawing their own windows and
+ * no longer need to show any saved surfaces. Windows that's still showing
+ * saved surfaces will be cleaned up after next animation pass.
+ */
+ final ArrayList<AppWindowToken> mFinishedEarlyAnim = new ArrayList<>();
+
+ /**
+ * List of app window tokens that are waiting for replacing windows. If the
+ * replacement doesn't come in time the stale windows needs to be disposed of.
+ */
+ final ArrayList<AppWindowToken> mReplacingWindowTimeouts = new ArrayList<>();
+
+ /**
* The input consumer added to the window manager which consumes input events to windows below
* it.
*/
@@ -2324,6 +2337,23 @@
Binder.restoreCallingIdentity(origId);
return;
}
+
+ if (win.isAnimatingWithSavedSurface() && !appToken.allDrawnExcludingSaved) {
+ // We started enter animation early with a saved surface, now the app asks to remove
+ // this window. If we remove it now and the app is not yet drawn, we'll show a
+ // flicker. Delay the removal now until it's really drawn.
+ if (DEBUG_ADD_REMOVE) {
+ Slog.d(TAG_WM, "removeWindowLocked: delay removal of " + win
+ + " due to early animation");
+ }
+ // Do not set mAnimatingExit to true here, it will cause the surface to be hidden
+ // immediately after the enter animation is done. If the app is not yet drawn then
+ // it will show up as a flicker.
+ win.mRemoveOnExit = true;
+ win.mWindowRemovalAllowed = true;
+ Binder.restoreCallingIdentity(origId);
+ return;
+ }
// If we are not currently running the exit animation, we need to see about starting one
wasVisible = win.isWinVisibleLw();
@@ -7536,6 +7566,9 @@
final boolean imeOnTop = (imeDockSide == DOCKED_TOP);
final boolean imeOnBottom = (imeDockSide == DOCKED_BOTTOM);
final boolean dockMinimized = displayContent.mDividerControllerLocked.isMinimizedDock();
+ final int imeHeight = mPolicy.getInputMethodWindowVisibleHeightLw();
+ final boolean imeHeightChanged = imeVisible &&
+ imeHeight != displayContent.mDividerControllerLocked.getImeHeightAdjustedFor();
// The divider could be adjusted for IME position, or be thinner than usual,
// or both. There are three possible cases:
@@ -7549,13 +7582,13 @@
final TaskStack stack = stacks.get(i);
final boolean isDockedOnBottom = stack.getDockSide() == DOCKED_BOTTOM;
if (stack.isVisibleLocked() && (imeOnBottom || isDockedOnBottom)) {
- stack.setAdjustedForIme(imeWin);
+ stack.setAdjustedForIme(imeWin, imeOnBottom && imeHeightChanged);
} else {
stack.resetAdjustedForIme(false);
}
}
displayContent.mDividerControllerLocked.setAdjustedForIme(
- imeOnBottom /*ime*/, true /*divider*/, true /*animate*/, imeWin);
+ imeOnBottom /*ime*/, true /*divider*/, true /*animate*/, imeWin, imeHeight);
} else {
final ArrayList<TaskStack> stacks = displayContent.getStacks();
for (int i = stacks.size() - 1; i >= 0; --i) {
@@ -7563,7 +7596,7 @@
stack.resetAdjustedForIme(!dockVisible);
}
displayContent.mDividerControllerLocked.setAdjustedForIme(
- false /*ime*/, false /*divider*/, dockVisible /*animate*/, imeWin);
+ false /*ime*/, false /*divider*/, dockVisible /*animate*/, imeWin, imeHeight);
}
}
@@ -8497,9 +8530,12 @@
}
break;
case WINDOW_REPLACEMENT_TIMEOUT: {
- final AppWindowToken token = (AppWindowToken) msg.obj;
synchronized (mWindowMap) {
- token.clearTimedoutReplacesLocked();
+ for (int i = mReplacingWindowTimeouts.size() - 1; i >= 0; i--) {
+ final AppWindowToken token = mReplacingWindowTimeouts.get(i);
+ token.clearTimedoutReplacesLocked();
+ }
+ mReplacingWindowTimeouts.clear();
}
}
case NOTIFY_APP_TRANSITION_STARTING: {
@@ -8548,6 +8584,15 @@
}
mDestroyPreservedSurface.clear();
}
+
+ void stopUsingSavedSurfaceLocked() {
+ for (int i = mFinishedEarlyAnim.size() - 1; i >= 0 ; i--) {
+ final AppWindowToken wtoken = mFinishedEarlyAnim.get(i);
+ wtoken.stopUsingSavedSurfaceLocked();
+ }
+ mFinishedEarlyAnim.clear();
+ }
+
// -------------------------------------------------------------
// IWindowManager API
// -------------------------------------------------------------
@@ -9172,6 +9217,18 @@
void updateResizingWindows(final WindowState w) {
final WindowStateAnimator winAnimator = w.mWinAnimator;
if (w.mHasSurface && w.mLayoutSeq == mLayoutSeq && !w.isGoneForLayoutLw()) {
+ final Task task = w.getTask();
+ // In the case of stack bound animations, the window frames
+ // will update (unlike other animations which just modifiy
+ // various transformation properties). We don't want to
+ // notify the client of frame changes in this case. Not only
+ // is it a lot of churn, but the frame may not correspond
+ // to the surface size or the onscreen area at various
+ // phases in the animation, and the client will become
+ // sad and confused.
+ if (task != null && task.mStack.getBoundsAnimating()) {
+ return;
+ }
w.setInsetsChanged();
boolean configChanged = w.isConfigChanged();
if (DEBUG_CONFIGURATION && configChanged) {
@@ -10757,16 +10814,22 @@
return;
}
if (replacing) {
- mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT);
- mH.sendMessageDelayed(
- mH.obtainMessage(H.WINDOW_REPLACEMENT_TIMEOUT, appWindowToken),
- WINDOW_REPLACEMENT_TIMEOUT_DURATION);
+ scheduleReplacingWindowTimeouts(appWindowToken);
} else {
appWindowToken.resetReplacingWindows();
}
}
}
+ void scheduleReplacingWindowTimeouts(AppWindowToken appWindowToken) {
+ if (!mReplacingWindowTimeouts.contains(appWindowToken)) {
+ mReplacingWindowTimeouts.add(appWindowToken);
+ }
+ mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT);
+ mH.sendEmptyMessageDelayed(
+ H.WINDOW_REPLACEMENT_TIMEOUT, WINDOW_REPLACEMENT_TIMEOUT_DURATION);
+ }
+
@Override
public int getDockedStackSide() {
synchronized (mWindowMap) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b66de89..c9d945a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -437,7 +437,9 @@
// used to start an entering animation earlier.
private boolean mSurfaceSaved = false;
- // Whether we're performing an entering animation with a saved surface.
+ // Whether we're performing an entering animation with a saved surface. This flag is
+ // true during the time we're showing a window with a previously saved surface. It's
+ // cleared when surface is destroyed, saved, or re-drawn by the app.
private boolean mAnimatingWithSavedSurface;
// Whether the window was visible when we set the app to invisible last time. WM uses
@@ -1256,6 +1258,32 @@
}
/**
+ * Whether this window's drawn state might affect the drawn states of the app token.
+ *
+ * @param visibleOnly Whether we should consider only the windows that's currently
+ * visible in layout. If true, windows that has not relayout to VISIBLE
+ * would always return false.
+ *
+ * @return true if the window should be considered while evaluating allDrawn flags.
+ */
+ boolean mightAffectAllDrawn(boolean visibleOnly) {
+ final boolean isViewVisible = (mViewVisibility == View.VISIBLE)
+ && (mAppToken == null || !mAppToken.clientHidden);
+ return (isOnScreenIgnoringKeyguard() && (!visibleOnly || isViewVisible)
+ || mWinAnimator.mAttrType == TYPE_BASE_APPLICATION)
+ && !mAnimatingExit && !mDestroying;
+ }
+
+ /**
+ * Whether this window is "interesting" when evaluating allDrawn. If it's interesting,
+ * it must be drawn before allDrawn can become true.
+ */
+ boolean isInteresting() {
+ return mAppToken != null && !mAppDied
+ && (!mAppToken.mAppAnimator.freezingScreen || !mAppFreezing);
+ }
+
+ /**
* Like isOnScreen(), but we don't return true if the window is part
* of a transition that has not yet been started.
*/
@@ -1960,6 +1988,11 @@
return mAnimatingWithSavedSurface;
}
+ boolean isAnimatingInvisibleWithSavedSurface() {
+ return mAnimatingWithSavedSurface
+ && (mViewVisibility != View.VISIBLE || mWindowRemovalAllowed);
+ }
+
public void setVisibleBeforeClientHidden() {
mWasVisibleBeforeClientHidden |=
(mViewVisibility == View.VISIBLE || mAnimatingWithSavedSurface);
@@ -2042,6 +2075,7 @@
if (mWinAnimator.mSurfaceController != null) {
mWinAnimator.mSurfaceController.disconnectInTransaction();
}
+ mAnimatingWithSavedSurface = false;
} else {
mWinAnimator.destroySurfaceLocked();
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index e20e245..7e9993d 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -792,15 +792,16 @@
+ " isOnScreen=" + w.isOnScreen() + " allDrawn=" + atoken.allDrawn
+ " freezingScreen=" + atoken.mAppAnimator.freezingScreen);
}
- if (atoken != null && (!atoken.allDrawn || atoken.mAppAnimator.freezingScreen)) {
+ if (atoken != null && (!atoken.allDrawn || !atoken.allDrawnExcludingSaved
+ || atoken.mAppAnimator.freezingScreen)) {
if (atoken.lastTransactionSequence != mService.mTransactionSequence) {
atoken.lastTransactionSequence = mService.mTransactionSequence;
atoken.numInterestingWindows = atoken.numDrawnWindows = 0;
+ atoken.numInterestingWindowsExcludingSaved = 0;
+ atoken.numDrawnWindowsExclusingSaved = 0;
atoken.startingDisplayed = false;
}
- if ((w.isOnScreenIgnoringKeyguard()
- || winAnimator.mAttrType == TYPE_BASE_APPLICATION)
- && !w.mAnimatingExit && !w.mDestroying) {
+ if (!atoken.allDrawn && w.mightAffectAllDrawn(false /* visibleOnly */)) {
if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) {
Slog.v(TAG, "Eval win " + w + ": isDrawn="
+ w.isDrawnLw()
@@ -816,13 +817,14 @@
}
}
if (w != atoken.startingWindow) {
- if (!w.mAppDied &&
- (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing)) {
+ if (w.isInteresting()) {
atoken.numInterestingWindows++;
if (w.isDrawnLw()) {
atoken.numDrawnWindows++;
if (DEBUG_VISIBILITY || DEBUG_ORIENTATION)
Slog.v(TAG, "tokenMayBeDrawn: " + atoken
+ + " w=" + w + " numInteresting="
+ + atoken.numInterestingWindows
+ " freezingScreen="
+ atoken.mAppAnimator.freezingScreen
+ " mAppFreezing=" + w.mAppFreezing);
@@ -834,6 +836,23 @@
atoken.startingDisplayed = true;
}
}
+ if (!atoken.allDrawnExcludingSaved
+ && w.mightAffectAllDrawn(true /* visibleOnly */)) {
+ if (w != atoken.startingWindow && w.isInteresting()) {
+ atoken.numInterestingWindowsExcludingSaved++;
+ if (w.isDrawnLw() && !w.isAnimatingWithSavedSurface()) {
+ atoken.numDrawnWindowsExclusingSaved++;
+ if (DEBUG_VISIBILITY || DEBUG_ORIENTATION)
+ Slog.v(TAG, "tokenMayBeDrawnExcludingSaved: " + atoken
+ + " w=" + w + " numInteresting="
+ + atoken.numInterestingWindowsExcludingSaved
+ + " freezingScreen="
+ + atoken.mAppAnimator.freezingScreen
+ + " mAppFreezing=" + w.mAppFreezing);
+ updateAllDrawn = true;
+ }
+ }
+ }
}
if (isDefaultDisplay && someoneLosingFocus && w == mService.mCurrentFocus
@@ -1539,6 +1558,22 @@
wtoken.token).sendToTarget();
}
}
+ if (!wtoken.allDrawnExcludingSaved) {
+ int numInteresting = wtoken.numInterestingWindowsExcludingSaved;
+ if (numInteresting > 0
+ && wtoken.numDrawnWindowsExclusingSaved >= numInteresting) {
+ if (DEBUG_VISIBILITY)
+ Slog.v(TAG, "allDrawnExcludingSaved: " + wtoken
+ + " interesting=" + numInteresting
+ + " drawn=" + wtoken.numDrawnWindowsExclusingSaved);
+ wtoken.allDrawnExcludingSaved = true;
+ displayContent.layoutNeeded = true;
+ if (wtoken.isAnimatingInvisibleWithSavedSurface()
+ && !mService.mFinishedEarlyAnim.contains(wtoken)) {
+ mService.mFinishedEarlyAnim.add(wtoken);
+ }
+ }
+ }
}
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 00b83841..e306d89 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -643,6 +643,10 @@
traceBeginAndSlog("ConnectivityMetricsLoggerService");
mSystemServiceManager.startService(MetricsLoggerService.class);
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+
+ traceBeginAndSlog("PinnerService");
+ mSystemServiceManager.startService(PinnerService.class);
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
} catch (RuntimeException e) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting core service", e);
diff --git a/services/net/java/android/net/apf/ApfCapabilities.java b/services/net/java/android/net/apf/ApfCapabilities.java
index 0ec50c4..b0e0230 100644
--- a/services/net/java/android/net/apf/ApfCapabilities.java
+++ b/services/net/java/android/net/apf/ApfCapabilities.java
@@ -38,7 +38,8 @@
*/
public final int apfPacketFormat;
- ApfCapabilities(int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat) {
+ public ApfCapabilities(int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat)
+ {
this.apfVersionSupported = apfVersionSupported;
this.maximumApfProgramSize = maximumApfProgramSize;
this.apfPacketFormat = apfPacketFormat;
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 5a10275..4cbfd9c 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -31,6 +31,7 @@
import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
@@ -69,7 +70,8 @@
*/
public class ApfFilter {
// Thread to listen for RAs.
- private class ReceiveThread extends Thread {
+ @VisibleForTesting
+ class ReceiveThread extends Thread {
private final byte[] mPacket = new byte[1514];
private final FileDescriptor mSocket;
private volatile boolean mStopped;
@@ -151,8 +153,10 @@
private final ApfCapabilities mApfCapabilities;
private final IpManager.Callback mIpManagerCallback;
private final NetworkInterface mNetworkInterface;
- private byte[] mHardwareAddress;
- private ReceiveThread mReceiveThread;
+ @VisibleForTesting
+ byte[] mHardwareAddress;
+ @VisibleForTesting
+ ReceiveThread mReceiveThread;
@GuardedBy("this")
private long mUniqueCounter;
@GuardedBy("this")
@@ -161,7 +165,8 @@
@GuardedBy("this")
private byte[] mIPv4Address;
- private ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
+ @VisibleForTesting
+ ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
IpManager.Callback ipManagerCallback, boolean multicastFilter) {
mApfCapabilities = apfCapabilities;
mIpManagerCallback = ipManagerCallback;
@@ -184,7 +189,8 @@
* Attempt to start listening for RAs and, if RAs are received, generating and installing
* filters to ignore useless RAs.
*/
- private void maybeStartFilter() {
+ @VisibleForTesting
+ void maybeStartFilter() {
FileDescriptor socket;
try {
mHardwareAddress = mNetworkInterface.getHardwareAddress();
@@ -724,7 +730,8 @@
}
@GuardedBy("this")
- private void installNewProgramLocked() {
+ @VisibleForTesting
+ void installNewProgramLocked() {
purgeExpiredRasLocked();
final byte[] program;
long programMinLifetime = Long.MAX_VALUE;
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 34152cf..d8eab35 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -276,6 +276,16 @@
public static class Builder {
private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
+ public Builder withoutIPv4() {
+ mConfig.mEnableIPv4 = false;
+ return this;
+ }
+
+ public Builder withoutIPv6() {
+ mConfig.mEnableIPv6 = false;
+ return this;
+ }
+
public Builder withoutIpReachabilityMonitor() {
mConfig.mUsingIpReachabilityMonitor = false;
return this;
@@ -311,6 +321,8 @@
}
}
+ /* package */ boolean mEnableIPv4 = true;
+ /* package */ boolean mEnableIPv6 = true;
/* package */ boolean mUsingIpReachabilityMonitor = true;
/* package */ int mRequestedPreDhcpActionMs;
/* package */ StaticIpConfiguration mStaticIpConfig;
@@ -320,6 +332,8 @@
public ProvisioningConfiguration() {}
public ProvisioningConfiguration(ProvisioningConfiguration other) {
+ mEnableIPv4 = other.mEnableIPv4;
+ mEnableIPv6 = other.mEnableIPv6;
mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
mStaticIpConfig = other.mStaticIpConfig;
@@ -330,6 +344,8 @@
@Override
public String toString() {
return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
+ .add("mEnableIPv4: " + mEnableIPv4)
+ .add("mEnableIPv6: " + mEnableIPv6)
.add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
.add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
.add("mStaticIpConfig: " + mStaticIpConfig)
@@ -883,6 +899,51 @@
}
}
+ private boolean startIPv4() {
+ // If we have a StaticIpConfiguration attempt to apply it and
+ // handle the result accordingly.
+ if (mConfiguration.mStaticIpConfig != null) {
+ if (setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
+ handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
+ } else {
+ if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
+ recordMetric(IpManagerEvent.PROVISIONING_FAIL);
+ mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
+ return false;
+ }
+ } else {
+ // Start DHCPv4.
+ mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpManager.this, mInterfaceName);
+ mDhcpClient.registerForPreDhcpNotification();
+ mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
+
+ if (mConfiguration.mProvisioningTimeoutMs > 0) {
+ final long alarmTime = SystemClock.elapsedRealtime() +
+ mConfiguration.mProvisioningTimeoutMs;
+ mProvisioningTimeoutAlarm.schedule(alarmTime);
+ }
+ }
+
+ return true;
+ }
+
+ private boolean startIPv6() {
+ // Set privacy extensions.
+ try {
+ mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
+ mNwService.enableIpv6(mInterfaceName);
+ } catch (RemoteException re) {
+ Log.e(mTag, "Unable to change interface settings: " + re);
+ return false;
+ } catch (IllegalStateException ie) {
+ Log.e(mTag, "Unable to change interface settings: " + ie);
+ return false;
+ }
+
+ return true;
+ }
+
+
class StoppedState extends State {
@Override
public void enter() {
@@ -978,15 +1039,9 @@
mCallback.setFallbackMulticastFilter(mMulticastFiltering);
}
- // Set privacy extensions.
- try {
- mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
- mNwService.enableIpv6(mInterfaceName);
- // TODO: Perhaps clearIPv4Address() as well.
- } catch (RemoteException re) {
- Log.e(mTag, "Unable to change interface settings: " + re);
- } catch (IllegalStateException ie) {
- Log.e(mTag, "Unable to change interface settings: " + ie);
+ if (mConfiguration.mEnableIPv6) {
+ // TODO: Consider transitionTo(mStoppingState) if this fails.
+ startIPv6();
}
if (mConfiguration.mUsingIpReachabilityMonitor) {
@@ -1001,31 +1056,10 @@
});
}
- // If we have a StaticIpConfiguration attempt to apply it and
- // handle the result accordingly.
- if (mConfiguration.mStaticIpConfig != null) {
- if (setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
- handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
- } else {
- if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
- recordMetric(IpManagerEvent.PROVISIONING_FAIL);
- mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
+ if (mConfiguration.mEnableIPv4) {
+ if (!startIPv4()) {
transitionTo(mStoppingState);
}
- } else {
- // Start DHCPv4.
- mDhcpClient = DhcpClient.makeDhcpClient(
- mContext,
- IpManager.this,
- mInterfaceName);
- mDhcpClient.registerForPreDhcpNotification();
- mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
-
- if (mConfiguration.mProvisioningTimeoutMs > 0) {
- final long alarmTime = SystemClock.elapsedRealtime() +
- mConfiguration.mProvisioningTimeoutMs;
- mProvisioningTimeoutAlarm.schedule(alarmTime);
- }
}
}
diff --git a/services/tests/servicestests/jni/apf_jni.cpp b/services/tests/servicestests/jni/apf_jni.cpp
index 7d142eb..ee43dd4 100644
--- a/services/tests/servicestests/jni/apf_jni.cpp
+++ b/services/tests/servicestests/jni/apf_jni.cpp
@@ -175,7 +175,7 @@
(void*)com_android_server_ApfTest_compareBpfApf },
};
- jniRegisterNativeMethods(env, "com/android/server/ApfTest",
+ jniRegisterNativeMethods(env, "android/net/apf/ApfTest",
gMethods, ARRAY_SIZE(gMethods));
return JNI_VERSION_1_6;
diff --git a/services/tests/servicestests/src/android/net/apf/ApfTest.java b/services/tests/servicestests/src/android/net/apf/ApfTest.java
new file mode 100644
index 0000000..e9c5bdd
--- /dev/null
+++ b/services/tests/servicestests/src/android/net/apf/ApfTest.java
@@ -0,0 +1,952 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.apf;
+
+import static android.system.OsConstants.*;
+
+import com.android.frameworks.servicestests.R;
+
+import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter;
+import android.net.apf.ApfGenerator;
+import android.net.apf.ApfGenerator.IllegalInstructionException;
+import android.net.apf.ApfGenerator.Register;
+import android.net.ip.IpManager;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.os.ConditionVariable;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.nio.ByteBuffer;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * Tests for APF program generator and interpreter.
+ *
+ * Build, install and run with:
+ * runtest frameworks-services -c com.android.server.ApfTest
+ */
+public class ApfTest extends AndroidTestCase {
+ private static final int TIMEOUT_MS = 500;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ // Load up native shared library containing APF interpreter exposed via JNI.
+ System.loadLibrary("servicestestsjni");
+ }
+
+ // Expected return codes from APF interpreter.
+ private final static int PASS = 1;
+ private final static int DROP = 0;
+ // Interpreter will just accept packets without link layer headers, so pad fake packet to at
+ // least the minimum packet size.
+ private final static int MIN_PKT_SIZE = 15;
+
+ private void assertVerdict(int expected, byte[] program, byte[] packet, int filterAge) {
+ assertEquals(expected, apfSimulate(program, packet, filterAge));
+ }
+
+ private void assertPass(byte[] program, byte[] packet, int filterAge) {
+ assertVerdict(PASS, program, packet, filterAge);
+ }
+
+ private void assertDrop(byte[] program, byte[] packet, int filterAge) {
+ assertVerdict(DROP, program, packet, filterAge);
+ }
+
+ private void assertVerdict(int expected, ApfGenerator gen, byte[] packet, int filterAge)
+ throws IllegalInstructionException {
+ assertEquals(expected, apfSimulate(gen.generate(), packet, filterAge));
+ }
+
+ private void assertPass(ApfGenerator gen, byte[] packet, int filterAge)
+ throws IllegalInstructionException {
+ assertVerdict(PASS, gen, packet, filterAge);
+ }
+
+ private void assertDrop(ApfGenerator gen, byte[] packet, int filterAge)
+ throws IllegalInstructionException {
+ assertVerdict(DROP, gen, packet, filterAge);
+ }
+
+ private void assertPass(ApfGenerator gen)
+ throws IllegalInstructionException {
+ assertVerdict(PASS, gen, new byte[MIN_PKT_SIZE], 0);
+ }
+
+ private void assertDrop(ApfGenerator gen)
+ throws IllegalInstructionException {
+ assertVerdict(DROP, gen, new byte[MIN_PKT_SIZE], 0);
+ }
+
+ /**
+ * Test each instruction by generating a program containing the instruction,
+ * generating bytecode for that program and running it through the
+ * interpreter to verify it functions correctly.
+ */
+ @LargeTest
+ public void testApfInstructions() throws IllegalInstructionException {
+ // Empty program should pass because having the program counter reach the
+ // location immediately after the program indicates the packet should be
+ // passed to the AP.
+ ApfGenerator gen = new ApfGenerator();
+ assertPass(gen);
+
+ // Test jumping to pass label.
+ gen = new ApfGenerator();
+ gen.addJump(gen.PASS_LABEL);
+ byte[] program = gen.generate();
+ assertEquals(1, program.length);
+ assertEquals((14 << 3) | (0 << 1) | 0, program[0]);
+ assertPass(program, new byte[MIN_PKT_SIZE], 0);
+
+ // Test jumping to drop label.
+ gen = new ApfGenerator();
+ gen.addJump(gen.DROP_LABEL);
+ program = gen.generate();
+ assertEquals(2, program.length);
+ assertEquals((14 << 3) | (1 << 1) | 0, program[0]);
+ assertEquals(1, program[1]);
+ assertDrop(program, new byte[15], 15);
+
+ // Test jumping if equal to 0.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if not equal to 0.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if registers equal.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0EqualsR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if registers not equal.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test load immediate.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test add.
+ gen = new ApfGenerator();
+ gen.addAdd(1234567890);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test subtract.
+ gen = new ApfGenerator();
+ gen.addAdd(-1234567890);
+ gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test or.
+ gen = new ApfGenerator();
+ gen.addOr(1234567890);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test and.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addAnd(123456789);
+ gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test left shift.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLeftShift(1);
+ gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test right shift.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addRightShift(1);
+ gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test multiply.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addMul(2);
+ gen.addJumpIfR0Equals(1234567890 * 2, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test divide.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addDiv(2);
+ gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test divide by zero.
+ gen = new ApfGenerator();
+ gen.addDiv(0);
+ gen.addJump(gen.DROP_LABEL);
+ assertPass(gen);
+
+ // Test add.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addAddR1();
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test subtract.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, -1234567890);
+ gen.addAddR1();
+ gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test or.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addOrR1();
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test and.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, 123456789);
+ gen.addAndR1();
+ gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test left shift.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addLeftShiftR1();
+ gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test right shift.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, -1);
+ gen.addLeftShiftR1();
+ gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test multiply.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, 2);
+ gen.addMulR1();
+ gen.addJumpIfR0Equals(1234567890 * 2, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test divide.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addLoadImmediate(Register.R1, 2);
+ gen.addDivR1();
+ gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test divide by zero.
+ gen = new ApfGenerator();
+ gen.addDivR1();
+ gen.addJump(gen.DROP_LABEL);
+ assertPass(gen);
+
+ // Test byte load.
+ gen = new ApfGenerator();
+ gen.addLoad8(Register.R0, 1);
+ gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test out of bounds load.
+ gen = new ApfGenerator();
+ gen.addLoad8(Register.R0, 16);
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test half-word load.
+ gen = new ApfGenerator();
+ gen.addLoad16(Register.R0, 1);
+ gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test word load.
+ gen = new ApfGenerator();
+ gen.addLoad32(Register.R0, 1);
+ gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test byte indexed load.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addLoad8Indexed(Register.R0, 0);
+ gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test out of bounds indexed load.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 8);
+ gen.addLoad8Indexed(Register.R0, 8);
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test half-word indexed load.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addLoad16Indexed(Register.R0, 0);
+ gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test word indexed load.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addLoad32Indexed(Register.R0, 0);
+ gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
+
+ // Test jumping if greater than.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if less than.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0LessThan(0, gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator();
+ gen.addJumpIfR0LessThan(1, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if any bits set.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
+ assertDrop(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 3);
+ gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if register greater than.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 2);
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if register less than.
+ gen = new ApfGenerator();
+ gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1);
+ gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jumping if any bits set in register.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 3);
+ gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
+ assertPass(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 3);
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
+ assertDrop(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 3);
+ gen.addLoadImmediate(Register.R0, 3);
+ gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test load from memory.
+ gen = new ApfGenerator();
+ gen.addLoadFromMemory(Register.R0, 0);
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test store to memory.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addStoreToMemory(Register.R1, 12);
+ gen.addLoadFromMemory(Register.R0, 12);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test filter age pre-filled memory.
+ gen = new ApfGenerator();
+ gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen, new byte[MIN_PKT_SIZE], 1234567890);
+
+ // Test packet size pre-filled memory.
+ gen = new ApfGenerator();
+ gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
+ gen.addJumpIfR0Equals(MIN_PKT_SIZE, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test IPv4 header size pre-filled memory.
+ gen = new ApfGenerator();
+ gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+ gen.addJumpIfR0Equals(20, gen.DROP_LABEL);
+ assertDrop(gen, new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x45}, 0);
+
+ // Test not.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addNot(Register.R0);
+ gen.addJumpIfR0Equals(~1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test negate.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addNeg(Register.R0);
+ gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test move.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addMove(Register.R0);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addMove(Register.R1);
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test swap.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R1, 1234567890);
+ gen.addSwap();
+ gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+ assertDrop(gen);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1234567890);
+ gen.addSwap();
+ gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+ assertDrop(gen);
+
+ // Test jump if bytes not equal.
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
+ program = gen.generate();
+ assertEquals(6, program.length);
+ assertEquals((13 << 3) | (1 << 1) | 0, program[0]);
+ assertEquals(1, program[1]);
+ assertEquals(((20 << 3) | (1 << 1) | 0) - 256, program[2]);
+ assertEquals(1, program[3]);
+ assertEquals(1, program[4]);
+ assertEquals(123, program[5]);
+ assertDrop(program, new byte[MIN_PKT_SIZE], 0);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
+ byte[] packet123 = new byte[]{0,123,0,0,0,0,0,0,0,0,0,0,0,0,0};
+ assertPass(gen, packet123, 0);
+ gen = new ApfGenerator();
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
+ assertDrop(gen, packet123, 0);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL);
+ byte[] packet12345 = new byte[]{0,1,2,3,4,5,0,0,0,0,0,0,0,0,0};
+ assertDrop(gen, packet12345, 0);
+ gen = new ApfGenerator();
+ gen.addLoadImmediate(Register.R0, 1);
+ gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL);
+ assertPass(gen, packet12345, 0);
+ }
+
+ /**
+ * Generate some BPF programs, translate them to APF, then run APF and BPF programs
+ * over packet traces and verify both programs filter out the same packets.
+ */
+ @LargeTest
+ public void testApfAgainstBpf() throws Exception {
+ String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
+ "arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
+ "arp or icmp6 or portrange 53-54", "portrange 53-54 or portrange 100-50000",
+ "tcp[tcpflags] & (tcp-ack|tcp-fin) != 0 and (ip[2:2] > 57 or icmp)" };
+ String pcap_filename = stageFile(R.raw.apf);
+ for (String tcpdump_filter : tcpdump_filters) {
+ byte[] apf_program = Bpf2Apf.convert(compileToBpf(tcpdump_filter));
+ assertTrue("Failed to match for filter: " + tcpdump_filter,
+ compareBpfApf(tcpdump_filter, pcap_filename, apf_program));
+ }
+ }
+
+ private class MockIpManagerCallback extends IpManager.Callback {
+ private final ConditionVariable mGotApfProgram = new ConditionVariable();
+ private byte[] mLastApfProgram;
+
+ @Override
+ public void installPacketFilter(byte[] filter) {
+ mLastApfProgram = filter;
+ mGotApfProgram.open();
+ }
+
+ public void resetApfProgramWait() {
+ mGotApfProgram.close();
+ }
+
+ public byte[] getApfProgram() {
+ assertTrue(mGotApfProgram.block(TIMEOUT_MS));
+ return mLastApfProgram;
+ }
+ }
+
+ private static class TestApfFilter extends ApfFilter {
+ public final static byte[] MOCK_MAC_ADDR = new byte[]{1,2,3,4,5,6};
+ private FileDescriptor mWriteSocket;
+
+ public TestApfFilter(IpManager.Callback ipManagerCallback, boolean multicastFilter) throws
+ Exception {
+ super(new ApfCapabilities(2, 1000, ARPHRD_ETHER), NetworkInterface.getByName("lo"),
+ ipManagerCallback, multicastFilter);
+ }
+
+ // Pretend an RA packet has been received and show it to ApfFilter.
+ public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException {
+ // ApfFilter's ReceiveThread will be waiting to read this.
+ Os.write(mWriteSocket, packet, 0, packet.length);
+ }
+
+ @Override
+ void maybeStartFilter() {
+ mHardwareAddress = MOCK_MAC_ADDR;
+ installNewProgramLocked();
+
+ // Create two sockets, "readSocket" and "mWriteSocket" and connect them together.
+ FileDescriptor readSocket = new FileDescriptor();
+ mWriteSocket = new FileDescriptor();
+ try {
+ Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket);
+ } catch (ErrnoException e) {
+ fail();
+ return;
+ }
+ // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs.
+ // This allows us to pretend RA packets have been recieved via pretendPacketReceived().
+ mReceiveThread = new ReceiveThread(readSocket);
+ mReceiveThread.start();
+ }
+
+ @Override
+ public void shutdown() {
+ super.shutdown();
+ IoUtils.closeQuietly(mWriteSocket);
+ }
+ }
+
+ private static final int ETH_HEADER_LEN = 14;
+ private static final int ETH_ETHERTYPE_OFFSET = 12;
+ private static final byte[] ETH_BROADCAST_MAC_ADDRESS = new byte[]{
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
+
+ private static final int IPV4_VERSION_IHL_OFFSET = ETH_HEADER_LEN + 0;
+ private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
+ private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
+
+ private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
+ private static final int IPV6_HEADER_LEN = 40;
+ private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
+ // The IPv6 all nodes address ff02::1
+ private static final byte[] IPV6_ALL_NODES_ADDRESS =
+ new byte[]{ (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+
+ private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
+ private static final int ICMP6_ROUTER_ADVERTISEMENT = 134;
+ private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
+
+ private static final int ICMP6_RA_HEADER_LEN = 16;
+ private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + 6;
+ private static final int ICMP6_RA_CHECKSUM_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
+ private static final int ICMP6_RA_OPTION_OFFSET =
+ ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
+
+ private static final int ICMP6_PREFIX_OPTION_TYPE = 3;
+ private static final int ICMP6_PREFIX_OPTION_LEN = 32;
+ private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4;
+ private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8;
+
+ // From RFC6106: Recursive DNS Server option
+ private static final int ICMP6_RDNSS_OPTION_TYPE = 25;
+ // From RFC6106: DNS Search List option
+ private static final int ICMP6_DNSSL_OPTION_TYPE = 31;
+
+ // From RFC4191: Route Information option
+ private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24;
+ // Above three options all have the same format:
+ private static final int ICMP6_4_BYTE_OPTION_LEN = 8;
+ private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4;
+ private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4;
+
+ private static final int UDP_HEADER_LEN = 8;
+ private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 22;
+
+ private static final int DHCP_CLIENT_PORT = 68;
+ private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 48;
+
+ private static int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
+ private static final byte[] ARP_IPV4_REQUEST_HEADER = new byte[]{
+ 0, 1, // Hardware type: Ethernet (1)
+ 8, 0, // Protocol type: IP (0x0800)
+ 6, // Hardware size: 6
+ 4, // Protocol size: 4
+ 0, 1 // Opcode: request (1)
+ };
+ private static int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24;
+
+ private static byte[] MOCK_IPV4_ADDR = new byte[]{10, 0, 0, 1};
+
+ @LargeTest
+ public void testApfFilterIPv4() throws Exception {
+ MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+ ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */);
+ byte[] program = ipManagerCallback.getApfProgram();
+
+ // Verify empty packet of 100 zero bytes is passed
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ assertPass(program, packet.array(), 0);
+
+ // Verify unicast IPv4 packet is passed
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ assertPass(program, packet.array(), 0);
+
+ // Verify broadcast IPv4, not DHCP to us, is dropped
+ packet.put(ETH_BROADCAST_MAC_ADDRESS);
+ assertDrop(program, packet.array(), 0);
+ packet.put(IPV4_VERSION_IHL_OFFSET, (byte)0x45);
+ assertDrop(program, packet.array(), 0);
+ packet.put(IPV4_PROTOCOL_OFFSET, (byte)IPPROTO_UDP);
+ assertDrop(program, packet.array(), 0);
+ packet.putShort(UDP_DESTINATION_PORT_OFFSET, (short)DHCP_CLIENT_PORT);
+ assertDrop(program, packet.array(), 0);
+
+ // Verify broadcast IPv4 DHCP to us is passed
+ packet.position(DHCP_CLIENT_MAC_OFFSET);
+ packet.put(TestApfFilter.MOCK_MAC_ADDR);
+ assertPass(program, packet.array(), 0);
+
+ apfFilter.shutdown();
+ }
+
+ @LargeTest
+ public void testApfFilterIPv6() throws Exception {
+ MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+ ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */);
+ byte[] program = ipManagerCallback.getApfProgram();
+
+ // Verify empty IPv6 packet is passed
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ assertPass(program, packet.array(), 0);
+
+ // Verify empty ICMPv6 packet is passed
+ packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+ assertPass(program, packet.array(), 0);
+
+ // Verify empty ICMPv6 NA packet is passed
+ packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_NEIGHBOR_ANNOUNCEMENT);
+ assertPass(program, packet.array(), 0);
+
+ // Verify ICMPv6 NA to ff02::1 is dropped
+ packet.position(IPV6_DEST_ADDR_OFFSET);
+ packet.put(IPV6_ALL_NODES_ADDRESS);
+ assertDrop(program, packet.array(), 0);
+
+ apfFilter.shutdown();
+ }
+
+ @LargeTest
+ public void testApfFilterIPv4Multicast() throws Exception {
+ MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+ ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */);
+ byte[] program = ipManagerCallback.getApfProgram();
+
+ // Verify initially disabled multicast filter is off
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+ packet.position(IPV4_DEST_ADDR_OFFSET);
+ packet.put(new byte[]{(byte)224,0,0,1});
+ assertPass(program, packet.array(), 0);
+
+ // Turn on multicast filter and verify it works
+ ipManagerCallback.resetApfProgramWait();
+ apfFilter.setMulticastFilter(true);
+ program = ipManagerCallback.getApfProgram();
+ assertDrop(program, packet.array(), 0);
+
+ // Turn off multicast filter and verify it's off
+ ipManagerCallback.resetApfProgramWait();
+ apfFilter.setMulticastFilter(false);
+ program = ipManagerCallback.getApfProgram();
+ assertPass(program, packet.array(), 0);
+
+ // Verify it can be initialized to on
+ ipManagerCallback.resetApfProgramWait();
+ apfFilter.shutdown();
+ apfFilter = new TestApfFilter(ipManagerCallback, true /* multicastFilter */);
+ program = ipManagerCallback.getApfProgram();
+ assertDrop(program, packet.array(), 0);
+
+ apfFilter.shutdown();
+ }
+
+ private void verifyArpFilter(MockIpManagerCallback ipManagerCallback, ApfFilter apfFilter,
+ LinkProperties linkProperties, int filterResult) {
+ ipManagerCallback.resetApfProgramWait();
+ apfFilter.setLinkProperties(linkProperties);
+ byte[] program = ipManagerCallback.getApfProgram();
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
+ assertPass(program, packet.array(), 0);
+ packet.position(ARP_HEADER_OFFSET);
+ packet.put(ARP_IPV4_REQUEST_HEADER);
+ assertVerdict(filterResult, program, packet.array(), 0);
+ packet.position(ARP_TARGET_IP_ADDRESS_OFFSET);
+ packet.put(MOCK_IPV4_ADDR);
+ assertPass(program, packet.array(), 0);
+ }
+
+ @LargeTest
+ public void testApfFilterArp() throws Exception {
+ MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+ ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */);
+ byte[] program = ipManagerCallback.getApfProgram();
+
+ // Verify initially ARP filter is off
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
+ assertPass(program, packet.array(), 0);
+ packet.position(ARP_HEADER_OFFSET);
+ packet.put(ARP_IPV4_REQUEST_HEADER);
+ assertPass(program, packet.array(), 0);
+ packet.position(ARP_TARGET_IP_ADDRESS_OFFSET);
+ packet.put(MOCK_IPV4_ADDR);
+ assertPass(program, packet.array(), 0);
+
+ // Inform ApfFilter of our address and verify ARP filtering is on
+ LinkProperties lp = new LinkProperties();
+ assertTrue(lp.addLinkAddress(
+ new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 24)));
+ verifyArpFilter(ipManagerCallback, apfFilter, lp, DROP);
+
+ // Inform ApfFilter of loss of IP and verify ARP filtering is off
+ verifyArpFilter(ipManagerCallback, apfFilter, new LinkProperties(), PASS);
+
+ apfFilter.shutdown();
+ }
+
+ // Verify that the last program pushed to the IpManager.Callback properly filters the
+ // given packet for the given lifetime.
+ private void verifyRaLifetime(MockIpManagerCallback ipManagerCallback, ByteBuffer packet,
+ int lifetime) {
+ byte[] program = ipManagerCallback.getApfProgram();
+
+ // Verify new program should drop RA for 1/6th its lifetime
+ assertDrop(program, packet.array(), 0);
+ assertDrop(program, packet.array(), lifetime/6);
+ assertPass(program, packet.array(), lifetime/6 + 1);
+ assertPass(program, packet.array(), lifetime);
+
+ // Verify RA checksum is ignored
+ packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)12345);
+ assertDrop(program, packet.array(), 0);
+ packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)-12345);
+ assertDrop(program, packet.array(), 0);
+
+ // Verify other changes to RA make it not match filter
+ packet.put(0, (byte)-1);
+ assertPass(program, packet.array(), 0);
+ packet.put(0, (byte)0);
+ assertDrop(program, packet.array(), 0);
+ }
+
+ // Test that when ApfFilter is shown the given packet, it generates a program to filter it
+ // for the given lifetime.
+ private void testRaLifetime(TestApfFilter apfFilter, MockIpManagerCallback ipManagerCallback,
+ ByteBuffer packet, int lifetime) throws IOException, ErrnoException {
+ // Verify new program generated if ApfFilter witnesses RA
+ ipManagerCallback.resetApfProgramWait();
+ apfFilter.pretendPacketReceived(packet.array());
+ ipManagerCallback.getApfProgram();
+
+ verifyRaLifetime(ipManagerCallback, packet, lifetime);
+ }
+
+ @LargeTest
+ public void testApfFilterRa() throws Exception {
+ MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+ TestApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */);
+ byte[] program = ipManagerCallback.getApfProgram();
+
+ // Verify RA is passed the first time
+ ByteBuffer basePacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
+ basePacket.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+ basePacket.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+ basePacket.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_ADVERTISEMENT);
+ basePacket.putShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET, (short)1000);
+ assertPass(program, basePacket.array(), 0);
+
+ testRaLifetime(apfFilter, ipManagerCallback, basePacket, 1000);
+
+ // Generate several RAs with different options and lifetimes, and verify when
+ // ApfFilter is shown these packets, it generates programs to filter them for the
+ // appropriate lifetime.
+ ByteBuffer prefixOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_LEN]);
+ basePacket.clear();
+ prefixOptionPacket.put(basePacket);
+ prefixOptionPacket.put((byte)ICMP6_PREFIX_OPTION_TYPE);
+ prefixOptionPacket.put((byte)(ICMP6_PREFIX_OPTION_LEN / 8));
+ prefixOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET, 100);
+ prefixOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET, 200);
+ testRaLifetime(apfFilter, ipManagerCallback, prefixOptionPacket, 100);
+
+ ByteBuffer rdnssOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+ basePacket.clear();
+ rdnssOptionPacket.put(basePacket);
+ rdnssOptionPacket.put((byte)ICMP6_RDNSS_OPTION_TYPE);
+ rdnssOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
+ rdnssOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, 300);
+ testRaLifetime(apfFilter, ipManagerCallback, rdnssOptionPacket, 300);
+
+ ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+ basePacket.clear();
+ routeInfoOptionPacket.put(basePacket);
+ routeInfoOptionPacket.put((byte)ICMP6_ROUTE_INFO_OPTION_TYPE);
+ routeInfoOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
+ routeInfoOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, 400);
+ testRaLifetime(apfFilter, ipManagerCallback, routeInfoOptionPacket, 400);
+
+ ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(
+ new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+ basePacket.clear();
+ dnsslOptionPacket.put(basePacket);
+ dnsslOptionPacket.put((byte)ICMP6_DNSSL_OPTION_TYPE);
+ dnsslOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
+ dnsslOptionPacket.putInt(
+ ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, 2000);
+ // Note that lifetime of 2000 will be ignored in favor of shorter
+ // route lifetime of 1000.
+ testRaLifetime(apfFilter, ipManagerCallback, dnsslOptionPacket, 1000);
+
+ // Verify that current program filters all five RAs:
+ verifyRaLifetime(ipManagerCallback, basePacket, 1000);
+ verifyRaLifetime(ipManagerCallback, prefixOptionPacket, 100);
+ verifyRaLifetime(ipManagerCallback, rdnssOptionPacket, 300);
+ verifyRaLifetime(ipManagerCallback, routeInfoOptionPacket, 400);
+ verifyRaLifetime(ipManagerCallback, dnsslOptionPacket, 1000);
+
+ apfFilter.shutdown();
+ }
+
+ /**
+ * Stage a file for testing, i.e. make it native accessible. Given a resource ID,
+ * copy that resource into the app's data directory and return the path to it.
+ */
+ private String stageFile(int rawId) throws Exception {
+ File file = new File(getContext().getFilesDir(), "staged_file");
+ new File(file.getParent()).mkdirs();
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = getContext().getResources().openRawResource(rawId);
+ out = new FileOutputStream(file);
+ Streams.copy(in, out);
+ } finally {
+ if (in != null) in.close();
+ if (out != null) out.close();
+ }
+ return file.getAbsolutePath();
+ }
+
+ /**
+ * Call the APF interpreter the run {@code program} on {@code packet} pretending the
+ * filter was installed {@code filter_age} seconds ago.
+ */
+ private native static int apfSimulate(byte[] program, byte[] packet, int filter_age);
+
+ /**
+ * Compile a tcpdump human-readable filter (e.g. "icmp" or "tcp port 54") into a BPF
+ * prorgam and return a human-readable dump of the BPF program identical to "tcpdump -d".
+ */
+ private native static String compileToBpf(String filter);
+
+ /**
+ * Open packet capture file {@code pcap_filename} and filter the packets using tcpdump
+ * human-readable filter (e.g. "icmp" or "tcp port 54") compiled to a BPF program and
+ * at the same time using APF program {@code apf_program}. Return {@code true} if
+ * both APF and BPF programs filter out exactly the same packets.
+ */
+ private native static boolean compareBpfApf(String filter, String pcap_filename,
+ byte[] apf_program);
+}
diff --git a/services/tests/servicestests/src/com/android/server/Bpf2Apf.java b/services/tests/servicestests/src/android/net/apf/Bpf2Apf.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/Bpf2Apf.java
rename to services/tests/servicestests/src/android/net/apf/Bpf2Apf.java
index 29594a8..220e54d 100644
--- a/services/tests/servicestests/src/com/android/server/Bpf2Apf.java
+++ b/services/tests/servicestests/src/android/net/apf/Bpf2Apf.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server;
+package android.net.apf;
import android.net.apf.ApfGenerator;
import android.net.apf.ApfGenerator.IllegalInstructionException;
@@ -31,9 +31,9 @@
*
* Example usage:
* javac net/java/android/net/apf/ApfGenerator.java \
- * tests/servicestests/src/com/android/server/Bpf2Apf.java
+ * tests/servicestests/src/android/net/apf/Bpf2Apf.java
* sudo tcpdump -i em1 -d icmp | java -classpath tests/servicestests/src:net/java \
- * com.android.server.Bpf2Apf
+ * android.net.apf.Bpf2Apf
*/
public class Bpf2Apf {
private static int parseImm(String line, String arg) {
diff --git a/services/tests/servicestests/src/com/android/server/ApfTest.java b/services/tests/servicestests/src/com/android/server/ApfTest.java
deleted file mode 100644
index 9ba27cb..0000000
--- a/services/tests/servicestests/src/com/android/server/ApfTest.java
+++ /dev/null
@@ -1,560 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-
-import com.android.frameworks.servicestests.R;
-import android.net.apf.ApfGenerator;
-import android.net.apf.ApfGenerator.IllegalInstructionException;
-import android.net.apf.ApfGenerator.Register;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
-/**
- * Tests for APF program generator and interpreter.
- *
- * Build, install and run with:
- * runtest frameworks-services -c com.android.server.ApfTest
- */
-public class ApfTest extends AndroidTestCase {
- @Override
- public void setUp() throws Exception {
- super.setUp();
- // Load up native shared library containing APF interpreter exposed via JNI.
- System.loadLibrary("servicestestsjni");
- }
-
- // Expected return codes from APF interpreter.
- private final static int PASS = 1;
- private final static int DROP = 0;
- // Interpreter will just accept packets without link layer headers, so pad fake packet to at
- // least the minimum packet size.
- private final static int MIN_PKT_SIZE = 15;
-
- private void assertVerdict(int expected, byte[] program, byte[] packet, int filterAge) {
- assertEquals(expected, apfSimulate(program, packet, filterAge));
- }
-
- private void assertPass(byte[] program, byte[] packet, int filterAge) {
- assertVerdict(PASS, program, packet, filterAge);
- }
-
- private void assertDrop(byte[] program, byte[] packet, int filterAge) {
- assertVerdict(DROP, program, packet, filterAge);
- }
-
- private void assertVerdict(int expected, ApfGenerator gen, byte[] packet, int filterAge)
- throws IllegalInstructionException {
- assertEquals(expected, apfSimulate(gen.generate(), packet, filterAge));
- }
-
- private void assertPass(ApfGenerator gen, byte[] packet, int filterAge)
- throws IllegalInstructionException {
- assertVerdict(PASS, gen, packet, filterAge);
- }
-
- private void assertDrop(ApfGenerator gen, byte[] packet, int filterAge)
- throws IllegalInstructionException {
- assertVerdict(DROP, gen, packet, filterAge);
- }
-
- private void assertPass(ApfGenerator gen)
- throws IllegalInstructionException {
- assertVerdict(PASS, gen, new byte[MIN_PKT_SIZE], 0);
- }
-
- private void assertDrop(ApfGenerator gen)
- throws IllegalInstructionException {
- assertVerdict(DROP, gen, new byte[MIN_PKT_SIZE], 0);
- }
-
- /**
- * Test each instruction by generating a program containing the instruction,
- * generating bytecode for that program and running it through the
- * interpreter to verify it functions correctly.
- */
- @LargeTest
- public void testApfInstructions() throws IllegalInstructionException {
- // Empty program should pass because having the program counter reach the
- // location immediately after the program indicates the packet should be
- // passed to the AP.
- ApfGenerator gen = new ApfGenerator();
- assertPass(gen);
-
- // Test jumping to pass label.
- gen = new ApfGenerator();
- gen.addJump(gen.PASS_LABEL);
- byte[] program = gen.generate();
- assertEquals(1, program.length);
- assertEquals((14 << 3) | (0 << 1) | 0, program[0]);
- assertPass(program, new byte[MIN_PKT_SIZE], 0);
-
- // Test jumping to drop label.
- gen = new ApfGenerator();
- gen.addJump(gen.DROP_LABEL);
- program = gen.generate();
- assertEquals(2, program.length);
- assertEquals((14 << 3) | (1 << 1) | 0, program[0]);
- assertEquals(1, program[1]);
- assertDrop(program, new byte[15], 15);
-
- // Test jumping if equal to 0.
- gen = new ApfGenerator();
- gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if not equal to 0.
- gen = new ApfGenerator();
- gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if registers equal.
- gen = new ApfGenerator();
- gen.addJumpIfR0EqualsR1(gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if registers not equal.
- gen = new ApfGenerator();
- gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test load immediate.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test add.
- gen = new ApfGenerator();
- gen.addAdd(1234567890);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test subtract.
- gen = new ApfGenerator();
- gen.addAdd(-1234567890);
- gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test or.
- gen = new ApfGenerator();
- gen.addOr(1234567890);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test and.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addAnd(123456789);
- gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test left shift.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLeftShift(1);
- gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test right shift.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addRightShift(1);
- gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test multiply.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addMul(2);
- gen.addJumpIfR0Equals(1234567890 * 2, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test divide.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addDiv(2);
- gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test divide by zero.
- gen = new ApfGenerator();
- gen.addDiv(0);
- gen.addJump(gen.DROP_LABEL);
- assertPass(gen);
-
- // Test add.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1234567890);
- gen.addAddR1();
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test subtract.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, -1234567890);
- gen.addAddR1();
- gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test or.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1234567890);
- gen.addOrR1();
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test and.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLoadImmediate(Register.R1, 123456789);
- gen.addAndR1();
- gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test left shift.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLoadImmediate(Register.R1, 1);
- gen.addLeftShiftR1();
- gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test right shift.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLoadImmediate(Register.R1, -1);
- gen.addLeftShiftR1();
- gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test multiply.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLoadImmediate(Register.R1, 2);
- gen.addMulR1();
- gen.addJumpIfR0Equals(1234567890 * 2, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test divide.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addLoadImmediate(Register.R1, 2);
- gen.addDivR1();
- gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test divide by zero.
- gen = new ApfGenerator();
- gen.addDivR1();
- gen.addJump(gen.DROP_LABEL);
- assertPass(gen);
-
- // Test byte load.
- gen = new ApfGenerator();
- gen.addLoad8(Register.R0, 1);
- gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test out of bounds load.
- gen = new ApfGenerator();
- gen.addLoad8(Register.R0, 16);
- gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
- assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test half-word load.
- gen = new ApfGenerator();
- gen.addLoad16(Register.R0, 1);
- gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test word load.
- gen = new ApfGenerator();
- gen.addLoad32(Register.R0, 1);
- gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test byte indexed load.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1);
- gen.addLoad8Indexed(Register.R0, 0);
- gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test out of bounds indexed load.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 8);
- gen.addLoad8Indexed(Register.R0, 8);
- gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
- assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test half-word indexed load.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1);
- gen.addLoad16Indexed(Register.R0, 0);
- gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test word indexed load.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1);
- gen.addLoad32Indexed(Register.R0, 0);
- gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
-
- // Test jumping if greater than.
- gen = new ApfGenerator();
- gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if less than.
- gen = new ApfGenerator();
- gen.addJumpIfR0LessThan(0, gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator();
- gen.addJumpIfR0LessThan(1, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if any bits set.
- gen = new ApfGenerator();
- gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
- assertDrop(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 3);
- gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if register greater than.
- gen = new ApfGenerator();
- gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 2);
- gen.addLoadImmediate(Register.R1, 1);
- gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if register less than.
- gen = new ApfGenerator();
- gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1);
- gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jumping if any bits set in register.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 3);
- gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
- assertPass(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 3);
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
- assertDrop(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 3);
- gen.addLoadImmediate(Register.R0, 3);
- gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test load from memory.
- gen = new ApfGenerator();
- gen.addLoadFromMemory(Register.R0, 0);
- gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test store to memory.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1234567890);
- gen.addStoreToMemory(Register.R1, 12);
- gen.addLoadFromMemory(Register.R0, 12);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test filter age pre-filled memory.
- gen = new ApfGenerator();
- gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen, new byte[MIN_PKT_SIZE], 1234567890);
-
- // Test packet size pre-filled memory.
- gen = new ApfGenerator();
- gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
- gen.addJumpIfR0Equals(MIN_PKT_SIZE, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test IPv4 header size pre-filled memory.
- gen = new ApfGenerator();
- gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
- gen.addJumpIfR0Equals(20, gen.DROP_LABEL);
- assertDrop(gen, new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x45}, 0);
-
- // Test not.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addNot(Register.R0);
- gen.addJumpIfR0Equals(~1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test negate.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addNeg(Register.R0);
- gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test move.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1234567890);
- gen.addMove(Register.R0);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addMove(Register.R1);
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test swap.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R1, 1234567890);
- gen.addSwap();
- gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
- assertDrop(gen);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1234567890);
- gen.addSwap();
- gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
- assertDrop(gen);
-
- // Test jump if bytes not equal.
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
- program = gen.generate();
- assertEquals(6, program.length);
- assertEquals((13 << 3) | (1 << 1) | 0, program[0]);
- assertEquals(1, program[1]);
- assertEquals(((20 << 3) | (1 << 1) | 0) - 256, program[2]);
- assertEquals(1, program[3]);
- assertEquals(1, program[4]);
- assertEquals(123, program[5]);
- assertDrop(program, new byte[MIN_PKT_SIZE], 0);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
- byte[] packet123 = new byte[]{0,123,0,0,0,0,0,0,0,0,0,0,0,0,0};
- assertPass(gen, packet123, 0);
- gen = new ApfGenerator();
- gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
- assertDrop(gen, packet123, 0);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL);
- byte[] packet12345 = new byte[]{0,1,2,3,4,5,0,0,0,0,0,0,0,0,0};
- assertDrop(gen, packet12345, 0);
- gen = new ApfGenerator();
- gen.addLoadImmediate(Register.R0, 1);
- gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL);
- assertPass(gen, packet12345, 0);
- }
-
- /**
- * Generate some BPF programs, translate them to APF, then run APF and BPF programs
- * over packet traces and verify both programs filter out the same packets.
- */
- @LargeTest
- public void testApfAgainstBpf() throws Exception {
- String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
- "arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
- "arp or icmp6 or portrange 53-54", "portrange 53-54 or portrange 100-50000",
- "tcp[tcpflags] & (tcp-ack|tcp-fin) != 0 and (ip[2:2] > 57 or icmp)" };
- String pcap_filename = stageFile(R.raw.apf);
- for (String tcpdump_filter : tcpdump_filters) {
- byte[] apf_program = Bpf2Apf.convert(compileToBpf(tcpdump_filter));
- assertTrue("Failed to match for filter: " + tcpdump_filter,
- compareBpfApf(tcpdump_filter, pcap_filename, apf_program));
- }
- }
-
- /**
- * Stage a file for testing, i.e. make it native accessible. Given a resource ID,
- * copy that resource into the app's data directory and return the path to it.
- */
- private String stageFile(int rawId) throws Exception {
- File file = new File(getContext().getFilesDir(), "staged_file");
- new File(file.getParent()).mkdirs();
- InputStream in = null;
- OutputStream out = null;
- try {
- in = getContext().getResources().openRawResource(rawId);
- out = new FileOutputStream(file);
- Streams.copy(in, out);
- } finally {
- if (in != null) in.close();
- if (out != null) out.close();
- }
- return file.getAbsolutePath();
- }
-
- /**
- * Call the APF interpreter the run {@code program} on {@code packet} pretending the
- * filter was installed {@code filter_age} seconds ago.
- */
- private native static int apfSimulate(byte[] program, byte[] packet, int filter_age);
-
- /**
- * Compile a tcpdump human-readable filter (e.g. "icmp" or "tcp port 54") into a BPF
- * prorgam and return a human-readable dump of the BPF program identical to "tcpdump -d".
- */
- private native static String compileToBpf(String filter);
-
- /**
- * Open packet capture file {@code pcap_filename} and filter the packets using tcpdump
- * human-readable filter (e.g. "icmp" or "tcp port 54") compiled to a BPF program and
- * at the same time using APF program {@code apf_program}. Return {@code true} if
- * both APF and BPF programs filter out exactly the same packets.
- */
- private native static boolean compareBpfApf(String filter, String pcap_filename,
- byte[] apf_program);
-}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 8f09bda..33e6cea 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -699,7 +699,7 @@
sDefaults.putBoolean(KEY_CARRIER_VOLTE_AVAILABLE_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_VT_AVAILABLE_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false);
- sDefaults.putBoolean(KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, true);
+ sDefaults.putBoolean(KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL, false);
sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT, 2);