Merge "Fixed connectivity state in some power saving scenarios." 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/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index ed6ab56..a5b2a91 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -825,13 +825,31 @@
if (ii == null) {
continue;
}
- final ActivityInfo ai = ii.resolveActivityInfo(pm, 0);
+
+ // We reimplement Intent#resolveActivityInfo here because if we have an
+ // implicit intent, we want the ResolveInfo returned by PackageManager
+ // instead of one we reconstruct ourselves. The ResolveInfo returned might
+ // have extra metadata and resolvePackageName set and we want to respect that.
+ ResolveInfo ri = null;
+ ActivityInfo ai = null;
+ final ComponentName cn = ii.getComponent();
+ if (cn != null) {
+ try {
+ ai = pm.getActivityInfo(ii.getComponent(), 0);
+ ri = new ResolveInfo();
+ ri.activityInfo = ai;
+ } catch (PackageManager.NameNotFoundException ignored) {
+ // ai will == null below
+ }
+ }
+ if (ai == null) {
+ ri = pm.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY);
+ ai = ri != null ? ri.activityInfo : null;
+ }
if (ai == null) {
Log.w(TAG, "No activity found for " + ii);
continue;
}
- ResolveInfo ri = new ResolveInfo();
- ri.activityInfo = ai;
UserManager userManager =
(UserManager) getSystemService(Context.USER_SERVICE);
if (ii instanceof LabeledIntent) {
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/guide/topics/manifest/permission-element.jd b/docs/html/guide/topics/manifest/permission-element.jd
index 4bb5f6a..d8dbf40 100644
--- a/docs/html/guide/topics/manifest/permission-element.jd
+++ b/docs/html/guide/topics/manifest/permission-element.jd
@@ -10,70 +10,80 @@
android:<a href="#label">label</a>="<i>string resource</i>"
android:<a href="#nm">name</a>="<i>string</i>"
android:<a href="#pgroup">permissionGroup</a>="<i>string</i>"
- android:<a href="#plevel">protectionLevel</a>=["normal" | "dangerous" |
+ android:<a href="#plevel">protectionLevel</a>=["normal" | "dangerous" |
"signature" | "signatureOrSystem"] /></pre></dd>
<dt>contained in:</dt>
-<dd><code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code></dd>
+<dd><code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"
+ ><manifest></a></code></dd>
<dt>description:</dt>
-<dd itemprop="description">Declares a security permission that can be used to limit access
-to specific components or features of this or other applications.
-See the <a href="{@docRoot}guide/topics/manifest/manifest-intro.html#perms">Permissions</a>
-section in the introduction,
-and the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
-document for more information on how permissions work.</dd>
+<dd itemprop="description">Declares a security permission that can be used to
+limit access to specific components or features of this or other applications.
+See the <a href="{@docRoot}guide/topics/manifest/manifest-intro.html#perms"
+>Permissions</a> section in the introduction, and the <a
+href="{@docRoot}guide/topics/security/security.html">Security and
+Permissions</a> document for more information on how permissions work.</dd>
<dt>attributes:</dt>
<dd><dl class="attr">
<dt><a name="desc"></a>{@code android:description}</dt>
-<dd>A user-readable description of the permission, longer and more
-informative than the label. It may be displayed to explain the
-permission to the user — for example, when the user is asked
+<dd>A user-readable description of the permission, longer and more
+informative than the label. It may be displayed to explain the
+permission to the user — for example, when the user is asked
whether to grant the permission to another application.
<p>
-This attribute must be set as a reference to a string resource;
+This attribute must be set as a reference to a string resource;
unlike the {@code label} attribute, it cannot be a raw string.
</p></dd>
<dt><a name="icon"></a>{@code android:icon}</dt>
-<dd>A reference to a drawable resource for an icon that represents the
+<dd>A reference to a drawable resource for an icon that represents the
permission.</dd>
<dt><a name="label"></a>{@code android:label}</dt>
-<dd>A name for the permission, one that can be displayed to users.
+<dd>A name for the permission, one that can be displayed to users.
<p>
-As a convenience, the label can be directly set
-as a raw string while you're developing the application. However,
-when the application is ready to be published, it should be set as a
-reference to a string resource, so that it can be localized like other
+As a convenience, the label can be directly set
+as a raw string while you're developing the application. However,
+when the application is ready to be published, it should be set as a
+reference to a string resource, so that it can be localized like other
strings in the user interface.
</p></dd>
<dt><a name="nm"></a>{@code android:name}</dt>
-<dd>The name of the permission. This is the name that will be used in
-code to refer to the permission — for example, in a
-<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code> element and the
+<dd>The name of the permission. This is the name that will be used in
+code to refer to the permission — for example, in a
+<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"
+ ><uses-permission></a></code> element and the
{@code permission} attributes of application components.
-<p>
-The name must be unique, so it should use Java-style scoping —
-for example, "{@code com.example.project.PERMITTED_ACTION}".
-</p></dd>
+
+<p class="note">
+ <strong>Note:</strong> The system does not allow multiple packages to declare
+ a permission with the same name, unless all the packages are signed with the
+ same certificate. If a package declares a permission, the system does not permit
+ the user to install other packages with the same permission name, unless
+ those packages are signed with the same certificate as the first package. To
+ avoid naming collisions, we recommend using reverse-domain-style naming for custom
+ permissions, for example <code>com.example.myapp.ENGAGE_HYPERSPACE</code>.
+</p>
+</dd>
<dt><a name="pgroup"></a>{@code android:permissionGroup}</dt>
-<dd>Assigns this permission to a group. The value of this attribute is
-the name of the group, which must be declared with the
-<code><a href="{@docRoot}guide/topics/manifest/permission-group-element.html"><permission-group></a></code> element in this
+<dd>Assigns this permission to a group. The value of this attribute is
+the name of the group, which must be declared with the
+<code><a href="{@docRoot}guide/topics/manifest/permission-group-element.html"
+ ><permission-group></a></code> element in this
or another application. If this attribute is not set, the permission
does not belong to a group.</dd>
<dt><a name="plevel"></a>{@code android:protectionLevel}</dt>
<dd>Characterizes the potential risk implied in the permission and
indicates the procedure the system should follow when determining
-whether or not to grant the permission to an application requesting it.
+whether or not to grant the permission to an application requesting it.
The value can be set to one of the following strings:
<table>
@@ -82,9 +92,9 @@
<th>Meaning</th>
</tr><tr>
<td>"{@code normal}"</td>
- <td>The default value. A lower-risk permission that gives requesting
- applications access to isolated application-level features, with
- minimal risk to other applications, the system, or the user.
+ <td>The default value. A lower-risk permission that gives requesting
+ applications access to isolated application-level features, with
+ minimal risk to other applications, the system, or the user.
The system automatically grants this type
of permission to a requesting application at installation, without
asking for the user's explicit approval (though the user always
@@ -109,11 +119,11 @@
asking for the user's explicit approval.
</tr><tr>
<td>"{@code signatureOrSystem}"</td>
- <td>A permission that the system grants only to applications that are
+ <td>A permission that the system grants only to applications that are
in the Android system image <em>or</em> that are signed with the same
- certificate as the application that declared the permission. Please avoid using this
- option, as the {@code signature} protection level should be sufficient
- for most needs and works regardless of exactly where applications are
+ certificate as the application that declared the permission. Please avoid using this
+ option, as the {@code signature} protection level should be sufficient
+ for most needs and works regardless of exactly where applications are
installed. The "{@code signatureOrSystem}"
permission is used for certain special situations where multiple
vendors have applications built into a system image and need
diff --git a/docs/html/guide/topics/security/permissions.jd b/docs/html/guide/topics/security/permissions.jd
index f7f70b3..e7bf760 100644
--- a/docs/html/guide/topics/security/permissions.jd
+++ b/docs/html/guide/topics/security/permissions.jd
@@ -18,6 +18,7 @@
</li>
<li><a href="#defining">Defining and Enforcing Permissions</a>
<ol>
+ <li><a href="#custom-recommendations">Custom permission recommendations</a></li>
<li><a href="#manifest">...in AndroidManifest.xml</a></li>
<li><a href="#broadcasts">...when Sending Broadcasts</a></li>
<li><a href="#enforcement">Other Permission Enforcement</a></li>
@@ -540,17 +541,19 @@
<a name="declaring"></a>
<h2 id="defining">Defining and Enforcing Permissions</h2>
-<p>To enforce your own permissions, you must first declare them in your
-<code>AndroidManifest.xml</code> using one or more
-<code>{@link android.R.styleable#AndroidManifestPermission <permission>}</code>
-tags.</p>
+<p>
+ To enforce your own permissions, you must first declare them in your
+ <code>AndroidManifest.xml</code> using one or more <a href=
+ "{@docRoot}guide/topics/manifest/permission-element.html"><code><permission></code></a>
+ elements.
+</p>
<p>For example, an application that wants to control who can start one
of its activities could declare a permission for this operation as follows:</p>
<pre><manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.me.app.myapp" >
- <permission android:name="com.me.app.myapp.permission.DEADLY_ACTIVITY"
+ package="com.example.myapp" >
+ <permission android:name="com.example.myapp.permission.DEADLY_ACTIVITY"
android:label="@string/permlab_deadlyActivity"
android:description="@string/permdesc_deadlyActivity"
android:permissionGroup="android.permission-group.COST_MONEY"
@@ -558,50 +561,65 @@
...
</manifest></pre>
+<p class="note">
+ <strong>Note:</strong> The system does not allow multiple packages to declare
+ a permission with the same name, unless all the packages are signed with the
+ same certificate. If a package declares a permission, the system does not permit
+ the user to install other packages with the same permission name, unless
+ those packages are signed with the same certificate as the first package. To
+ avoid naming collisions, we recommend using reverse-domain-style naming for custom
+ permissions, for example <code>com.example.myapp.ENGAGE_HYPERSPACE</code>.
+</p>
+
<p>The {@link android.R.styleable#AndroidManifestPermission_protectionLevel
-<protectionLevel>} attribute is required, telling the system how the
+protectionLevel} attribute is required, telling the system how the
user is to be informed of applications requiring the permission, or who is
allowed to hold that permission, as described in the linked documentation.</p>
-<p>The {@link android.R.styleable#AndroidManifestPermission_permissionGroup
-<permissionGroup>} attribute is optional, and only used to help the system display
-permissions to the user. You will usually want to set this to either a standard
-system group (listed in {@link android.Manifest.permission_group
-android.Manifest.permission_group}) or in more rare cases to one defined by
-yourself. It is preferred to use an existing group, as this simplifies the
-permission UI shown to the user.</p>
+<p>
+ The <a href=
+ "{@docRoot}guide/topics/manifest/permission-group-element.html"
+ ><code>android:permissionGroup</code></a>
+ attribute is optional, and only used to help the system display permissions
+ to the user. In most cases you will want to set this to a standard system
+ group (listed in {@link android.Manifest.permission_group
+ android.Manifest.permission_group}), although you can define a group yourself.
+ It is preferable to use an existing group, as this simplifies the
+ permission UI shown to the user.
+</p>
-<p>Note that both a label and description should be supplied for the
-permission. These are string resources that can be displayed to the user when
+<p>You need to supply both a label and description for the
+permission. These are string resources that the user can see when
they are viewing a list of permissions
(<code>{@link android.R.styleable#AndroidManifestPermission_label android:label}</code>)
or details on a single permission (
<code>{@link android.R.styleable#AndroidManifestPermission_description android:description}</code>).
-The label should be short, a few words
+The label should be short; a few words
describing the key piece of functionality the permission is protecting. The
-description should be a couple sentences describing what the permission allows
-a holder to do. Our convention for the description is two sentences, the first
-describing the permission, the second warning the user of what bad things
-can happen if an application is granted the permission.</p>
+description should be a couple of sentences describing what the permission allows
+a holder to do. Our convention is a two-sentence description:
+the first sentence describes the permission, and the second sentence warns the
+user of the type of things that can go wrong if an application is granted the
+permission.</p>
<p>Here is an example of a label and description for the CALL_PHONE
permission:</p>
<pre>
- <string name="permlab_callPhone">directly call phone numbers</string>
- <string name="permdesc_callPhone">Allows the application to call
- phone numbers without your intervention. Malicious applications may
- cause unexpected calls on your phone bill. Note that this does not
- allow the application to call emergency numbers.</string>
+<string name="permlab_callPhone">directly call phone numbers</string>
+<string name="permdesc_callPhone">Allows the application to call
+ phone numbers without your intervention. Malicious applications may
+ cause unexpected calls on your phone bill. Note that this does not
+ allow the application to call emergency numbers.</string>
</pre>
-<p>You can look at the permissions currently defined in the system with the
+<p>You can view at the permissions currently defined in the system using the
Settings app and the shell command <code>adb shell pm list permissions</code>.
-To use the Settings app, go to Settings > Applications. Pick an app and
+To use the Settings app, go to <b>Settings</b> > <b>Applications</b>. Pick an app and
scroll down to see the permissions that the app uses. For developers, the adb '-s'
option displays the permissions in a form similar to how the user will see them:</p>
-<pre>
+<pre class="no-pretty-print">
$ adb shell pm list permissions -s
All Permissions:
@@ -615,14 +633,53 @@
...</pre>
+<h3 id="custom-recommendations">
+ Custom permission recommendations
+</h3>
+
+<p>
+ Apps can define their own custom permissions and request custom permissions
+ from other apps by defining <a href=
+ "{@docRoot}guide/topics/manifest/uses-permission-element.html"><code
+ ><uses-permission></code></a> elements.
+ However, you should carefully assess whether it is necessary for your app to
+ do so.
+</p>
+
+<ul>
+ <li>If you are designing a suite of apps that expose functionality to one
+ another, try to design the apps so that each permission is defined only once.
+ You must do this if the apps are not all signed with the same certificate.
+ Even if the apps are all signed with the same certificate, it's a
+ best practice to define each permission once only.
+ </li>
+
+ <li>If the functionality is only available to apps signed with the same
+ signature as the providing app, you may be able to avoid defining custom
+ permissions by using signature checks. When one of your apps makes a request
+ of another of your apps, the second app can verify that both apps are signed
+ with the same certificate before complying with the request.
+ </li>
+
+ <li>If you are developing a suite of apps runs only on your own
+ devices, you should develop and install a package that
+ manages permissions for all the apps in the suite. This package does not need
+ to provide any services itself. It just declares all the permissions, and the
+ other apps in the suite request those permissions with the <a href=
+ "{@docRoot}guide/topics/manifest/uses-permission-element.html"><code
+ ><uses-permission></code></a>
+ element.
+ </li>
+</ul>
+
<a name="manifest"></a>
<h3>Enforcing Permissions in AndroidManifest.xml</h3>
-<p>High-level permissions restricting access to entire components of the
-system or application can be applied through your
-<code>AndroidManifest.xml</code>. All that this requires is including an {@link
+<p>TYou can apply high-level permissions restricting access to entire components
+of the system or application through your
+<code>AndroidManifest.xml</code>. To do this, include an {@link
android.R.attr#permission android:permission} attribute on the desired
-component, naming the permission that will be used to control access to
+component, naming the permission that controls access to
it.</p>
<p><strong>{@link android.app.Activity}</strong> permissions
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..09227f2 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>
@@ -35,9 +36,11 @@
<li><a href="#default_trusted_ca">Default Trusted CA</a></li>
<li><a href="#apk_signature_v2">APK signature scheme v2</a></li>
<li><a href="#scoped_directory_access">Scoped directory access</a></li>
+ <li><a href="#keyboard_shortcuts_helper">Keyboard Shortcuts Helper</a></li>
+ <li><a href="#sustained_performance_api">Sustained Performance API</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>
+ <li><a href="#framemetrics_api">FrameMetricsListener API</a></li>
</ol>
</div>
</div>
@@ -354,7 +357,7 @@
<p>
Vulkan is only available to apps on devices with Vulkan-capable hardware,
- such as Nexus 5X and Nexus 6P. We're working closely with our
+ such as Nexus 5X, Nexus 6P, and Nexus Player. We're working closely with our
partners to bring Vulkan to more devices as soon as possible.
</p>
@@ -479,6 +482,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>
@@ -629,7 +675,7 @@
<p>Direct boot improves device startup times and lets registered
apps have limited functionality even after an unexpected reboot.
For example, if an encrypted device reboots while the user is sleeping,
-registered alarms, messages and incoming calls can now continue notify
+registered alarms, messages and incoming calls can now continue to notify
the user as normal. This also means accessibility services can also be
available immediately after a restart.</p>
@@ -733,19 +779,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 +812,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>
@@ -793,6 +847,53 @@
<a href="{@docRoot}preview/features/scoped-folder-access.html">Scoped
Directory Access</a> developer documentation.</p>
+<h2 id="keyboard_shortcuts_helper">Keyboard Shortcuts Helper</h2>
+
+<p>
+In Android N, the user can press Meta+/ to trigger a Keyboard Shortcuts
+screen that displays all shortcuts available both from the system and from
+the app in focus. These are retrieved automatically from the app’s menu if
+available, but developers can provide their own fine-tuned shortcuts lists
+for the screen. This is done simply by overriding the following method
+in {@code Activity.java}:
+</p>
+
+<pre>
+public void onProvideKeyboardShortcuts(
+ List<KeyboardShortcutGroup> data, Menu menu, int deviceId)
+</pre>
+
+<p>
+To trigger the Keyboard Shortcuts Helper from anywhere in your app,
+call {@code requestKeyboardShortcutsHelper} for the relevant activity.
+</p>
+
+<h2 id="sustained_performance_api">Sustained Performance API</h2>
+
+<p>
+Performance can fluctuate dramatically for long-running apps, because the
+system throttles system-on-chip engines as device components reach their
+temperature limits. This fluctuation presents a moving target for app
+developers creating high-performance, long-running apps.
+</p>
+
+<p>
+To address these limitations, Android N includes support for
+<em>sustained performance mode</em>, enabling OEMs to provide hints on
+device-performance capabilities for long-running applications. App developers
+can use these hints to tune applications for a predictable,
+consistent level of device performance over long periods of time.
+</p>
+
+<p>
+App developers can try out this new API in the N Developer Preview on
+Nexus 6P devices only. To use this feature,
+set the sustained performance window flag for the window
+you want to run in sustained performance mode. Set this flag using the
+{@code setSustainedPerformanceMode(boolean)} method. The system automatically
+disables this mode when the window is no longer in focus.
+</p>
+
<h2 id="print_svc">Print Service Enhancements</h2>
<p>
@@ -834,6 +935,51 @@
"{@docRoot}preview/setup-sdk.html#docs-dl">API Reference</a>.
</p>
+<h2 id="framemetrics_api">FrameMetricsListener API</h2>
+
+<p>
+The FrameMetricsListener API allows an app to monitor its UI rendering
+performance by exposing a streaming pubsub API to transfer frame
+timing info for the app's current window. The data returned is
+equivalent to that displayed by {@code adb shell dumpsys gfxinfo framestats},
+but is not limited to the past 120 frames.
+</p>
+
+<p>
+You can use FrameMetricsListener to measure interaction-level UI
+performance in production, without a USB connection. This API
+allows collection of data at a much higher granularity than does
+{@code adb shell dumpsys gfxinfo}. This higher granularity is possible because
+the system can collect data for particular interactions in the app; the system
+need not capture a global summary of the entire app’s
+performance, or clear any global state. You can use this
+capability to gather performance data and catch regressions in UI performance
+for real use cases within an app.
+</p>
+
+<p>
+The API provides a callback interface to be implemented and
+registered on the window that you wish to monitor:
+</p>
+
+<pre>
+ public interface FrameMetricsListener {
+ void onMetricsAvailable(Window window, FrameMetrics frameMetrics,
+ int dropCountSinceLastInvocation);
+ }
+</pre>
+
+<p>
+The API returns a FrameMetrics object, which contains timing data that
+the rendering subsystem reports for various milestones in a frame lifecycle.
+The supported metrics are: {@code UNKNOWN_DELAY_DURATION},
+{@code INPUT_HANDLING_DURATION}, {@code ANIMATION_DURATION},
+{@code LAYOUT_MEASURE_DURATION}, {@code DRAW_DURATION}, {@code SYNC_DURATION},
+{@code COMMAND_ISSUE_DURATION}, {@code SWAP_BUFFERS_DURATION},
+{@code TOTAL_DURATION}, and {@code FIRST_DRAW_FRAME}
+</p>
+
+
<h2 id="virtual_files">Virtual Files</h2>
<p>
@@ -916,41 +1062,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..0b74e31 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,22 @@
// 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.
- 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))) {
+ // Remove the task if it is blacklisted
+ if (sRecentsBlacklist.contains(t.realActivity.getClassName())) {
iter.remove();
continue;
}
+
+ // 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;
+ isExcluded |= quietProfileIds.contains(t.userId);
+ 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/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
index f801963..0a3197c 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
@@ -154,7 +154,7 @@
getContext().sendBroadcast(intent);
intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_CLOCK);
- intent.putExtra("hhmm", "0600");
+ intent.putExtra("hhmm", "0700");
getContext().sendBroadcast(intent);
intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_NETWORK);
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/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index be53cfc..be021ea 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -73,6 +73,7 @@
"/system/bin/surfaceflinger",
"media.codec", // system/bin/mediacodec
"media.extractor", // system/bin/mediaextractor
+ "com.android.bluetooth", // Bluetooth service
};
static Watchdog sWatchdog;
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index c15effb..64bd14c 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -677,16 +677,17 @@
// app in a locked managed profile from an unlocked parent allow it to resolve
// as user will be sent via confirm credentials to unlock the profile.
UserManager userManager = UserManager.get(mService.mContext);
- UserInfo parent = null;
+ boolean profileLockedAndParentUnlockingOrUnlocked = false;
long token = Binder.clearCallingIdentity();
try {
- parent = userManager.getProfileParent(userId);
+ UserInfo parent = userManager.getProfileParent(userId);
+ profileLockedAndParentUnlockingOrUnlocked = (parent != null)
+ && userManager.isUserUnlockingOrUnlocked(parent.id)
+ && !userManager.isUserUnlockingOrUnlocked(userId);
} finally {
Binder.restoreCallingIdentity(token);
}
- if (parent != null
- && userManager.isUserUnlockingOrUnlocked(parent.getUserHandle())
- && !userManager.isUserUnlockingOrUnlocked(userInfo.getUserHandle())) {
+ if (profileLockedAndParentUnlockingOrUnlocked) {
rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId,
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
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/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 41a563c..b6c269e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5011,6 +5011,25 @@
ri = new ResolveInfo(mResolveInfo);
ri.activityInfo = new ActivityInfo(ri.activityInfo);
ri.activityInfo.labelRes = ResolverActivity.getLabelRes(intent.getAction());
+ // If all of the options come from the same package, show the application's
+ // label and icon instead of the generic resolver's.
+ // Some calls like Intent.resolveActivityInfo query the ResolveInfo from here
+ // and then throw away the ResolveInfo itself, meaning that the caller loses
+ // the resolvePackageName. Therefore the activityInfo.labelRes above provides
+ // a fallback for this case; we only set the target package's resources on
+ // the ResolveInfo, not the ActivityInfo.
+ final String intentPackage = intent.getPackage();
+ if (!TextUtils.isEmpty(intentPackage) && allHavePackage(query, intentPackage)) {
+ final ApplicationInfo appi = query.get(0).activityInfo.applicationInfo;
+ ri.resolvePackageName = intentPackage;
+ if (userNeedsBadging(userId)) {
+ ri.noResourceId = true;
+ } else {
+ ri.icon = appi.icon;
+ }
+ ri.iconResourceId = appi.icon;
+ ri.labelRes = appi.labelRes;
+ }
ri.activityInfo.applicationInfo = new ApplicationInfo(
ri.activityInfo.applicationInfo);
if (userId != 0) {
@@ -5026,6 +5045,24 @@
return null;
}
+ /**
+ * Return true if the given list is not empty and all of its contents have
+ * an activityInfo with the given package name.
+ */
+ private boolean allHavePackage(List<ResolveInfo> list, String packageName) {
+ if (ArrayUtils.isEmpty(list)) {
+ return false;
+ }
+ for (int i = 0, N = list.size(); i < N; i++) {
+ final ResolveInfo ri = list.get(i);
+ final ActivityInfo ai = ri != null ? ri.activityInfo : null;
+ if (ai == null || !packageName.equals(ai.packageName)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType,
int flags, List<ResolveInfo> query, boolean debug, int userId) {
final int N = query.size();
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/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 9e66c28..5678ad9 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -829,8 +829,8 @@
// Adjust for surface insets.
mTmpSize.left -= scale * attrs.surfaceInsets.left;
mTmpSize.top -= scale * attrs.surfaceInsets.top;
- mTmpSize.right += scale * (attrs.surfaceInsets.left + attrs.surfaceInsets.right);
- mTmpSize.bottom += scale * (attrs.surfaceInsets.top + attrs.surfaceInsets.bottom);
+ mTmpSize.right += scale * attrs.surfaceInsets.right;
+ mTmpSize.bottom += scale * attrs.surfaceInsets.bottom;
}
boolean hasSurface() {
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);