Merge changes Ibdf23227,I3681f98c

* changes:
  Update Wifi to use new keystore function
  Add signing to keystore
diff --git a/Android.mk b/Android.mk
index 5572c50..15a4196 100644
--- a/Android.mk
+++ b/Android.mk
@@ -324,9 +324,6 @@
 # include definition of libcore_to_document
 include $(LOCAL_PATH)/../../libcore/Docs.mk
 
-# include definition of libfilterfw_to_document
-include $(LOCAL_PATH)/../../system/media/mca/Docs.mk
-
 non_base_dirs := \
 	../../external/apache-http/src/org/apache/http
 
@@ -349,8 +346,7 @@
 # Common sources for doc check and api check
 common_src_files := \
 	$(call find-other-html-files, $(html_dirs)) \
-	$(addprefix ../../libcore/, $(call libcore_to_document, $(LOCAL_PATH)/../../libcore)) \
-	$(addprefix ../../system/media/mca/, $(call libfilterfw_to_document, $(LOCAL_PATH)/../../system/media/mca)) \
+	$(addprefix ../../libcore/, $(call libcore_to_document, $(LOCAL_PATH)/../../libcore))
 
 # These are relative to frameworks/base
 framework_docs_LOCAL_SRC_FILES := \
diff --git a/api/current.txt b/api/current.txt
index 505ee68..4cdd30b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2744,6 +2744,7 @@
     method public static boolean isUserAMonkey();
     method public void killBackgroundProcesses(java.lang.String);
     method public void moveTaskToFront(int, int);
+    method public void moveTaskToFront(int, int, android.os.Bundle);
     method public deprecated void restartPackage(java.lang.String);
     field public static final int MOVE_TASK_NO_USER_ACTION = 2; // 0x2
     field public static final int MOVE_TASK_WITH_HOME = 1; // 0x1
@@ -2867,9 +2868,14 @@
   public class ActivityOptions {
     method public void join(android.app.ActivityOptions);
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
+    method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int, android.app.ActivityOptions.OnAnimationStartedListener);
     method public android.os.Bundle toBundle();
   }
 
+  public static abstract interface ActivityOptions.OnAnimationStartedListener {
+    method public abstract void onAnimationStarted();
+  }
+
   public class AlarmManager {
     method public void cancel(android.app.PendingIntent);
     method public void set(int, long, android.app.PendingIntent);
@@ -3694,6 +3700,7 @@
 
   public static class Notification.Builder {
     ctor public Notification.Builder(android.content.Context);
+    method public android.app.Notification.Builder addAction(int, java.lang.CharSequence, android.app.PendingIntent);
     method public android.app.Notification.Builder addKind(java.lang.String);
     method public android.app.Notification getNotification();
     method public android.app.Notification.Builder setAutoCancel(boolean);
@@ -3705,6 +3712,7 @@
     method public android.app.Notification.Builder setDefaults(int);
     method public android.app.Notification.Builder setDeleteIntent(android.app.PendingIntent);
     method public android.app.Notification.Builder setFullScreenIntent(android.app.PendingIntent, boolean);
+    method public android.app.Notification.Builder setIntruderActionsShowText(boolean);
     method public android.app.Notification.Builder setLargeIcon(android.graphics.Bitmap);
     method public android.app.Notification.Builder setLights(int, int, int);
     method public android.app.Notification.Builder setNumber(int);
@@ -3718,6 +3726,7 @@
     method public android.app.Notification.Builder setSound(android.net.Uri, int);
     method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
     method public android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+    method public android.app.Notification.Builder setUsesIntruderAlert(boolean);
     method public android.app.Notification.Builder setVibrate(long[]);
     method public android.app.Notification.Builder setWhen(long);
   }
@@ -5203,6 +5212,7 @@
     field public static final java.lang.String LAYOUT_INFLATER_SERVICE = "layout_inflater";
     field public static final java.lang.String LOCATION_SERVICE = "location";
     field public static final int MODE_APPEND = 32768; // 0x8000
+    field public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 8; // 0x8
     field public static final int MODE_MULTI_PROCESS = 4; // 0x4
     field public static final int MODE_PRIVATE = 0; // 0x0
     field public static final int MODE_WORLD_READABLE = 1; // 0x1
@@ -7312,6 +7322,7 @@
     method public static android.database.sqlite.SQLiteDatabase create(android.database.sqlite.SQLiteDatabase.CursorFactory);
     method public int delete(java.lang.String, java.lang.String, java.lang.String[]);
     method public static boolean deleteDatabase(java.io.File);
+    method public void disableWriteAheadLogging();
     method public boolean enableWriteAheadLogging();
     method public void endTransaction();
     method public void execSQL(java.lang.String) throws android.database.SQLException;
@@ -7332,6 +7343,7 @@
     method public deprecated boolean isDbLockedByOtherThreads();
     method public boolean isOpen();
     method public boolean isReadOnly();
+    method public boolean isWriteAheadLoggingEnabled();
     method public deprecated void markTableSyncable(java.lang.String, java.lang.String);
     method public deprecated void markTableSyncable(java.lang.String, java.lang.String, java.lang.String);
     method public boolean needUpgrade(int);
@@ -7354,6 +7366,7 @@
     method public static int releaseMemory();
     method public long replace(java.lang.String, java.lang.String, android.content.ContentValues);
     method public long replaceOrThrow(java.lang.String, java.lang.String, android.content.ContentValues) throws android.database.SQLException;
+    method public void setForeignKeyConstraintsEnabled(boolean);
     method public void setLocale(java.util.Locale);
     method public deprecated void setLockingEnabled(boolean);
     method public void setMaxSqlCacheSize(int);
@@ -7373,6 +7386,7 @@
     field public static final int CONFLICT_REPLACE = 5; // 0x5
     field public static final int CONFLICT_ROLLBACK = 1; // 0x1
     field public static final int CREATE_IF_NECESSARY = 268435456; // 0x10000000
+    field public static final int ENABLE_WRITE_AHEAD_LOGGING = 536870912; // 0x20000000
     field public static final int MAX_SQL_CACHE_SIZE = 100; // 0x64
     field public static final int NO_LOCALIZED_COLLATORS = 16; // 0x10
     field public static final int OPEN_READONLY = 1; // 0x1
@@ -7432,10 +7446,12 @@
     method public java.lang.String getDatabaseName();
     method public android.database.sqlite.SQLiteDatabase getReadableDatabase();
     method public android.database.sqlite.SQLiteDatabase getWritableDatabase();
+    method public void onConfigure(android.database.sqlite.SQLiteDatabase);
     method public abstract void onCreate(android.database.sqlite.SQLiteDatabase);
     method public void onDowngrade(android.database.sqlite.SQLiteDatabase, int, int);
     method public void onOpen(android.database.sqlite.SQLiteDatabase);
     method public abstract void onUpgrade(android.database.sqlite.SQLiteDatabase, int, int);
+    method public void setWriteAheadLoggingEnabled(boolean);
   }
 
   public class SQLiteOutOfMemoryException extends android.database.sqlite.SQLiteException {
@@ -11910,8 +11926,10 @@
     method public java.lang.String[] getDefaultCipherSuites();
     method public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(int, android.net.SSLSessionCache);
     method public static javax.net.ssl.SSLSocketFactory getInsecure(int, android.net.SSLSessionCache);
+    method public byte[] getNpnSelectedProtocol(java.net.Socket);
     method public java.lang.String[] getSupportedCipherSuites();
     method public void setKeyManagers(javax.net.ssl.KeyManager[]);
+    method public void setNpnProtocols(byte[][]);
     method public void setTrustManagers(javax.net.ssl.TrustManager[]);
   }
 
@@ -23175,6 +23193,7 @@
     method public void buildLayer();
     method public boolean callOnClick();
     method public boolean canResolveLayoutDirection();
+    method public boolean canResolveTextDirection();
     method public boolean canScrollHorizontally(int);
     method public boolean canScrollVertically(int);
     method public void cancelLongPress();
@@ -23457,6 +23476,7 @@
     method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
     method public void resetResolvedLayoutDirection();
     method public void resetResolvedTextDirection();
+    method public void resolveLayoutDirection();
     method public void resolvePadding();
     method public static int resolveSize(int, int);
     method public static int resolveSizeAndState(int, int, int);
@@ -23566,7 +23586,6 @@
     method public boolean willNotCacheDrawing();
     method public boolean willNotDraw();
     field public static final android.util.Property ALPHA;
-    field protected static int DEFAULT_TEXT_DIRECTION;
     field public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
     field public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
     field public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
@@ -23600,10 +23619,10 @@
     field public static final int LAYER_TYPE_HARDWARE = 2; // 0x2
     field public static final int LAYER_TYPE_NONE = 0; // 0x0
     field public static final int LAYER_TYPE_SOFTWARE = 1; // 0x1
-    field public static final int LAYOUT_DIRECTION_INHERIT = -2147483648; // 0x80000000
-    field public static final int LAYOUT_DIRECTION_LOCALE = -1073741824; // 0xc0000000
+    field public static final int LAYOUT_DIRECTION_INHERIT = 2; // 0x2
+    field public static final int LAYOUT_DIRECTION_LOCALE = 3; // 0x3
     field public static final int LAYOUT_DIRECTION_LTR = 0; // 0x0
-    field public static final int LAYOUT_DIRECTION_RTL = 1073741824; // 0x40000000
+    field public static final int LAYOUT_DIRECTION_RTL = 1; // 0x1
     field public static final int MEASURED_HEIGHT_STATE_SHIFT = 16; // 0x10
     field public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff
     field public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000
@@ -23650,6 +23669,7 @@
     field public static final int SYSTEM_UI_FLAG_LOW_PROFILE = 1; // 0x1
     field public static final int SYSTEM_UI_FLAG_VISIBLE = 0; // 0x0
     field public static final int TEXT_DIRECTION_ANY_RTL = 2; // 0x2
+    field protected static int TEXT_DIRECTION_DEFAULT;
     field public static final int TEXT_DIRECTION_FIRST_STRONG = 1; // 0x1
     field public static final int TEXT_DIRECTION_INHERIT = 0; // 0x0
     field public static final int TEXT_DIRECTION_LOCALE = 5; // 0x5
@@ -25899,6 +25919,7 @@
     ctor public AbsSeekBar(android.content.Context, android.util.AttributeSet);
     ctor public AbsSeekBar(android.content.Context, android.util.AttributeSet, int);
     method public int getKeyProgressIncrement();
+    method public android.graphics.drawable.Drawable getThumb();
     method public int getThumbOffset();
     method public void setKeyProgressIncrement(int);
     method public void setThumb(android.graphics.drawable.Drawable);
@@ -26036,6 +26057,7 @@
   public class AdapterViewFlipper extends android.widget.AdapterViewAnimator {
     ctor public AdapterViewFlipper(android.content.Context);
     ctor public AdapterViewFlipper(android.content.Context, android.util.AttributeSet);
+    method public int getFlipInterval();
     method public boolean isAutoStart();
     method public boolean isFlipping();
     method public void setAutoStart(boolean);
@@ -26621,8 +26643,14 @@
     ctor public GridView(android.content.Context, android.util.AttributeSet);
     ctor public GridView(android.content.Context, android.util.AttributeSet, int);
     method public android.widget.ListAdapter getAdapter();
+    method public int getColumnWidth();
+    method public int getGravity();
+    method public int getHorizontalSpacing();
     method public int getNumColumns();
+    method public int getRequestedColumnWidth();
+    method public int getRequestedHorizontalSpacing();
     method public int getStretchMode();
+    method public int getVerticalSpacing();
     method public void setColumnWidth(int);
     method public void setGravity(int);
     method public void setHorizontalSpacing(int);
@@ -26705,19 +26733,27 @@
     ctor public ImageView(android.content.Context, android.util.AttributeSet);
     ctor public ImageView(android.content.Context, android.util.AttributeSet, int);
     method public final void clearColorFilter();
+    method public boolean getAdjustViewBounds();
     method public boolean getBaselineAlignBottom();
+    method public android.graphics.ColorFilter getColorFilter();
+    method public boolean getCropToPadding();
     method public android.graphics.drawable.Drawable getDrawable();
+    method public int getImageAlpha();
     method public android.graphics.Matrix getImageMatrix();
+    method public int getMaxHeight();
+    method public int getMaxWidth();
     method public android.widget.ImageView.ScaleType getScaleType();
     method public int[] onCreateDrawableState(int);
     method public void setAdjustViewBounds(boolean);
-    method public void setAlpha(int);
+    method public deprecated void setAlpha(int);
     method public void setBaseline(int);
     method public void setBaselineAlignBottom(boolean);
     method public final void setColorFilter(int, android.graphics.PorterDuff.Mode);
     method public final void setColorFilter(int);
     method public void setColorFilter(android.graphics.ColorFilter);
+    method public void setCropToPadding(boolean);
     method protected boolean setFrame(int, int, int, int);
+    method public void setImageAlpha(int);
     method public void setImageBitmap(android.graphics.Bitmap);
     method public void setImageDrawable(android.graphics.drawable.Drawable);
     method public void setImageLevel(int);
@@ -26958,7 +26994,6 @@
     method public void setOnValueChangedListener(android.widget.NumberPicker.OnValueChangeListener);
     method public void setValue(int);
     method public void setWrapSelectorWheel(boolean);
-    field public static final int SELECTOR_WHEEL_ITEM_COUNT = 5; // 0x5
   }
 
   public static abstract interface NumberPicker.Formatter {
@@ -27251,6 +27286,8 @@
     method public void setShort(int, java.lang.String, short);
     method public void setString(int, java.lang.String, java.lang.String);
     method public void setTextColor(int, int);
+    method public void setTextViewCompoundDrawables(int, int, int, int, int);
+    method public void setTextViewCompoundDrawablesRelative(int, int, int, int, int);
     method public void setTextViewText(int, java.lang.CharSequence);
     method public void setUri(int, java.lang.String, android.net.Uri);
     method public void setViewVisibility(int, int);
diff --git a/build/jarjar-rules.txt b/build/jarjar-rules.txt
deleted file mode 100644
index 5fdb022..0000000
--- a/build/jarjar-rules.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-rule org.apache.commons com.android.internal.apache.commons
-
diff --git a/build/phone-hdpi-512-dalvik-heap.mk b/build/phone-hdpi-512-dalvik-heap.mk
deleted file mode 100644
index 16e0505..0000000
--- a/build/phone-hdpi-512-dalvik-heap.mk
+++ /dev/null
@@ -1,23 +0,0 @@
-#
-# Copyright (C) 2010 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.
-#
-
-# Provides overrides to configure the Dalvik heap for a standard high density
-# phone with around 512MB total RAM.
-
-PRODUCT_PROPERTY_OVERRIDES += \
-    dalvik.vm.heapstartsize=5m \
-    dalvik.vm.heapgrowthlimit=48m \
-    dalvik.vm.heapsize=128m
diff --git a/build/phone-hdpi-dalvik-heap.mk b/build/phone-hdpi-dalvik-heap.mk
deleted file mode 100644
index ab33b96..0000000
--- a/build/phone-hdpi-dalvik-heap.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-#
-# Copyright (C) 2010 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.
-#
-
-# Provides overrides to configure the Dalvik heap for a standard high density phone.
-
-PRODUCT_PROPERTY_OVERRIDES += \
-    dalvik.vm.heapstartsize=5m \
-    dalvik.vm.heapsize=32m
diff --git a/build/tablet-dalvik-heap.mk b/build/tablet-dalvik-heap.mk
deleted file mode 100644
index 826a380..0000000
--- a/build/tablet-dalvik-heap.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-#
-# Copyright (C) 2010 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.
-#
-
-# Provides overrides to configure the Dalvik heap for a standard tablet device.
-
-PRODUCT_PROPERTY_OVERRIDES += \
-    dalvik.vm.heapstartsize=5m \
-    dalvik.vm.heapgrowthlimit=48m \
-    dalvik.vm.heapsize=256m
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index c15c49f..53a0186 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -62,6 +62,7 @@
     private boolean mStopOption = false;
 
     private int mRepeat = 0;
+    private int mUserId = 0;
 
     private String mProfileFile;
 
@@ -135,7 +136,7 @@
             runToUri(false);
         } else if (op.equals("to-intent-uri")) {
             runToUri(true);
-        } else if (op.equals("switch-profile")) {
+        } else if (op.equals("switch-user")) {
             runSwitchUser();
         } else {
             throw new IllegalArgumentException("Unknown command: " + op);
@@ -152,6 +153,7 @@
         mStopOption = false;
         mRepeat = 0;
         mProfileFile = null;
+        mUserId = 0;
         Uri data = null;
         String type = null;
 
@@ -308,6 +310,8 @@
                 mStopOption = true;
             } else if (opt.equals("--opengl-trace")) {
                 mStartFlags |= ActivityManager.START_FLAG_OPENGL_TRACES;
+            } else if (opt.equals("--user")) {
+                mUserId = Integer.parseInt(nextArgRequired());
             } else {
                 System.err.println("Error: Unknown option: " + opt);
                 showUsage();
@@ -407,7 +411,8 @@
                         System.err.println("Error: Package manager not running; aborting");
                         return;
                     }
-                    List<ResolveInfo> activities = pm.queryIntentActivities(intent, mimeType, 0);
+                    List<ResolveInfo> activities = pm.queryIntentActivities(intent, mimeType, 0,
+                            mUserId);
                     if (activities == null || activities.size() <= 0) {
                         System.err.println("Error: Intent does not match any activities: "
                                 + intent);
@@ -550,7 +555,7 @@
         IntentReceiver receiver = new IntentReceiver();
         System.out.println("Broadcasting: " + intent);
         mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, null, true, false,
-                Binder.getOrigCallingUser());
+                mUserId);
         receiver.waitForFinish();
     }
 
@@ -1294,6 +1299,7 @@
                 "       am display-size [reset|MxN]\n" +
                 "       am to-uri [INTENT]\n" +
                 "       am to-intent-uri [INTENT]\n" +
+                "       am switch-user <USER_ID>\n" +
                 "\n" +
                 "am start: start an Activity.  Options are:\n" +
                 "    -D: enable debugging\n" +
diff --git a/cmds/dumpstate/Android.mk b/cmds/dumpstate/Android.mk
deleted file mode 100644
index d602500..0000000
--- a/cmds/dumpstate/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-ifdef BOARD_WLAN_DEVICE
-LOCAL_CFLAGS := -DFWDUMP_$(BOARD_WLAN_DEVICE)
-endif
-
-LOCAL_SRC_FILES := dumpstate.c utils.c
-
-LOCAL_MODULE := dumpstate
-
-LOCAL_SHARED_LIBRARIES := libcutils
-
-ifdef BOARD_LIB_DUMPSTATE
-LOCAL_STATIC_LIBRARIES := $(BOARD_LIB_DUMPSTATE)
-LOCAL_CFLAGS += -DBOARD_HAS_DUMPSTATE
-endif
-
-include $(BUILD_EXECUTABLE)
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c
deleted file mode 100644
index aa95b35..0000000
--- a/cmds/dumpstate/dumpstate.c
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <errno.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/resource.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/wait.h>
-#include <unistd.h>
-#include <linux/capability.h>
-#include <linux/prctl.h>
-
-#include <cutils/properties.h>
-
-#include "private/android_filesystem_config.h"
-
-#define LOG_TAG "dumpstate"
-#include <utils/Log.h>
-
-#include "dumpstate.h"
-
-/* read before root is shed */
-static char cmdline_buf[16384] = "(unknown)";
-static const char *dump_traces_path = NULL;
-
-static char screenshot_path[PATH_MAX] = "";
-
-/* dumps the current system state to stdout */
-static void dumpstate() {
-    time_t now = time(NULL);
-    char build[PROPERTY_VALUE_MAX], fingerprint[PROPERTY_VALUE_MAX];
-    char radio[PROPERTY_VALUE_MAX], bootloader[PROPERTY_VALUE_MAX];
-    char network[PROPERTY_VALUE_MAX], date[80];
-    char build_type[PROPERTY_VALUE_MAX];
-
-    property_get("ro.build.display.id", build, "(unknown)");
-    property_get("ro.build.fingerprint", fingerprint, "(unknown)");
-    property_get("ro.build.type", build_type, "(unknown)");
-    property_get("ro.baseband", radio, "(unknown)");
-    property_get("ro.bootloader", bootloader, "(unknown)");
-    property_get("gsm.operator.alpha", network, "(unknown)");
-    strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", localtime(&now));
-
-    printf("========================================================\n");
-    printf("== dumpstate: %s\n", date);
-    printf("========================================================\n");
-
-    printf("\n");
-    printf("Build: %s\n", build);
-    printf("Build fingerprint: '%s'\n", fingerprint); /* format is important for other tools */
-    printf("Bootloader: %s\n", bootloader);
-    printf("Radio: %s\n", radio);
-    printf("Network: %s\n", network);
-
-    printf("Kernel: ");
-    dump_file(NULL, "/proc/version");
-    printf("Command line: %s\n", strtok(cmdline_buf, "\n"));
-    printf("\n");
-
-    run_command("UPTIME", 10, "uptime", NULL);
-    dump_file("MEMORY INFO", "/proc/meminfo");
-    run_command("CPU INFO", 10, "top", "-n", "1", "-d", "1", "-m", "30", "-t", NULL);
-    run_command("PROCRANK", 20, "procrank", NULL);
-    dump_file("VIRTUAL MEMORY STATS", "/proc/vmstat");
-    dump_file("VMALLOC INFO", "/proc/vmallocinfo");
-    dump_file("SLAB INFO", "/proc/slabinfo");
-    dump_file("ZONEINFO", "/proc/zoneinfo");
-    dump_file("PAGETYPEINFO", "/proc/pagetypeinfo");
-    dump_file("BUDDYINFO", "/proc/buddyinfo");
-
-
-    dump_file("KERNEL WAKELOCKS", "/proc/wakelocks");
-    dump_file("KERNEL CPUFREQ", "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state");
-
-    run_command("PROCESSES", 10, "ps", "-P", NULL);
-    run_command("PROCESSES AND THREADS", 10, "ps", "-t", "-p", "-P", NULL);
-    run_command("LIBRANK", 10, "librank", NULL);
-
-    do_dmesg();
-
-    run_command("LIST OF OPEN FILES", 10, SU_PATH, "root", "lsof", NULL);
-
-    for_each_pid(do_showmap, "SMAPS OF ALL PROCESSES");
-    for_each_pid(show_wchan, "BLOCKED PROCESS WAIT-CHANNELS");
-
-    // dump_file("EVENT LOG TAGS", "/etc/event-log-tags");
-    run_command("SYSTEM LOG", 20, "logcat", "-v", "threadtime", "-d", "*:v", NULL);
-    run_command("EVENT LOG", 20, "logcat", "-b", "events", "-v", "threadtime", "-d", "*:v", NULL);
-    run_command("RADIO LOG", 20, "logcat", "-b", "radio", "-v", "threadtime", "-d", "*:v", NULL);
-
-
-    /* show the traces we collected in main(), if that was done */
-    if (dump_traces_path != NULL) {
-        dump_file("VM TRACES JUST NOW", dump_traces_path);
-    }
-
-    /* only show ANR traces if they're less than 15 minutes old */
-    struct stat st;
-    char anr_traces_path[PATH_MAX];
-    property_get("dalvik.vm.stack-trace-file", anr_traces_path, "");
-    if (!anr_traces_path[0]) {
-        printf("*** NO VM TRACES FILE DEFINED (dalvik.vm.stack-trace-file)\n\n");
-    } else if (stat(anr_traces_path, &st)) {
-        printf("*** NO ANR VM TRACES FILE (%s): %s\n\n", anr_traces_path, strerror(errno));
-    } else {
-        dump_file("VM TRACES AT LAST ANR", anr_traces_path);
-    }
-
-    /* slow traces for slow operations */
-    if (anr_traces_path[0] != 0) {
-        int tail = strlen(anr_traces_path)-1;
-        while (tail > 0 && anr_traces_path[tail] != '/') {
-            tail--;
-        }
-        int i = 0;
-        while (1) {
-            sprintf(anr_traces_path+tail+1, "slow%02d.txt", i);
-            if (stat(anr_traces_path, &st)) {
-                // No traces file at this index, done with the files.
-                break;
-            }
-            dump_file("VM TRACES WHEN SLOW", anr_traces_path);
-            i++;
-        }
-    }
-
-    dump_file("NETWORK DEV INFO", "/proc/net/dev");
-    dump_file("QTAGUID NETWORK INTERFACES INFO", "/proc/net/xt_qtaguid/iface_stat_all");
-    dump_file("QTAGUID CTRL INFO", "/proc/net/xt_qtaguid/ctrl");
-    dump_file("QTAGUID STATS INFO", "/proc/net/xt_qtaguid/stats");
-
-    dump_file("NETWORK ROUTES", "/proc/net/route");
-    dump_file("NETWORK ROUTES IPV6", "/proc/net/ipv6_route");
-
-    /* TODO: Make last_kmsg CAP_SYSLOG protected. b/5555691 */
-    dump_file("LAST KMSG", "/proc/last_kmsg");
-    dump_file("LAST PANIC CONSOLE", "/data/dontpanic/apanic_console");
-    dump_file("LAST PANIC THREADS", "/data/dontpanic/apanic_threads");
-
-    if (screenshot_path[0]) {
-        ALOGI("taking screenshot\n");
-        run_command(NULL, 5, SU_PATH, "root", "screenshot", screenshot_path, NULL);
-        ALOGI("wrote screenshot: %s\n", screenshot_path);
-    }
-
-    run_command("SYSTEM SETTINGS", 20, SU_PATH, "root", "sqlite3",
-            "/data/data/com.android.providers.settings/databases/settings.db",
-            "pragma user_version; select * from system; select * from secure;", NULL);
-
-    /* The following have a tendency to get wedged when wifi drivers/fw goes belly-up. */
-    run_command("NETWORK INTERFACES", 10, SU_PATH, "root", "netcfg", NULL);
-    run_command("IP RULES", 10, "ip", "rule", "show", NULL);
-    run_command("IP RULES v6", 10, "ip", "-6", "rule", "show", NULL);
-    run_command("ROUTE TABLE 60", 10, "ip", "route", "show", "table", "60", NULL);
-    run_command("ROUTE TABLE 61 v6", 10, "ip", "-6", "route", "show", "table", "60", NULL);
-    run_command("ROUTE TABLE 61", 10, "ip", "route", "show", "table", "61", NULL);
-    run_command("ROUTE TABLE 61 v6", 10, "ip", "-6", "route", "show", "table", "61", NULL);
-    dump_file("ARP CACHE", "/proc/net/arp");
-    run_command("IPTABLES", 10, SU_PATH, "root", "iptables", "-L", "-nvx", NULL);
-    run_command("IP6TABLES", 10, SU_PATH, "root", "ip6tables", "-L", "-nvx", NULL);
-    run_command("IPTABLE NAT", 10, SU_PATH, "root", "iptables", "-t", "nat", "-L", "-n", NULL);
-    run_command("IPT6ABLE NAT", 10, SU_PATH, "root", "ip6tables", "-t", "nat", "-L", "-n", NULL);
-
-    run_command("WIFI NETWORKS", 20,
-            SU_PATH, "root", "wpa_cli", "list_networks", NULL);
-
-    property_get("dhcp.wlan0.gateway", network, "");
-    if (network[0])
-        run_command("PING GATEWAY", 10, SU_PATH, "root", "ping", "-c", "3", "-i", ".5", network, NULL);
-    property_get("dhcp.wlan0.dns1", network, "");
-    if (network[0])
-        run_command("PING DNS1", 10, SU_PATH, "root", "ping", "-c", "3", "-i", ".5", network, NULL);
-    property_get("dhcp.wlan0.dns2", network, "");
-    if (network[0])
-        run_command("PING DNS2", 10, SU_PATH, "root", "ping", "-c", "3", "-i", ".5", network, NULL);
-#ifdef FWDUMP_bcm4329
-    run_command("DUMP WIFI STATUS", 20,
-            SU_PATH, "root", "dhdutil", "-i", "wlan0", "dump", NULL);
-    run_command("DUMP WIFI INTERNAL COUNTERS", 20,
-            SU_PATH, "root", "wlutil", "counters", NULL);
-#endif
-
-    print_properties();
-
-    run_command("VOLD DUMP", 10, "vdc", "dump", NULL);
-    run_command("SECURE CONTAINERS", 10, "vdc", "asec", "list", NULL);
-
-    run_command("FILESYSTEMS & FREE SPACE", 10, SU_PATH, "root", "df", NULL);
-
-    dump_file("PACKAGE SETTINGS", "/data/system/packages.xml");
-    dump_file("PACKAGE UID ERRORS", "/data/system/uiderrors.txt");
-
-    run_command("LAST RADIO LOG", 10, "parse_radio_log", "/proc/last_radio_log", NULL);
-
-    printf("------ BACKLIGHTS ------\n");
-    printf("LCD brightness=");
-    dump_file(NULL, "/sys/class/leds/lcd-backlight/brightness");
-    printf("Button brightness=");
-    dump_file(NULL, "/sys/class/leds/button-backlight/brightness");
-    printf("Keyboard brightness=");
-    dump_file(NULL, "/sys/class/leds/keyboard-backlight/brightness");
-    printf("ALS mode=");
-    dump_file(NULL, "/sys/class/leds/lcd-backlight/als");
-    printf("LCD driver registers:\n");
-    dump_file(NULL, "/sys/class/leds/lcd-backlight/registers");
-    printf("\n");
-
-    /* Binder state is expensive to look at as it uses a lot of memory. */
-    dump_file("BINDER FAILED TRANSACTION LOG", "/sys/kernel/debug/binder/failed_transaction_log");
-    dump_file("BINDER TRANSACTION LOG", "/sys/kernel/debug/binder/transaction_log");
-    dump_file("BINDER TRANSACTIONS", "/sys/kernel/debug/binder/transactions");
-    dump_file("BINDER STATS", "/sys/kernel/debug/binder/stats");
-    dump_file("BINDER STATE", "/sys/kernel/debug/binder/state");
-
-#ifdef BOARD_HAS_DUMPSTATE
-    printf("========================================================\n");
-    printf("== Board\n");
-    printf("========================================================\n");
-
-    dumpstate_board();
-    printf("\n");
-#endif
-
-    /* Migrate the ril_dumpstate to a dumpstate_board()? */
-    char ril_dumpstate_timeout[PROPERTY_VALUE_MAX] = {0};
-    property_get("ril.dumpstate.timeout", ril_dumpstate_timeout, "30");
-    if (strnlen(ril_dumpstate_timeout, PROPERTY_VALUE_MAX - 1) > 0) {
-        if (0 == strncmp(build_type, "user", PROPERTY_VALUE_MAX - 1)) {
-            // su does not exist on user builds, so try running without it.
-            // This way any implementations of vril-dump that do not require
-            // root can run on user builds.
-            run_command("DUMP VENDOR RIL LOGS", atoi(ril_dumpstate_timeout),
-                    "vril-dump", NULL);
-        } else {
-            run_command("DUMP VENDOR RIL LOGS", atoi(ril_dumpstate_timeout),
-                    SU_PATH, "root", "vril-dump", NULL);
-        }
-    }
-
-    printf("========================================================\n");
-    printf("== Android Framework Services\n");
-    printf("========================================================\n");
-
-    /* the full dumpsys is starting to take a long time, so we need
-       to increase its timeout.  we really need to do the timeouts in
-       dumpsys itself... */
-    run_command("DUMPSYS", 60, "dumpsys", NULL);
-
-    printf("========================================================\n");
-    printf("== Running Application Activities\n");
-    printf("========================================================\n");
-
-    run_command("APP ACTIVITIES", 30, "dumpsys", "activity", "all", NULL);
-
-    printf("========================================================\n");
-    printf("== Running Application Services\n");
-    printf("========================================================\n");
-
-    run_command("APP SERVICES", 30, "dumpsys", "activity", "service", "all", NULL);
-
-    printf("========================================================\n");
-    printf("== Running Application Providers\n");
-    printf("========================================================\n");
-
-    run_command("APP SERVICES", 30, "dumpsys", "activity", "provider", "all", NULL);
-
-
-    printf("========================================================\n");
-    printf("== dumpstate: done\n");
-    printf("========================================================\n");
-}
-
-static void usage() {
-    fprintf(stderr, "usage: dumpstate [-b soundfile] [-e soundfile] [-o file [-d] [-p] [-z]] [-s]\n"
-            "  -o: write to file (instead of stdout)\n"
-            "  -d: append date to filename (requires -o)\n"
-            "  -z: gzip output (requires -o)\n"
-            "  -p: capture screenshot to filename.png (requires -o)\n"
-            "  -s: write output to control socket (for init)\n"
-            "  -b: play sound file instead of vibrate, at beginning of job\n"
-            "  -e: play sound file instead of vibrate, at end of job\n"
-		);
-}
-
-int main(int argc, char *argv[]) {
-    int do_add_date = 0;
-    int do_compress = 0;
-    char* use_outfile = 0;
-    char* begin_sound = 0;
-    char* end_sound = 0;
-    int use_socket = 0;
-    int do_fb = 0;
-
-    ALOGI("begin\n");
-
-    /* set as high priority, and protect from OOM killer */
-    setpriority(PRIO_PROCESS, 0, -20);
-    FILE *oom_adj = fopen("/proc/self/oom_adj", "w");
-    if (oom_adj) {
-        fputs("-17", oom_adj);
-        fclose(oom_adj);
-    }
-
-    /* very first thing, collect VM traces from Dalvik (needs root) */
-    dump_traces_path = dump_vm_traces();
-
-    int c;
-    while ((c = getopt(argc, argv, "b:de:ho:svzp")) != -1) {
-        switch (c) {
-            case 'b': begin_sound = optarg;  break;
-            case 'd': do_add_date = 1;       break;
-            case 'e': end_sound = optarg;    break;
-            case 'o': use_outfile = optarg;  break;
-            case 's': use_socket = 1;        break;
-            case 'v': break;  // compatibility no-op
-            case 'z': do_compress = 6;       break;
-            case 'p': do_fb = 1;             break;
-            case '?': printf("\n");
-            case 'h':
-                usage();
-                exit(1);
-        }
-    }
-
-    /* open the vibrator before dropping root */
-    FILE *vibrator = fopen("/sys/class/timed_output/vibrator/enable", "w");
-    if (vibrator) fcntl(fileno(vibrator), F_SETFD, FD_CLOEXEC);
-
-    /* read /proc/cmdline before dropping root */
-    FILE *cmdline = fopen("/proc/cmdline", "r");
-    if (cmdline != NULL) {
-        fgets(cmdline_buf, sizeof(cmdline_buf), cmdline);
-        fclose(cmdline);
-    }
-
-    if (getuid() == 0) {
-        if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
-            ALOGE("prctl(PR_SET_KEEPCAPS) failed: %s\n", strerror(errno));
-            return -1;
-        }
-
-        /* switch to non-root user and group */
-        gid_t groups[] = { AID_LOG, AID_SDCARD_RW, AID_MOUNT, AID_INET, AID_NET_BW_STATS };
-        if (setgroups(sizeof(groups)/sizeof(groups[0]), groups) != 0) {
-            ALOGE("Unable to setgroups, aborting: %s\n", strerror(errno));
-            return -1;
-        }
-        if (setgid(AID_SHELL) != 0) {
-            ALOGE("Unable to setgid, aborting: %s\n", strerror(errno));
-            return -1;
-        }
-        if (setuid(AID_SHELL) != 0) {
-            ALOGE("Unable to setuid, aborting: %s\n", strerror(errno));
-            return -1;
-        }
-
-        struct __user_cap_header_struct capheader;
-        struct __user_cap_data_struct capdata[2];
-        memset(&capheader, 0, sizeof(capheader));
-        memset(&capdata, 0, sizeof(capdata));
-        capheader.version = _LINUX_CAPABILITY_VERSION_3;
-        capheader.pid = 0;
-
-        capdata[CAP_TO_INDEX(CAP_SYSLOG)].permitted = CAP_TO_MASK(CAP_SYSLOG);
-        capdata[CAP_TO_INDEX(CAP_SYSLOG)].effective = CAP_TO_MASK(CAP_SYSLOG);
-        capdata[0].inheritable = 0;
-        capdata[1].inheritable = 0;
-
-        if (capset(&capheader, &capdata[0]) < 0) {
-            ALOGE("capset failed: %s\n", strerror(errno));
-            return -1;
-        }
-    }
-
-    char path[PATH_MAX], tmp_path[PATH_MAX];
-    pid_t gzip_pid = -1;
-
-    if (use_socket) {
-        redirect_to_socket(stdout, "dumpstate");
-    } else if (use_outfile) {
-        strlcpy(path, use_outfile, sizeof(path));
-        if (do_add_date) {
-            char date[80];
-            time_t now = time(NULL);
-            strftime(date, sizeof(date), "-%Y-%m-%d-%H-%M-%S", localtime(&now));
-            strlcat(path, date, sizeof(path));
-        }
-        if (do_fb) {
-            strlcpy(screenshot_path, path, sizeof(screenshot_path));
-            strlcat(screenshot_path, ".png", sizeof(screenshot_path));
-        }
-        strlcat(path, ".txt", sizeof(path));
-        if (do_compress) strlcat(path, ".gz", sizeof(path));
-        strlcpy(tmp_path, path, sizeof(tmp_path));
-        strlcat(tmp_path, ".tmp", sizeof(tmp_path));
-        gzip_pid = redirect_to_file(stdout, tmp_path, do_compress);
-    }
-
-    if (begin_sound) {
-        play_sound(begin_sound);
-    } else if (vibrator) {
-        fputs("150", vibrator);
-        fflush(vibrator);
-    }
-
-    dumpstate();
-
-    if (end_sound) {
-        play_sound(end_sound);
-    } else if (vibrator) {
-        int i;
-        for (i = 0; i < 3; i++) {
-            fputs("75\n", vibrator);
-            fflush(vibrator);
-            usleep((75 + 50) * 1000);
-        }
-        fclose(vibrator);
-    }
-
-    /* wait for gzip to finish, otherwise it might get killed when we exit */
-    if (gzip_pid > 0) {
-        fclose(stdout);
-        waitpid(gzip_pid, NULL, 0);
-    }
-
-    /* rename the (now complete) .tmp file to its final location */
-    if (use_outfile && rename(tmp_path, path)) {
-        fprintf(stderr, "rename(%s, %s): %s\n", tmp_path, path, strerror(errno));
-    }
-
-    ALOGI("done\n");
-
-    return 0;
-}
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
deleted file mode 100644
index c1c2ad8..0000000
--- a/cmds/dumpstate/dumpstate.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _DUMPSTATE_H_
-#define _DUMPSTATE_H_
-
-#include <time.h>
-#include <unistd.h>
-#include <stdio.h>
-
-#define SU_PATH "/system/xbin/su"
-
-/* prints the contents of a file */
-int dump_file(const char *title, const char* path);
-
-/* forks a command and waits for it to finish -- terminate args with NULL */
-int run_command(const char *title, int timeout_seconds, const char *command, ...);
-
-/* prints all the system properties */
-void print_properties();
-
-/* redirect output to a service control socket */
-void redirect_to_socket(FILE *redirect, const char *service);
-
-/* redirect output to a file, optionally gzipping; returns gzip pid */
-pid_t redirect_to_file(FILE *redirect, char *path, int gzip_level);
-
-/* dump Dalvik stack traces, return the trace file location (NULL if none) */
-const char *dump_vm_traces();
-
-/* for each process in the system, run the specified function */
-void for_each_pid(void (*func)(int, const char *), const char *header);
-
-/* Displays a blocked processes in-kernel wait channel */
-void show_wchan(int pid, const char *name);
-
-/* Runs "showmap" for a process */
-void do_showmap(int pid, const char *name);
-
-/* Gets the dmesg output for the kernel */
-void do_dmesg();
-
-/* Play a sound via Stagefright */
-void play_sound(const char* path);
-
-/* Implemented by libdumpstate_board to dump board-specific info */
-void dumpstate_board();
-
-#endif /* _DUMPSTATE_H_ */
diff --git a/cmds/dumpstate/utils.c b/cmds/dumpstate/utils.c
deleted file mode 100644
index 0d5ab90..0000000
--- a/cmds/dumpstate/utils.c
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <poll.h>
-#include <signal.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/inotify.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/wait.h>
-#include <sys/klog.h>
-#include <time.h>
-#include <unistd.h>
-
-#include <cutils/properties.h>
-#include <cutils/sockets.h>
-#include <private/android_filesystem_config.h>
-
-#include "dumpstate.h"
-
-void for_each_pid(void (*func)(int, const char *), const char *header) {
-    DIR *d;
-    struct dirent *de;
-
-    if (!(d = opendir("/proc"))) {
-        printf("Failed to open /proc (%s)\n", strerror(errno));
-        return;
-    }
-
-    printf("\n------ %s ------\n", header);
-    while ((de = readdir(d))) {
-        int pid;
-        int fd;
-        char cmdpath[255];
-        char cmdline[255];
-
-        if (!(pid = atoi(de->d_name))) {
-            continue;
-        }
-
-        sprintf(cmdpath,"/proc/%d/cmdline", pid);
-        memset(cmdline, 0, sizeof(cmdline));
-        if ((fd = open(cmdpath, O_RDONLY)) < 0) {
-            strcpy(cmdline, "N/A");
-        } else {
-            read(fd, cmdline, sizeof(cmdline));
-            close(fd);
-        }
-        func(pid, cmdline);
-    }
-
-    closedir(d);
-}
-
-void show_wchan(int pid, const char *name) {
-    char path[255];
-    char buffer[255];
-    int fd;
-
-    memset(buffer, 0, sizeof(buffer));
-
-    sprintf(path, "/proc/%d/wchan", pid);
-    if ((fd = open(path, O_RDONLY)) < 0) {
-        printf("Failed to open '%s' (%s)\n", path, strerror(errno));
-        return;
-    }
-
-    if (read(fd, buffer, sizeof(buffer)) < 0) {
-        printf("Failed to read '%s' (%s)\n", path, strerror(errno));
-        goto out_close;
-    }
-
-    printf("%-7d %-32s %s\n", pid, name, buffer);
-
-out_close:
-    close(fd);
-    return;
-}
-
-void do_dmesg() {
-    printf("------ KERNEL LOG (dmesg) ------\n");
-    int size = klogctl(10, NULL, 0); /* Get size of kernel buffer */
-    if (size <= 0) {
-        printf("Unexpected klogctl return value: %d\n\n", size);
-        return;
-    }
-    char *buf = (char *) malloc(size + 1);
-    if (buf == NULL) {
-        printf("memory allocation failed\n\n");
-        return;
-    }
-    int retval = klogctl(KLOG_READ_ALL, buf, size);
-    if (retval < 0) {
-        printf("klogctl failure\n\n");
-        free(buf);
-        return;
-    }
-    buf[retval] = '\0';
-    printf("%s\n\n", buf);
-    free(buf);
-    return;
-}
-
-void do_showmap(int pid, const char *name) {
-    char title[255];
-    char arg[255];
-
-    sprintf(title, "SHOW MAP %d (%s)", pid, name);
-    sprintf(arg, "%d", pid);
-    run_command(title, 10, SU_PATH, "root", "showmap", arg, NULL);
-}
-
-/* prints the contents of a file */
-int dump_file(const char *title, const char* path) {
-    char buffer[32768];
-    int fd = open(path, O_RDONLY);
-    if (fd < 0) {
-        int err = errno;
-        if (title) printf("------ %s (%s) ------\n", title, path);
-        printf("*** %s: %s\n", path, strerror(err));
-        if (title) printf("\n");
-        return -1;
-    }
-
-    if (title) printf("------ %s (%s", title, path);
-
-    if (title) {
-        struct stat st;
-        if (memcmp(path, "/proc/", 6) && memcmp(path, "/sys/", 5) && !fstat(fd, &st)) {
-            char stamp[80];
-            time_t mtime = st.st_mtime;
-            strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S", localtime(&mtime));
-            printf(": %s", stamp);
-        }
-        printf(") ------\n");
-    }
-
-    int newline = 0;
-    for (;;) {
-        int ret = read(fd, buffer, sizeof(buffer));
-        if (ret > 0) {
-            newline = (buffer[ret - 1] == '\n');
-            ret = fwrite(buffer, ret, 1, stdout);
-        }
-        if (ret <= 0) break;
-    }
-
-    close(fd);
-    if (!newline) printf("\n");
-    if (title) printf("\n");
-    return 0;
-}
-
-/* forks a command and waits for it to finish */
-int run_command(const char *title, int timeout_seconds, const char *command, ...) {
-    fflush(stdout);
-    clock_t start = clock();
-    pid_t pid = fork();
-
-    /* handle error case */
-    if (pid < 0) {
-        printf("*** fork: %s\n", strerror(errno));
-        return pid;
-    }
-
-    /* handle child case */
-    if (pid == 0) {
-        const char *args[1024] = {command};
-        size_t arg;
-
-        va_list ap;
-        va_start(ap, command);
-        if (title) printf("------ %s (%s", title, command);
-        for (arg = 1; arg < sizeof(args) / sizeof(args[0]); ++arg) {
-            args[arg] = va_arg(ap, const char *);
-            if (args[arg] == NULL) break;
-            if (title) printf(" %s", args[arg]);
-        }
-        if (title) printf(") ------\n");
-        fflush(stdout);
-
-        execvp(command, (char**) args);
-        printf("*** exec(%s): %s\n", command, strerror(errno));
-        fflush(stdout);
-        _exit(-1);
-    }
-
-    /* handle parent case */
-    for (;;) {
-        int status;
-        pid_t p = waitpid(pid, &status, WNOHANG);
-        float elapsed = (float) (clock() - start) / CLOCKS_PER_SEC;
-        if (p == pid) {
-            if (WIFSIGNALED(status)) {
-                printf("*** %s: Killed by signal %d\n", command, WTERMSIG(status));
-            } else if (WIFEXITED(status) && WEXITSTATUS(status) > 0) {
-                printf("*** %s: Exit code %d\n", command, WEXITSTATUS(status));
-            }
-            if (title) printf("[%s: %.1fs elapsed]\n\n", command, elapsed);
-            return status;
-        }
-
-        if (timeout_seconds && elapsed > timeout_seconds) {
-            printf("*** %s: Timed out after %.1fs (killing pid %d)\n", command, elapsed, pid);
-            kill(pid, SIGTERM);
-            return -1;
-        }
-
-        usleep(100000);  // poll every 0.1 sec
-    }
-}
-
-size_t num_props = 0;
-static char* props[2000];
-
-static void print_prop(const char *key, const char *name, void *user) {
-    (void) user;
-    if (num_props < sizeof(props) / sizeof(props[0])) {
-        char buf[PROPERTY_KEY_MAX + PROPERTY_VALUE_MAX + 10];
-        snprintf(buf, sizeof(buf), "[%s]: [%s]\n", key, name);
-        props[num_props++] = strdup(buf);
-    }
-}
-
-static int compare_prop(const void *a, const void *b) {
-    return strcmp(*(char * const *) a, *(char * const *) b);
-}
-
-/* prints all the system properties */
-void print_properties() {
-    size_t i;
-    num_props = 0;
-    property_list(print_prop, NULL);
-    qsort(&props, num_props, sizeof(props[0]), compare_prop);
-
-    printf("------ SYSTEM PROPERTIES ------\n");
-    for (i = 0; i < num_props; ++i) {
-        fputs(props[i], stdout);
-        free(props[i]);
-    }
-    printf("\n");
-}
-
-/* redirect output to a service control socket */
-void redirect_to_socket(FILE *redirect, const char *service) {
-    int s = android_get_control_socket(service);
-    if (s < 0) {
-        fprintf(stderr, "android_get_control_socket(%s): %s\n", service, strerror(errno));
-        exit(1);
-    }
-    if (listen(s, 4) < 0) {
-        fprintf(stderr, "listen(control socket): %s\n", strerror(errno));
-        exit(1);
-    }
-
-    struct sockaddr addr;
-    socklen_t alen = sizeof(addr);
-    int fd = accept(s, &addr, &alen);
-    if (fd < 0) {
-        fprintf(stderr, "accept(control socket): %s\n", strerror(errno));
-        exit(1);
-    }
-
-    fflush(redirect);
-    dup2(fd, fileno(redirect));
-    close(fd);
-}
-
-/* redirect output to a file, optionally gzipping; returns gzip pid (or -1) */
-pid_t redirect_to_file(FILE *redirect, char *path, int gzip_level) {
-    char *chp = path;
-
-    /* skip initial slash */
-    if (chp[0] == '/')
-        chp++;
-
-    /* create leading directories, if necessary */
-    while (chp && chp[0]) {
-        chp = strchr(chp, '/');
-        if (chp) {
-            *chp = 0;
-            mkdir(path, 0775);  /* drwxrwxr-x */
-            *chp++ = '/';
-        }
-    }
-
-    int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
-    if (fd < 0) {
-        fprintf(stderr, "%s: %s\n", path, strerror(errno));
-        exit(1);
-    }
-
-    pid_t gzip_pid = -1;
-    if (gzip_level > 0) {
-        int fds[2];
-        if (pipe(fds)) {
-            fprintf(stderr, "pipe: %s\n", strerror(errno));
-            exit(1);
-        }
-
-        fflush(redirect);
-        fflush(stdout);
-
-        gzip_pid = fork();
-        if (gzip_pid < 0) {
-            fprintf(stderr, "fork: %s\n", strerror(errno));
-            exit(1);
-        }
-
-        if (gzip_pid == 0) {
-            dup2(fds[0], STDIN_FILENO);
-            dup2(fd, STDOUT_FILENO);
-
-            close(fd);
-            close(fds[0]);
-            close(fds[1]);
-
-            char level[10];
-            snprintf(level, sizeof(level), "-%d", gzip_level);
-            execlp("gzip", "gzip", level, NULL);
-            fprintf(stderr, "exec(gzip): %s\n", strerror(errno));
-            _exit(-1);
-        }
-
-        close(fd);
-        close(fds[0]);
-        fd = fds[1];
-    }
-
-    dup2(fd, fileno(redirect));
-    close(fd);
-    return gzip_pid;
-}
-
-/* dump Dalvik stack traces, return the trace file location (NULL if none) */
-const char *dump_vm_traces() {
-    char traces_path[PROPERTY_VALUE_MAX] = "";
-    property_get("dalvik.vm.stack-trace-file", traces_path, "");
-    if (!traces_path[0]) return NULL;
-
-    /* move the old traces.txt (if any) out of the way temporarily */
-    char anr_traces_path[PATH_MAX];
-    strlcpy(anr_traces_path, traces_path, sizeof(anr_traces_path));
-    strlcat(anr_traces_path, ".anr", sizeof(anr_traces_path));
-    if (rename(traces_path, anr_traces_path) && errno != ENOENT) {
-        fprintf(stderr, "rename(%s, %s): %s\n", traces_path, anr_traces_path, strerror(errno));
-        return NULL;  // Can't rename old traces.txt -- no permission? -- leave it alone instead
-    }
-
-    /* make the directory if necessary */
-    char anr_traces_dir[PATH_MAX];
-    strlcpy(anr_traces_dir, traces_path, sizeof(anr_traces_dir));
-    char *slash = strrchr(anr_traces_dir, '/');
-    if (slash != NULL) {
-        *slash = '\0';
-        if (!mkdir(anr_traces_dir, 0775)) {
-            chown(anr_traces_dir, AID_SYSTEM, AID_SYSTEM);
-        } else if (errno != EEXIST) {
-            fprintf(stderr, "mkdir(%s): %s\n", anr_traces_dir, strerror(errno));
-            return NULL;
-        }
-    }
-
-    /* create a new, empty traces.txt file to receive stack dumps */
-    int fd = open(traces_path, O_CREAT | O_WRONLY | O_TRUNC, 0666);  /* -rw-rw-rw- */
-    if (fd < 0) {
-        fprintf(stderr, "%s: %s\n", traces_path, strerror(errno));
-        return NULL;
-    }
-    close(fd);
-
-    /* walk /proc and kill -QUIT all Dalvik processes */
-    DIR *proc = opendir("/proc");
-    if (proc == NULL) {
-        fprintf(stderr, "/proc: %s\n", strerror(errno));
-        return NULL;
-    }
-
-    /* use inotify to find when processes are done dumping */
-    int ifd = inotify_init();
-    if (ifd < 0) {
-        fprintf(stderr, "inotify_init: %s\n", strerror(errno));
-        return NULL;
-    }
-
-    int wfd = inotify_add_watch(ifd, traces_path, IN_CLOSE_WRITE);
-    if (wfd < 0) {
-        fprintf(stderr, "inotify_add_watch(%s): %s\n", traces_path, strerror(errno));
-        return NULL;
-    }
-
-    struct dirent *d;
-    int dalvik_found = 0;
-    while ((d = readdir(proc))) {
-        int pid = atoi(d->d_name);
-        if (pid <= 0) continue;
-
-        /* identify Dalvik: /proc/(pid)/exe = /system/bin/app_process */
-        char path[PATH_MAX], data[PATH_MAX];
-        snprintf(path, sizeof(path), "/proc/%d/exe", pid);
-        size_t len = readlink(path, data, sizeof(data) - 1);
-        if (len <= 0 || memcmp(data, "/system/bin/app_process", 23)) continue;
-
-        /* skip zygote -- it won't dump its stack anyway */
-        snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
-        int fd = open(path, O_RDONLY);
-        len = read(fd, data, sizeof(data) - 1);
-        close(fd);
-        if (len <= 0 || !memcmp(data, "zygote", 6)) continue;
-
-        ++dalvik_found;
-        if (kill(pid, SIGQUIT)) {
-            fprintf(stderr, "kill(%d, SIGQUIT): %s\n", pid, strerror(errno));
-            continue;
-        }
-
-        /* wait for the writable-close notification from inotify */
-        struct pollfd pfd = { ifd, POLLIN, 0 };
-        int ret = poll(&pfd, 1, 200);  /* 200 msec timeout */
-        if (ret < 0) {
-            fprintf(stderr, "poll: %s\n", strerror(errno));
-        } else if (ret == 0) {
-            fprintf(stderr, "warning: timed out dumping pid %d\n", pid);
-        } else {
-            struct inotify_event ie;
-            read(ifd, &ie, sizeof(ie));
-        }
-    }
-
-    close(ifd);
-    if (dalvik_found == 0) {
-        fprintf(stderr, "Warning: no Dalvik processes found to dump stacks\n");
-    }
-
-    static char dump_traces_path[PATH_MAX];
-    strlcpy(dump_traces_path, traces_path, sizeof(dump_traces_path));
-    strlcat(dump_traces_path, ".bugreport", sizeof(dump_traces_path));
-    if (rename(traces_path, dump_traces_path)) {
-        fprintf(stderr, "rename(%s, %s): %s\n", traces_path, dump_traces_path, strerror(errno));
-        return NULL;
-    }
-
-    /* replace the saved [ANR] traces.txt file */
-    rename(anr_traces_path, traces_path);
-    return dump_traces_path;
-}
-
-void play_sound(const char* path) {
-    run_command(NULL, 5, "/system/bin/stagefright", "-o", "-a", path, NULL);
-}
diff --git a/cmds/dumpsys/Android.mk b/cmds/dumpsys/Android.mk
deleted file mode 100644
index 42b1b73..0000000
--- a/cmds/dumpsys/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
-	dumpsys.cpp
-
-LOCAL_SHARED_LIBRARIES := \
-	libutils \
-	libbinder
-	
-
-ifeq ($(TARGET_OS),linux)
-	LOCAL_CFLAGS += -DXP_UNIX
-	#LOCAL_SHARED_LIBRARIES += librt
-endif
-
-LOCAL_MODULE:= dumpsys
-
-include $(BUILD_EXECUTABLE)
diff --git a/cmds/dumpsys/MODULE_LICENSE_APACHE2 b/cmds/dumpsys/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/cmds/dumpsys/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/cmds/dumpsys/NOTICE b/cmds/dumpsys/NOTICE
deleted file mode 100644
index c5b1efa..0000000
--- a/cmds/dumpsys/NOTICE
+++ /dev/null
@@ -1,190 +0,0 @@
-
-   Copyright (c) 2005-2008, The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-
-   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.
-
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
diff --git a/cmds/dumpsys/dumpsys.cpp b/cmds/dumpsys/dumpsys.cpp
deleted file mode 100644
index 7dad6b6..0000000
--- a/cmds/dumpsys/dumpsys.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Command that dumps interesting system state to the log.
- *
- */
-
-#define LOG_TAG "dumpsys"
-
-#include <utils/Log.h>
-#include <binder/Parcel.h>
-#include <binder/ProcessState.h>
-#include <binder/IServiceManager.h>
-#include <utils/TextOutput.h>
-#include <utils/Vector.h>
-
-#include <getopt.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <sys/time.h>
-
-using namespace android;
-
-static int sort_func(const String16* lhs, const String16* rhs)
-{
-    return lhs->compare(*rhs);
-}
-
-int main(int argc, char* const argv[])
-{
-    sp<IServiceManager> sm = defaultServiceManager();
-    fflush(stdout);
-    if (sm == NULL) {
-		ALOGE("Unable to get default service manager!");
-        aerr << "dumpsys: Unable to get default service manager!" << endl;
-        return 20;
-    }
-
-    Vector<String16> services;
-    Vector<String16> args;
-    if (argc == 1) {
-        services = sm->listServices();
-        services.sort(sort_func);
-        args.add(String16("-a"));
-    } else {
-        services.add(String16(argv[1]));
-        for (int i=2; i<argc; i++) {
-            args.add(String16(argv[i]));
-        }
-    }
-
-    const size_t N = services.size();
-
-    if (N > 1) {
-        // first print a list of the current services
-        aout << "Currently running services:" << endl;
-    
-        for (size_t i=0; i<N; i++) {
-            sp<IBinder> service = sm->checkService(services[i]);
-            if (service != NULL) {
-                aout << "  " << services[i] << endl;
-            }
-        }
-    }
-
-    for (size_t i=0; i<N; i++) {
-        sp<IBinder> service = sm->checkService(services[i]);
-        if (service != NULL) {
-            if (N > 1) {
-                aout << "------------------------------------------------------------"
-                        "-------------------" << endl;
-                aout << "DUMP OF SERVICE " << services[i] << ":" << endl;
-            }
-            int err = service->dump(STDOUT_FILENO, args);
-            if (err != 0) {
-                aerr << "Error dumping service info: (" << strerror(err)
-                        << ") " << services[i] << endl;
-            }
-        } else {
-            aerr << "Can't find service: " << services[i] << endl;
-        }
-    }
-
-    return 0;
-}
diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c
index 7f94a96..c2c749a 100644
--- a/cmds/installd/installd.c
+++ b/cmds/installd/installd.c
@@ -332,12 +332,15 @@
         ret = 0;
         // Make the /data/user directory if necessary
         if (access(user_data_dir, R_OK) < 0) {
-            if (mkdir(user_data_dir, 0755) < 0) {
+            if (mkdir(user_data_dir, 0711) < 0) {
                 return -1;
             }
             if (chown(user_data_dir, AID_SYSTEM, AID_SYSTEM) < 0) {
                 return -1;
             }
+            if (chmod(user_data_dir, 0711) < 0) {
+                return -1;
+            }
         }
         // Make the /data/user/0 symlink to /data/data if necessary
         if (access(primary_data_dir, R_OK) < 0) {
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index ac5bffe..4d638d0 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -146,18 +146,18 @@
             return;
         }
 
-        if ("create-profile".equals(op)) {
-            runCreateProfile();
+        if ("create-user".equals(op)) {
+            runCreateUser();
             return;
         }
 
-        if ("remove-profile".equals(op)) {
-            runRemoveProfile();
+        if ("remove-user".equals(op)) {
+            runRemoveUser();
             return;
         }
 
-        if ("list-profiles".equals(op)) {
-            runListProfiles();
+        if ("list-users".equals(op)) {
+            runListUsers();
             return;
         }
 
@@ -215,6 +215,8 @@
             runListLibraries();
         } else if ("instrumentation".equals(type)) {
             runListInstrumentation();
+        } else if ("users".equals(type)) {
+            runListUsers();
         } else {
             System.err.println("Error: unknown list type '" + type + "'");
             showUsage();
@@ -832,10 +834,10 @@
         }
     }
 
-    public void runCreateProfile() {
+    public void runCreateUser() {
         // Need to be run as root
         if (Process.myUid() != ROOT_UID) {
-            System.err.println("Error: create-profile must be run as root");
+            System.err.println("Error: create-user must be run as root");
             return;
         }
         String name;
@@ -848,7 +850,7 @@
         name = arg;
         try {
             if (mPm.createUser(name, 0) == null) {
-                System.err.println("Error: couldn't create profile.");
+                System.err.println("Error: couldn't create User.");
                 showUsage();
             }
         } catch (RemoteException e) {
@@ -858,10 +860,10 @@
 
     }
 
-    public void runRemoveProfile() {
+    public void runRemoveUser() {
         // Need to be run as root
         if (Process.myUid() != ROOT_UID) {
-            System.err.println("Error: remove-profile must be run as root");
+            System.err.println("Error: remove-user must be run as root");
             return;
         }
         int userId;
@@ -880,7 +882,7 @@
         }
         try {
             if (!mPm.removeUser(userId)) {
-                System.err.println("Error: couldn't remove profile.");
+                System.err.println("Error: couldn't remove user.");
                 showUsage();
             }
         } catch (RemoteException e) {
@@ -889,10 +891,10 @@
         }
     }
 
-    public void runListProfiles() {
+    public void runListUsers() {
         // Need to be run as root
         if (Process.myUid() != ROOT_UID) {
-            System.err.println("Error: list-profiles must be run as root");
+            System.err.println("Error: list-users must be run as root");
             return;
         }
         try {
@@ -1029,7 +1031,29 @@
         return "unknown";
     }
 
+    private boolean isNumber(String s) {
+        try {
+            Integer.parseInt(s);
+        } catch (NumberFormatException nfe) {
+            return false;
+        }
+        return true;
+    }
+
     private void runSetEnabledSetting(int state) {
+        int userId = 0;
+        String option = nextOption();
+        if (option != null && option.equals("--user")) {
+            String optionData = nextOptionData();
+            if (optionData == null || !isNumber(optionData)) {
+                System.err.println("Error: no USER_ID specified");
+                showUsage();
+                return;
+            } else {
+                userId = Integer.parseInt(optionData);
+            }
+        }
+
         String pkg = nextArg();
         if (pkg == null) {
             System.err.println("Error: no package or component specified");
@@ -1039,20 +1063,20 @@
         ComponentName cn = ComponentName.unflattenFromString(pkg);
         if (cn == null) {
             try {
-                mPm.setApplicationEnabledSetting(pkg, state, 0);
+                mPm.setApplicationEnabledSetting(pkg, state, 0, userId);
                 System.err.println("Package " + pkg + " new state: "
                         + enabledSettingToString(
-                                mPm.getApplicationEnabledSetting(pkg)));
+                        mPm.getApplicationEnabledSetting(pkg, userId)));
             } catch (RemoteException e) {
                 System.err.println(e.toString());
                 System.err.println(PM_NOT_RUNNING_ERR);
             }
         } else {
             try {
-                mPm.setComponentEnabledSetting(cn, state, 0);
+                mPm.setComponentEnabledSetting(cn, state, 0, userId);
                 System.err.println("Component " + cn.toShortString() + " new state: "
                         + enabledSettingToString(
-                                mPm.getComponentEnabledSetting(cn)));
+                        mPm.getComponentEnabledSetting(cn, userId)));
             } catch (RemoteException e) {
                 System.err.println(e.toString());
                 System.err.println(PM_NOT_RUNNING_ERR);
@@ -1096,7 +1120,7 @@
      */
     private void displayPackageFilePath(String pckg) {
         try {
-            PackageInfo info = mPm.getPackageInfo(pckg, 0);
+            PackageInfo info = mPm.getPackageInfo(pckg, 0, 0);
             if (info != null && info.applicationInfo != null) {
                 System.out.print("package:");
                 System.out.println(info.applicationInfo.sourceDir);
@@ -1112,7 +1136,7 @@
         if (res != null) return res;
 
         try {
-            ApplicationInfo ai = mPm.getApplicationInfo(pii.packageName, 0);
+            ApplicationInfo ai = mPm.getApplicationInfo(pii.packageName, 0, 0);
             AssetManager am = new AssetManager();
             am.addAssetPath(ai.publicSourceDir);
             res = new Resources(am, null, null);
@@ -1178,19 +1202,20 @@
         System.err.println("       pm list instrumentation [-f] [TARGET-PACKAGE]");
         System.err.println("       pm list features");
         System.err.println("       pm list libraries");
+        System.err.println("       pm list users");
         System.err.println("       pm path PACKAGE");
         System.err.println("       pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f] PATH");
         System.err.println("       pm uninstall [-k] PACKAGE");
         System.err.println("       pm clear PACKAGE");
-        System.err.println("       pm enable PACKAGE_OR_COMPONENT");
-        System.err.println("       pm disable PACKAGE_OR_COMPONENT");
-        System.err.println("       pm disable-user PACKAGE_OR_COMPONENT");
+        System.err.println("       pm enable [--user USER_ID] PACKAGE_OR_COMPONENT");
+        System.err.println("       pm disable [--user USER_ID] PACKAGE_OR_COMPONENT");
+        System.err.println("       pm disable-user [--user USER_ID] PACKAGE_OR_COMPONENT");
         System.err.println("       pm grant PACKAGE PERMISSION");
         System.err.println("       pm revoke PACKAGE PERMISSION");
         System.err.println("       pm set-install-location [0/auto] [1/internal] [2/external]");
         System.err.println("       pm get-install-location");
-        System.err.println("       pm create-profile USER_NAME");
-        System.err.println("       pm remove-profile USER_ID");
+        System.err.println("       pm create-user USER_NAME");
+        System.err.println("       pm remove-user USER_ID");
         System.err.println("");
         System.err.println("pm list packages: prints all packages, optionally only");
         System.err.println("  those whose package name contains the text in FILTER.  Options:");
diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk
index 30be7fa..52a9293 100644
--- a/cmds/stagefright/Android.mk
+++ b/cmds/stagefright/Android.mk
@@ -4,19 +4,18 @@
 
 LOCAL_SRC_FILES:=       \
 	stagefright.cpp \
+	jpeg.cpp	\
 	SineSource.cpp
 
 LOCAL_SHARED_LIBRARIES := \
 	libstagefright libmedia libmedia_native libutils libbinder libstagefright_foundation \
-        libskia libgui
+        libjpeg libgui
 
 LOCAL_C_INCLUDES:= \
-	$(JNI_H_INCLUDE) \
 	frameworks/base/media/libstagefright \
 	frameworks/base/media/libstagefright/include \
 	$(TOP)/frameworks/native/include/media/openmax \
-	external/skia/include/core \
-	external/skia/include/images \
+	external/jpeg \
 
 LOCAL_CFLAGS += -Wno-multichar
 
@@ -38,7 +37,6 @@
 	libstagefright liblog libutils libbinder libstagefright_foundation
 
 LOCAL_C_INCLUDES:= \
-	$(JNI_H_INCLUDE) \
 	frameworks/base/media/libstagefright \
 	$(TOP)/frameworks/native/include/media/openmax
 
@@ -62,7 +60,6 @@
 	libstagefright liblog libutils libbinder libstagefright_foundation
 
 LOCAL_C_INCLUDES:= \
-	$(JNI_H_INCLUDE) \
 	frameworks/base/media/libstagefright \
 	$(TOP)/frameworks/native/include/media/openmax
 
@@ -87,7 +84,6 @@
 	libstagefright liblog libutils libbinder libstagefright_foundation
 
 LOCAL_C_INCLUDES:= \
-	$(JNI_H_INCLUDE) \
 	frameworks/base/media/libstagefright \
 	$(TOP)/frameworks/native/include/media/openmax
 
@@ -111,7 +107,6 @@
         libstagefright_foundation libmedia libmedia_native
 
 LOCAL_C_INCLUDES:= \
-	$(JNI_H_INCLUDE) \
 	frameworks/base/media/libstagefright \
 	$(TOP)/frameworks/native/include/media/openmax
 
@@ -135,7 +130,6 @@
         libmedia libmedia_native libgui libcutils libui
 
 LOCAL_C_INCLUDES:= \
-	$(JNI_H_INCLUDE) \
 	frameworks/base/media/libstagefright \
 	$(TOP)/frameworks/native/include/media/openmax
 
@@ -160,7 +154,6 @@
         libmedia libmedia_native libgui libcutils libui
 
 LOCAL_C_INCLUDES:= \
-	$(JNI_H_INCLUDE) \
 	frameworks/base/media/libstagefright \
 	$(TOP)/frameworks/native/include/media/openmax
 
diff --git a/cmds/stagefright/codec.cpp b/cmds/stagefright/codec.cpp
index fea62cc..cf2909e 100644
--- a/cmds/stagefright/codec.cpp
+++ b/cmds/stagefright/codec.cpp
@@ -28,6 +28,7 @@
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/DataSource.h>
 #include <media/stagefright/MediaCodec.h>
+#include <media/stagefright/MediaCodecList.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/NuMediaExtractor.h>
 #include <gui/SurfaceComposerClient.h>
@@ -36,7 +37,9 @@
     fprintf(stderr, "usage: %s [-a] use audio\n"
                     "\t\t[-v] use video\n"
                     "\t\t[-p] playback\n"
-                    "\t\t[-S] allocate buffers from a surface\n", me);
+                    "\t\t[-S] allocate buffers from a surface\n"
+                    "\t\t[-D] decrypt input buffers\n",
+                    me);
 
     exit(1);
 }
@@ -63,7 +66,8 @@
         const char *path,
         bool useAudio,
         bool useVideo,
-        const android::sp<android::Surface> &surface) {
+        const android::sp<android::Surface> &surface,
+        bool decryptInputBuffers) {
     using namespace android;
 
     static int64_t kTimeout = 500ll;
@@ -109,13 +113,31 @@
         state->mNumBuffersDecoded = 0;
         state->mIsAudio = isAudio;
 
-        state->mCodec = MediaCodec::CreateByType(
-                looper, mime.c_str(), false /* encoder */);
+        if (decryptInputBuffers && !isAudio) {
+            static const MediaCodecList *list = MediaCodecList::getInstance();
+
+            ssize_t index =
+                list->findCodecByType(mime.c_str(), false /* encoder */);
+
+            CHECK_GE(index, 0);
+
+            const char *componentName = list->getCodecName(index);
+
+            AString fullName = componentName;
+            fullName.append(".secure");
+
+            state->mCodec = MediaCodec::CreateByComponentName(
+                    looper, fullName.c_str());
+        } else {
+            state->mCodec = MediaCodec::CreateByType(
+                    looper, mime.c_str(), false /* encoder */);
+        }
 
         CHECK(state->mCodec != NULL);
 
         err = state->mCodec->configure(
-                format, isVideo ? surface : NULL, 0 /* flags */);
+                format, isVideo ? surface : NULL,
+                decryptInputBuffers ? MediaCodec::CONFIGURE_FLAG_SECURE : 0);
 
         CHECK_EQ(err, (status_t)OK);
 
@@ -202,12 +224,24 @@
                     err = extractor->getSampleTime(&timeUs);
                     CHECK_EQ(err, (status_t)OK);
 
+                    uint32_t bufferFlags = 0;
+
+                    uint32_t sampleFlags;
+                    err = extractor->getSampleFlags(&sampleFlags);
+                    CHECK_EQ(err, (status_t)OK);
+
+                    if (sampleFlags & NuMediaExtractor::SAMPLE_FLAG_ENCRYPTED) {
+                        CHECK(decryptInputBuffers);
+
+                        bufferFlags |= MediaCodec::BUFFER_FLAG_ENCRYPTED;
+                    }
+
                     err = state->mCodec->queueInputBuffer(
                             index,
                             0 /* offset */,
                             buffer->size(),
                             timeUs,
-                            0 /* flags */);
+                            bufferFlags);
 
                     CHECK_EQ(err, (status_t)OK);
 
@@ -341,9 +375,10 @@
     bool useVideo = false;
     bool playback = false;
     bool useSurface = false;
+    bool decryptInputBuffers = false;
 
     int res;
-    while ((res = getopt(argc, argv, "havpS")) >= 0) {
+    while ((res = getopt(argc, argv, "havpSD")) >= 0) {
         switch (res) {
             case 'a':
             {
@@ -369,6 +404,12 @@
                 break;
             }
 
+            case 'D':
+            {
+                decryptInputBuffers = true;
+                break;
+            }
+
             case '?':
             case 'h':
             default:
@@ -440,7 +481,8 @@
         player->stop();
         player->reset();
     } else {
-        decode(looper, argv[0], useAudio, useVideo, surface);
+        decode(looper, argv[0],
+               useAudio, useVideo, surface, decryptInputBuffers);
     }
 
     if (playback || (useSurface && useVideo)) {
diff --git a/cmds/stagefright/jpeg.cpp b/cmds/stagefright/jpeg.cpp
new file mode 100644
index 0000000..7e859c3
--- /dev/null
+++ b/cmds/stagefright/jpeg.cpp
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <setjmp.h>
+#include <stdio.h>
+
+extern "C" {
+#include "jpeglib.h"
+}
+
+static inline uint8_t from565to8(uint16_t p, int start, int bits) {
+    uint8_t c = (p >> start) & ((1 << bits) - 1);
+    return (c << (8 - bits)) | (c >> (bits - (8 - bits)));
+}
+
+struct sf_jpeg_error_mgr {
+    struct jpeg_error_mgr jerr;
+    jmp_buf longjmp_buffer;
+};
+
+void sf_jpeg_error_exit(j_common_ptr cinfo) {
+    struct sf_jpeg_error_mgr *sf_err = (struct sf_jpeg_error_mgr *)cinfo->err;
+    longjmp(sf_err->longjmp_buffer, 0);
+}
+
+int writeJpegFile(const char *filename, uint8_t *frame, int width, int height) {
+    struct sf_jpeg_error_mgr sf_err;
+    struct jpeg_compress_struct cinfo;
+    uint8_t row_data[width * 3];
+    JSAMPROW row_pointer = row_data;
+    FILE *f;
+
+    f = fopen(filename, "w");
+    if (!f) {
+        return -errno;
+    }
+
+    cinfo.err = jpeg_std_error(&sf_err.jerr);
+    sf_err.jerr.error_exit = sf_jpeg_error_exit;
+    if (setjmp(sf_err.longjmp_buffer)) {
+        jpeg_destroy_compress(&cinfo);
+        fclose(f);
+        return -1;
+    }
+
+    jpeg_create_compress(&cinfo);
+    jpeg_stdio_dest(&cinfo, f);
+
+    cinfo.image_width = width;
+    cinfo.image_height = height;
+    cinfo.input_components = 3;
+    cinfo.in_color_space = JCS_RGB;
+
+    jpeg_set_defaults(&cinfo);
+    jpeg_set_quality(&cinfo, 80, TRUE);
+
+    jpeg_start_compress(&cinfo, TRUE);
+
+    for (int row = 0; row < height; row++) {
+        uint16_t *src = (uint16_t *)(frame + row * width * 2);
+        uint8_t *dst = row_data;
+        for (int col = 0; col < width; col++) {
+            dst[0] = from565to8(*src, 11, 5);
+            dst[1] = from565to8(*src, 5, 6);
+            dst[2] = from565to8(*src, 0, 5);
+            dst += 3;
+            src++;
+        }
+        jpeg_write_scanlines(&cinfo, &row_pointer, 1);
+    }
+
+    jpeg_finish_compress(&cinfo);
+    jpeg_destroy_compress(&cinfo);
+
+    fclose(f);
+    return 0;
+}
diff --git a/cmds/stagefright/jpeg.h b/cmds/stagefright/jpeg.h
new file mode 100644
index 0000000..ce86cf2
--- /dev/null
+++ b/cmds/stagefright/jpeg.h
@@ -0,0 +1,6 @@
+#ifndef _STAGEFRIGHT_JPEG_H_
+#define _STAGEFRIGHT_JPEG_H_
+
+int writeJpegFile(const char *filename, uint8_t *frame, int width, int height);
+
+#endif
diff --git a/cmds/stagefright/sf2.cpp b/cmds/stagefright/sf2.cpp
index e47cdc0..3bbfbdc 100644
--- a/cmds/stagefright/sf2.cpp
+++ b/cmds/stagefright/sf2.cpp
@@ -176,8 +176,9 @@
                     }
 
                     onDrainThisBuffer(msg);
-                } else if (what == ACodec::kWhatEOS) {
-                    printf("$\n");
+                } else if (what == ACodec::kWhatEOS
+                        || what == ACodec::kWhatError) {
+                    printf((what == ACodec::kWhatEOS) ? "$\n" : "E\n");
 
                     int64_t delayUs = ALooper::GetNowUs() - mStartTimeUs;
 
@@ -286,6 +287,11 @@
 
             msg->setInt32("channel-count", numChannels);
             msg->setInt32("sample-rate", sampleRate);
+
+            int32_t isADTS;
+            if (meta->findInt32(kKeyIsADTS, &isADTS) && isADTS != 0) {
+                msg->setInt32("is-adts", true);
+            }
         }
 
         uint32_t type;
@@ -412,7 +418,8 @@
         sp<AMessage> reply;
         CHECK(msg->findMessage("reply", &reply));
 
-        if (mSeekState == SEEK_FLUSHING) {
+        if (mSource == NULL || mSeekState == SEEK_FLUSHING) {
+            reply->setInt32("err", ERROR_END_OF_STREAM);
             reply->post();
             return;
         }
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index dab2e0f..d70c862 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -24,6 +24,7 @@
 #include <string.h>
 #include <unistd.h>
 
+#include "jpeg.h"
 #include "SineSource.h"
 
 #include <binder/IServiceManager.h>
@@ -49,8 +50,6 @@
 #include <media/stagefright/MPEG4Writer.h>
 
 #include <private/media/VideoFrame.h>
-#include <SkBitmap.h>
-#include <SkImageEncoder.h>
 
 #include <fcntl.h>
 
@@ -787,16 +786,9 @@
 
                 VideoFrame *frame = (VideoFrame *)mem->pointer();
 
-                SkBitmap bitmap;
-                bitmap.setConfig(
-                        SkBitmap::kRGB_565_Config, frame->mWidth, frame->mHeight);
-
-                bitmap.setPixels((uint8_t *)frame + sizeof(VideoFrame));
-
-                CHECK(SkImageEncoder::EncodeFile(
-                            "/sdcard/out.jpg", bitmap,
-                            SkImageEncoder::kJPEG_Type,
-                            SkImageEncoder::kDefaultQuality));
+                CHECK_EQ(writeJpegFile("/sdcard/out.jpg",
+                            (uint8_t *)frame + sizeof(VideoFrame),
+                            frame->mWidth, frame->mHeight), 0);
             }
 
             {
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index cf1e8f3..197c1bd 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -217,10 +217,10 @@
         mAuthenticatorCache = authenticatorCache;
         mAuthenticatorCache.setListener(this, null /* Handler */);
 
-        UserAccounts accounts = initUser(0);
-
         sThis.set(this);
 
+        UserAccounts accounts = initUser(0);
+
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         intentFilter.addDataScheme("package");
@@ -231,6 +231,14 @@
             }
         }, intentFilter);
 
+        IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(Intent.ACTION_USER_REMOVED);
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                onUserRemoved(intent);
+            }
+        }, userFilter);
     }
 
     private UserAccounts initUser(int userId) {
@@ -347,6 +355,28 @@
         }
     }
 
+    private void onUserRemoved(Intent intent) {
+        int userId = intent.getIntExtra(Intent.EXTRA_USERID, -1);
+        if (userId < 1) return;
+
+        UserAccounts accounts;
+        synchronized (mUsers) {
+            accounts = mUsers.get(userId);
+            mUsers.remove(userId);
+        }
+        if (accounts == null) {
+            File dbFile = new File(getDatabaseName(userId));
+            dbFile.delete();
+            return;
+        }
+
+        synchronized (accounts.cacheLock) {
+            accounts.openHelper.close();
+            File dbFile = new File(getDatabaseName(userId));
+            dbFile.delete();
+        }
+    }
+
     private List<UserInfo> getAllUsers() {
         try {
             return AppGlobals.getPackageManager().getUsers();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ea32745..b277efb 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -3639,7 +3639,7 @@
      */
     public void startActivityFromChild(Activity child, Intent intent,
             int requestCode) {
-        startActivityFromChild(child, intent, requestCode);
+        startActivityFromChild(child, intent, requestCode, null);
     }
 
     /**
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d056b17..531a695 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -31,6 +31,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.Parcel;
@@ -816,6 +817,19 @@
     public static final int MOVE_TASK_NO_USER_ACTION = 0x00000002;
 
     /**
+     * Equivalent to calling {@link #moveTaskToFront(int, int, Bundle)}
+     * with a null options argument.
+     *
+     * @param taskId The identifier of the task to be moved, as found in
+     * {@link RunningTaskInfo} or {@link RecentTaskInfo}.
+     * @param flags Additional operational flags, 0 or more of
+     * {@link #MOVE_TASK_WITH_HOME}.
+     */
+    public void moveTaskToFront(int taskId, int flags) {
+        moveTaskToFront(taskId, flags, null);
+    }
+
+    /**
      * Ask that the task associated with a given task ID be moved to the
      * front of the stack, so it is now visible to the user.  Requires that
      * the caller hold permission {@link android.Manifest.permission#REORDER_TASKS}
@@ -825,10 +839,13 @@
      * {@link RunningTaskInfo} or {@link RecentTaskInfo}.
      * @param flags Additional operational flags, 0 or more of
      * {@link #MOVE_TASK_WITH_HOME}.
+     * @param options Additional options for the operation, either null or
+     * as per {@link Context#startActivity(Intent, android.os.Bundle)
+     * Context.startActivity(Intent, Bundle)}.
      */
-    public void moveTaskToFront(int taskId, int flags) {
+    public void moveTaskToFront(int taskId, int flags, Bundle options) {
         try {
-            ActivityManagerNative.getDefault().moveTaskToFront(taskId, flags);
+            ActivityManagerNative.getDefault().moveTaskToFront(taskId, flags, options);
         } catch (RemoteException e) {
             // System dead, we will be dead too soon!
         }
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index a3cc352..c402329 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -510,7 +510,9 @@
             data.enforceInterface(IActivityManager.descriptor);
             int task = data.readInt();
             int fl = data.readInt();
-            moveTaskToFront(task, fl);
+            Bundle options = data.readInt() != 0
+                    ? Bundle.CREATOR.createFromParcel(data) : null;
+            moveTaskToFront(task, fl, options);
             reply.writeNoException();
             return true;
         }
@@ -1055,6 +1057,15 @@
             return true;
         }
 
+        case KILL_PROCESSES_BELOW_FOREGROUND_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            String reason = data.readString();
+            boolean res = killProcessesBelowForeground(reason);
+            reply.writeNoException();
+            reply.writeInt(res ? 1 : 0);
+            return true;
+        }
+
         case START_RUNNING_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             String pkg = data.readString();
@@ -2134,13 +2145,19 @@
         reply.recycle();
         return list;
     }
-    public void moveTaskToFront(int task, int flags) throws RemoteException
+    public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException
     {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeInt(task);
         data.writeInt(flags);
+        if (options != null) {
+            data.writeInt(1);
+            options.writeToParcel(data, 0);
+        } else {
+            data.writeInt(0);
+        }
         mRemote.transact(MOVE_TASK_TO_FRONT_TRANSACTION, data, reply, 0);
         reply.readException();
         data.recycle();
@@ -2902,6 +2919,18 @@
         reply.recycle();
         return res;
     }
+    @Override
+    public boolean killProcessesBelowForeground(String reason) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeString(reason);
+        mRemote.transact(KILL_PROCESSES_BELOW_FOREGROUND_TRANSACTION, data, reply, 0);
+        boolean res = reply.readInt() != 0;
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
     public void startRunning(String pkg, String cls, String action,
             String indata) throws RemoteException {
         Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 03bc338..c637df0 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -17,7 +17,13 @@
 package android.app;
 
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.Message;
+import android.os.RemoteException;
+import android.view.View;
 
 /**
  * Helper class for building an options Bundle that can be used with
@@ -32,6 +38,12 @@
     public static final String KEY_PACKAGE_NAME = "android:packageName";
 
     /**
+     * Type of animation that arguments specify.
+     * @hide
+     */
+    public static final String KEY_ANIM_TYPE = "android:animType";
+
+    /**
      * Custom enter animation resource ID.
      * @hide
      */
@@ -43,10 +55,45 @@
      */
     public static final String KEY_ANIM_EXIT_RES_ID = "android:animExitRes";
 
+    /**
+     * Bitmap for thumbnail animation.
+     * @hide
+     */
+    public static final String KEY_ANIM_THUMBNAIL = "android:animThumbnail";
+
+    /**
+     * Start X position of thumbnail animation.
+     * @hide
+     */
+    public static final String KEY_ANIM_START_X = "android:animStartX";
+
+    /**
+     * Start Y position of thumbnail animation.
+     * @hide
+     */
+    public static final String KEY_ANIM_START_Y = "android:animStartY";
+
+    /**
+     * Callback for when animation is started.
+     * @hide
+     */
+    public static final String KEY_ANIM_START_LISTENER = "android:animStartListener";
+
+    /** @hide */
+    public static final int ANIM_NONE = 0;
+    /** @hide */
+    public static final int ANIM_CUSTOM = 1;
+    /** @hide */
+    public static final int ANIM_THUMBNAIL = 2;
+
     private String mPackageName;
-    private boolean mIsCustomAnimation;
+    private int mAnimationType = ANIM_NONE;
     private int mCustomEnterResId;
     private int mCustomExitResId;
+    private Bitmap mThumbnail;
+    private int mStartX;
+    private int mStartY;
+    private IRemoteCallback mAnimationStartedListener;
 
     /**
      * Create an ActivityOptions specifying a custom animation to run when
@@ -65,22 +112,79 @@
             int enterResId, int exitResId) {
         ActivityOptions opts = new ActivityOptions();
         opts.mPackageName = context.getPackageName();
-        opts.mIsCustomAnimation = true;
+        opts.mAnimationType = ANIM_CUSTOM;
         opts.mCustomEnterResId = enterResId;
         opts.mCustomExitResId = exitResId;
         return opts;
     }
 
+    /**
+     * Callback for use with {@link ActivityOptions#makeThumbnailScaleUpAnimation}
+     * to find out when the given animation has started running.
+     */
+    public interface OnAnimationStartedListener {
+        void onAnimationStarted();
+    }
+
+    /**
+     * Create an ActivityOptions specifying an animation where a thumbnail
+     * is scaled from a given position to the new activity window that is
+     * being started.
+     *
+     * @param source The View that this thumbnail is animating from.  This
+     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
+     * @param thumbnail The bitmap that will be shown as the initial thumbnail
+     * of the animation.
+     * @param startX The x starting location of the bitmap, in screen coordiantes.
+     * @param startY The y starting location of the bitmap, in screen coordinates.
+     * @param listener Optional OnAnimationStartedListener to find out when the
+     * requested animation has started running.  If for some reason the animation
+     * is not executed, the callback will happen immediately.
+     * @return Returns a new ActivityOptions object that you can use to
+     * supply these options as the options Bundle when starting an activity.
+     */
+    public static ActivityOptions makeThumbnailScaleUpAnimation(View source,
+            Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) {
+        ActivityOptions opts = new ActivityOptions();
+        opts.mPackageName = source.getContext().getPackageName();
+        opts.mAnimationType = ANIM_THUMBNAIL;
+        opts.mThumbnail = thumbnail;
+        int[] pts = new int[2];
+        source.getLocationOnScreen(pts);
+        opts.mStartX = pts[0] + startX;
+        opts.mStartY = pts[1] + startY;
+        if (listener != null) {
+            final Handler h = source.getHandler();
+            final OnAnimationStartedListener finalListener = listener;
+            opts.mAnimationStartedListener = new IRemoteCallback.Stub() {
+                @Override public void sendResult(Bundle data) throws RemoteException {
+                    h.post(new Runnable() {
+                        @Override public void run() {
+                            finalListener.onAnimationStarted();
+                        }
+                    });
+                }
+            };
+        }
+        return opts;
+    }
+
     private ActivityOptions() {
     }
 
     /** @hide */
     public ActivityOptions(Bundle opts) {
         mPackageName = opts.getString(KEY_PACKAGE_NAME);
-        if (opts.containsKey(KEY_ANIM_ENTER_RES_ID)) {
-            mIsCustomAnimation = true;
+        mAnimationType = opts.getInt(KEY_ANIM_TYPE);
+        if (mAnimationType == ANIM_CUSTOM) {
             mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0);
             mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0);
+        } else if (mAnimationType == ANIM_THUMBNAIL) {
+            mThumbnail = (Bitmap)opts.getParcelable(KEY_ANIM_THUMBNAIL);
+            mStartX = opts.getInt(KEY_ANIM_START_X, 0);
+            mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
+            mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
+                    opts.getIBinder(KEY_ANIM_START_LISTENER));
         }
     }
 
@@ -90,8 +194,8 @@
     }
 
     /** @hide */
-    public boolean isCustomAnimation() {
-        return mIsCustomAnimation;
+    public int getAnimationType() {
+        return mAnimationType;
     }
 
     /** @hide */
@@ -104,6 +208,43 @@
         return mCustomExitResId;
     }
 
+    /** @hide */
+    public Bitmap getThumbnail() {
+        return mThumbnail;
+    }
+
+    /** @hide */
+    public int getStartX() {
+        return mStartX;
+    }
+
+    /** @hide */
+    public int getStartY() {
+        return mStartY;
+    }
+
+    /** @hide */
+    public IRemoteCallback getOnAnimationStartListener() {
+        return mAnimationStartedListener;
+    }
+
+    /** @hide */
+    public void abort() {
+        if (mAnimationStartedListener != null) {
+            try {
+                mAnimationStartedListener.sendResult(null);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /** @hide */
+    public static void abort(Bundle options) {
+        if (options != null) {
+            (new ActivityOptions(options)).abort();
+        }
+    }
+
     /**
      * Join the values in <var>otherOptions</var> in to this one.  Any values
      * defined in <var>otherOptions</var> replace those in the base options.
@@ -112,10 +253,27 @@
         if (otherOptions.mPackageName != null) {
             mPackageName = otherOptions.mPackageName;
         }
-        if (otherOptions.mIsCustomAnimation) {
-            mIsCustomAnimation = true;
-            mCustomEnterResId = otherOptions.mCustomEnterResId;
-            mCustomExitResId = otherOptions.mCustomExitResId;
+        switch (otherOptions.mAnimationType) {
+            case ANIM_CUSTOM:
+                mAnimationType = otherOptions.mAnimationType;
+                mCustomEnterResId = otherOptions.mCustomEnterResId;
+                mCustomExitResId = otherOptions.mCustomExitResId;
+                mThumbnail = null;
+                mAnimationStartedListener = null;
+                break;
+            case ANIM_THUMBNAIL:
+                mAnimationType = otherOptions.mAnimationType;
+                mThumbnail = otherOptions.mThumbnail;
+                mStartX = otherOptions.mStartX;
+                mStartY = otherOptions.mStartY;
+                if (otherOptions.mAnimationStartedListener != null) {
+                    try {
+                        otherOptions.mAnimationStartedListener.sendResult(null);
+                    } catch (RemoteException e) {
+                    }
+                }
+                mAnimationStartedListener = otherOptions.mAnimationStartedListener;
+                break;
         }
     }
 
@@ -132,9 +290,19 @@
         if (mPackageName != null) {
             b.putString(KEY_PACKAGE_NAME, mPackageName);
         }
-        if (mIsCustomAnimation) {
-            b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
-            b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
+        switch (mAnimationType) {
+            case ANIM_CUSTOM:
+                b.putInt(KEY_ANIM_TYPE, mAnimationType);
+                b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
+                b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
+                break;
+            case ANIM_THUMBNAIL:
+                b.putInt(KEY_ANIM_TYPE, mAnimationType);
+                b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail);
+                b.putInt(KEY_ANIM_START_X, mStartX);
+                b.putInt(KEY_ANIM_START_Y, mStartY);
+                b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
+                        != null ? mAnimationStartedListener.asBinder() : null);
         }
         return b;
     }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2a3e213..ab4e73d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -404,6 +404,7 @@
                     try {
                         fd.close();
                     } catch (IOException e) {
+                        // Ignore
                     }
                 }
                 return;
@@ -412,6 +413,7 @@
                 try {
                     profileFd.close();
                 } catch (IOException e) {
+                    // Ignore
                 }
             }
             profileFile = file;
@@ -843,14 +845,13 @@
             FileOutputStream fout = new FileOutputStream(fd);
             PrintWriter pw = new PrintWriter(fout);
             try {
-                return dumpMemInfo(pw, checkin, all, args);
+                return dumpMemInfo(pw, checkin, all);
             } finally {
                 pw.flush();
             }
         }
 
-        private Debug.MemoryInfo dumpMemInfo(PrintWriter pw, boolean checkin, boolean all,
-                String[] args) {
+        private Debug.MemoryInfo dumpMemInfo(PrintWriter pw, boolean checkin, boolean all) {
             long nativeMax = Debug.getNativeHeapSize() / 1024;
             long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
             long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
@@ -1458,17 +1459,6 @@
         return dm;
     }
 
-    static Configuration applyConfigCompat(Configuration config, CompatibilityInfo compat) {
-        if (config == null) {
-            return null;
-        }
-        if (compat != null && !compat.supportsScreen()) {
-            config = new Configuration(config);
-            compat.applyToConfiguration(config);
-        }
-        return config;
-    }
-
     private Configuration mMainThreadConfig = new Configuration();
     Configuration applyConfigCompatMainThread(Configuration config, CompatibilityInfo compat) {
         if (config == null) {
@@ -1586,7 +1576,7 @@
         ApplicationInfo ai = null;
         try {
             ai = getPackageManager().getApplicationInfo(packageName,
-                    PackageManager.GET_SHARED_LIBRARY_FILES);
+                    PackageManager.GET_SHARED_LIBRARY_FILES, UserId.myUserId());
         } catch (RemoteException e) {
             // Ignore
         }
@@ -2509,7 +2499,7 @@
         return r;
     }
 
-    final void cleanUpPendingRemoveWindows(ActivityClientRecord r) {
+    static final void cleanUpPendingRemoveWindows(ActivityClientRecord r) {
         if (r.mPendingRemoveWindow != null) {
             r.mPendingRemoveWindowManager.removeViewImmediate(r.mPendingRemoveWindow);
             IBinder wtoken = r.mPendingRemoveWindow.getWindowToken();
@@ -3437,15 +3427,12 @@
                 = new ArrayList<ComponentCallbacks2>();
 
         if (mActivities.size() > 0) {
-            Iterator<ActivityClientRecord> it = mActivities.values().iterator();
-            while (it.hasNext()) {
-                ActivityClientRecord ar = it.next();
+            for (ActivityClientRecord ar : mActivities.values()) {
                 Activity a = ar.activity;
                 if (a != null) {
                     Configuration thisConfig = applyConfigCompatMainThread(newConfig,
                             ar.packageInfo.mCompatibilityInfo.getIfNeeded());
-                    if (!ar.activity.mFinished && (allActivities ||
-                            (a != null && !ar.paused))) {
+                    if (!ar.activity.mFinished && (allActivities || !ar.paused)) {
                         // If the activity is currently resumed, its configuration
                         // needs to change right now.
                         callbacks.add(a);
@@ -3455,24 +3442,24 @@
                         // the activity manager may, before then, decide the
                         // activity needs to be destroyed to handle its new
                         // configuration.
-                        if (DEBUG_CONFIGURATION) Slog.v(TAG, "Setting activity "
-                                + ar.activityInfo.name + " newConfig=" + thisConfig);
+                        if (DEBUG_CONFIGURATION) {
+                            Slog.v(TAG, "Setting activity "
+                                    + ar.activityInfo.name + " newConfig=" + thisConfig);
+                        }
                         ar.newConfig = thisConfig;
                     }
                 }
             }
         }
         if (mServices.size() > 0) {
-            Iterator<Service> it = mServices.values().iterator();
-            while (it.hasNext()) {
-                callbacks.add(it.next());
+            for (Service service : mServices.values()) {
+                callbacks.add(service);
             }
         }
         synchronized (mProviderMap) {
             if (mLocalProviders.size() > 0) {
-                Iterator<ProviderClientRecord> it = mLocalProviders.values().iterator();
-                while (it.hasNext()) {
-                    callbacks.add(it.next().mLocalProvider);
+                for (ProviderClientRecord providerClientRecord : mLocalProviders.values()) {
+                    callbacks.add(providerClientRecord.mLocalProvider);
                 }
             }
         }
@@ -3484,8 +3471,7 @@
         return callbacks;
     }
 
-    private final void performConfigurationChanged(
-            ComponentCallbacks2 cb, Configuration config) {
+    private static void performConfigurationChanged(ComponentCallbacks2 cb, Configuration config) {
         // Only for Activity objects, check that they actually call up to their
         // superclass implementation.  ComponentCallbacks2 is an interface, so
         // we check the runtime type and act accordingly.
@@ -3692,7 +3678,7 @@
         }
     }
 
-    final void handleDumpHeap(boolean managed, DumpHeapData dhd) {
+    static final void handleDumpHeap(boolean managed, DumpHeapData dhd) {
         if (managed) {
             try {
                 Debug.dumpHprofData(dhd.path, dhd.fd.getFileDescriptor());
@@ -3769,7 +3755,7 @@
         }
 
         final int N = callbacks.size();
-        for (int i=0; i<N; i++) {
+        for (int i = 0; i < N; i++) {
             callbacks.get(i).onTrimMemory(level);
         }
         WindowManagerImpl.getDefault().terminateEgl();
@@ -4057,9 +4043,7 @@
         final ArrayList<IActivityManager.ContentProviderHolder> results =
             new ArrayList<IActivityManager.ContentProviderHolder>();
 
-        Iterator<ProviderInfo> i = providers.iterator();
-        while (i.hasNext()) {
-            ProviderInfo cpi = i.next();
+        for (ProviderInfo cpi : providers) {
             StringBuilder buf = new StringBuilder(128);
             buf.append("Pub ");
             buf.append(cpi.authority);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 758ce09..0510de1 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -49,6 +49,7 @@
 import android.net.Uri;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserId;
 import android.util.Log;
 
 import java.lang.ref.WeakReference;
@@ -67,7 +68,7 @@
     public PackageInfo getPackageInfo(String packageName, int flags)
             throws NameNotFoundException {
         try {
-            PackageInfo pi = mPM.getPackageInfo(packageName, flags);
+            PackageInfo pi = mPM.getPackageInfo(packageName, flags, UserId.myUserId());
             if (pi != null) {
                 return pi;
             }
@@ -197,7 +198,7 @@
     public ApplicationInfo getApplicationInfo(String packageName, int flags)
             throws NameNotFoundException {
         try {
-            ApplicationInfo ai = mPM.getApplicationInfo(packageName, flags);
+            ApplicationInfo ai = mPM.getApplicationInfo(packageName, flags, UserId.myUserId());
             if (ai != null) {
                 return ai;
             }
@@ -212,7 +213,7 @@
     public ActivityInfo getActivityInfo(ComponentName className, int flags)
             throws NameNotFoundException {
         try {
-            ActivityInfo ai = mPM.getActivityInfo(className, flags);
+            ActivityInfo ai = mPM.getActivityInfo(className, flags, UserId.myUserId());
             if (ai != null) {
                 return ai;
             }
@@ -227,7 +228,7 @@
     public ActivityInfo getReceiverInfo(ComponentName className, int flags)
             throws NameNotFoundException {
         try {
-            ActivityInfo ai = mPM.getReceiverInfo(className, flags);
+            ActivityInfo ai = mPM.getReceiverInfo(className, flags, UserId.myUserId());
             if (ai != null) {
                 return ai;
             }
@@ -242,7 +243,7 @@
     public ServiceInfo getServiceInfo(ComponentName className, int flags)
             throws NameNotFoundException {
         try {
-            ServiceInfo si = mPM.getServiceInfo(className, flags);
+            ServiceInfo si = mPM.getServiceInfo(className, flags, UserId.myUserId());
             if (si != null) {
                 return si;
             }
@@ -257,7 +258,7 @@
     public ProviderInfo getProviderInfo(ComponentName className, int flags)
             throws NameNotFoundException {
         try {
-            ProviderInfo pi = mPM.getProviderInfo(className, flags);
+            ProviderInfo pi = mPM.getProviderInfo(className, flags, UserId.myUserId());
             if (pi != null) {
                 return pi;
             }
@@ -422,6 +423,7 @@
     @SuppressWarnings("unchecked")
     @Override
     public List<ApplicationInfo> getInstalledApplications(int flags) {
+        int userId = UserId.getUserId(Process.myUid());
         try {
             final List<ApplicationInfo> applicationInfos = new ArrayList<ApplicationInfo>();
             ApplicationInfo lastItem = null;
@@ -429,7 +431,7 @@
 
             do {
                 final String lastKey = lastItem != null ? lastItem.packageName : null;
-                slice = mPM.getInstalledApplications(flags, lastKey);
+                slice = mPM.getInstalledApplications(flags, lastKey, userId);
                 lastItem = slice.populateList(applicationInfos, ApplicationInfo.CREATOR);
             } while (!slice.isLastSlice());
 
@@ -445,7 +447,7 @@
             return mPM.resolveIntent(
                 intent,
                 intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                flags);
+                    flags, UserId.myUserId());
         } catch (RemoteException e) {
             throw new RuntimeException("Package manager has died", e);
         }
@@ -458,7 +460,8 @@
             return mPM.queryIntentActivities(
                 intent,
                 intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                flags);
+                flags,
+                UserId.myUserId());
         } catch (RemoteException e) {
             throw new RuntimeException("Package manager has died", e);
         }
@@ -490,7 +493,7 @@
         try {
             return mPM.queryIntentActivityOptions(caller, specifics,
                                                   specificTypes, intent, intent.resolveTypeIfNeeded(resolver),
-                                                  flags);
+                                                  flags, UserId.myUserId());
         } catch (RemoteException e) {
             throw new RuntimeException("Package manager has died", e);
         }
@@ -502,7 +505,8 @@
             return mPM.queryIntentReceivers(
                 intent,
                 intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                flags);
+                flags,
+                UserId.myUserId());
         } catch (RemoteException e) {
             throw new RuntimeException("Package manager has died", e);
         }
@@ -514,7 +518,8 @@
             return mPM.resolveService(
                 intent,
                 intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                flags);
+                flags,
+                UserId.myUserId());
         } catch (RemoteException e) {
             throw new RuntimeException("Package manager has died", e);
         }
@@ -526,7 +531,8 @@
             return mPM.queryIntentServices(
                 intent,
                 intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                flags);
+                flags,
+                UserId.myUserId());
         } catch (RemoteException e) {
             throw new RuntimeException("Package manager has died", e);
         }
@@ -536,7 +542,7 @@
     public ProviderInfo resolveContentProvider(String name,
                                                int flags) {
         try {
-            return mPM.resolveContentProvider(name, flags);
+            return mPM.resolveContentProvider(name, flags, UserId.myUserId());
         } catch (RemoteException e) {
             throw new RuntimeException("Package manager has died", e);
         }
@@ -1026,7 +1032,7 @@
     public void clearApplicationUserData(String packageName,
                                          IPackageDataObserver observer) {
         try {
-            mPM.clearApplicationUserData(packageName, observer);
+            mPM.clearApplicationUserData(packageName, observer, UserId.myUserId());
         } catch (RemoteException e) {
             // Should never happen!
         }
@@ -1139,7 +1145,7 @@
     public void setComponentEnabledSetting(ComponentName componentName,
                                            int newState, int flags) {
         try {
-            mPM.setComponentEnabledSetting(componentName, newState, flags);
+            mPM.setComponentEnabledSetting(componentName, newState, flags, UserId.myUserId());
         } catch (RemoteException e) {
             // Should never happen!
         }
@@ -1148,7 +1154,7 @@
     @Override
     public int getComponentEnabledSetting(ComponentName componentName) {
         try {
-            return mPM.getComponentEnabledSetting(componentName);
+            return mPM.getComponentEnabledSetting(componentName, UserId.myUserId());
         } catch (RemoteException e) {
             // Should never happen!
         }
@@ -1159,7 +1165,7 @@
     public void setApplicationEnabledSetting(String packageName,
                                              int newState, int flags) {
         try {
-            mPM.setApplicationEnabledSetting(packageName, newState, flags);
+            mPM.setApplicationEnabledSetting(packageName, newState, flags, UserId.myUserId());
         } catch (RemoteException e) {
             // Should never happen!
         }
@@ -1168,7 +1174,7 @@
     @Override
     public int getApplicationEnabledSetting(String packageName) {
         try {
-            return mPM.getApplicationEnabledSetting(packageName);
+            return mPM.getApplicationEnabledSetting(packageName, UserId.myUserId());
         } catch (RemoteException e) {
             // Should never happen!
         }
@@ -1209,6 +1215,18 @@
      * @hide
      */
     @Override
+    public UserInfo getUser(int userId) {
+        try {
+            return mPM.getUser(userId);
+        } catch (RemoteException re) {
+            return null;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
     public boolean removeUser(int id) {
         try {
             return mPM.removeUser(id);
@@ -1222,7 +1240,10 @@
      */
     @Override
     public void updateUserName(int id, String name) {
-        // TODO:
+        try {
+            mPM.updateUserName(id, name);
+        } catch (RemoteException re) {
+        }
     }
 
     /**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 7043a73..d758ecae 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -766,17 +766,18 @@
 
     @Override
     public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) {
-        File f = validateFilePath(name, true);
-        SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(f, factory);
-        setFilePermissionsFromMode(f.getPath(), mode, 0);
-        return db;
+        return openOrCreateDatabase(name, mode, factory, null);
     }
 
     @Override
     public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory,
             DatabaseErrorHandler errorHandler) {
         File f = validateFilePath(name, true);
-        SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(f.getPath(), factory, errorHandler);
+        int flags = SQLiteDatabase.CREATE_IF_NECESSARY;
+        if ((mode & MODE_ENABLE_WRITE_AHEAD_LOGGING) != 0) {
+            flags |= SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING;
+        }
+        SQLiteDatabase db = SQLiteDatabase.openDatabase(f.getPath(), factory, flags, errorHandler);
         setFilePermissionsFromMode(f.getPath(), mode, 0);
         return db;
     }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 31066b5..1d994d8 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -105,7 +105,7 @@
     public List getServices(int maxNum, int flags) throws RemoteException;
     public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState()
             throws RemoteException;
-    public void moveTaskToFront(int task, int flags) throws RemoteException;
+    public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException;
     public void moveTaskToBack(int task) throws RemoteException;
     public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException;
     public void moveTaskBackwards(int task) throws RemoteException;
@@ -216,9 +216,10 @@
     public void enterSafeMode() throws RemoteException;
     
     public void noteWakeupAlarm(IIntentSender sender) throws RemoteException;
-    
+
     public boolean killPids(int[] pids, String reason, boolean secure) throws RemoteException;
-    
+    public boolean killProcessesBelowForeground(String reason) throws RemoteException;
+
     // Special low-level communication with activity manager.
     public void startRunning(String pkg, String cls, String action,
             String data) throws RemoteException;
@@ -573,4 +574,5 @@
     int GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+140;
     int REMOVE_CONTENT_PROVIDER_EXTERNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+141;
     int GET_MY_MEMORY_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+142;
+    int KILL_PROCESSES_BELOW_FOREGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+143;
 }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 16299de..e4f7950 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1381,6 +1381,7 @@
         }
         try {
             intent.setAllowFds(false);
+            intent.migrateExtraStreamToClipData();
             int result = ActivityManagerNative.getDefault()
                 .startActivity(whoThread, intent,
                         intent.resolveTypeIfNeeded(who.getContentResolver()),
@@ -1479,6 +1480,7 @@
         }
         try {
             intent.setAllowFds(false);
+            intent.migrateExtraStreamToClipData();
             int result = ActivityManagerNative.getDefault()
                 .startActivity(whoThread, intent,
                         intent.resolveTypeIfNeeded(who.getContentResolver()),
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index de9470e..5340fbb 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -194,7 +194,7 @@
         ApplicationInfo ai = null;
         try {
             ai = ActivityThread.getPackageManager().getApplicationInfo(packageName,
-                    PackageManager.GET_SHARED_LIBRARY_FILES);
+                    PackageManager.GET_SHARED_LIBRARY_FILES, UserId.myUserId());
         } catch (RemoteException e) {
             throw new AssertionError(e);
         }
@@ -351,7 +351,7 @@
         IPackageManager pm = ActivityThread.getPackageManager();
         android.content.pm.PackageInfo pi;
         try {
-            pi = pm.getPackageInfo(mPackageName, 0);
+            pi = pm.getPackageInfo(mPackageName, 0, UserId.myUserId());
         } catch (RemoteException e) {
             throw new AssertionError(e);
         }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 5325af0..bbb6a4e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -23,9 +23,11 @@
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.IntProperty;
 import android.view.View;
 import android.widget.ProgressBar;
 import android.widget.RemoteViews;
@@ -185,6 +187,13 @@
      */
     public RemoteViews contentView;
 
+
+    /**
+     * The view that will represent this notification in the pop-up "intruder alert" dialog.
+     * @hide
+     */
+    public RemoteViews intruderView;
+
     /**
      * The bitmap that may escape the bounds of the panel and bar.
      */
@@ -418,6 +427,64 @@
     private Bundle extras;
 
     /**
+     * Structure to encapsulate an "action", including title and icon, that can be attached to a Notification.
+     * @hide
+     */
+    private static class Action implements Parcelable {
+        public int icon;
+        public CharSequence title;
+        public PendingIntent actionIntent;
+        @SuppressWarnings("unused")
+        public Action() { }
+        private Action(Parcel in) {
+            icon = in.readInt();
+            title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            if (in.readInt() == 1) {
+                actionIntent = PendingIntent.CREATOR.createFromParcel(in);
+            }
+        }
+        public Action(int icon_, CharSequence title_, PendingIntent intent_) {
+            this.icon = icon_;
+            this.title = title_;
+            this.actionIntent = intent_;
+        }
+        @Override
+        public Action clone() {
+            return new Action(
+                this.icon,
+                this.title.toString(),
+                this.actionIntent // safe to alias
+            );
+        }
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(icon);
+            TextUtils.writeToParcel(title, out, flags);
+            if (actionIntent != null) {
+                out.writeInt(1);
+                actionIntent.writeToParcel(out, flags);
+            } else {
+                out.writeInt(0);
+            }
+        }
+        public static final Parcelable.Creator<Action> CREATOR
+        = new Parcelable.Creator<Action>() {
+            public Action createFromParcel(Parcel in) {
+                return new Action(in);
+            }
+            public Action[] newArray(int size) {
+                return new Action[size];
+            }
+        };
+    }
+
+    private Action[] actions;
+
+    /**
      * Constructs a Notification object with default values.
      * You might want to consider using {@link Builder} instead.
      */
@@ -506,12 +573,17 @@
         }
 
         priority = parcel.readInt();
-        
+
         kind = parcel.createStringArray(); // may set kind to null
 
         if (parcel.readInt() != 0) {
             extras = parcel.readBundle();
         }
+
+        actions = parcel.createTypedArray(Action.CREATOR);
+        if (parcel.readInt() != 0) {
+            intruderView = RemoteViews.CREATOR.createFromParcel(parcel);
+        }
     }
 
     @Override
@@ -571,6 +643,14 @@
 
         }
 
+        that.actions = new Action[this.actions.length];
+        for(int i=0; i<this.actions.length; i++) {
+            that.actions[i] = this.actions[i].clone();
+        }
+        if (this.intruderView != null) {
+            that.intruderView = this.intruderView.clone();
+        }
+
         return that;
     }
 
@@ -658,6 +738,15 @@
         } else {
             parcel.writeInt(0);
         }
+
+        parcel.writeTypedArray(actions, 0);
+
+        if (intruderView != null) {
+            parcel.writeInt(1);
+            intruderView.writeToParcel(parcel, 0);
+        } else {
+            parcel.writeInt(0);
+        }
     }
 
     /**
@@ -769,7 +858,14 @@
                 sb.append(this.kind[i]);
             }
         }
-        sb.append("])");
+        sb.append("]");
+        if (actions != null) {
+            sb.append(" ");
+            sb.append(actions.length);
+            sb.append(" action");
+            if (actions.length > 1) sb.append("s");
+        }
+        sb.append(")");
         return sb.toString();
     }
 
@@ -821,6 +917,9 @@
         private ArrayList<String> mKindList = new ArrayList<String>(1);
         private Bundle mExtras;
         private int mPriority;
+        private ArrayList<Action> mActions = new ArrayList<Action>(3);
+        private boolean mCanHasIntruder;
+        private boolean mIntruderActionsShowText;
 
         /**
          * Constructs a new Builder with the defaults:
@@ -1203,6 +1302,51 @@
             return this;
         }
 
+        /**
+         * Add an action to this notification. Actions are typically displayed by
+         * the system as a button adjacent to the notification content.
+         *
+         * @param icon Resource ID of a drawable that represents the action.
+         * @param title Text describing the action.
+         * @param intent PendingIntent to be fired when the action is invoked.
+         */
+        public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
+            mActions.add(new Action(icon, title, intent));
+            return this;
+        }
+
+        /**
+         * Specify whether this notification should pop up as an
+         * "intruder alert" (a small window that shares the screen with the
+         * current activity). This sort of notification is (as the name implies)
+         * very intrusive, so use it sparingly for notifications that require
+         * the user's attention.
+         *
+         * Notes:
+         * <ul>
+         * <li>Intruder alerts only show when the screen is on.</li>
+         * <li>Intruder alerts take precedence over fullScreenIntents.</li>
+         * </ul>
+         *
+         * @param intrude Whether to pop up an intruder alert (default false).
+         */
+        public Builder setUsesIntruderAlert(boolean intrude) {
+            mCanHasIntruder = intrude;
+            return this;
+        }
+
+        /**
+         * Control text on intruder alert action buttons. By default, action
+         * buttons in intruders do not show textual labels.
+         * 
+         * @param showActionText Whether to show text labels beneath action
+         *            icons (default false).
+         */
+        public Builder setIntruderActionsShowText(boolean showActionText) {
+            mIntruderActionsShowText = showActionText;
+            return this;
+        }
+
         private void setFlag(int mask, boolean value) {
             if (value) {
                 mFlags |= mask;
@@ -1284,6 +1428,45 @@
             }
         }
 
+        private RemoteViews makeIntruderView(boolean showLabels) {
+            RemoteViews intruderView = new RemoteViews(mContext.getPackageName(),
+                    R.layout.notification_intruder_content);
+            if (mLargeIcon != null) {
+                intruderView.setImageViewBitmap(R.id.icon, mLargeIcon);
+                intruderView.setViewVisibility(R.id.icon, View.VISIBLE);
+            } else if (mSmallIcon != 0) {
+                intruderView.setImageViewResource(R.id.icon, mSmallIcon);
+                intruderView.setViewVisibility(R.id.icon, View.VISIBLE);
+            } else {
+                intruderView.setViewVisibility(R.id.icon, View.GONE);
+            }
+            if (mContentTitle != null) {
+                intruderView.setTextViewText(R.id.title, mContentTitle);
+            }
+            if (mContentText != null) {
+                intruderView.setTextViewText(R.id.text, mContentText);
+            }
+            if (mActions.size() > 0) {
+                intruderView.setViewVisibility(R.id.actions, View.VISIBLE);
+                int N = mActions.size();
+                if (N>3) N=3;
+                final int[] BUTTONS = { R.id.action0, R.id.action1, R.id.action2 };
+                for (int i=0; i<N; i++) {
+                    final Action action = mActions.get(i);
+                    final int buttonId = BUTTONS[i];
+
+                    intruderView.setViewVisibility(buttonId, View.VISIBLE);
+                    intruderView.setTextViewText(buttonId, showLabels ? action.title : null);
+                    intruderView.setTextViewCompoundDrawables(buttonId, 0, action.icon, 0, 0);
+                    intruderView.setContentDescription(buttonId, action.title);
+                    intruderView.setOnClickPendingIntent(buttonId, action.actionIntent);
+                }
+            } else {
+                intruderView.setViewVisibility(R.id.actions, View.GONE);
+            }
+            return intruderView;
+        }
+
         /**
          * Combine all of the options that have been set and return a new {@link Notification}
          * object.
@@ -1309,6 +1492,9 @@
             n.ledOffMS = mLedOffMs;
             n.defaults = mDefaults;
             n.flags = mFlags;
+            if (mCanHasIntruder) {
+                n.intruderView = makeIntruderView(mIntruderActionsShowText);
+            }
             if (mLedOnMs != 0 && mLedOffMs != 0) {
                 n.flags |= FLAG_SHOW_LIGHTS;
             }
@@ -1323,6 +1509,10 @@
             }
             n.priority = mPriority;
             n.extras = mExtras != null ? new Bundle(mExtras) : null;
+            if (mActions.size() > 0) {
+                n.actions = new Action[mActions.size()];
+                mActions.toArray(n.actions);
+            }
             return n;
         }
     }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 741a6e9..2902504 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -99,6 +99,16 @@
     public static final int MODE_MULTI_PROCESS = 0x0004;
 
     /**
+     * Database open flag: when set, the database is opened with write-ahead
+     * logging enabled by default.
+     *
+     * @see #openOrCreateDatabase(String, int, CursorFactory)
+     * @see #openOrCreateDatabase(String, int, CursorFactory, DatabaseErrorHandler)
+     * @see SQLiteDatabase#enableWriteAheadLogging
+     */
+    public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 0x0008;
+
+    /**
      * Flag for {@link #bindService}: automatically create the service as long
      * as the binding exists.  Note that while this will create the service,
      * its {@link android.app.Service#onStartCommand}
@@ -691,6 +701,7 @@
      * @param mode Operating mode.  Use 0 or {@link #MODE_PRIVATE} for the
      *     default operation, {@link #MODE_WORLD_READABLE}
      *     and {@link #MODE_WORLD_WRITEABLE} to control permissions.
+     *     Use {@link #MODE_ENABLE_WRITE_AHEAD_LOGGING} to enable write-ahead logging by default.
      * @param factory An optional factory class that is called to instantiate a
      *     cursor when query is called.
      *
@@ -700,6 +711,7 @@
      * @see #MODE_PRIVATE
      * @see #MODE_WORLD_READABLE
      * @see #MODE_WORLD_WRITEABLE
+     * @see #MODE_ENABLE_WRITE_AHEAD_LOGGING
      * @see #deleteDatabase
      */
     public abstract SQLiteDatabase openOrCreateDatabase(String name,
@@ -716,6 +728,7 @@
      * @param mode Operating mode.  Use 0 or {@link #MODE_PRIVATE} for the
      *     default operation, {@link #MODE_WORLD_READABLE}
      *     and {@link #MODE_WORLD_WRITEABLE} to control permissions.
+     *     Use {@link #MODE_ENABLE_WRITE_AHEAD_LOGGING} to enable write-ahead logging by default.
      * @param factory An optional factory class that is called to instantiate a
      *     cursor when query is called.
      * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
@@ -726,6 +739,7 @@
      * @see #MODE_PRIVATE
      * @see #MODE_WORLD_READABLE
      * @see #MODE_WORLD_WRITEABLE
+     * @see #MODE_ENABLE_WRITE_AHEAD_LOGGING
      * @see #deleteDatabase
      */
     public abstract SQLiteDatabase openOrCreateDatabase(String name,
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 6cf5b43..2a9f1af 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2141,6 +2141,30 @@
     public static final String ACTION_PRE_BOOT_COMPLETED =
             "android.intent.action.PRE_BOOT_COMPLETED";
 
+    /**
+     * Broadcast sent to the system when a user is added. Carries an extra EXTRA_USERID that has the
+     * userid of the new user.
+     * @hide
+     */
+    public static final String ACTION_USER_ADDED =
+            "android.intent.action.USER_ADDED";
+
+    /**
+     * Broadcast sent to the system when a user is removed. Carries an extra EXTRA_USERID that has
+     * the userid of the user.
+     * @hide
+     */
+    public static final String ACTION_USER_REMOVED =
+            "android.intent.action.USER_REMOVED";
+
+    /**
+     * Broadcast sent to the system when the user switches. Carries an extra EXTRA_USERID that has
+     * the userid of the user to become the current one.
+     * @hide
+     */
+    public static final String ACTION_USER_SWITCHED =
+            "android.intent.action.USER_SWITCHED";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Standard intent categories (see addCategory()).
@@ -2682,6 +2706,13 @@
     public static final String EXTRA_LOCAL_ONLY =
         "android.intent.extra.LOCAL_ONLY";
 
+    /**
+     * The userid carried with broadcast intents related to addition, removal and switching of users
+     * - {@link #ACTION_USER_ADDED}, {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}.
+     * @hide
+     */
+    public static final String EXTRA_USERID =
+            "android.intent.extra.user_id";
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Intent flags (see mFlags variable).
@@ -6467,4 +6498,56 @@
         }
         return type;
     }
+
+    /**
+     * Migrate any {@link #EXTRA_STREAM} in {@link #ACTION_SEND} and
+     * {@link #ACTION_SEND_MULTIPLE} to {@link ClipData}.
+     *
+     * @hide
+     */
+    public void migrateExtraStreamToClipData() {
+        // Refuse to touch if extras already parcelled
+        if (mExtras != null && mExtras.isParcelled()) return;
+
+        // Bail when someone already gave us ClipData
+        if (getClipData() != null) return;
+
+        final String action = getAction();
+        if (ACTION_SEND.equals(action)) {
+            final Uri stream;
+            try {
+                stream = getParcelableExtra(EXTRA_STREAM);
+            } catch (ClassCastException e) {
+                return;
+            }
+            if (stream != null) {
+                final ClipData clipData = new ClipData(
+                        null, new String[] { getType() }, new ClipData.Item(stream));
+
+                setClipData(clipData);
+                addFlags(FLAG_GRANT_READ_URI_PERMISSION);
+            }
+
+        } else if (ACTION_SEND_MULTIPLE.equals(action)) {
+            final ArrayList<Uri> streams;
+            try {
+                streams = getParcelableArrayListExtra(EXTRA_STREAM);
+            } catch (ClassCastException e) {
+                return;
+            }
+            if (streams != null && streams.size() > 0) {
+                final Uri firstStream = streams.get(0);
+                final ClipData clipData = new ClipData(
+                        null, new String[] { getType() }, new ClipData.Item(firstStream));
+
+                final int size = streams.size();
+                for (int i = 1; i < size; i++) {
+                    clipData.addItem(new ClipData.Item(streams.get(i)));
+                }
+
+                setClipData(clipData);
+                addFlags(FLAG_GRANT_READ_URI_PERMISSION);
+            }
+        }
+    }
 }
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index b7dfe92..06dfe90 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -326,6 +326,13 @@
         }
     };
 
+    private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            onUserRemoved(intent);
+        }
+    };
+
     private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM";
     private final SyncHandler mSyncHandler;
 
@@ -420,6 +427,10 @@
         intentFilter.setPriority(100);
         context.registerReceiver(mShutdownIntentReceiver, intentFilter);
 
+        intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+        mContext.registerReceiver(mUserIntentReceiver, intentFilter);
+
         if (!factoryTest) {
             mNotificationMgr = (NotificationManager)
                 context.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -905,6 +916,18 @@
         }
     }
 
+    private void onUserRemoved(Intent intent) {
+        int userId = intent.getIntExtra(Intent.EXTRA_USERID, -1);
+        if (userId == -1) return;
+
+        // Clean up the storage engine database
+        mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId);
+        onAccountsUpdated(null);
+        synchronized (mSyncQueue) {
+            mSyncQueue.removeUser(userId);
+        }
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java
index 06da6fa..c18c86bf 100644
--- a/core/java/android/content/SyncQueue.java
+++ b/core/java/android/content/SyncQueue.java
@@ -117,6 +117,19 @@
         return true;
     }
 
+    public void removeUser(int userId) {
+        ArrayList<SyncOperation> opsToRemove = new ArrayList<SyncOperation>();
+        for (SyncOperation op : mOperationsMap.values()) {
+            if (op.userId == userId) {
+                opsToRemove.add(op);
+            }
+        }
+
+        for (SyncOperation op : opsToRemove) {
+            remove(op);
+        }
+    }
+
     /**
      * Remove the specified operation if it is in the queue.
      * @param operation the operation to remove
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 9bd1940..56fd5f8 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -49,8 +49,8 @@
  *  {@hide}
  */
 interface IPackageManager {
-    PackageInfo getPackageInfo(String packageName, int flags);
-    int getPackageUid(String packageName);
+    PackageInfo getPackageInfo(String packageName, int flags, int userId);
+    int getPackageUid(String packageName, int userId);
     int[] getPackageGids(String packageName);
     
     String[] currentToCanonicalPackageNames(in String[] names);
@@ -64,15 +64,15 @@
     
     List<PermissionGroupInfo> getAllPermissionGroups(int flags);
     
-    ApplicationInfo getApplicationInfo(String packageName, int flags);
+    ApplicationInfo getApplicationInfo(String packageName, int flags ,int userId);
 
-    ActivityInfo getActivityInfo(in ComponentName className, int flags);
+    ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId);
 
-    ActivityInfo getReceiverInfo(in ComponentName className, int flags);
+    ActivityInfo getReceiverInfo(in ComponentName className, int flags, int userId);
 
-    ServiceInfo getServiceInfo(in ComponentName className, int flags);
+    ServiceInfo getServiceInfo(in ComponentName className, int flags, int userId);
 
-    ProviderInfo getProviderInfo(in ComponentName className, int flags);
+    ProviderInfo getProviderInfo(in ComponentName className, int flags, int userId);
 
     int checkPermission(String permName, String pkgName);
     
@@ -98,24 +98,24 @@
     
     int getUidForSharedUser(String sharedUserName);
     
-    ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags);
+    ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags, int userId);
 
     List<ResolveInfo> queryIntentActivities(in Intent intent, 
-            String resolvedType, int flags);
+            String resolvedType, int flags, int userId);
 
     List<ResolveInfo> queryIntentActivityOptions(
             in ComponentName caller, in Intent[] specifics,
             in String[] specificTypes, in Intent intent,
-            String resolvedType, int flags);
+            String resolvedType, int flags, int userId);
 
     List<ResolveInfo> queryIntentReceivers(in Intent intent,
-            String resolvedType, int flags);
+            String resolvedType, int flags, int userId);
 
     ResolveInfo resolveService(in Intent intent,
-            String resolvedType, int flags);
+            String resolvedType, int flags, int userId);
 
     List<ResolveInfo> queryIntentServices(in Intent intent,
-            String resolvedType, int flags);
+            String resolvedType, int flags, int userId);
 
     /**
      * This implements getInstalledPackages via a "last returned row"
@@ -131,7 +131,7 @@
      * limit that kicks in when flags are included that bloat up the data
      * returned.
      */
-    ParceledListSlice getInstalledApplications(int flags, in String lastRead);
+    ParceledListSlice getInstalledApplications(int flags, in String lastRead, int userId);
 
     /**
      * Retrieve all applications that are marked as persistent.
@@ -141,7 +141,7 @@
      */
     List<ApplicationInfo> getPersistentApplications(int flags);
 
-    ProviderInfo resolveContentProvider(String name, int flags);
+    ProviderInfo resolveContentProvider(String name, int flags, int userId);
 
     /**
      * Retrieve sync information for all content providers.
@@ -212,28 +212,28 @@
      * As per {@link android.content.pm.PackageManager#setComponentEnabledSetting}.
      */
     void setComponentEnabledSetting(in ComponentName componentName,
-            in int newState, in int flags);
+            in int newState, in int flags, int userId);
 
     /**
      * As per {@link android.content.pm.PackageManager#getComponentEnabledSetting}.
      */
-    int getComponentEnabledSetting(in ComponentName componentName);
+    int getComponentEnabledSetting(in ComponentName componentName, int userId);
     
     /**
      * As per {@link android.content.pm.PackageManager#setApplicationEnabledSetting}.
      */
-    void setApplicationEnabledSetting(in String packageName, in int newState, int flags);
+    void setApplicationEnabledSetting(in String packageName, in int newState, int flags, int userId);
     
     /**
      * As per {@link android.content.pm.PackageManager#getApplicationEnabledSetting}.
      */
-    int getApplicationEnabledSetting(in String packageName);
+    int getApplicationEnabledSetting(in String packageName, int userId);
     
     /**
      * Set whether the given package should be considered stopped, making
      * it not visible to implicit intents that filter out stopped packages.
      */
-    void setPackageStoppedState(String packageName, boolean stopped);
+    void setPackageStoppedState(String packageName, boolean stopped, int userId);
 
     /**
      * Free storage by deleting LRU sorted list of cache files across
@@ -296,7 +296,7 @@
      * files need to be deleted
      * @param observer a callback used to notify when the operation is completed.
      */
-    void clearApplicationUserData(in String packageName, IPackageDataObserver observer);
+    void clearApplicationUserData(in String packageName, IPackageDataObserver observer, int userId);
     
    /**
      * Get package statistics including the code, data and cache size for
@@ -358,6 +358,7 @@
 
     UserInfo createUser(in String name, int flags);
     boolean removeUser(int userId);
+    void updateUserName(int userId, String name);
 
     void installPackageWithVerification(in Uri packageURI, in IPackageInstallObserver observer,
             int flags, in String installerPackageName, in Uri verificationURI,
@@ -370,6 +371,7 @@
     boolean isFirstBoot();
 
     List<UserInfo> getUsers();
+    UserInfo getUser(int userId);
 
     void setPermissionEnforcement(String permission, int enforcement);
     int getPermissionEnforcement(String permission);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 55426b8..b06b4a5 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2153,7 +2153,8 @@
         if ((flags & GET_SIGNATURES) != 0) {
             packageParser.collectCertificates(pkg, 0);
         }
-        return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null);
+        return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, false,
+                COMPONENT_ENABLED_STATE_DEFAULT);
     }
 
     /**
@@ -2637,10 +2638,17 @@
     public abstract void updateUserFlags(int id, int flags);
 
     /**
-     * Returns the device identity that verifiers can use to associate their
-     * scheme to a particular device. This should not be used by anything other
-     * than a package verifier.
-     *
+     * Returns the details for the user specified by userId.
+     * @param userId the user id of the user
+     * @return UserInfo for the specified user, or null if no such user exists.
+     * @hide
+     */
+    public abstract UserInfo getUser(int userId);
+
+    /**
+     * Returns the device identity that verifiers can use to associate their scheme to a particular
+     * device. This should not be used by anything other than a package verifier.
+     * 
      * @return identity that uniquely identifies current device
      * @hide
      */
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 207f077..eb8536f 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -230,6 +230,15 @@
         return name.endsWith(".apk");
     }
 
+    public static PackageInfo generatePackageInfo(PackageParser.Package p,
+            int gids[], int flags, long firstInstallTime, long lastUpdateTime,
+            HashSet<String> grantedPermissions) {
+
+        return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime,
+                grantedPermissions, false, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+                UserId.getCallingUserId());
+    }
+
     /**
      * Generate and return the {@link PackageInfo} for a parsed package.
      *
@@ -238,9 +247,15 @@
      */
     public static PackageInfo generatePackageInfo(PackageParser.Package p,
             int gids[], int flags, long firstInstallTime, long lastUpdateTime,
-            HashSet<String> grantedPermissions) {
+            HashSet<String> grantedPermissions, boolean stopped, int enabledState) {
 
-        final int userId = Binder.getOrigCallingUser();
+        return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime,
+                grantedPermissions, stopped, enabledState, UserId.getCallingUserId());
+    }
+
+    public static PackageInfo generatePackageInfo(PackageParser.Package p,
+            int gids[], int flags, long firstInstallTime, long lastUpdateTime,
+            HashSet<String> grantedPermissions, boolean stopped, int enabledState, int userId) {
 
         PackageInfo pi = new PackageInfo();
         pi.packageName = p.packageName;
@@ -248,7 +263,7 @@
         pi.versionName = p.mVersionName;
         pi.sharedUserId = p.mSharedUserId;
         pi.sharedUserLabel = p.mSharedUserLabel;
-        pi.applicationInfo = generateApplicationInfo(p, flags);
+        pi.applicationInfo = generateApplicationInfo(p, flags, stopped, enabledState, userId);
         pi.installLocation = p.installLocation;
         pi.firstInstallTime = firstInstallTime;
         pi.lastUpdateTime = lastUpdateTime;
@@ -284,7 +299,7 @@
                     if (activity.info.enabled
                         || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
                         pi.activities[j++] = generateActivityInfo(p.activities.get(i), flags,
-                                userId);
+                                stopped, enabledState, userId);
                     }
                 }
             }
@@ -305,7 +320,8 @@
                     final Activity activity = p.receivers.get(i);
                     if (activity.info.enabled
                         || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
-                        pi.receivers[j++] = generateActivityInfo(p.receivers.get(i), flags, userId);
+                        pi.receivers[j++] = generateActivityInfo(p.receivers.get(i), flags,
+                                stopped, enabledState, userId);
                     }
                 }
             }
@@ -326,7 +342,8 @@
                     final Service service = p.services.get(i);
                     if (service.info.enabled
                         || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
-                        pi.services[j++] = generateServiceInfo(p.services.get(i), flags, userId);
+                        pi.services[j++] = generateServiceInfo(p.services.get(i), flags, stopped,
+                                enabledState, userId);
                     }
                 }
             }
@@ -347,7 +364,8 @@
                     final Provider provider = p.providers.get(i);
                     if (provider.info.enabled
                         || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
-                        pi.providers[j++] = generateProviderInfo(p.providers.get(i), flags, userId);
+                        pi.providers[j++] = generateProviderInfo(p.providers.get(i), flags, stopped,
+                                enabledState, userId);
                     }
                 }
             }
@@ -3062,11 +3080,11 @@
         // For use by package manager to keep track of where it has done dexopt.
         public boolean mDidDexOpt;
         
-        // User set enabled state.
-        public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
-
-        // Whether the package has been stopped.
-        public boolean mSetStopped = false;
+        // // User set enabled state.
+        // public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        //
+        // // Whether the package has been stopped.
+        // public boolean mSetStopped = false;
 
         // Additional data supplied by callers.
         public Object mExtras;
@@ -3331,9 +3349,9 @@
         }
     }
 
-    private static boolean copyNeeded(int flags, Package p, Bundle metaData) {
-        if (p.mSetEnabled != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
-            boolean enabled = p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+    private static boolean copyNeeded(int flags, Package p, int enabledState, Bundle metaData) {
+        if (enabledState != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
+            boolean enabled = enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
             if (p.applicationInfo.enabled != enabled) {
                 return true;
             }
@@ -3349,23 +3367,32 @@
         return false;
     }
 
-    public static ApplicationInfo generateApplicationInfo(Package p, int flags) {
-        return generateApplicationInfo(p, flags, UserId.getUserId(Binder.getCallingUid()));
+    public static ApplicationInfo generateApplicationInfo(Package p, int flags, boolean stopped,
+            int enabledState) {
+        return generateApplicationInfo(p, flags, stopped, enabledState, UserId.getCallingUserId());
     }
 
-    public static ApplicationInfo generateApplicationInfo(Package p, int flags, int userId) {
+    public static ApplicationInfo generateApplicationInfo(Package p, int flags,
+            boolean stopped, int enabledState, int userId) {
         if (p == null) return null;
-        if (!copyNeeded(flags, p, null) && userId == 0) {
+        if (!copyNeeded(flags, p, enabledState, null) && userId == 0) {
             // CompatibilityMode is global state. It's safe to modify the instance
             // of the package.
             if (!sCompatibilityModeEnabled) {
                 p.applicationInfo.disableCompatibilityMode();
             }
-            if (p.mSetStopped) {
+            if (stopped) {
                 p.applicationInfo.flags |= ApplicationInfo.FLAG_STOPPED;
             } else {
                 p.applicationInfo.flags &= ~ApplicationInfo.FLAG_STOPPED;
             }
+            if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+                p.applicationInfo.enabled = true;
+            } else if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+                    || enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+                p.applicationInfo.enabled = false;
+            }
+            p.applicationInfo.enabledSetting = enabledState;
             return p.applicationInfo;
         }
 
@@ -3384,18 +3411,18 @@
         if (!sCompatibilityModeEnabled) {
             ai.disableCompatibilityMode();
         }
-        if (p.mSetStopped) {
+        if (stopped) {
             p.applicationInfo.flags |= ApplicationInfo.FLAG_STOPPED;
         } else {
             p.applicationInfo.flags &= ~ApplicationInfo.FLAG_STOPPED;
         }
-        if (p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+        if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
             ai.enabled = true;
-        } else if (p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
-                || p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+        } else if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+                || enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
             ai.enabled = false;
         }
-        ai.enabledSetting = p.mSetEnabled;
+        ai.enabledSetting = enabledState;
         return ai;
     }
 
@@ -3442,15 +3469,16 @@
         }
     }
 
-    public static final ActivityInfo generateActivityInfo(Activity a, int flags, int userId) {
+    public static final ActivityInfo generateActivityInfo(Activity a, int flags, boolean stopped,
+            int enabledState, int userId) {
         if (a == null) return null;
-        if (!copyNeeded(flags, a.owner, a.metaData) && userId == 0) {
+        if (!copyNeeded(flags, a.owner, enabledState, a.metaData) && userId == 0) {
             return a.info;
         }
         // Make shallow copies so we can store the metadata safely
         ActivityInfo ai = new ActivityInfo(a.info);
         ai.metaData = a.metaData;
-        ai.applicationInfo = generateApplicationInfo(a.owner, flags, userId);
+        ai.applicationInfo = generateApplicationInfo(a.owner, flags, stopped, enabledState, userId);
         return ai;
     }
 
@@ -3475,16 +3503,17 @@
         }
     }
 
-    public static final ServiceInfo generateServiceInfo(Service s, int flags, int userId) {
+    public static final ServiceInfo generateServiceInfo(Service s, int flags, boolean stopped,
+            int enabledState, int userId) {
         if (s == null) return null;
-        if (!copyNeeded(flags, s.owner, s.metaData)
+        if (!copyNeeded(flags, s.owner, enabledState, s.metaData)
                 && userId == UserId.getUserId(s.info.applicationInfo.uid)) {
             return s.info;
         }
         // Make shallow copies so we can store the metadata safely
         ServiceInfo si = new ServiceInfo(s.info);
         si.metaData = s.metaData;
-        si.applicationInfo = generateApplicationInfo(s.owner, flags, userId);
+        si.applicationInfo = generateApplicationInfo(s.owner, flags, stopped, enabledState, userId);
         return si;
     }
 
@@ -3517,9 +3546,10 @@
         }
     }
 
-    public static final ProviderInfo generateProviderInfo(Provider p, int flags, int userId) {
+    public static final ProviderInfo generateProviderInfo(Provider p, int flags, boolean stopped,
+            int enabledState, int userId) {
         if (p == null) return null;
-        if (!copyNeeded(flags, p.owner, p.metaData)
+        if (!copyNeeded(flags, p.owner, enabledState, p.metaData)
                 && ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0
                         || p.info.uriPermissionPatterns == null)
                 && userId == 0) {
@@ -3531,7 +3561,7 @@
         if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
             pi.uriPermissionPatterns = null;
         }
-        pi.applicationInfo = generateApplicationInfo(p.owner, flags, userId);
+        pi.applicationInfo = generateApplicationInfo(p.owner, flags, stopped, enabledState, userId);
         return pi;
     }
 
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index e2c222b..254f652 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -211,8 +211,8 @@
                 SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME);
 
         setPageSize();
-        setSyncModeFromConfiguration();
-        setJournalModeFromConfiguration();
+        setForeignKeyModeFromConfiguration();
+        setWalModeFromConfiguration();
         setJournalSizeLimit();
         setAutoCheckpointInterval();
         setLocaleFromConfiguration();
@@ -268,31 +268,82 @@
         }
     }
 
-    private void setSyncModeFromConfiguration() {
-        if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
-            final String newValue = mConfiguration.syncMode;
-            String value = executeForString("PRAGMA synchronous", null, null);
-            if (!value.equalsIgnoreCase(newValue)) {
-                execute("PRAGMA synchronous=" + newValue, null, null);
+    private void setForeignKeyModeFromConfiguration() {
+        if (!mIsReadOnlyConnection) {
+            final long newValue = mConfiguration.foreignKeyConstraintsEnabled ? 1 : 0;
+            long value = executeForLong("PRAGMA foreign_keys", null, null);
+            if (value != newValue) {
+                execute("PRAGMA foreign_keys=" + newValue, null, null);
             }
         }
     }
 
-    private void setJournalModeFromConfiguration() {
+    private void setWalModeFromConfiguration() {
         if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
-            final String newValue = mConfiguration.journalMode;
-            String value = executeForString("PRAGMA journal_mode", null, null);
-            if (!value.equalsIgnoreCase(newValue)) {
-                value = executeForString("PRAGMA journal_mode=" + newValue, null, null);
-                if (!value.equalsIgnoreCase(newValue)) {
-                    Log.e(TAG, "setting journal_mode to " + newValue
-                            + " failed for db: " + mConfiguration.label
-                            + " (on pragma set journal_mode, sqlite returned:" + value);
-                }
+            if ((mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) {
+                setJournalMode("WAL");
+                setSyncMode(SQLiteGlobal.getWALSyncMode());
+            } else {
+                setJournalMode(SQLiteGlobal.getDefaultJournalMode());
+                setSyncMode(SQLiteGlobal.getDefaultSyncMode());
             }
         }
     }
 
+    private void setSyncMode(String newValue) {
+        String value = executeForString("PRAGMA synchronous", null, null);
+        if (!canonicalizeSyncMode(value).equalsIgnoreCase(
+                canonicalizeSyncMode(newValue))) {
+            execute("PRAGMA synchronous=" + newValue, null, null);
+        }
+    }
+
+    private static String canonicalizeSyncMode(String value) {
+        if (value.equals("0")) {
+            return "OFF";
+        } else if (value.equals("1")) {
+            return "NORMAL";
+        } else if (value.equals("2")) {
+            return "FULL";
+        }
+        return value;
+    }
+
+    private void setJournalMode(String newValue) {
+        String value = executeForString("PRAGMA journal_mode", null, null);
+        if (!value.equalsIgnoreCase(newValue)) {
+            try {
+                String result = executeForString("PRAGMA journal_mode=" + newValue, null, null);
+                if (result.equalsIgnoreCase(newValue)) {
+                    return;
+                }
+                // PRAGMA journal_mode silently fails and returns the original journal
+                // mode in some cases if the journal mode could not be changed.
+            } catch (SQLiteDatabaseLockedException ex) {
+                // This error (SQLITE_BUSY) occurs if one connection has the database
+                // open in WAL mode and another tries to change it to non-WAL.
+            }
+            // Because we always disable WAL mode when a database is first opened
+            // (even if we intend to re-enable it), we can encounter problems if
+            // there is another open connection to the database somewhere.
+            // This can happen for a variety of reasons such as an application opening
+            // the same database in multiple processes at the same time or if there is a
+            // crashing content provider service that the ActivityManager has
+            // removed from its registry but whose process hasn't quite died yet
+            // by the time it is restarted in a new process.
+            //
+            // If we don't change the journal mode, nothing really bad happens.
+            // In the worst case, an application that enables WAL might not actually
+            // get it, although it can still use connection pooling.
+            Log.w(TAG, "Could not change the database journal mode of '"
+                    + mConfiguration.label + "' from '" + value + "' to '" + newValue
+                    + "' because the database is locked.  This usually means that "
+                    + "there are other open connections to the database which prevents "
+                    + "the database from enabling or disabling write-ahead logging mode.  "
+                    + "Proceeding without changing the journal mode.");
+        }
+    }
+
     private void setLocaleFromConfiguration() {
         if ((mConfiguration.openFlags & SQLiteDatabase.NO_LOCALIZED_COLLATORS) != 0) {
             return;
@@ -349,10 +400,10 @@
         }
 
         // Remember what changed.
-        boolean syncModeChanged = !configuration.syncMode.equalsIgnoreCase(
-                mConfiguration.syncMode);
-        boolean journalModeChanged = !configuration.journalMode.equalsIgnoreCase(
-                mConfiguration.journalMode);
+        boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
+                != mConfiguration.foreignKeyConstraintsEnabled;
+        boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
+                & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
         boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
 
         // Update configuration parameters.
@@ -361,14 +412,14 @@
         // Update prepared statement cache size.
         mPreparedStatementCache.resize(configuration.maxSqlCacheSize);
 
-        // Update sync mode.
-        if (syncModeChanged) {
-            setSyncModeFromConfiguration();
+        // Update foreign key mode.
+        if (foreignKeyModeChanged) {
+            setForeignKeyModeFromConfiguration();
         }
 
-        // Update journal mode.
-        if (journalModeChanged) {
-            setJournalModeFromConfiguration();
+        // Update WAL.
+        if (walModeChanged) {
+            setWalModeFromConfiguration();
         }
 
         // Update locale.
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 3562e89..5c8e38bf 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -81,6 +81,7 @@
     private final Object mLock = new Object();
     private final AtomicBoolean mConnectionLeaked = new AtomicBoolean();
     private final SQLiteDatabaseConfiguration mConfiguration;
+    private int mMaxConnectionPoolSize;
     private boolean mIsOpen;
     private int mNextConnectionId;
 
@@ -146,6 +147,7 @@
 
     private SQLiteConnectionPool(SQLiteDatabaseConfiguration configuration) {
         mConfiguration = new SQLiteDatabaseConfiguration(configuration);
+        setMaxConnectionPoolSizeLocked();
     }
 
     @Override
@@ -257,7 +259,46 @@
         synchronized (mLock) {
             throwIfClosedLocked();
 
+            boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
+                    & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
+            if (walModeChanged) {
+                // WAL mode can only be changed if there are no acquired connections
+                // because we need to close all but the primary connection first.
+                if (!mAcquiredConnections.isEmpty()) {
+                    throw new IllegalStateException("Write Ahead Logging (WAL) mode cannot "
+                            + "be enabled or disabled while there are transactions in "
+                            + "progress.  Finish all transactions and release all active "
+                            + "database connections first.");
+                }
+
+                // Close all non-primary connections.  This should happen immediately
+                // because none of them are in use.
+                closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
+                assert mAvailableNonPrimaryConnections.isEmpty();
+            }
+
+            boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
+                    != mConfiguration.foreignKeyConstraintsEnabled;
+            if (foreignKeyModeChanged) {
+                // Foreign key constraints can only be changed if there are no transactions
+                // in progress.  To make this clear, we throw an exception if there are
+                // any acquired connections.
+                if (!mAcquiredConnections.isEmpty()) {
+                    throw new IllegalStateException("Foreign Key Constraints cannot "
+                            + "be enabled or disabled while there are transactions in "
+                            + "progress.  Finish all transactions and release all active "
+                            + "database connections first.");
+                }
+            }
+
             if (mConfiguration.openFlags != configuration.openFlags) {
+                // If we are changing open flags and WAL mode at the same time, then
+                // we have no choice but to close the primary connection beforehand
+                // because there can only be one connection open when we change WAL mode.
+                if (walModeChanged) {
+                    closeAvailableConnectionsAndLogExceptionsLocked();
+                }
+
                 // Try to reopen the primary connection using the new open flags then
                 // close and discard all existing connections.
                 // This might throw if the database is corrupt or cannot be opened in
@@ -270,9 +311,11 @@
 
                 mAvailablePrimaryConnection = newPrimaryConnection;
                 mConfiguration.updateParametersFrom(configuration);
+                setMaxConnectionPoolSizeLocked();
             } else {
                 // Reconfigure the database connections in place.
                 mConfiguration.updateParametersFrom(configuration);
+                setMaxConnectionPoolSizeLocked();
 
                 closeExcessConnectionsAndLogExceptionsLocked();
                 reconfigureAllConnectionsLocked();
@@ -334,8 +377,7 @@
                     mAvailablePrimaryConnection = connection;
                 }
                 wakeConnectionWaitersLocked();
-            } else if (mAvailableNonPrimaryConnections.size() >=
-                    mConfiguration.maxConnectionPoolSize - 1) {
+            } else if (mAvailableNonPrimaryConnections.size() >= mMaxConnectionPoolSize - 1) {
                 closeConnectionAndLogExceptionsLocked(connection);
             } else {
                 if (recycleConnectionLocked(connection, status)) {
@@ -453,11 +495,7 @@
 
     // Can't throw.
     private void closeAvailableConnectionsAndLogExceptionsLocked() {
-        final int count = mAvailableNonPrimaryConnections.size();
-        for (int i = 0; i < count; i++) {
-            closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i));
-        }
-        mAvailableNonPrimaryConnections.clear();
+        closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
 
         if (mAvailablePrimaryConnection != null) {
             closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
@@ -466,9 +504,18 @@
     }
 
     // Can't throw.
+    private void closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked() {
+        final int count = mAvailableNonPrimaryConnections.size();
+        for (int i = 0; i < count; i++) {
+            closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i));
+        }
+        mAvailableNonPrimaryConnections.clear();
+    }
+
+    // Can't throw.
     private void closeExcessConnectionsAndLogExceptionsLocked() {
         int availableCount = mAvailableNonPrimaryConnections.size();
-        while (availableCount-- > mConfiguration.maxConnectionPoolSize - 1) {
+        while (availableCount-- > mMaxConnectionPoolSize - 1) {
             SQLiteConnection connection =
                     mAvailableNonPrimaryConnections.remove(availableCount);
             closeConnectionAndLogExceptionsLocked(connection);
@@ -843,7 +890,7 @@
         if (mAvailablePrimaryConnection != null) {
             openConnections += 1;
         }
-        if (openConnections >= mConfiguration.maxConnectionPoolSize) {
+        if (openConnections >= mMaxConnectionPoolSize) {
             return null;
         }
         connection = openConnectionLocked(mConfiguration,
@@ -895,6 +942,18 @@
         return (connectionFlags & CONNECTION_FLAG_INTERACTIVE) != 0 ? 1 : 0;
     }
 
+    private void setMaxConnectionPoolSizeLocked() {
+        if ((mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) {
+            mMaxConnectionPoolSize = SQLiteGlobal.getWALConnectionPoolSize();
+        } else {
+            // TODO: We don't actually need to restrict the connection pool size to 1
+            // for non-WAL databases.  There might be reasons to use connection pooling
+            // with other journal modes.  For now, enabling connection pooling and
+            // using WAL are the same thing in the API.
+            mMaxConnectionPoolSize = 1;
+        }
+    }
+
     private void throwIfClosedLocked() {
         if (!mIsOpen) {
             throw new IllegalStateException("Cannot perform this operation "
@@ -941,7 +1000,7 @@
         synchronized (mLock) {
             printer.println("Connection pool for " + mConfiguration.path + ":");
             printer.println("  Open: " + mIsOpen);
-            printer.println("  Max connections: " + mConfiguration.maxConnectionPoolSize);
+            printer.println("  Max connections: " + mMaxConnectionPoolSize);
 
             printer.println("  Available primary connection:");
             if (mAvailablePrimaryConnection != null) {
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index bf32ea7..7bd0c8d 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -127,10 +127,6 @@
     // INVARIANT: Guarded by mLock.
     private boolean mHasAttachedDbsLocked;
 
-    // True if the database is in WAL mode.
-    // INVARIANT: Guarded by mLock.
-    private boolean mIsWALEnabledLocked;
-
     /**
      * When a constraint violation occurs, an immediate ROLLBACK occurs,
      * thus ending the current transaction, and the command aborts with a
@@ -236,6 +232,18 @@
     public static final int CREATE_IF_NECESSARY = 0x10000000;     // update native code if changing
 
     /**
+     * Open flag: Flag for {@link #openDatabase} to open the database file with
+     * write-ahead logging enabled by default.  Using this flag is more efficient
+     * than calling {@link #enableWriteAheadLogging}.
+     *
+     * Write-ahead logging cannot be used with read-only databases so the value of
+     * this flag is ignored if the database is opened read-only.
+     *
+     * @see #enableWriteAheadLogging
+     */
+    public static final int ENABLE_WRITE_AHEAD_LOGGING = 0x20000000;
+
+    /**
      * Absolute max value that can be set by {@link #setMaxSqlCacheSize(int)}.
      *
      * Each prepared-statement is between 1K - 6K, depending on the complexity of the
@@ -658,7 +666,7 @@
      * @throws SQLiteException if the database cannot be opened
      */
     public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags) {
-        return openDatabase(path, factory, flags, new DefaultDatabaseErrorHandler());
+        return openDatabase(path, factory, flags, null);
     }
 
     /**
@@ -698,7 +706,7 @@
      * Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY).
      */
     public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory) {
-        return openDatabase(path, factory, CREATE_IF_NECESSARY);
+        return openDatabase(path, factory, CREATE_IF_NECESSARY, null);
     }
 
     /**
@@ -834,8 +842,14 @@
 
         synchronized (mLock) {
             throwIfNotOpenLocked();
+
             mConfigurationLocked.customFunctions.add(wrapper);
-            mConnectionPoolLocked.reconfigure(mConfigurationLocked);
+            try {
+                mConnectionPoolLocked.reconfigure(mConfigurationLocked);
+            } catch (RuntimeException ex) {
+                mConfigurationLocked.customFunctions.remove(wrapper);
+                throw ex;
+            }
         }
     }
 
@@ -1733,8 +1747,15 @@
 
         synchronized (mLock) {
             throwIfNotOpenLocked();
+
+            final Locale oldLocale = mConfigurationLocked.locale;
             mConfigurationLocked.locale = locale;
-            mConnectionPoolLocked.reconfigure(mConfigurationLocked);
+            try {
+                mConnectionPoolLocked.reconfigure(mConfigurationLocked);
+            } catch (RuntimeException ex) {
+                mConfigurationLocked.locale = oldLocale;
+                throw ex;
+            }
         }
     }
 
@@ -1759,58 +1780,144 @@
 
         synchronized (mLock) {
             throwIfNotOpenLocked();
+
+            final int oldMaxSqlCacheSize = mConfigurationLocked.maxSqlCacheSize;
             mConfigurationLocked.maxSqlCacheSize = cacheSize;
-            mConnectionPoolLocked.reconfigure(mConfigurationLocked);
+            try {
+                mConnectionPoolLocked.reconfigure(mConfigurationLocked);
+            } catch (RuntimeException ex) {
+                mConfigurationLocked.maxSqlCacheSize = oldMaxSqlCacheSize;
+                throw ex;
+            }
         }
     }
 
     /**
-     * This method enables parallel execution of queries from multiple threads on the same database.
-     * It does this by opening multiple handles to the database and using a different
-     * database handle for each query.
+     * Sets whether foreign key constraints are enabled for the database.
      * <p>
-     * If a transaction is in progress on one connection handle and say, a table is updated in the
-     * transaction, then query on the same table on another connection handle will block for the
-     * transaction to complete. But this method enables such queries to execute by having them
-     * return old version of the data from the table. Most often it is the data that existed in the
-     * table prior to the above transaction updates on that table.
-     * <p>
-     * Maximum number of simultaneous handles used to execute queries in parallel is
-     * dependent upon the device memory and possibly other properties.
-     * <p>
-     * After calling this method, execution of queries in parallel is enabled as long as this
-     * database handle is open. To disable execution of queries in parallel, database should
-     * be closed and reopened.
-     * <p>
-     * If a query is part of a transaction, then it is executed on the same database handle the
-     * transaction was begun.
-     * <p>
-     * If the database has any attached databases, then execution of queries in paralel is NOT
-     * possible. In such cases, a message is printed to logcat and false is returned.
-     * <p>
-     * This feature is not available for :memory: databases. In such cases,
-     * a message is printed to logcat and false is returned.
-     * <p>
-     * A typical way to use this method is the following:
-     * <pre>
-     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
-     *             CREATE_IF_NECESSARY, myDatabaseErrorHandler);
-     *     db.enableWriteAheadLogging();
-     * </pre>
-     * <p>
-     * Writers should use {@link #beginTransactionNonExclusive()} or
-     * {@link #beginTransactionWithListenerNonExclusive(SQLiteTransactionListener)}
-     * to start a trsnsaction.
-     * Non-exclusive mode allows database file to be in readable by threads executing queries.
+     * By default, foreign key constraints are not enforced by the database.
+     * This method allows an application to enable foreign key constraints.
+     * It must be called each time the database is opened to ensure that foreign
+     * key constraints are enabled for the session.
+     * </p><p>
+     * A good time to call this method is right after calling {@link #openOrCreateDatabase}
+     * or in the {@link SQLiteOpenHelper#onConfigure} callback.
+     * </p><p>
+     * When foreign key constraints are disabled, the database does not check whether
+     * changes to the database will violate foreign key constraints.  Likewise, when
+     * foreign key constraints are disabled, the database will not execute cascade
+     * delete or update triggers.  As a result, it is possible for the database
+     * state to become inconsistent.  To perform a database integrity check,
+     * call {@link #isDatabaseIntegrityOk}.
+     * </p><p>
+     * This method must not be called while a transaction is in progress.
+     * </p><p>
+     * See also <a href="http://sqlite.org/foreignkeys.html">SQLite Foreign Key Constraints</a>
+     * for more details about foreign key constraint support.
      * </p>
      *
-     * @return true if write-ahead-logging is set. false otherwise
+     * @param enable True to enable foreign key constraints, false to disable them.
+     *
+     * @throws IllegalStateException if the are transactions is in progress
+     * when this method is called.
+     */
+    public void setForeignKeyConstraintsEnabled(boolean enable) {
+        synchronized (mLock) {
+            throwIfNotOpenLocked();
+
+            if (mConfigurationLocked.foreignKeyConstraintsEnabled == enable) {
+                return;
+            }
+
+            mConfigurationLocked.foreignKeyConstraintsEnabled = enable;
+            try {
+                mConnectionPoolLocked.reconfigure(mConfigurationLocked);
+            } catch (RuntimeException ex) {
+                mConfigurationLocked.foreignKeyConstraintsEnabled = !enable;
+                throw ex;
+            }
+        }
+    }
+
+    /**
+     * This method enables parallel execution of queries from multiple threads on the
+     * same database.  It does this by opening multiple connections to the database
+     * and using a different database connection for each query.  The database
+     * journal mode is also changed to enable writes to proceed concurrently with reads.
+     * <p>
+     * When write-ahead logging is not enabled (the default), it is not possible for
+     * reads and writes to occur on the database at the same time.  Before modifying the
+     * database, the writer implicitly acquires an exclusive lock on the database which
+     * prevents readers from accessing the database until the write is completed.
+     * </p><p>
+     * In contrast, when write-ahead logging is enabled (by calling this method), write
+     * operations occur in a separate log file which allows reads to proceed concurrently.
+     * While a write is in progress, readers on other threads will perceive the state
+     * of the database as it was before the write began.  When the write completes, readers
+     * on other threads will then perceive the new state of the database.
+     * </p><p>
+     * It is a good idea to enable write-ahead logging whenever a database will be
+     * concurrently accessed and modified by multiple threads at the same time.
+     * However, write-ahead logging uses significantly more memory than ordinary
+     * journaling because there are multiple connections to the same database.
+     * So if a database will only be used by a single thread, or if optimizing
+     * concurrency is not very important, then write-ahead logging should be disabled.
+     * </p><p>
+     * After calling this method, execution of queries in parallel is enabled as long as
+     * the database remains open.  To disable execution of queries in parallel, either
+     * call {@link #disableWriteAheadLogging} or close the database and reopen it.
+     * </p><p>
+     * The maximum number of connections used to execute queries in parallel is
+     * dependent upon the device memory and possibly other properties.
+     * </p><p>
+     * If a query is part of a transaction, then it is executed on the same database handle the
+     * transaction was begun.
+     * </p><p>
+     * Writers should use {@link #beginTransactionNonExclusive()} or
+     * {@link #beginTransactionWithListenerNonExclusive(SQLiteTransactionListener)}
+     * to start a transaction.  Non-exclusive mode allows database file to be in readable
+     * by other threads executing queries.
+     * </p><p>
+     * If the database has any attached databases, then execution of queries in parallel is NOT
+     * possible.  Likewise, write-ahead logging is not supported for read-only databases
+     * or memory databases.  In such cases, {@link #enableWriteAheadLogging} returns false.
+     * </p><p>
+     * The best way to enable write-ahead logging is to pass the
+     * {@link #ENABLE_WRITE_AHEAD_LOGGING} flag to {@link #openDatabase}.  This is
+     * more efficient than calling {@link #enableWriteAheadLogging}.
+     * <code><pre>
+     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
+     *             SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING,
+     *             myDatabaseErrorHandler);
+     *     db.enableWriteAheadLogging();
+     * </pre></code>
+     * </p><p>
+     * Another way to enable write-ahead logging is to call {@link #enableWriteAheadLogging}
+     * after opening the database.
+     * <code><pre>
+     *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
+     *             SQLiteDatabase.CREATE_IF_NECESSARY, myDatabaseErrorHandler);
+     *     db.enableWriteAheadLogging();
+     * </pre></code>
+     * </p><p>
+     * See also <a href="http://sqlite.org/wal.html">SQLite Write-Ahead Logging</a> for
+     * more details about how write-ahead logging works.
+     * </p>
+     *
+     * @return True if write-ahead logging is enabled.
+     *
+     * @throws IllegalStateException if there are transactions in progress at the
+     * time this method is called.  WAL mode can only be changed when there are no
+     * transactions in progress.
+     *
+     * @see #ENABLE_WRITE_AHEAD_LOGGING
+     * @see #disableWriteAheadLogging
      */
     public boolean enableWriteAheadLogging() {
         synchronized (mLock) {
             throwIfNotOpenLocked();
 
-            if (mIsWALEnabledLocked) {
+            if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0) {
                 return true;
             }
 
@@ -1835,32 +1942,57 @@
                 return false;
             }
 
-            mIsWALEnabledLocked = true;
-            mConfigurationLocked.maxConnectionPoolSize = SQLiteGlobal.getWALConnectionPoolSize();
-            mConfigurationLocked.syncMode = SQLiteGlobal.getWALSyncMode();
-            mConfigurationLocked.journalMode = "WAL";
-            mConnectionPoolLocked.reconfigure(mConfigurationLocked);
+            mConfigurationLocked.openFlags |= ENABLE_WRITE_AHEAD_LOGGING;
+            try {
+                mConnectionPoolLocked.reconfigure(mConfigurationLocked);
+            } catch (RuntimeException ex) {
+                mConfigurationLocked.openFlags &= ~ENABLE_WRITE_AHEAD_LOGGING;
+                throw ex;
+            }
         }
         return true;
     }
 
     /**
      * This method disables the features enabled by {@link #enableWriteAheadLogging()}.
-     * @hide
+     *
+     * @throws IllegalStateException if there are transactions in progress at the
+     * time this method is called.  WAL mode can only be changed when there are no
+     * transactions in progress.
+     *
+     * @see #enableWriteAheadLogging
      */
     public void disableWriteAheadLogging() {
         synchronized (mLock) {
             throwIfNotOpenLocked();
 
-            if (!mIsWALEnabledLocked) {
+            if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) == 0) {
                 return;
             }
 
-            mIsWALEnabledLocked = false;
-            mConfigurationLocked.maxConnectionPoolSize = 1;
-            mConfigurationLocked.syncMode = SQLiteGlobal.getDefaultSyncMode();
-            mConfigurationLocked.journalMode = SQLiteGlobal.getDefaultJournalMode();
-            mConnectionPoolLocked.reconfigure(mConfigurationLocked);
+            mConfigurationLocked.openFlags &= ~ENABLE_WRITE_AHEAD_LOGGING;
+            try {
+                mConnectionPoolLocked.reconfigure(mConfigurationLocked);
+            } catch (RuntimeException ex) {
+                mConfigurationLocked.openFlags |= ENABLE_WRITE_AHEAD_LOGGING;
+                throw ex;
+            }
+        }
+    }
+
+    /**
+     * Returns true if write-ahead logging has been enabled for this database.
+     *
+     * @return True if write-ahead logging has been enabled for this database.
+     *
+     * @see #enableWriteAheadLogging
+     * @see #ENABLE_WRITE_AHEAD_LOGGING
+     */
+    public boolean isWriteAheadLoggingEnabled() {
+        synchronized (mLock) {
+            throwIfNotOpenLocked();
+
+            return (mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0;
         }
     }
 
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
index efbcaca..549ab90 100644
--- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -62,14 +62,6 @@
     public int openFlags;
 
     /**
-     * The maximum number of connections to retain in the connection pool.
-     * Must be at least 1.
-     *
-     * Default is 1.
-     */
-    public int maxConnectionPoolSize;
-
-    /**
      * The maximum size of the prepared statement cache for each database connection.
      * Must be non-negative.
      *
@@ -85,18 +77,11 @@
     public Locale locale;
 
     /**
-     * The database synchronization mode.
+     * True if foreign key constraints are enabled.
      *
-     * Default is {@link SQLiteGlobal#getDefaultSyncMode()}.
+     * Default is false.
      */
-    public String syncMode;
-
-    /**
-     * The database journal mode.
-     *
-     * Default is {@link SQLiteGlobal#getDefaultJournalMode()}.
-     */
-    public String journalMode;
+    public boolean foreignKeyConstraintsEnabled;
 
     /**
      * The custom functions to register.
@@ -121,11 +106,8 @@
         this.openFlags = openFlags;
 
         // Set default values for optional parameters.
-        maxConnectionPoolSize = 1;
         maxSqlCacheSize = 25;
         locale = Locale.getDefault();
-        syncMode = SQLiteGlobal.getDefaultSyncMode();
-        journalMode = SQLiteGlobal.getDefaultJournalMode();
     }
 
     /**
@@ -159,11 +141,9 @@
         }
 
         openFlags = other.openFlags;
-        maxConnectionPoolSize = other.maxConnectionPoolSize;
         maxSqlCacheSize = other.maxSqlCacheSize;
         locale = other.locale;
-        syncMode = other.syncMode;
-        journalMode = other.journalMode;
+        foreignKeyConstraintsEnabled = other.foreignKeyConstraintsEnabled;
         customFunctions.clear();
         customFunctions.addAll(other.customFunctions);
     }
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index ffa4663..431eca2 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -58,6 +58,7 @@
 
     private SQLiteDatabase mDatabase;
     private boolean mIsInitializing;
+    private boolean mEnableWriteAheadLogging;
     private final DatabaseErrorHandler mErrorHandler;
 
     /**
@@ -74,7 +75,7 @@
      *     newer, {@link #onDowngrade} will be used to downgrade the database
      */
     public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
-        this(context, name, factory, version, new DefaultDatabaseErrorHandler());
+        this(context, name, factory, version, null);
     }
 
     /**
@@ -92,14 +93,11 @@
      *     {@link #onUpgrade} will be used to upgrade the database; if the database is
      *     newer, {@link #onDowngrade} will be used to downgrade the database
      * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
-     * corruption.
+     * corruption, or null to use the default error handler.
      */
     public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
             DatabaseErrorHandler errorHandler) {
         if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
-        if (errorHandler == null) {
-            throw new IllegalArgumentException("DatabaseErrorHandler param value can't be null.");
-        }
 
         mContext = context;
         mName = name;
@@ -117,6 +115,32 @@
     }
 
     /**
+     * Enables or disables the use of write-ahead logging for the database.
+     *
+     * Write-ahead logging cannot be used with read-only databases so the value of
+     * this flag is ignored if the database is opened read-only.
+     *
+     * @param enabled True if write-ahead logging should be enabled, false if it
+     * should be disabled.
+     *
+     * @see SQLiteDatabase#enableWriteAheadLogging()
+     */
+    public void setWriteAheadLoggingEnabled(boolean enabled) {
+        synchronized (this) {
+            if (mEnableWriteAheadLogging != enabled) {
+                if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
+                    if (enabled) {
+                        mDatabase.enableWriteAheadLogging();
+                    } else {
+                        mDatabase.disableWriteAheadLogging();
+                    }
+                }
+                mEnableWriteAheadLogging = enabled;
+            }
+        }
+    }
+
+    /**
      * Create and/or open a database that will be used for reading and writing.
      * The first time this is called, the database will be opened and
      * {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
@@ -197,7 +221,9 @@
                         db = SQLiteDatabase.openDatabase(path, mFactory,
                                 SQLiteDatabase.OPEN_READONLY, mErrorHandler);
                     } else {
-                        db = mContext.openOrCreateDatabase(mName, 0, mFactory, mErrorHandler);
+                        db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?
+                                Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,
+                                mFactory, mErrorHandler);
                     }
                 } catch (SQLiteException ex) {
                     if (writable) {
@@ -211,6 +237,8 @@
                 }
             }
 
+            onConfigure(db);
+
             final int version = db.getVersion();
             if (version != mNewVersion) {
                 if (db.isReadOnly()) {
@@ -235,6 +263,7 @@
                     db.endTransaction();
                 }
             }
+
             onOpen(db);
 
             if (db.isReadOnly()) {
@@ -264,6 +293,25 @@
     }
 
     /**
+     * Called when the database connection is being configured, to enable features
+     * such as write-ahead logging or foreign key support.
+     * <p>
+     * This method is called before {@link #onCreate}, {@link #onUpgrade},
+     * {@link #onDowngrade}, or {@link #onOpen} are called.  It should not modify
+     * the database except to configure the database connection as required.
+     * </p><p>
+     * This method should only call methods that configure the parameters of the
+     * database connection, such as {@link SQLiteDatabase#enableWriteAheadLogging}
+     * {@link SQLiteDatabase#setForeignKeyConstraintsEnabled},
+     * {@link SQLiteDatabase#setLocale}, {@link SQLiteDatabase#setMaximumSize},
+     * or executing PRAGMA statements.
+     * </p>
+     *
+     * @param db The database.
+     */
+    public void onConfigure(SQLiteDatabase db) {}
+
+    /**
      * Called when the database is created for the first time. This is where the
      * creation of tables and the initial population of the tables should happen.
      *
@@ -276,11 +324,16 @@
      * should use this method to drop tables, add tables, or do anything else it
      * needs to upgrade to the new schema version.
      *
-     * <p>The SQLite ALTER TABLE documentation can be found
+     * <p>
+     * The SQLite ALTER TABLE documentation can be found
      * <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns
      * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
      * you can use ALTER TABLE to rename the old table, then create the new table and then
      * populate the new table with the contents of the old table.
+     * </p><p>
+     * This method executes within a transaction.  If an exception is thrown, all changes
+     * will automatically be rolled back.
+     * </p>
      *
      * @param db The database.
      * @param oldVersion The old database version.
@@ -290,11 +343,16 @@
 
     /**
      * Called when the database needs to be downgraded. This is strictly similar to
-     * onUpgrade() method, but is called whenever current version is newer than requested one.
+     * {@link #onUpgrade} method, but is called whenever current version is newer than requested one.
      * However, this method is not abstract, so it is not mandatory for a customer to
      * implement it. If not overridden, default implementation will reject downgrade and
      * throws SQLiteException
      *
+     * <p>
+     * This method executes within a transaction.  If an exception is thrown, all changes
+     * will automatically be rolled back.
+     * </p>
+     *
      * @param db The database.
      * @param oldVersion The old database version.
      * @param newVersion The new database version.
@@ -308,6 +366,12 @@
      * Called when the database has been opened.  The implementation
      * should check {@link SQLiteDatabase#isReadOnly} before updating the
      * database.
+     * <p>
+     * This method is called after the database connection has been configured
+     * and after the database schema has been created, upgraded or downgraded as necessary.
+     * If the database connection must be configured in some way before the schema
+     * is created, upgraded, or downgraded, do it in {@link #onConfigure} instead.
+     * </p>
      *
      * @param db The database.
      */
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index 43efb03..9410243 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -398,16 +398,16 @@
         throwIfNoTransaction();
         assert mConnection != null;
 
-        endTransactionUnchecked(cancellationSignal);
+        endTransactionUnchecked(cancellationSignal, false);
     }
 
-    private void endTransactionUnchecked(CancellationSignal cancellationSignal) {
+    private void endTransactionUnchecked(CancellationSignal cancellationSignal, boolean yielding) {
         if (cancellationSignal != null) {
             cancellationSignal.throwIfCanceled();
         }
 
         final Transaction top = mTransactionStack;
-        boolean successful = top.mMarkedSuccessful && !top.mChildFailed;
+        boolean successful = (top.mMarkedSuccessful || yielding) && !top.mChildFailed;
 
         RuntimeException listenerException = null;
         final SQLiteTransactionListener listener = top.mListener;
@@ -534,7 +534,7 @@
         final int transactionMode = mTransactionStack.mMode;
         final SQLiteTransactionListener listener = mTransactionStack.mListener;
         final int connectionFlags = mConnectionFlags;
-        endTransactionUnchecked(cancellationSignal); // might throw
+        endTransactionUnchecked(cancellationSignal, true); // might throw
 
         if (sleepAfterYieldDelayMillis > 0) {
             try {
diff --git a/core/java/android/net/Downloads.java b/core/java/android/net/Downloads.java
deleted file mode 100644
index ed6d103..0000000
--- a/core/java/android/net/Downloads.java
+++ /dev/null
@@ -1,644 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.provider.BaseColumns;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.File;
-import java.io.InputStream;
-
-/**
- * The Download Manager
- *
- * @hide
- */
-public final class Downloads {
-
-
-    /**
-     * Download status codes
-     */
-
-    /**
-     * This download hasn't started yet
-     */
-    public static final int STATUS_PENDING = 190;
-
-    /**
-     * This download has started
-     */
-    public static final int STATUS_RUNNING = 192;
-
-    /**
-     * This download has successfully completed.
-     * Warning: there might be other status values that indicate success
-     * in the future.
-     * Use isSucccess() to capture the entire category.
-     */
-    public static final int STATUS_SUCCESS = 200;
-
-    /**
-     * This download can't be performed because the content type cannot be
-     * handled.
-     */
-    public static final int STATUS_NOT_ACCEPTABLE = 406;
-
-    /**
-     * This download has completed with an error.
-     * Warning: there will be other status values that indicate errors in
-     * the future. Use isStatusError() to capture the entire category.
-     */
-    public static final int STATUS_UNKNOWN_ERROR = 491;
-
-    /**
-     * This download couldn't be completed because of an HTTP
-     * redirect response that the download manager couldn't
-     * handle.
-     */
-    public static final int STATUS_UNHANDLED_REDIRECT = 493;
-
-    /**
-     * This download couldn't be completed due to insufficient storage
-     * space.  Typically, this is because the SD card is full.
-     */
-    public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498;
-
-    /**
-     * This download couldn't be completed because no external storage
-     * device was found.  Typically, this is because the SD card is not
-     * mounted.
-     */
-    public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;
-
-    /**
-     * Returns whether the status is a success (i.e. 2xx).
-     */
-    public static boolean isStatusSuccess(int status) {
-        return (status >= 200 && status < 300);
-    }
-
-    /**
-     * Returns whether the status is an error (i.e. 4xx or 5xx).
-     */
-    public static boolean isStatusError(int status) {
-        return (status >= 400 && status < 600);
-    }
-
-    /**
-     * Download destinations
-     */
-
-    /**
-     * This download will be saved to the external storage. This is the
-     * default behavior, and should be used for any file that the user
-     * can freely access, copy, delete. Even with that destination,
-     * unencrypted DRM files are saved in secure internal storage.
-     * Downloads to the external destination only write files for which
-     * there is a registered handler. The resulting files are accessible
-     * by filename to all applications.
-     */
-    public static final int DOWNLOAD_DESTINATION_EXTERNAL = 1;
-
-    /**
-     * This download will be saved to the download manager's private
-     * partition. This is the behavior used by applications that want to
-     * download private files that are used and deleted soon after they
-     * get downloaded. All file types are allowed, and only the initiating
-     * application can access the file (indirectly through a content
-     * provider). This requires the
-     * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission.
-     */
-    public static final int DOWNLOAD_DESTINATION_CACHE = 2;
-
-    /**
-     * This download will be saved to the download manager's private
-     * partition and will be purged as necessary to make space. This is
-     * for private files (similar to CACHE_PARTITION) that aren't deleted
-     * immediately after they are used, and are kept around by the download
-     * manager as long as space is available.
-     */
-    public static final int DOWNLOAD_DESTINATION_CACHE_PURGEABLE = 3;
-
-
-    /**
-     * An invalid download id
-     */
-    public static final long DOWNLOAD_ID_INVALID = -1;
-
-
-    /**
-     * Broadcast Action: this is sent by the download manager to the app
-     * that had initiated a download when that download completes. The
-     * download's content: uri is specified in the intent's data.
-     */
-    public static final String ACTION_DOWNLOAD_COMPLETED =
-            "android.intent.action.DOWNLOAD_COMPLETED";
-
-    /**
-     * If extras are specified when requesting a download they will be provided in the intent that
-     * is sent to the specified class and package when a download has finished.
-     * <P>Type: TEXT</P>
-     * <P>Owner can Init</P>
-     */
-    public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras";
-
-
-    /**
-     * Status class for a download
-     */
-    public static final class StatusInfo {
-        public boolean completed = false;
-        /** The filename of the active download. */
-        public String filename = null;
-        /** An opaque id for the download */
-        public long id = DOWNLOAD_ID_INVALID;
-        /** An opaque status code for the download */
-        public int statusCode = -1;
-        /** Approximate number of bytes downloaded so far, for debugging purposes. */
-        public long bytesSoFar = -1;
-
-        /**
-         * Returns whether the download is completed
-         * @return a boolean whether the download is complete.
-         */
-        public boolean isComplete() {
-            return android.provider.Downloads.Impl.isStatusCompleted(statusCode);
-        }
-
-        /**
-         * Returns whether the download is successful
-         * @return a boolean whether the download is successful.
-         */
-        public boolean isSuccessful() {
-            return android.provider.Downloads.Impl.isStatusSuccess(statusCode);
-        }
-    }
-
-    /**
-     * Class to access initiate and query download by server uri
-     */
-    public static final class ByUri extends DownloadBase {
-        /** @hide */
-        private ByUri() {}
-
-        /**
-         * Query where clause by app data.
-         * @hide
-         */
-        private static final String QUERY_WHERE_APP_DATA_CLAUSE =
-                android.provider.Downloads.Impl.COLUMN_APP_DATA + "=?";
-
-        /**
-         * Gets a Cursor pointing to the download(s) of the current system update.
-         * @hide
-         */
-        private static final Cursor getCurrentOtaDownloads(Context context, String url) {
-            return context.getContentResolver().query(
-                    android.provider.Downloads.Impl.CONTENT_URI,
-                    DOWNLOADS_PROJECTION,
-                    QUERY_WHERE_APP_DATA_CLAUSE,
-                    new String[] {url},
-                    null);
-        }
-
-        /**
-         * Returns a StatusInfo with the result of trying to download the
-         * given URL.  Returns null if no attempts have been made.
-         */
-        public static final StatusInfo getStatus(
-                Context context,
-                String url,
-                long redownload_threshold) {
-            StatusInfo result = null;
-            boolean hasFailedDownload = false;
-            long failedDownloadModificationTime = 0;
-            Cursor c = getCurrentOtaDownloads(context, url);
-            try {
-                while (c != null && c.moveToNext()) {
-                    if (result == null) {
-                        result = new StatusInfo();
-                    }
-                    int status = getStatusOfDownload(c, redownload_threshold);
-                    if (status == STATUS_DOWNLOADING_UPDATE ||
-                        status == STATUS_DOWNLOADED_UPDATE) {
-                        result.completed = (status == STATUS_DOWNLOADED_UPDATE);
-                        result.filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
-                        result.id = c.getLong(DOWNLOADS_COLUMN_ID);
-                        result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
-                        result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
-                        return result;
-                    }
-
-                    long modTime = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
-                    if (hasFailedDownload &&
-                        modTime < failedDownloadModificationTime) {
-                        // older than the one already in result; skip it.
-                        continue;
-                    }
-
-                    hasFailedDownload = true;
-                    failedDownloadModificationTime = modTime;
-                    result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
-                    result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
-                }
-            } finally {
-                if (c != null) {
-                    c.close();
-                }
-            }
-            return result;
-        }
-
-        /**
-         * Query where clause for general querying.
-         */
-        private static final String QUERY_WHERE_CLAUSE =
-                android.provider.Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE + "=? AND " +
-                android.provider.Downloads.Impl.COLUMN_NOTIFICATION_CLASS + "=?";
-
-        /**
-         * Delete all the downloads for a package/class pair.
-         */
-        public static final void removeAllDownloadsByPackage(
-                Context context,
-                String notification_package,
-                String notification_class) {
-            context.getContentResolver().delete(
-                    android.provider.Downloads.Impl.CONTENT_URI,
-                    QUERY_WHERE_CLAUSE,
-                    new String[] { notification_package, notification_class });
-        }
-
-        /**
-         * The column for the id in the Cursor returned by
-         * getProgressCursor()
-         */
-        public static final int getProgressColumnId() {
-            return 0;
-        }
-
-        /**
-         * The column for the current byte count in the Cursor returned by
-         * getProgressCursor()
-         */
-        public static final int getProgressColumnCurrentBytes() {
-            return 1;
-        }
-
-        /**
-         * The column for the total byte count in the Cursor returned by
-         * getProgressCursor()
-         */
-        public static final int getProgressColumnTotalBytes() {
-            return 2;
-        }
-
-        /** @hide */
-        private static final String[] PROJECTION = {
-            BaseColumns._ID,
-            android.provider.Downloads.Impl.COLUMN_CURRENT_BYTES,
-            android.provider.Downloads.Impl.COLUMN_TOTAL_BYTES
-        };
-
-        /**
-         * Returns a Cursor representing the progress of the download identified by the ID.
-         */
-        public static final Cursor getProgressCursor(Context context, long id) {
-            Uri downloadUri = Uri.withAppendedPath(android.provider.Downloads.Impl.CONTENT_URI,
-                    String.valueOf(id));
-            return context.getContentResolver().query(downloadUri, PROJECTION, null, null, null);
-        }
-    }
-
-    /**
-     * Class to access downloads by opaque download id
-     */
-    public static final class ById extends DownloadBase {
-        /** @hide */
-        private ById() {}
-
-        /**
-         * Get the mime tupe of the download specified by the download id
-         */
-        public static String getMimeTypeForId(Context context, long downloadId) {
-            ContentResolver cr = context.getContentResolver();
-
-            String mimeType = null;
-            Cursor downloadCursor = null;
-
-            try {
-                Uri downloadUri = getDownloadUri(downloadId);
-
-                downloadCursor = cr.query(
-                        downloadUri, new String[]{android.provider.Downloads.Impl.COLUMN_MIME_TYPE},
-                        null, null, null);
-                if (downloadCursor.moveToNext()) {
-                    mimeType = downloadCursor.getString(0);
-                }
-            } finally {
-                if (downloadCursor != null) downloadCursor.close();
-            }
-            return mimeType;
-        }
-
-        /**
-         * Delete a download by Id
-         */
-        public static void deleteDownload(Context context, long downloadId) {
-            ContentResolver cr = context.getContentResolver();
-
-            String mimeType = null;
-
-            Uri downloadUri = getDownloadUri(downloadId);
-
-            cr.delete(downloadUri, null, null);
-        }
-
-        /**
-         * Open a filedescriptor to a particular download
-         */
-        public static ParcelFileDescriptor openDownload(
-                Context context, long downloadId, String mode)
-            throws FileNotFoundException
-        {
-            ContentResolver cr = context.getContentResolver();
-
-            String mimeType = null;
-
-            Uri downloadUri = getDownloadUri(downloadId);
-
-            return cr.openFileDescriptor(downloadUri, mode);
-        }
-
-        /**
-         * Open a stream to a particular download
-         */
-        public static InputStream openDownloadStream(Context context, long downloadId)
-                throws FileNotFoundException, IOException
-        {
-            ContentResolver cr = context.getContentResolver();
-
-            String mimeType = null;
-
-            Uri downloadUri = getDownloadUri(downloadId);
-
-            return cr.openInputStream(downloadUri);
-        }
-
-        private static Uri getDownloadUri(long downloadId) {
-            return Uri.parse(android.provider.Downloads.Impl.CONTENT_URI + "/" + downloadId);
-        }
-
-        /**
-         * Returns a StatusInfo with the result of trying to download the
-         * given URL.  Returns null if no attempts have been made.
-         */
-        public static final StatusInfo getStatus(
-                Context context,
-                long downloadId) {
-            StatusInfo result = null;
-            boolean hasFailedDownload = false;
-            long failedDownloadModificationTime = 0;
-
-            Uri downloadUri = getDownloadUri(downloadId);
-
-            ContentResolver cr = context.getContentResolver();
-
-            Cursor c = cr.query(downloadUri, DOWNLOADS_PROJECTION, null /* selection */,
-                    null /* selection args */, null /* sort order */);
-            try {
-                if (c == null || !c.moveToNext()) {
-                    return result;
-                }
-
-                if (result == null) {
-                    result = new StatusInfo();
-                }
-                int status = getStatusOfDownload(c,0);
-                if (status == STATUS_DOWNLOADING_UPDATE ||
-                        status == STATUS_DOWNLOADED_UPDATE) {
-                    result.completed = (status == STATUS_DOWNLOADED_UPDATE);
-                    result.filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
-                    result.id = c.getLong(DOWNLOADS_COLUMN_ID);
-                    result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
-                    result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
-                    return result;
-                }
-
-                long modTime = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
-
-                result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
-                result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
-            } finally {
-                if (c != null) {
-                    c.close();
-                }
-            }
-            return result;
-        }
-    }
-
-
-    /**
-     * Base class with common functionality for the various download classes
-     */
-    public static class DownloadBase {
-        /** @hide */
-        DownloadBase() {}
-
-        /**
-          * Initiate a download where the download will be tracked by its URI.
-          */
-        public static long startDownloadByUri(
-                Context context,
-                String url,
-                String cookieData,
-                boolean showDownload,
-                int downloadDestination,
-                boolean allowRoaming,
-                boolean skipIntegrityCheck,
-                String title,
-                String notification_package,
-                String notification_class,
-                String notification_extras) {
-            ContentResolver cr = context.getContentResolver();
-
-            // Tell download manager to start downloading update.
-            ContentValues values = new ContentValues();
-            values.put(android.provider.Downloads.Impl.COLUMN_URI, url);
-            values.put(android.provider.Downloads.Impl.COLUMN_COOKIE_DATA, cookieData);
-            values.put(android.provider.Downloads.Impl.COLUMN_VISIBILITY,
-                       showDownload ? android.provider.Downloads.Impl.VISIBILITY_VISIBLE
-                       : android.provider.Downloads.Impl.VISIBILITY_HIDDEN);
-            if (title != null) {
-                values.put(android.provider.Downloads.Impl.COLUMN_TITLE, title);
-            }
-            values.put(android.provider.Downloads.Impl.COLUMN_APP_DATA, url);
-
-
-            // NOTE:  destination should be seperated from whether the download
-            // can happen when roaming
-            int destination = android.provider.Downloads.Impl.DESTINATION_EXTERNAL;
-            switch (downloadDestination) {
-                case DOWNLOAD_DESTINATION_EXTERNAL:
-                    destination = android.provider.Downloads.Impl.DESTINATION_EXTERNAL;
-                    break;
-                case DOWNLOAD_DESTINATION_CACHE:
-                    if (allowRoaming) {
-                        destination = android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION;
-                    } else {
-                        destination =
-                                android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING;
-                    }
-                    break;
-                case DOWNLOAD_DESTINATION_CACHE_PURGEABLE:
-                    destination =
-                            android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE;
-                    break;
-            }
-            values.put(android.provider.Downloads.Impl.COLUMN_DESTINATION, destination);
-            values.put(android.provider.Downloads.Impl.COLUMN_NO_INTEGRITY,
-                    skipIntegrityCheck);  // Don't check ETag
-            if (notification_package != null && notification_class != null) {
-                values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
-                        notification_package);
-                values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_CLASS,
-                        notification_class);
-
-                if (notification_extras != null) {
-                    values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS,
-                            notification_extras);
-                }
-            }
-
-            Uri downloadUri = cr.insert(android.provider.Downloads.Impl.CONTENT_URI, values);
-
-            long downloadId = DOWNLOAD_ID_INVALID;
-            if (downloadUri != null) {
-                downloadId = Long.parseLong(downloadUri.getLastPathSegment());
-            }
-            return downloadId;
-        }
-    }
-
-    /** @hide */
-    private static final int STATUS_INVALID = 0;
-    /** @hide */
-    private static final int STATUS_DOWNLOADING_UPDATE = 3;
-    /** @hide */
-    private static final int STATUS_DOWNLOADED_UPDATE = 4;
-
-    /**
-     * Column projection for the query to the download manager. This must match
-     * with the constants DOWNLOADS_COLUMN_*.
-     * @hide
-     */
-    private static final String[] DOWNLOADS_PROJECTION = {
-            BaseColumns._ID,
-            android.provider.Downloads.Impl.COLUMN_APP_DATA,
-            android.provider.Downloads.Impl.COLUMN_STATUS,
-            android.provider.Downloads.Impl._DATA,
-            android.provider.Downloads.Impl.COLUMN_LAST_MODIFICATION,
-            android.provider.Downloads.Impl.COLUMN_CURRENT_BYTES,
-    };
-
-    /**
-     * The column index for the ID.
-     * @hide
-     */
-    private static final int DOWNLOADS_COLUMN_ID = 0;
-    /**
-     * The column index for the URI.
-     * @hide
-     */
-    private static final int DOWNLOADS_COLUMN_URI = 1;
-    /**
-     * The column index for the status code.
-     * @hide
-     */
-    private static final int DOWNLOADS_COLUMN_STATUS = 2;
-    /**
-     * The column index for the filename.
-     * @hide
-     */
-    private static final int DOWNLOADS_COLUMN_FILENAME = 3;
-    /**
-     * The column index for the last modification time.
-     * @hide
-     */
-    private static final int DOWNLOADS_COLUMN_LAST_MODIFICATION = 4;
-    /**
-     * The column index for the number of bytes downloaded so far.
-     * @hide
-     */
-    private static final int DOWNLOADS_COLUMN_CURRENT_BYTES = 5;
-
-    /**
-     * Gets the status of a download.
-     *
-     * @param c A Cursor pointing to a download.  The URL column is assumed to be valid.
-     * @return The status of the download.
-     * @hide
-     */
-    private static final int getStatusOfDownload( Cursor c, long redownload_threshold) {
-        int status = c.getInt(DOWNLOADS_COLUMN_STATUS);
-        long realtime = SystemClock.elapsedRealtime();
-
-        // TODO(dougz): special handling of 503, 404?  (eg, special
-        // explanatory messages to user)
-
-        if (!android.provider.Downloads.Impl.isStatusCompleted(status)) {
-            // Check if it's stuck
-            long modified = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
-            long now = System.currentTimeMillis();
-            if (now < modified || now - modified > redownload_threshold) {
-                return STATUS_INVALID;
-            }
-
-            return STATUS_DOWNLOADING_UPDATE;
-        }
-
-        if (android.provider.Downloads.Impl.isStatusError(status)) {
-            return STATUS_INVALID;
-        }
-
-        String filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
-        if (filename == null) {
-            return STATUS_INVALID;
-        }
-
-        return STATUS_DOWNLOADED_UPDATE;
-    }
-
-
-    /**
-     * @hide
-     */
-    private Downloads() {}
-}
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 442535a..89c9c36 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -30,8 +30,8 @@
 interface INetworkPolicyManager {
 
     /** Control UID policies. */
-    void setUidPolicy(int uid, int policy);
-    int getUidPolicy(int uid);
+    void setAppPolicy(int appId, int policy);
+    int getAppPolicy(int appId);
 
     boolean isUidForeground(int uid);
 
diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java
index 5b94784..c1f58a3 100644
--- a/core/java/android/net/NetworkPolicy.java
+++ b/core/java/android/net/NetworkPolicy.java
@@ -42,18 +42,20 @@
     public long lastWarningSnooze;
     public long lastLimitSnooze;
     public boolean metered;
+    public boolean inferred;
 
     private static final long DEFAULT_MTU = 1500;
 
+    @Deprecated
     public NetworkPolicy(NetworkTemplate template, int cycleDay, String cycleTimezone,
             long warningBytes, long limitBytes, boolean metered) {
         this(template, cycleDay, cycleTimezone, warningBytes, limitBytes, SNOOZE_NEVER,
-                SNOOZE_NEVER, metered);
+                SNOOZE_NEVER, metered, false);
     }
 
     public NetworkPolicy(NetworkTemplate template, int cycleDay, String cycleTimezone,
             long warningBytes, long limitBytes, long lastWarningSnooze, long lastLimitSnooze,
-            boolean metered) {
+            boolean metered, boolean inferred) {
         this.template = checkNotNull(template, "missing NetworkTemplate");
         this.cycleDay = cycleDay;
         this.cycleTimezone = checkNotNull(cycleTimezone, "missing cycleTimezone");
@@ -62,6 +64,7 @@
         this.lastWarningSnooze = lastWarningSnooze;
         this.lastLimitSnooze = lastLimitSnooze;
         this.metered = metered;
+        this.inferred = inferred;
     }
 
     public NetworkPolicy(Parcel in) {
@@ -73,6 +76,7 @@
         lastWarningSnooze = in.readLong();
         lastLimitSnooze = in.readLong();
         metered = in.readInt() != 0;
+        inferred = in.readInt() != 0;
     }
 
     @Override
@@ -85,6 +89,7 @@
         dest.writeLong(lastWarningSnooze);
         dest.writeLong(lastLimitSnooze);
         dest.writeInt(metered ? 1 : 0);
+        dest.writeInt(inferred ? 1 : 0);
     }
 
     @Override
@@ -134,7 +139,7 @@
     @Override
     public int hashCode() {
         return Objects.hashCode(template, cycleDay, cycleTimezone, warningBytes, limitBytes,
-                lastWarningSnooze, lastLimitSnooze, metered);
+                lastWarningSnooze, lastLimitSnooze, metered, inferred);
     }
 
     @Override
@@ -145,6 +150,7 @@
                     && limitBytes == other.limitBytes
                     && lastWarningSnooze == other.lastWarningSnooze
                     && lastLimitSnooze == other.lastLimitSnooze && metered == other.metered
+                    && inferred == other.inferred
                     && Objects.equal(cycleTimezone, other.cycleTimezone)
                     && Objects.equal(template, other.template);
         }
@@ -156,7 +162,7 @@
         return "NetworkPolicy[" + template + "]: cycleDay=" + cycleDay + ", cycleTimezone="
                 + cycleTimezone + ", warningBytes=" + warningBytes + ", limitBytes=" + limitBytes
                 + ", lastWarningSnooze=" + lastWarningSnooze + ", lastLimitSnooze="
-                + lastLimitSnooze + ", metered=" + metered;
+                + lastLimitSnooze + ", metered=" + metered + ", inferred=" + inferred;
     }
 
     public static final Creator<NetworkPolicy> CREATOR = new Creator<NetworkPolicy>() {
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 7173751..c09c676 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -88,21 +88,21 @@
     }
 
     /**
-     * Set policy flags for specific UID.
+     * Set policy flags for specific application.
      *
      * @param policy {@link #POLICY_NONE} or combination of flags like
      *            {@link #POLICY_REJECT_METERED_BACKGROUND}.
      */
-    public void setUidPolicy(int uid, int policy) {
+    public void setAppPolicy(int appId, int policy) {
         try {
-            mService.setUidPolicy(uid, policy);
+            mService.setAppPolicy(appId, policy);
         } catch (RemoteException e) {
         }
     }
 
-    public int getUidPolicy(int uid) {
+    public int getAppPolicy(int appId) {
         try {
-            return mService.getUidPolicy(uid);
+            return mService.getAppPolicy(appId);
         } catch (RemoteException e) {
             return POLICY_NONE;
         }
@@ -203,6 +203,7 @@
      * Check if given UID can have a {@link #setUidPolicy(int, int)} defined,
      * usually to protect critical system services.
      */
+    @Deprecated
     public static boolean isUidValidForPolicy(Context context, int uid) {
         // first, quick-reject non-applications
         if (uid < android.os.Process.FIRST_APPLICATION_UID
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index 5c4b258..6a4f1f2 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -18,13 +18,11 @@
 
 import android.os.SystemProperties;
 import android.util.Log;
-
 import java.io.IOException;
 import java.net.InetAddress;
 import java.net.Socket;
 import java.security.KeyManagementException;
 import java.security.cert.X509Certificate;
-
 import javax.net.SocketFactory;
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.HttpsURLConnection;
@@ -36,7 +34,6 @@
 import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;
-
 import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl;
 import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
 import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
@@ -89,6 +86,7 @@
     private SSLSocketFactory mSecureFactory = null;
     private TrustManager[] mTrustManagers = null;
     private KeyManager[] mKeyManagers = null;
+    private byte[] mNpnProtocols = null;
 
     private final int mHandshakeTimeoutMillis;
     private final SSLClientSessionCache mSessionCache;
@@ -251,6 +249,60 @@
     }
 
     /**
+     * Sets the <a href="http://technotes.googlecode.com/git/nextprotoneg.html">Next
+     * Protocol Negotiation (NPN)</a> protocols that this peer is interested in.
+     *
+     * <p>For servers this is the sequence of protocols to advertise as
+     * supported, in order of preference. This list is sent unencrypted to
+     * all clients that support NPN.
+     *
+     * <p>For clients this is a list of supported protocols to match against the
+     * server's list. If there is no protocol supported by both client and
+     * server then the first protocol in the client's list will be selected.
+     * The order of the client's protocols is otherwise insignificant.
+     *
+     * @param npnProtocols a possibly-empty list of protocol byte arrays. All
+     *     arrays must be non-empty and of length less than 256.
+     */
+    public void setNpnProtocols(byte[][] npnProtocols) {
+        this.mNpnProtocols = toNpnProtocolsList(npnProtocols);
+    }
+
+    /**
+     * Returns an array containing the concatenation of length-prefixed byte
+     * strings.
+     */
+    static byte[] toNpnProtocolsList(byte[]... npnProtocols) {
+        int totalLength = 0;
+        for (byte[] s : npnProtocols) {
+            if (s.length == 0 || s.length > 255) {
+                throw new IllegalArgumentException("s.length == 0 || s.length > 255: " + s.length);
+            }
+            totalLength += 1 + s.length;
+        }
+        byte[] result = new byte[totalLength];
+        int pos = 0;
+        for (byte[] s : npnProtocols) {
+            result[pos++] = (byte) s.length;
+            for (byte b : s) {
+                result[pos++] = b;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Returns the <a href="http://technotes.googlecode.com/git/nextprotoneg.html">Next
+     * Protocol Negotiation (NPN)</a> protocol selected by client and server, or
+     * null if no protocol was negotiated.
+     *
+     * @param socket a socket created by this factory.
+     */
+    public byte[] getNpnSelectedProtocol(Socket socket) {
+        return ((OpenSSLSocketImpl) socket).getNpnSelectedProtocol();
+    }
+
+    /**
      * Sets the {@link KeyManager}s to be used for connections made by this factory.
      */
     public void setKeyManagers(KeyManager[] keyManagers) {
@@ -271,6 +323,7 @@
     @Override
     public Socket createSocket(Socket k, String host, int port, boolean close) throws IOException {
         OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(k, host, port, close);
+        s.setNpnProtocols(mNpnProtocols);
         s.setHandshakeTimeout(mHandshakeTimeoutMillis);
         if (mSecure) {
             verifyHostname(s, host);
@@ -289,6 +342,7 @@
     @Override
     public Socket createSocket() throws IOException {
         OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket();
+        s.setNpnProtocols(mNpnProtocols);
         s.setHandshakeTimeout(mHandshakeTimeoutMillis);
         return s;
     }
@@ -305,6 +359,7 @@
             throws IOException {
         OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(
                 addr, port, localAddr, localPort);
+        s.setNpnProtocols(mNpnProtocols);
         s.setHandshakeTimeout(mHandshakeTimeoutMillis);
         return s;
     }
@@ -319,6 +374,7 @@
     @Override
     public Socket createSocket(InetAddress addr, int port) throws IOException {
         OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(addr, port);
+        s.setNpnProtocols(mNpnProtocols);
         s.setHandshakeTimeout(mHandshakeTimeoutMillis);
         return s;
     }
@@ -334,6 +390,7 @@
             throws IOException {
         OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(
                 host, port, localAddr, localPort);
+        s.setNpnProtocols(mNpnProtocols);
         s.setHandshakeTimeout(mHandshakeTimeoutMillis);
         if (mSecure) {
             verifyHostname(s, host);
@@ -350,6 +407,7 @@
     @Override
     public Socket createSocket(String host, int port) throws IOException {
         OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(host, port);
+        s.setNpnProtocols(mNpnProtocols);
         s.setHandshakeTimeout(mHandshakeTimeoutMillis);
         if (mSecure) {
             verifyHostname(s, host);
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 28206b7..51cb91c8 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -226,6 +226,13 @@
     }
 
     /**
+     * @hide
+     */
+    public boolean isParcelled() {
+        return mParcelledData != null;
+    }
+
+    /**
      * Returns the number of mappings contained in this Bundle.
      *
      * @return the number of mappings as an int.
diff --git a/core/java/android/os/UserId.java b/core/java/android/os/UserId.java
index 0da67d63..8bf6c6e 100644
--- a/core/java/android/os/UserId.java
+++ b/core/java/android/os/UserId.java
@@ -61,6 +61,15 @@
         return uid >= Process.FIRST_ISOLATED_UID && uid <= Process.LAST_ISOLATED_UID;
     }
 
+    public static boolean isApp(int uid) {
+        if (uid > 0) {
+            uid = UserId.getAppId(uid);
+            return uid >= Process.FIRST_APPLICATION_UID && uid <= Process.LAST_APPLICATION_UID;
+        } else {
+            return false;
+        }
+    }
+
     /**
      * Returns the user id for a given uid.
      * @hide
@@ -96,4 +105,12 @@
     public static final int getAppId(int uid) {
         return uid % PER_USER_RANGE;
     }
+
+    /**
+     * Returns the user id of the current process
+     * @return user id of the current process
+     */
+    public static final int myUserId() {
+        return getUserId(Process.myUid());
+    }
 }
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index ba4804d..bd6170b 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -17,6 +17,7 @@
 package android.provider;
 
 import android.app.DownloadManager;
+import android.content.Context;
 import android.net.NetworkPolicyManager;
 import android.net.Uri;
 
@@ -742,4 +743,19 @@
             public static final String INSERT_KEY_PREFIX = "http_header_";
         }
     }
+
+    /**
+     * Query where clause for general querying.
+     */
+    private static final String QUERY_WHERE_CLAUSE = Impl.COLUMN_NOTIFICATION_PACKAGE + "=? AND "
+            + Impl.COLUMN_NOTIFICATION_CLASS + "=?";
+
+    /**
+     * Delete all the downloads for a package/class pair.
+     */
+    public static final void removeAllDownloadsByPackage(
+            Context context, String notification_package, String notification_class) {
+        context.getContentResolver().delete(Impl.CONTENT_URI, QUERY_WHERE_CLAUSE,
+                new String[] { notification_package, notification_class });
+    }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index fbb3273..d74ccb8 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -793,6 +793,7 @@
             MOVED_TO_SECURE.add(Secure.HTTP_PROXY);
             MOVED_TO_SECURE.add(Secure.INSTALL_NON_MARKET_APPS);
             MOVED_TO_SECURE.add(Secure.LOCATION_PROVIDERS_ALLOWED);
+            MOVED_TO_SECURE.add(Secure.LOCK_BIOMETRIC_WEAK_FLAGS);
             MOVED_TO_SECURE.add(Secure.LOCK_PATTERN_ENABLED);
             MOVED_TO_SECURE.add(Secure.LOCK_PATTERN_VISIBLE);
             MOVED_TO_SECURE.add(Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED);
@@ -2657,6 +2658,13 @@
         public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";
 
         /**
+         * A flag containing settings used for biometric weak
+         * @hide
+         */
+        public static final String LOCK_BIOMETRIC_WEAK_FLAGS =
+                "lock_biometric_weak_flags";
+
+        /**
          * Whether autolock is enabled (0 = false, 1 = true)
          */
         public static final String LOCK_PATTERN_ENABLED = "lock_pattern_autolock";
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index fecc8f9..850349b 100755
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -46,6 +46,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.res.Resources;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -554,12 +555,15 @@
     private synchronized void updateSdpRecords() {
         ArrayList<ParcelUuid> uuids = new ArrayList<ParcelUuid>();
 
-        // Add the default records
-        uuids.add(BluetoothUuid.HSP_AG);
-        uuids.add(BluetoothUuid.ObexObjectPush);
+        Resources R = mContext.getResources();
 
-        if (mContext.getResources().
-                getBoolean(com.android.internal.R.bool.config_voice_capable)) {
+        // Add the default records
+        if (R.getBoolean(com.android.internal.R.bool.config_bluetooth_default_profiles)) {
+            uuids.add(BluetoothUuid.HSP_AG);
+            uuids.add(BluetoothUuid.ObexObjectPush);
+        }
+
+        if (R.getBoolean(com.android.internal.R.bool.config_voice_capable)) {
             uuids.add(BluetoothUuid.Handsfree_AG);
             uuids.add(BluetoothUuid.PBAP_PSE);
         }
@@ -567,14 +571,16 @@
         // Add SDP records for profiles maintained by Android userspace
         addReservedSdpRecords(uuids);
 
-        // Enable profiles maintained by Bluez userspace.
-        setBluetoothTetheringNative(true, BluetoothPanProfileHandler.NAP_ROLE,
-                BluetoothPanProfileHandler.NAP_BRIDGE);
+        if (R.getBoolean(com.android.internal.R.bool.config_bluetooth_default_profiles)) {
+            // Enable profiles maintained by Bluez userspace.
+            setBluetoothTetheringNative(true, BluetoothPanProfileHandler.NAP_ROLE,
+                   BluetoothPanProfileHandler.NAP_BRIDGE);
 
-        // Add SDP records for profiles maintained by Bluez userspace
-        uuids.add(BluetoothUuid.AudioSource);
-        uuids.add(BluetoothUuid.AvrcpTarget);
-        uuids.add(BluetoothUuid.NAP);
+            // Add SDP records for profiles maintained by Bluez userspace
+            uuids.add(BluetoothUuid.AudioSource);
+            uuids.add(BluetoothUuid.AvrcpTarget);
+            uuids.add(BluetoothUuid.NAP);
+        }
 
         // Cannot cast uuids.toArray directly since ParcelUuid is parcelable
         mAdapterUuids = new ParcelUuid[uuids.size()];
diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java
index 1dabad2..e2aafa9 100644
--- a/core/java/android/view/DisplayList.java
+++ b/core/java/android/view/DisplayList.java
@@ -16,8 +16,6 @@
 
 package android.view;
 
-import android.os.Handler;
-
 /**
  * A display lists records a series of graphics related operation and can replay
  * them later. Display lists are usually built by recording operations on a
@@ -37,6 +35,30 @@
      */
     public static final int FLAG_CLIP_CHILDREN = 0x1;
 
+    // NOTE: The STATUS_* values *must* match the enum in DrawGlInfo.h
+
+    /**
+     * Indicates that the display list is done drawing.
+     * 
+     * @see HardwareCanvas#drawDisplayList(DisplayList, int, int, android.graphics.Rect, int) 
+     */
+    public static final int STATUS_DONE = 0x0;
+
+    /**
+     * Indicates that the display list needs another drawing pass.
+     * 
+     * @see HardwareCanvas#drawDisplayList(DisplayList, int, int, android.graphics.Rect, int)
+     */
+    public static final int STATUS_DRAW = 0x1;
+
+    /**
+     * Indicates that the display list needs to re-execute its GL functors.
+     * 
+     * @see HardwareCanvas#drawDisplayList(DisplayList, int, int, android.graphics.Rect, int)
+     * @see HardwareCanvas#callDrawGLFunction(int) 
+     */
+    public static final int STATUS_INVOKE = 0x2;
+
     /**
      * Starts recording the display list. All operations performed on the
      * returned canvas are recorded and stored in this display list.
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index d9bf918..9639faf 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -79,25 +79,45 @@
             switch (direction) {
                 case View.FOCUS_RIGHT:
                 case View.FOCUS_DOWN:
+                    setFocusBottomRight(root);
+                    break;
                 case View.FOCUS_FORWARD:
-                    final int rootTop = root.getScrollY();
-                    final int rootLeft = root.getScrollX();
-                    mFocusedRect.set(rootLeft, rootTop, rootLeft, rootTop);
+                    if (focused != null && focused.isLayoutRtl()) {
+                        setFocusTopLeft(root);
+                    } else {
+                        setFocusBottomRight(root);
+                    }
                     break;
 
                 case View.FOCUS_LEFT:
                 case View.FOCUS_UP:
-                case View.FOCUS_BACKWARD:
-                    final int rootBottom = root.getScrollY() + root.getHeight();
-                    final int rootRight = root.getScrollX() + root.getWidth();
-                    mFocusedRect.set(rootRight, rootBottom,
-                            rootRight, rootBottom);
+                    setFocusTopLeft(root);
                     break;
+                case View.FOCUS_BACKWARD:
+                    if (focused != null && focused.isLayoutRtl()) {
+                        setFocusBottomRight(root);
+                    } else {
+                        setFocusTopLeft(root);
+                    break;
+                }
             }
         }
         return findNextFocus(root, focused, mFocusedRect, direction);
     }
 
+    private void setFocusTopLeft(ViewGroup root) {
+        final int rootBottom = root.getScrollY() + root.getHeight();
+        final int rootRight = root.getScrollX() + root.getWidth();
+        mFocusedRect.set(rootRight, rootBottom,
+                rootRight, rootBottom);
+    }
+
+    private void setFocusBottomRight(ViewGroup root) {
+        final int rootTop = root.getScrollY();
+        final int rootLeft = root.getScrollX();
+        mFocusedRect.set(rootLeft, rootTop, rootLeft, rootTop);
+    }
+
     /**
      * Find the next view to take focus in root's descendants, searching from
      * a particular rectangle in root's coordinates.
@@ -135,22 +155,10 @@
             final int count = focusables.size();
             switch (direction) {
                 case View.FOCUS_FORWARD:
-                    if (focused != null) {
-                        int position = focusables.lastIndexOf(focused);
-                        if (position >= 0 && position + 1 < count) {
-                            return focusables.get(position + 1);
-                        }
-                    }
-                    return focusables.get(0);
+                    return getForwardFocusable(focused, focusables, count);
 
                 case View.FOCUS_BACKWARD:
-                    if (focused != null) {
-                        int position = focusables.indexOf(focused);
-                        if (position > 0) {
-                            return focusables.get(position - 1);
-                        }
-                    }
-                    return focusables.get(count - 1);
+                    return getBackwardFocusable(focused, focusables, count);
             }
             return null;
         }
@@ -193,6 +201,38 @@
         return closest;
     }
 
+    private View getForwardFocusable(View focused, ArrayList<View> focusables, int count) {
+        return (focused != null && focused.isLayoutRtl()) ?
+                getPreviousFocusable(focused, focusables, count) :
+                getNextFocusable(focused, focusables, count);
+    }
+
+    private View getNextFocusable(View focused, ArrayList<View> focusables, int count) {
+        if (focused != null) {
+            int position = focusables.lastIndexOf(focused);
+            if (position >= 0 && position + 1 < count) {
+                return focusables.get(position + 1);
+            }
+        }
+        return focusables.get(0);
+    }
+
+    private View getBackwardFocusable(View focused, ArrayList<View> focusables, int count) {
+        return (focused != null && focused.isLayoutRtl()) ?
+                getNextFocusable(focused, focusables, count) :
+                getPreviousFocusable(focused, focusables, count);
+    }
+
+    private View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) {
+        if (focused != null) {
+            int position = focusables.indexOf(focused);
+            if (position > 0) {
+                return focusables.get(position - 1);
+            }
+        }
+        return focusables.get(count - 1);
+    }
+
     /**
      * Is rect1 a better candidate than rect2 for a focus search in a particular
      * direction from a source rect?  This is the core routine that determines
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 1f75e70..0e96742 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -296,11 +296,11 @@
     ///////////////////////////////////////////////////////////////////////////
 
     @Override
-    public boolean callDrawGLFunction(int drawGLFunction) {
+    public int callDrawGLFunction(int drawGLFunction) {
         return nCallDrawGLFunction(mRenderer, drawGLFunction);
     }
 
-    private static native boolean nCallDrawGLFunction(int renderer, int drawGLFunction);
+    private static native int nCallDrawGLFunction(int renderer, int drawGLFunction);
 
     ///////////////////////////////////////////////////////////////////////////
     // Memory
@@ -394,13 +394,13 @@
     private static native void nSetDisplayListName(int displayList, String name);
 
     @Override
-    public boolean drawDisplayList(DisplayList displayList, int width, int height,
+    public int drawDisplayList(DisplayList displayList, int width, int height,
             Rect dirty, int flags) {
         return nDrawDisplayList(mRenderer, ((GLES20DisplayList) displayList).getNativeDisplayList(),
                 width, height, dirty, flags);
     }
 
-    private static native boolean nDrawDisplayList(int renderer, int displayList,
+    private static native int nDrawDisplayList(int renderer, int displayList,
             int width, int height, Rect dirty, int flags);
 
     @Override
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index 838c03c..2636ea2 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -59,11 +59,11 @@
      *        if this method returns true, can be null.
      * @param flags Optional flags about drawing, see {@link DisplayList} for
      *              the possible flags.
-     * 
-     * @return True if the content of the display list requires another
-     *         drawing pass (invalidate()), false otherwise
+     *
+     * @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW} or
+     *         {@link DisplayList#STATUS_INVOKE}
      */
-    public abstract boolean drawDisplayList(DisplayList displayList, int width, int height,
+    public abstract int drawDisplayList(DisplayList displayList, int width, int height,
             Rect dirty, int flags);
 
     /**
@@ -90,10 +90,12 @@
      * This function may return true if an invalidation is needed after the call.
      *
      * @param drawGLFunction A native function pointer
-     * @return true if an invalidate is needed after the call, false otherwise
+     *                       
+     * @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW} or
+     *         {@link DisplayList#STATUS_INVOKE}
      */
-    public boolean callDrawGLFunction(int drawGLFunction) {
+    public int callDrawGLFunction(int drawGLFunction) {
         // Noop - this is done in the display list recorder subclass
-        return false;
+        return DisplayList.STATUS_DONE;
     }
 }
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index d08a61f..d40043f 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -464,8 +464,8 @@
         static final Object[] sEglLock = new Object[0];
         int mWidth = -1, mHeight = -1;
 
-        static final ThreadLocal<Gl20Renderer.Gl20RendererEglContext> sEglContextStorage
-                = new ThreadLocal<Gl20Renderer.Gl20RendererEglContext>();
+        static final ThreadLocal<ManagedEGLContext> sEglContextStorage
+                = new ThreadLocal<ManagedEGLContext>();
 
         EGLContext mEglContext;
         Thread mEglThread;
@@ -622,7 +622,7 @@
             }
         }
 
-        abstract GLES20Canvas createCanvas();
+        abstract HardwareCanvas createCanvas();
 
         abstract int[] getConfig(boolean dirtyRegions);
 
@@ -662,16 +662,18 @@
                 }
             }
 
-            Gl20Renderer.Gl20RendererEglContext managedContext = sEglContextStorage.get();
+            ManagedEGLContext managedContext = sEglContextStorage.get();
             mEglContext = managedContext != null ? managedContext.getContext() : null;
             mEglThread = Thread.currentThread();
 
             if (mEglContext == null) {
                 mEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
-                sEglContextStorage.set(new Gl20Renderer.Gl20RendererEglContext(mEglContext));
+                sEglContextStorage.set(createManagedContext(mEglContext));
             }
         }
 
+        abstract ManagedEGLContext createManagedContext(EGLContext eglContext);
+
         private EGLConfig chooseEglConfig() {
             EGLConfig[] configs = new EGLConfig[1];
             int[] configsCount = new int[1];
@@ -704,7 +706,7 @@
             return null;
         }
 
-        private void printConfig(EGLConfig config) {
+        private static void printConfig(EGLConfig config) {
             int[] value = new int[1];
 
             Log.d(LOG_TAG, "EGL configuration " + config + ":");
@@ -964,7 +966,6 @@
                                 Log.d("DLProperties", "getDisplayList():\t" +
                                         mProfileData[mProfileCurrentFrame]);
                             }
-
                         }
 
                         if (displayList != null) {
@@ -973,7 +974,7 @@
                                 drawDisplayListStartTime = System.nanoTime();
                             }
 
-                            boolean invalidateNeeded = canvas.drawDisplayList(displayList,
+                            int status = canvas.drawDisplayList(displayList,
                                     view.getWidth(), view.getHeight(), mRedrawClip,
                                     DisplayList.FLAG_CLIP_CHILDREN);
 
@@ -984,18 +985,18 @@
 
                                 if (ViewDebug.DEBUG_LATENCY) {
                                     Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- drawDisplayList() took " +
-                                            total + "ms, invalidateNeeded=" +
-                                            invalidateNeeded + ".");
+                                            total + "ms, status=" + status);
                                 }
                             }
 
-                            if (invalidateNeeded) {
-                                if (mRedrawClip.isEmpty() || view.getParent() == null) {
-                                    view.invalidate();
+                            if (status != DisplayList.STATUS_DONE) {
+                                if (mRedrawClip.isEmpty()) {
+                                    attachInfo.mViewRootImpl.invalidate();
                                 } else {
-                                    view.getParent().invalidateChild(view, mRedrawClip);
+                                    attachInfo.mViewRootImpl.invalidateChildInParent(
+                                            null, mRedrawClip);
+                                    mRedrawClip.setEmpty();
                                 }
-                                mRedrawClip.setEmpty();
                             }
                         } else {
                             // Shouldn't reach here
@@ -1102,7 +1103,8 @@
                 // Make sure we do this on the correct thread.
                 if (mHandler.getLooper() != Looper.myLooper()) {
                     mHandler.post(new Runnable() {
-                        @Override public void run() {
+                        @Override
+                        public void run() {
                             onTerminate(eglContext);
                         }
                     });
@@ -1117,6 +1119,7 @@
                         GLES20Canvas.terminateCaches();
 
                         sEgl.eglDestroyContext(sEglDisplay, eglContext);
+                        sEglContextStorage.set(null);
                         sEglContextStorage.remove();
 
                         sEgl.eglDestroySurface(sEglDisplay, sPbuffer);
@@ -1130,7 +1133,6 @@
                         sEglDisplay = null;
                         sEglConfig = null;
                         sPbuffer = null;
-                        sEglContextStorage.set(null);
                     }
                 }
             }
@@ -1141,11 +1143,16 @@
         }
 
         @Override
-        GLES20Canvas createCanvas() {
+        HardwareCanvas createCanvas() {
             return mGlCanvas = new GLES20Canvas(mTranslucent);
         }
 
         @Override
+        ManagedEGLContext createManagedContext(EGLContext eglContext) {
+            return new Gl20Renderer.Gl20RendererEglContext(mEglContext);
+        }
+
+        @Override
         int[] getConfig(boolean dirtyRegions) {
             return new int[] {
                     EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
@@ -1229,7 +1236,7 @@
         }
 
         private static void destroyHardwareLayer(View view) {
-            view.destroyLayer();
+            view.destroyLayer(true);
 
             if (view instanceof ViewGroup) {
                 ViewGroup group = (ViewGroup) view;
@@ -1248,7 +1255,8 @@
                 if (isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) needsContext = false;
 
                 if (needsContext) {
-                    Gl20RendererEglContext managedContext = sEglContextStorage.get();
+                    Gl20RendererEglContext managedContext =
+                            (Gl20RendererEglContext) sEglContextStorage.get();
                     if (managedContext == null) return;
                     usePbufferSurface(managedContext.getContext());
                 }
@@ -1281,7 +1289,8 @@
         static void trimMemory(int level) {
             if (sEgl == null || sEglConfig == null) return;
 
-            Gl20RendererEglContext managedContext = sEglContextStorage.get();
+            Gl20RendererEglContext managedContext =
+                    (Gl20RendererEglContext) sEglContextStorage.get();
             // We do not have OpenGL objects
             if (managedContext == null) {
                 return;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index c54d09e..14cd48f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -89,6 +89,8 @@
     void prepareAppTransition(int transit, boolean alwaysKeepCurrent);
     int getPendingAppTransition();
     void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim);
+    void overridePendingAppTransitionThumb(in Bitmap srcThumb, int startX, int startY,
+            IRemoteCallback startedCallback);
     void executeAppTransition();
     void setAppStartingWindow(IBinder token, String pkg, int theme,
             in CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 92e8f4e..77fd8d2 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1654,14 +1654,22 @@
             }
         }
     }
-    
+
     /**
-     * Scales down the coordination of this event by the given scale.
+     * Applies a scale factor to all points within this event.
      *
+     * This method is used to adjust touch events to simulate different density
+     * displays for compatibility mode.  The values returned by {@link #getRawX()},
+     * {@link #getRawY()}, {@link #getXPrecision()} and {@link #getYPrecision()}
+     * are also affected by the scale factor.
+     *
+     * @param scale The scale factor to apply.
      * @hide
      */
     public final void scale(float scale) {
-        nativeScale(mNativePtr, scale);
+        if (scale != 1.0f) {
+            nativeScale(mNativePtr, scale);
+        }
     }
 
     /** {@inheritDoc} */
@@ -2631,7 +2639,9 @@
      * @param deltaY Amount to add to the current Y coordinate of the event.
      */
     public final void offsetLocation(float deltaX, float deltaY) {
-        nativeOffsetLocation(mNativePtr, deltaX, deltaY);
+        if (deltaX != 0.0f || deltaY != 0.0f) {
+            nativeOffsetLocation(mNativePtr, deltaX, deltaY);
+        }
     }
 
     /**
@@ -2644,7 +2654,7 @@
     public final void setLocation(float x, float y) {
         float oldX = getX();
         float oldY = getY();
-        nativeOffsetLocation(mNativePtr, x - oldX, y - oldY);
+        offsetLocation(x - oldX, y - oldY);
     }
     
     /**
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index edaa262..eb80290d 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -20,6 +20,7 @@
 import android.graphics.*;
 import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.SystemProperties;
 import android.util.Log;
 
 /**
@@ -35,6 +36,15 @@
     public static final int ROTATION_180     = 2;
     public static final int ROTATION_270     = 3;
 
+    private static final boolean headless = "1".equals(
+        SystemProperties.get("ro.config.headless", "0"));
+
+    private static void checkHeadless() {
+        if(headless) {
+            throw new UnsupportedOperationException("Device is headless");
+        }
+    }
+
     /**
      * Create Surface from a {@link SurfaceTexture}.
      *
@@ -46,6 +56,8 @@
      * Surface.
      */
     public Surface(SurfaceTexture surfaceTexture) {
+        checkHeadless();
+
         if (DEBUG_RELEASE) {
             mCreationStack = new Exception();
         }
@@ -244,6 +256,8 @@
     public Surface(SurfaceSession s,
             int pid, int display, int w, int h, int format, int flags)
         throws OutOfResourcesException {
+        checkHeadless();
+
         if (DEBUG_RELEASE) {
             mCreationStack = new Exception();
         }
@@ -255,6 +269,8 @@
     public Surface(SurfaceSession s,
             int pid, String name, int display, int w, int h, int format, int flags)
         throws OutOfResourcesException {
+        checkHeadless();
+
         if (DEBUG_RELEASE) {
             mCreationStack = new Exception();
         }
@@ -269,6 +285,8 @@
      * @hide
      */
     public Surface() {
+        checkHeadless();
+
         if (DEBUG_RELEASE) {
             mCreationStack = new Exception();
         }
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index fc02cc1..83999a1 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -299,7 +299,7 @@
     }
 
     @Override
-    boolean destroyLayer() {
+    boolean destroyLayer(boolean valid) {
         return false;
     }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index fdf3a814..2deeba6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -698,14 +698,14 @@
     private static final int[] VISIBILITY_FLAGS = {VISIBLE, INVISIBLE, GONE};
 
     /**
-     * This view is enabled. Intrepretation varies by subclass.
+     * This view is enabled. Interpretation varies by subclass.
      * Use with ENABLED_MASK when calling setFlags.
      * {@hide}
      */
     static final int ENABLED = 0x00000000;
 
     /**
-     * This view is disabled. Intrepretation varies by subclass.
+     * This view is disabled. Interpretation varies by subclass.
      * Use with ENABLED_MASK when calling setFlags.
      * {@hide}
      */
@@ -955,50 +955,6 @@
     static final int PARENT_SAVE_DISABLED_MASK = 0x20000000;
 
     /**
-     * Horizontal direction of this view is from Left to Right.
-     * Use with {@link #setLayoutDirection}.
-     */
-    public static final int LAYOUT_DIRECTION_LTR = 0x00000000;
-
-    /**
-     * Horizontal direction of this view is from Right to Left.
-     * Use with {@link #setLayoutDirection}.
-     */
-    public static final int LAYOUT_DIRECTION_RTL = 0x40000000;
-
-    /**
-     * Horizontal direction of this view is inherited from its parent.
-     * Use with {@link #setLayoutDirection}.
-     */
-    public static final int LAYOUT_DIRECTION_INHERIT = 0x80000000;
-
-    /**
-     * Horizontal direction of this view is from deduced from the default language
-     * script for the locale. Use with {@link #setLayoutDirection}.
-     */
-    public static final int LAYOUT_DIRECTION_LOCALE = 0xC0000000;
-
-    /**
-     * Mask for use with setFlags indicating bits used for horizontalDirection.
-     * {@hide}
-     */
-    static final int LAYOUT_DIRECTION_MASK = 0xC0000000;
-
-    /*
-     * Array of horizontal direction flags for mapping attribute "horizontalDirection" to correct
-     * flag value.
-     * {@hide}
-     */
-    private static final int[] LAYOUT_DIRECTION_FLAGS = {LAYOUT_DIRECTION_LTR,
-        LAYOUT_DIRECTION_RTL, LAYOUT_DIRECTION_INHERIT, LAYOUT_DIRECTION_LOCALE};
-
-    /**
-     * Default horizontalDirection.
-     * {@hide}
-     */
-    private static final int LAYOUT_DIRECTION_DEFAULT = LAYOUT_DIRECTION_INHERIT;
-
-    /**
      * View flag indicating whether {@link #addFocusables(ArrayList, int, int)}
      * should add all focusable Views regardless if they are focusable in touch mode.
      */
@@ -1748,19 +1704,77 @@
     static final int DRAG_HOVERED                 = 0x00000002;
 
     /**
-     * Indicates whether the view layout direction has been resolved and drawn to the
-     * right-to-left direction.
-     *
-     * @hide
+     * Horizontal layout direction of this view is from Left to Right.
+     * Use with {@link #setLayoutDirection}.
      */
-    static final int LAYOUT_DIRECTION_RESOLVED_RTL = 0x00000004;
+    public static final int LAYOUT_DIRECTION_LTR = 0;
 
     /**
-     * Indicates whether the view layout direction has been resolved.
-     *
+     * Horizontal layout direction of this view is from Right to Left.
+     * Use with {@link #setLayoutDirection}.
+     */
+    public static final int LAYOUT_DIRECTION_RTL = 1;
+
+    /**
+     * Horizontal layout direction of this view is inherited from its parent.
+     * Use with {@link #setLayoutDirection}.
+     */
+    public static final int LAYOUT_DIRECTION_INHERIT = 2;
+
+    /**
+     * Horizontal layout direction of this view is from deduced from the default language
+     * script for the locale. Use with {@link #setLayoutDirection}.
+     */
+    public static final int LAYOUT_DIRECTION_LOCALE = 3;
+
+    /**
+     * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED)
      * @hide
      */
-    static final int LAYOUT_DIRECTION_RESOLVED = 0x00000008;
+    static final int LAYOUT_DIRECTION_MASK_SHIFT = 2;
+
+    /**
+     * Mask for use with private flags indicating bits used for horizontal layout direction.
+     * @hide
+     */
+    static final int LAYOUT_DIRECTION_MASK = 0x00000003 << LAYOUT_DIRECTION_MASK_SHIFT;
+
+    /**
+     * Indicates whether the view horizontal layout direction has been resolved and drawn to the
+     * right-to-left direction.
+     * @hide
+     */
+    static final int LAYOUT_DIRECTION_RESOLVED_RTL = 4 << LAYOUT_DIRECTION_MASK_SHIFT;
+
+    /**
+     * Indicates whether the view horizontal layout direction has been resolved.
+     * @hide
+     */
+    static final int LAYOUT_DIRECTION_RESOLVED = 8 << LAYOUT_DIRECTION_MASK_SHIFT;
+
+    /**
+     * Mask for use with private flags indicating bits used for resolved horizontal layout direction.
+     * @hide
+     */
+    static final int LAYOUT_DIRECTION_RESOLVED_MASK = 0x0000000C << LAYOUT_DIRECTION_MASK_SHIFT;
+
+    /*
+     * Array of horizontal layout direction flags for mapping attribute "layoutDirection" to correct
+     * flag value.
+     * @hide
+     */
+    private static final int[] LAYOUT_DIRECTION_FLAGS = {
+            LAYOUT_DIRECTION_LTR,
+            LAYOUT_DIRECTION_RTL,
+            LAYOUT_DIRECTION_INHERIT,
+            LAYOUT_DIRECTION_LOCALE
+    };
+
+    /**
+     * Default horizontal layout direction.
+     * @hide
+     */
+    private static final int LAYOUT_DIRECTION_DEFAULT = LAYOUT_DIRECTION_INHERIT;
 
 
     /**
@@ -1770,7 +1784,98 @@
      *
      * @hide
      */
-    static final int HAS_TRANSIENT_STATE = 0x00000010;
+    static final int HAS_TRANSIENT_STATE = 0x00000100;
+
+
+    /**
+     * Text direction is inherited thru {@link ViewGroup}
+     */
+    public static final int TEXT_DIRECTION_INHERIT = 0;
+
+    /**
+     * Text direction is using "first strong algorithm". The first strong directional character
+     * determines the paragraph direction. If there is no strong directional character, the
+     * paragraph direction is the view's resolved layout direction.
+     */
+    public static final int TEXT_DIRECTION_FIRST_STRONG = 1;
+
+    /**
+     * Text direction is using "any-RTL" algorithm. The paragraph direction is RTL if it contains
+     * any strong RTL character, otherwise it is LTR if it contains any strong LTR characters.
+     * If there are neither, the paragraph direction is the view's resolved layout direction.
+     */
+    public static final int TEXT_DIRECTION_ANY_RTL = 2;
+
+    /**
+     * Text direction is forced to LTR.
+     */
+    public static final int TEXT_DIRECTION_LTR = 3;
+
+    /**
+     * Text direction is forced to RTL.
+     */
+    public static final int TEXT_DIRECTION_RTL = 4;
+
+    /**
+     * Text direction is coming from the system Locale.
+     */
+    public static final int TEXT_DIRECTION_LOCALE = 5;
+
+    /**
+     * Bit shift to get the horizontal layout direction. (bits after LAYOUT_DIRECTION_RESOLVED)
+     * @hide
+     */
+    static final int TEXT_DIRECTION_MASK_SHIFT = 6;
+
+    /**
+     * Default text direction is inherited
+     */
+    protected static int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT;
+
+    /**
+     * Mask for use with private flags indicating bits used for text direction.
+     * @hide
+     */
+    static final int TEXT_DIRECTION_MASK = 0x00000007 << TEXT_DIRECTION_MASK_SHIFT;
+
+    /**
+     * Array of text direction flags for mapping attribute "textDirection" to correct
+     * flag value.
+     * @hide
+     */
+    private static final int[] TEXT_DIRECTION_FLAGS = {
+            TEXT_DIRECTION_INHERIT << TEXT_DIRECTION_MASK_SHIFT,
+            TEXT_DIRECTION_FIRST_STRONG << TEXT_DIRECTION_MASK_SHIFT,
+            TEXT_DIRECTION_ANY_RTL << TEXT_DIRECTION_MASK_SHIFT,
+            TEXT_DIRECTION_LTR << TEXT_DIRECTION_MASK_SHIFT,
+            TEXT_DIRECTION_RTL << TEXT_DIRECTION_MASK_SHIFT,
+            TEXT_DIRECTION_LOCALE << TEXT_DIRECTION_MASK_SHIFT
+    };
+
+    /**
+     * Indicates whether the view text direction has been resolved.
+     * @hide
+     */
+    static final int TEXT_DIRECTION_RESOLVED = 0x00000008 << TEXT_DIRECTION_MASK_SHIFT;
+
+    /**
+     * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED)
+     * @hide
+     */
+    static final int TEXT_DIRECTION_RESOLVED_MASK_SHIFT = 10;
+
+    /**
+     * Mask for use with private flags indicating bits used for resolved text direction.
+     * @hide
+     */
+    static final int TEXT_DIRECTION_RESOLVED_MASK = 0x00000007 << TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
+
+    /**
+     * Indicates whether the view text direction has been resolved to the "first strong" heuristic.
+     * @hide
+     */
+    static final int TEXT_DIRECTION_RESOLVED_DEFAULT =
+            TEXT_DIRECTION_FIRST_STRONG << TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
 
 
     /* End of masks for mPrivateFlags2 */
@@ -2647,82 +2752,6 @@
     AccessibilityDelegate mAccessibilityDelegate;
 
     /**
-     * Text direction is inherited thru {@link ViewGroup}
-     */
-    public static final int TEXT_DIRECTION_INHERIT = 0;
-
-    /**
-     * Text direction is using "first strong algorithm". The first strong directional character
-     * determines the paragraph direction. If there is no strong directional character, the
-     * paragraph direction is the view's resolved layout direction.
-     *
-     */
-    public static final int TEXT_DIRECTION_FIRST_STRONG = 1;
-
-    /**
-     * Text direction is using "any-RTL" algorithm. The paragraph direction is RTL if it contains
-     * any strong RTL character, otherwise it is LTR if it contains any strong LTR characters.
-     * If there are neither, the paragraph direction is the view's resolved layout direction.
-     *
-     */
-    public static final int TEXT_DIRECTION_ANY_RTL = 2;
-
-    /**
-     * Text direction is forced to LTR.
-     *
-     */
-    public static final int TEXT_DIRECTION_LTR = 3;
-
-    /**
-     * Text direction is forced to RTL.
-     *
-     */
-    public static final int TEXT_DIRECTION_RTL = 4;
-
-    /**
-     * Text direction is coming from the system Locale.
-     *
-     */
-    public static final int TEXT_DIRECTION_LOCALE = 5;
-
-    /**
-     * Default text direction is inherited
-     *
-     */
-    protected static int DEFAULT_TEXT_DIRECTION = TEXT_DIRECTION_INHERIT;
-
-    /**
-     * The text direction that has been defined by {@link #setTextDirection(int)}.
-     *
-     */
-    @ViewDebug.ExportedProperty(category = "text", mapping = {
-            @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"),
-            @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"),
-            @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"),
-            @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"),
-            @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"),
-            @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE")
-    })
-    private int mTextDirection = DEFAULT_TEXT_DIRECTION;
-
-    /**
-     * The resolved text direction.  This needs resolution if the value is
-     * TEXT_DIRECTION_INHERIT.  The resolution matches mTextDirection if it is
-     * not TEXT_DIRECTION_INHERIT, otherwise resolution proceeds up the parent
-     * chain of the view.
-     *
-     */
-    @ViewDebug.ExportedProperty(category = "text", mapping = {
-            @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"),
-            @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"),
-            @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"),
-            @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"),
-            @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"),
-            @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE")
-    })
-    private int mResolvedTextDirection = TEXT_DIRECTION_INHERIT;
-
-    /**
      * Consistency verifier for debugging purposes.
      * @hide
      */
@@ -2739,7 +2768,10 @@
     public View(Context context) {
         mContext = context;
         mResources = context != null ? context.getResources() : null;
-        mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | LAYOUT_DIRECTION_INHERIT;
+        mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
+        // Set layout and text direction defaults
+        mPrivateFlags2 = (LAYOUT_DIRECTION_DEFAULT << LAYOUT_DIRECTION_MASK_SHIFT) |
+                (TEXT_DIRECTION_DEFAULT << TEXT_DIRECTION_MASK_SHIFT);
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
         mUserPaddingStart = -1;
@@ -2949,17 +2981,13 @@
                     }
                     break;
                 case com.android.internal.R.styleable.View_layoutDirection:
-                    // Clear any HORIZONTAL_DIRECTION flag already set
-                    viewFlagValues &= ~LAYOUT_DIRECTION_MASK;
-                    // Set the HORIZONTAL_DIRECTION flags depending on the value of the attribute
+                    // Clear any layout direction flags (included resolved bits) already set
+                    mPrivateFlags2 &= ~(LAYOUT_DIRECTION_MASK | LAYOUT_DIRECTION_RESOLVED_MASK);
+                    // Set the layout direction flags depending on the value of the attribute
                     final int layoutDirection = a.getInt(attr, -1);
-                    if (layoutDirection != -1) {
-                        viewFlagValues |= LAYOUT_DIRECTION_FLAGS[layoutDirection];
-                    } else {
-                        // Set to default (LAYOUT_DIRECTION_INHERIT)
-                        viewFlagValues |= LAYOUT_DIRECTION_DEFAULT;
-                    }
-                    viewFlagMasks |= LAYOUT_DIRECTION_MASK;
+                    final int value = (layoutDirection != -1) ?
+                            LAYOUT_DIRECTION_FLAGS[layoutDirection] : LAYOUT_DIRECTION_DEFAULT;
+                    mPrivateFlags2 |= (value << LAYOUT_DIRECTION_MASK_SHIFT);
                     break;
                 case com.android.internal.R.styleable.View_drawingCacheQuality:
                     final int cacheQuality = a.getInt(attr, 0);
@@ -3103,7 +3131,13 @@
                     setLayerType(a.getInt(attr, LAYER_TYPE_NONE), null);
                     break;
                 case R.styleable.View_textDirection:
-                    mTextDirection = a.getInt(attr, DEFAULT_TEXT_DIRECTION);
+                    // Clear any text direction flag already set
+                    mPrivateFlags2 &= ~TEXT_DIRECTION_MASK;
+                    // Set the text direction flags depending on the value of the attribute
+                    final int textDirection = a.getInt(attr, -1);
+                    if (textDirection != -1) {
+                        mPrivateFlags2 |= TEXT_DIRECTION_FLAGS[textDirection];
+                    }
                     break;
             }
         }
@@ -4882,7 +4916,7 @@
         @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LOCALE,  to = "LOCALE")
     })
     public int getLayoutDirection() {
-        return mViewFlags & LAYOUT_DIRECTION_MASK;
+        return (mPrivateFlags2 & LAYOUT_DIRECTION_MASK) >> LAYOUT_DIRECTION_MASK_SHIFT;
     }
 
     /**
@@ -4899,9 +4933,13 @@
     @RemotableViewMethod
     public void setLayoutDirection(int layoutDirection) {
         if (getLayoutDirection() != layoutDirection) {
+            // Reset the current layout direction and the resolved one
+            mPrivateFlags2 &= ~LAYOUT_DIRECTION_MASK;
             resetResolvedLayoutDirection();
-            // Setting the flag will also request a layout.
-            setFlags(layoutDirection, LAYOUT_DIRECTION_MASK);
+            // Set the new layout direction (filtered) and ask for a layout pass
+            mPrivateFlags2 |=
+                    ((layoutDirection << LAYOUT_DIRECTION_MASK_SHIFT) & LAYOUT_DIRECTION_MASK);
+            requestLayout();
         }
     }
 
@@ -4909,21 +4947,24 @@
      * Returns the resolved layout direction for this view.
      *
      * @return {@link #LAYOUT_DIRECTION_RTL} if the layout direction is RTL or returns
-     * {@link #LAYOUT_DIRECTION_LTR} id the layout direction is not RTL.
+     * {@link #LAYOUT_DIRECTION_LTR} if the layout direction is not RTL.
      */
     @ViewDebug.ExportedProperty(category = "layout", mapping = {
-        @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR,     to = "RESOLVED_DIRECTION_LTR"),
-        @ViewDebug.IntToString(from = LAYOUT_DIRECTION_RTL,     to = "RESOLVED_DIRECTION_RTL")
+        @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR, to = "RESOLVED_DIRECTION_LTR"),
+        @ViewDebug.IntToString(from = LAYOUT_DIRECTION_RTL, to = "RESOLVED_DIRECTION_RTL")
     })
     public int getResolvedLayoutDirection() {
-        resolveLayoutDirectionIfNeeded();
+        // The layout diretion will be resolved only if needed
+        if ((mPrivateFlags2 & LAYOUT_DIRECTION_RESOLVED) != LAYOUT_DIRECTION_RESOLVED) {
+            resolveLayoutDirection();
+        }
         return ((mPrivateFlags2 & LAYOUT_DIRECTION_RESOLVED_RTL) == LAYOUT_DIRECTION_RESOLVED_RTL) ?
                 LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR;
     }
 
     /**
-     * <p>Indicates whether or not this view's layout is right-to-left. This is resolved from
-     * layout attribute and/or the inherited value from the parent.</p>
+     * Indicates whether or not this view's layout is right-to-left. This is resolved from
+     * layout attribute and/or the inherited value from the parent
      *
      * @return true if the layout is right-to-left.
      */
@@ -6940,10 +6981,6 @@
                 mParent.recomputeViewAttributes(this);
             }
         }
-
-        if ((changed & LAYOUT_DIRECTION_MASK) != 0) {
-            requestLayout();
-        }
     }
 
     /**
@@ -7364,7 +7401,7 @@
 
         invalidateViewProperty(false, false);
         if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) {
-            mDisplayList.setCameraDistance(distance);
+            mDisplayList.setCameraDistance(-Math.abs(distance) / dpi);
         }
     }
 
@@ -9797,7 +9834,7 @@
         jumpDrawablesToCurrentState();
         // Order is important here: LayoutDirection MUST be resolved before Padding
         // and TextDirection
-        resolveLayoutDirectionIfNeeded();
+        resolveLayoutDirection();
         resolvePadding();
         resolveTextDirection();
         if (isFocused()) {
@@ -9828,31 +9865,24 @@
     /**
      * Resolve and cache the layout direction. LTR is set initially. This is implicitly supposing
      * that the parent directionality can and will be resolved before its children.
+     * Will call {@link View#onResolvedLayoutDirectionChanged} when resolution is done.
      */
-    private void resolveLayoutDirectionIfNeeded() {
-        // Do not resolve if it is not needed
-        if ((mPrivateFlags2 & LAYOUT_DIRECTION_RESOLVED) == LAYOUT_DIRECTION_RESOLVED) return;
-
+    public void resolveLayoutDirection() {
         // Clear any previous layout direction resolution
-        mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED_RTL;
+        mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED_MASK;
 
         // Set resolved depending on layout direction
         switch (getLayoutDirection()) {
             case LAYOUT_DIRECTION_INHERIT:
-                // We cannot do the resolution if there is no parent
-                if (mParent == null) return;
-
                 // If this is root view, no need to look at parent's layout dir.
-                if (mParent instanceof ViewGroup) {
+                if (canResolveLayoutDirection()) {
                     ViewGroup viewGroup = ((ViewGroup) mParent);
 
-                    // Check if the parent view group can resolve
-                    if (! viewGroup.canResolveLayoutDirection()) {
-                        return;
-                    }
                     if (viewGroup.getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) {
                         mPrivateFlags2 |= LAYOUT_DIRECTION_RESOLVED_RTL;
                     }
+                } else {
+                    // Nothing to do, LTR by default
                 }
                 break;
             case LAYOUT_DIRECTION_RTL:
@@ -9955,7 +9985,7 @@
     public boolean canResolveLayoutDirection() {
         switch (getLayoutDirection()) {
             case LAYOUT_DIRECTION_INHERIT:
-                return (mParent != null);
+                return (mParent != null) && (mParent instanceof ViewGroup);
             default:
                 return true;
         }
@@ -9966,8 +9996,8 @@
      * when reset is done.
      */
     public void resetResolvedLayoutDirection() {
-        // Reset the current View resolution
-        mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED;
+        // Reset the current resolved bits
+        mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED_MASK;
         onResolvedLayoutDirectionReset();
         // Reset also the text direction
         resetResolvedTextDirection();
@@ -10010,7 +10040,7 @@
 
         destroyDrawingCache();
 
-        destroyLayer();
+        destroyLayer(false);
 
         if (mAttachInfo != null) {
             if (mDisplayList != null) {
@@ -10386,7 +10416,7 @@
         // Destroy any previous software drawing cache if needed
         switch (mLayerType) {
             case LAYER_TYPE_HARDWARE:
-                destroyLayer();
+                destroyLayer(false);
                 // fall through - non-accelerated views may use software layer mechanism instead
             case LAYER_TYPE_SOFTWARE:
                 destroyDrawingCache();
@@ -10524,11 +10554,12 @@
      * @see #setLayerType(int, android.graphics.Paint) 
      * @see #LAYER_TYPE_HARDWARE
      */
-    boolean destroyLayer() {
+    boolean destroyLayer(boolean valid) {
         if (mHardwareLayer != null) {
             AttachInfo info = mAttachInfo;
             if (info != null && info.mHardwareRenderer != null &&
-                    info.mHardwareRenderer.isEnabled() && info.mHardwareRenderer.validate()) {
+                    info.mHardwareRenderer.isEnabled() &&
+                    (valid || info.mHardwareRenderer.validate())) {
                 mHardwareLayer.destroy();
                 mHardwareLayer = null;
 
@@ -10552,7 +10583,7 @@
      * @hide
      */
     protected void destroyHardwareResources() {
-        destroyLayer();
+        destroyLayer(true);
     }
 
     /**
@@ -10716,16 +10747,25 @@
                 int layerType = (
                         !(mParent instanceof ViewGroup) || ((ViewGroup)mParent).mDrawLayers) ?
                         getLayerType() : LAYER_TYPE_NONE;
-                if (!isLayer && layerType == LAYER_TYPE_HARDWARE && USE_DISPLAY_LIST_PROPERTIES) {
-                    final HardwareLayer layer = getHardwareLayer();
-                    if (layer != null && layer.isValid()) {
-                        canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint);
+                if (!isLayer && layerType != LAYER_TYPE_NONE && USE_DISPLAY_LIST_PROPERTIES) {
+                    if (layerType == LAYER_TYPE_HARDWARE) {
+                        final HardwareLayer layer = getHardwareLayer();
+                        if (layer != null && layer.isValid()) {
+                            canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint);
+                        } else {
+                            canvas.saveLayer(0, 0, mRight - mLeft, mBottom - mTop, mLayerPaint,
+                                    Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
+                                            Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+                        }
+                        caching = true;
                     } else {
-                        canvas.saveLayer(0, 0,
-                                mRight - mLeft, mBottom - mTop, mLayerPaint,
-                                Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
+                        buildDrawingCache(true);
+                        Bitmap cache = getDrawingCache(true);
+                        if (cache != null) {
+                            canvas.drawBitmap(cache, 0, 0, mLayerPaint);
+                            caching = true;
+                        }
                     }
-                    caching = true;
                 } else {
 
                     computeScroll();
@@ -11364,7 +11404,11 @@
                         mTransformationInfo.mRotation, mTransformationInfo.mRotationX,
                         mTransformationInfo.mRotationY, mTransformationInfo.mScaleX,
                         mTransformationInfo.mScaleY);
-                displayList.setCameraDistance(getCameraDistance());
+                if (mTransformationInfo.mCamera == null) {
+                    mTransformationInfo.mCamera = new Camera();
+                    mTransformationInfo.matrix3D = new Matrix();
+                }
+                displayList.setCameraDistance(mTransformationInfo.mCamera.getLocationZ());
                 if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == PIVOT_EXPLICITLY_SET) {
                     displayList.setPivotX(getPivotX());
                     displayList.setPivotY(getPivotY());
@@ -11458,8 +11502,12 @@
             } else {
                 switch (layerType) {
                     case LAYER_TYPE_SOFTWARE:
-                        buildDrawingCache(true);
-                        cache = getDrawingCache(true);
+                        if (useDisplayListProperties) {
+                            hasDisplayList = canHaveDisplayList();
+                        } else {
+                            buildDrawingCache(true);
+                            cache = getDrawingCache(true);
+                        }
                         break;
                     case LAYER_TYPE_HARDWARE:
                         if (useDisplayListProperties) {
@@ -11481,7 +11529,7 @@
                 layerType != LAYER_TYPE_HARDWARE;
 
         int restoreTo = -1;
-        if (!useDisplayListProperties) {
+        if (!useDisplayListProperties || transformToApply != null) {
             restoreTo = canvas.save();
         }
         if (offsetForScroll) {
@@ -11515,11 +11563,9 @@
                     if (concatMatrix) {
                         // Undo the scroll translation, apply the transformation matrix,
                         // then redo the scroll translate to get the correct result.
-                        if (!useDisplayListProperties) {
-                            canvas.translate(-transX, -transY);
-                            canvas.concat(transformToApply.getMatrix());
-                            canvas.translate(transX, transY);
-                        }
+                        canvas.translate(-transX, -transY);
+                        canvas.concat(transformToApply.getMatrix());
+                        canvas.translate(transX, transY);
                         parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                     }
 
@@ -11548,12 +11594,10 @@
                             layerFlags |= Canvas.CLIP_TO_LAYER_SAVE_FLAG;
                         }
                         if (layerType == LAYER_TYPE_NONE) {
-                            if (!useDisplayListProperties) {
-                                final int scrollX = hasDisplayList ? 0 : sx;
-                                final int scrollY = hasDisplayList ? 0 : sy;
-                                canvas.saveLayerAlpha(scrollX, scrollY, scrollX + mRight - mLeft,
-                                        scrollY + mBottom - mTop, multipliedAlpha, layerFlags);
-                            }
+                            final int scrollX = hasDisplayList ? 0 : sx;
+                            final int scrollY = hasDisplayList ? 0 : sy;
+                            canvas.saveLayerAlpha(scrollX, scrollY, scrollX + mRight - mLeft,
+                                    scrollY + mBottom - mTop, multipliedAlpha, layerFlags);
                         }
                     } else {
                         // Alpha is handled by the child directly, clobber the layer's alpha
@@ -14487,8 +14531,16 @@
      * {@link #TEXT_DIRECTION_RTL},
      * {@link #TEXT_DIRECTION_LOCALE},
      */
+    @ViewDebug.ExportedProperty(category = "text", mapping = {
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"),
+            @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE")
+    })
     public int getTextDirection() {
-        return mTextDirection;
+        return (mPrivateFlags2 & TEXT_DIRECTION_MASK) >> TEXT_DIRECTION_MASK_SHIFT;
     }
 
     /**
@@ -14504,17 +14556,26 @@
      * {@link #TEXT_DIRECTION_LOCALE},
      */
     public void setTextDirection(int textDirection) {
-        if (textDirection != mTextDirection) {
-            mTextDirection = textDirection;
+        if (getTextDirection() != textDirection) {
+            // Reset the current text direction and the resolved one
+            mPrivateFlags2 &= ~TEXT_DIRECTION_MASK;
             resetResolvedTextDirection();
+            // Set the new text direction
+            mPrivateFlags2 |= ((textDirection << TEXT_DIRECTION_MASK_SHIFT) & TEXT_DIRECTION_MASK);
             requestLayout();
+            invalidate(true);
         }
     }
 
     /**
      * Return the resolved text direction.
      *
-     * @return the resolved text direction. Return one of:
+     * This needs resolution if the value is TEXT_DIRECTION_INHERIT. The resolution matches
+     * {@link #getTextDirection()}if it is not TEXT_DIRECTION_INHERIT, otherwise resolution proceeds
+     * up the parent chain of the view. if there is no parent, then it will return the default
+     * {@link #TEXT_DIRECTION_FIRST_STRONG}.
+     *
+     * @return the resolved text direction. Returns one of:
      *
      * {@link #TEXT_DIRECTION_FIRST_STRONG}
      * {@link #TEXT_DIRECTION_ANY_RTL},
@@ -14523,10 +14584,11 @@
      * {@link #TEXT_DIRECTION_LOCALE},
      */
     public int getResolvedTextDirection() {
-        if (mResolvedTextDirection == TEXT_DIRECTION_INHERIT) {
+        // The text direction will be resolved only if needed
+        if ((mPrivateFlags2 & TEXT_DIRECTION_RESOLVED) != TEXT_DIRECTION_RESOLVED) {
             resolveTextDirection();
         }
-        return mResolvedTextDirection;
+        return (mPrivateFlags2 & TEXT_DIRECTION_RESOLVED_MASK) >> TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
     }
 
     /**
@@ -14534,17 +14596,51 @@
      * resolution is done.
      */
     public void resolveTextDirection() {
-        if (mResolvedTextDirection != TEXT_DIRECTION_INHERIT) {
-            // Resolution has already been done.
-            return;
+        // Reset any previous text direction resolution
+        mPrivateFlags2 &= ~(TEXT_DIRECTION_RESOLVED | TEXT_DIRECTION_RESOLVED_MASK);
+
+        // Set resolved text direction flag depending on text direction flag
+        final int textDirection = getTextDirection();
+        switch(textDirection) {
+            case TEXT_DIRECTION_INHERIT:
+                if (canResolveTextDirection()) {
+                    ViewGroup viewGroup = ((ViewGroup) mParent);
+
+                    // Set current resolved direction to the same value as the parent's one
+                    final int parentResolvedDirection = viewGroup.getResolvedTextDirection();
+                    switch (parentResolvedDirection) {
+                        case TEXT_DIRECTION_FIRST_STRONG:
+                        case TEXT_DIRECTION_ANY_RTL:
+                        case TEXT_DIRECTION_LTR:
+                        case TEXT_DIRECTION_RTL:
+                        case TEXT_DIRECTION_LOCALE:
+                            mPrivateFlags2 |=
+                                    (parentResolvedDirection << TEXT_DIRECTION_RESOLVED_MASK_SHIFT);
+                            break;
+                        default:
+                            // Default resolved direction is "first strong" heuristic
+                            mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED_DEFAULT;
+                    }
+                } else {
+                    // We cannot do the resolution if there is no parent, so use the default one
+                    mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED_DEFAULT;
+                }
+                break;
+            case TEXT_DIRECTION_FIRST_STRONG:
+            case TEXT_DIRECTION_ANY_RTL:
+            case TEXT_DIRECTION_LTR:
+            case TEXT_DIRECTION_RTL:
+            case TEXT_DIRECTION_LOCALE:
+                // Resolved direction is the same as text direction
+                mPrivateFlags2 |= (textDirection << TEXT_DIRECTION_RESOLVED_MASK_SHIFT);
+                break;
+            default:
+                // Default resolved direction is "first strong" heuristic
+                mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED_DEFAULT;
         }
-        if (mTextDirection != TEXT_DIRECTION_INHERIT) {
-            mResolvedTextDirection = mTextDirection;
-        } else if (mParent != null && mParent instanceof ViewGroup) {
-            mResolvedTextDirection = ((ViewGroup) mParent).getResolvedTextDirection();
-        } else {
-            mResolvedTextDirection = TEXT_DIRECTION_FIRST_STRONG;
-        }
+
+        // Set to resolved
+        mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED;
         onResolvedTextDirectionChanged();
     }
 
@@ -14558,12 +14654,26 @@
     }
 
     /**
+     * Check if text direction resolution can be done.
+     *
+     * @return true if text direction resolution can be done otherwise return false.
+     */
+    public boolean canResolveTextDirection() {
+        switch (getTextDirection()) {
+            case TEXT_DIRECTION_INHERIT:
+                return (mParent != null) && (mParent instanceof ViewGroup);
+            default:
+                return true;
+        }
+    }
+
+    /**
      * Reset resolved text direction. Text direction can be resolved with a call to
      * getResolvedTextDirection(). Will call {@link View#onResolvedTextDirectionReset} when
      * reset is done.
      */
     public void resetResolvedTextDirection() {
-        mResolvedTextDirection = TEXT_DIRECTION_INHERIT;
+        mPrivateFlags2 &= ~(TEXT_DIRECTION_RESOLVED | TEXT_DIRECTION_RESOLVED_MASK);
         onResolvedTextDirectionReset();
     }
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 42426b98..30d6ec7 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1820,7 +1820,7 @@
      * Resets the cancel next up flag.
      * Returns true if the flag was previously set.
      */
-    private boolean resetCancelNextUpFlag(View view) {
+    private static boolean resetCancelNextUpFlag(View view) {
         if ((view.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
             view.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
             return true;
@@ -2679,15 +2679,6 @@
         return child.draw(canvas, this, drawingTime);
     }
 
-    @Override
-    public void requestLayout() {
-        if (mChildrenCount > 0 && getAccessibilityNodeProvider() != null) {
-            throw new IllegalStateException("Views with AccessibilityNodeProvider"
-                    + " can't have children.");
-        }
-        super.requestLayout();
-    }
-
     /**
      * 
      * @param enabled True if children should be drawn with layers, false otherwise.
@@ -3109,11 +3100,6 @@
     private void addViewInner(View child, int index, LayoutParams params,
             boolean preventRequestLayout) {
 
-        if (getAccessibilityNodeProvider() != null) {
-            throw new IllegalStateException("Views with AccessibilityNodeProvider"
-                    + " can't have children.");
-        }
-
         if (mTransition != null) {
             // Don't prevent other add transitions from completing, but cancel remove
             // transitions to let them complete the process before we add to the container
@@ -3920,9 +3906,15 @@
             do {
                 if (parent instanceof ViewGroup) {
                     ViewGroup parentVG = (ViewGroup) parent;
-                    parent = parentVG.invalidateChildInParentFast(left, top, dirty);
-                    left = parentVG.mLeft;
-                    top = parentVG.mTop;
+                    if (parentVG.mLayerType != LAYER_TYPE_NONE) {
+                        // Layered parents should be recreated, not just re-issued
+                        parentVG.invalidate();
+                        parent = null;
+                    } else {
+                        parent = parentVG.invalidateChildInParentFast(left, top, dirty);
+                        left = parentVG.mLeft;
+                        top = parentVG.mTop;
+                    }
                 } else {
                     // Reached the top; this calls into the usual invalidate method in
                     // ViewRootImpl, which schedules a traversal
@@ -4678,6 +4670,7 @@
     public void clearDisappearingChildren() {
         if (mDisappearingChildren != null) {
             mDisappearingChildren.clear();
+            invalidate();
         }
     }
 
@@ -4789,7 +4782,7 @@
                         view.mParent = null;
                     }
                 }
-                mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
+                invalidate();
             }
         }
     }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 4eb70ab..befc1c6 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1809,7 +1809,7 @@
      *
      * @return The measure spec to use to measure the root view.
      */
-    private int getRootMeasureSpec(int windowSize, int rootDimension) {
+    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
         int measureSpec;
         switch (rootDimension) {
 
@@ -1919,7 +1919,9 @@
     }
 
     private void performDraw() {
-        if (!mAttachInfo.mScreenOn) return;
+        if (!mAttachInfo.mScreenOn && !mReportNextDraw) {
+            return;
+        }
 
         final long drawStartTime;
         if (ViewDebug.DEBUG_LATENCY) {
@@ -2430,12 +2432,12 @@
                 mAccessibilityInteractionConnectionManager);
         removeSendWindowContentChangedCallback();
 
+        destroyHardwareRenderer();
+
         mView = null;
         mAttachInfo.mRootView = null;
         mAttachInfo.mSurface = null;
 
-        destroyHardwareRenderer();
-
         mSurface.release();
 
         if (mInputQueueCallback != null && mInputQueue != null) {
@@ -2891,7 +2893,7 @@
      * @param focused The currently focused view.
      * @return An appropriate view, or null if no such view exists.
      */
-    private ViewGroup findAncestorToTakeFocusInTouchMode(View focused) {
+    private static ViewGroup findAncestorToTakeFocusInTouchMode(View focused) {
         ViewParent parent = focused.getParent();
         while (parent instanceof ViewGroup) {
             final ViewGroup vgParent = (ViewGroup) parent;
@@ -3761,7 +3763,7 @@
         }
     }
 
-    private void getGfxInfo(View view, int[] info) {
+    private static void getGfxInfo(View view, int[] info) {
         DisplayList displayList = view.mDisplayList;
         info[0]++;
         if (displayList != null) {
@@ -3782,6 +3784,7 @@
         if (immediate) {
             doDie();
         } else {
+            destroyHardwareRenderer();
             mHandler.sendEmptyMessage(MSG_DIE);
         }
     }
@@ -3824,10 +3827,18 @@
     }
 
     private void destroyHardwareRenderer() {
-        if (mAttachInfo.mHardwareRenderer != null) {
-            mAttachInfo.mHardwareRenderer.destroy(true);
-            mAttachInfo.mHardwareRenderer = null;
-            mAttachInfo.mHardwareAccelerated = false;
+        AttachInfo attachInfo = mAttachInfo;
+        HardwareRenderer hardwareRenderer = attachInfo.mHardwareRenderer;
+
+        if (hardwareRenderer != null) {
+            if (mView != null) {
+                hardwareRenderer.destroyHardwareResources(mView);
+            }
+            hardwareRenderer.destroy(true);
+            hardwareRenderer.setRequested(false);
+
+            attachInfo.mHardwareRenderer = null;
+            attachInfo.mHardwareAccelerated = false;
         }
     }
 
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 0e4a30f..f2ee9f9 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -354,7 +354,7 @@
     View removeViewLocked(int index) {
         ViewRootImpl root = mRoots[index];
         View view = root.getView();
-        
+
         // Don't really remove until we have matched all calls to add().
         root.mAddNesting--;
         if (root.mAddNesting > 0) {
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index dc8c71b7..d92ebcd 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -875,7 +875,7 @@
      * otherwise.
      *
      * @param currentTime Where we are in the animation. This is wall clock time.
-     * @param outTransformation A tranformation object that is provided by the
+     * @param outTransformation A transformation object that is provided by the
      *        caller and will be filled in by the animation.
      * @param scale Scaling factor to apply to any inputs to the transform operation, such
      *        pivot points being rotated or scaled around.
diff --git a/core/java/android/view/animation/Transformation.java b/core/java/android/view/animation/Transformation.java
index cf210c8..e8c1d23 100644
--- a/core/java/android/view/animation/Transformation.java
+++ b/core/java/android/view/animation/Transformation.java
@@ -112,6 +112,16 @@
     }
     
     /**
+     * Like {@link #compose(Transformation)} but does this.postConcat(t) of
+     * the transformation matrix.
+     * @hide
+     */
+    public void postCompose(Transformation t) {
+        mAlpha *= t.getAlpha();
+        mMatrix.postConcat(t.getMatrix());
+    }
+
+    /**
      * @return The 3x3 Matrix representing the trnasformation to apply to the
      * coordinates of the object being animated
      */
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0e5ff20..17dbde8 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -403,6 +403,9 @@
                                 mIInputContext.finishComposingText();
                             } catch (RemoteException e) {
                             }
+                            // Check focus again in case that "onWindowFocus" is called before
+                            // handling this message.
+                            checkFocus(mHasBeenInactive);
                         }
                     }
                     return;
@@ -1173,13 +1176,17 @@
         }
     }
 
+    private void checkFocus(boolean forceNewFocus) {
+        if (checkFocusNoStartInput(forceNewFocus)) {
+            startInputInner(null, 0, 0, 0);
+        }
+    }
+
     /**
      * @hide
      */
     public void checkFocus() {
-        if (checkFocusNoStartInput(false)) {
-            startInputInner(null, 0, 0, 0);
-        }
+        checkFocus(false);
     }
 
     private boolean checkFocusNoStartInput(boolean forceNewFocus) {
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index 18dec52..c22750e 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -27,6 +27,7 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.IllegalFormatException;
 import java.util.List;
 import java.util.Locale;
 
@@ -45,6 +46,9 @@
     private static final String TAG = InputMethodSubtype.class.getSimpleName();
     private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
     private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
+    // TODO: remove this
+    private static final String EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME =
+            "UntranslatableReplacementStringInSubtypeName";
 
     private final boolean mIsAuxiliary;
     private final boolean mOverridesImplicitlyEnabledSubtype;
@@ -215,7 +219,17 @@
         final CharSequence subtypeName = context.getPackageManager().getText(
                 packageName, mSubtypeNameResId, appInfo);
         if (!TextUtils.isEmpty(subtypeName)) {
-            return String.format(subtypeName.toString(), localeStr);
+            final String replacementString =
+                    containsExtraValueKey(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)
+                            ? getExtraValueOf(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)
+                            : localeStr;
+            try {
+                return String.format(
+                        subtypeName.toString(), replacementString != null ? replacementString : "");
+            } catch (IllegalFormatException e) {
+                Slog.w(TAG, "Found illegal format in subtype name("+ subtypeName + "): " + e);
+                return "";
+            }
         } else {
             return localeStr;
         }
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index dbcea71..72af251 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -448,7 +448,7 @@
         // loadType is not used yet
         if (isMainFrame) {
             mCommitted = true;
-            mWebViewCore.getWebView().mViewManager.postResetStateAll();
+            mWebViewCore.getWebViewClassic().mViewManager.postResetStateAll();
         }
     }
 
@@ -910,7 +910,7 @@
      * Close this frame and window.
      */
     private void closeWindow(WebViewCore w) {
-        mCallbackProxy.onCloseWindow(w.getWebView());
+        mCallbackProxy.onCloseWindow(w.getWebViewClassic());
     }
 
     // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore
diff --git a/core/java/android/webkit/DebugFlags.java b/core/java/android/webkit/DebugFlags.java
index a21d3ee..349113e 100644
--- a/core/java/android/webkit/DebugFlags.java
+++ b/core/java/android/webkit/DebugFlags.java
@@ -42,12 +42,7 @@
     public static final boolean WEB_BACK_FORWARD_LIST = false;
     public static final boolean WEB_SETTINGS = false;
     public static final boolean WEB_SYNC_MANAGER = false;
-    public static final boolean WEB_TEXT_VIEW = false;
     public static final boolean WEB_VIEW = false;
     public static final boolean WEB_VIEW_CORE = false;
-    /*
-     * Set to true to allow the WebTextView to draw on top of the web page in a
-     * different color with no background so you can see how the two line up.
-     */
-    public static final boolean DRAW_WEBTEXTVIEW = false;
+    public static final boolean MEASURE_PAGE_SWAP_FPS = false;
 }
diff --git a/core/java/android/webkit/FindListener.java b/core/java/android/webkit/FindListener.java
deleted file mode 100644
index 124f737..0000000
--- a/core/java/android/webkit/FindListener.java
+++ /dev/null
@@ -1,32 +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 android.webkit;
-
-/**
- * @hide
- */
-public interface FindListener {
-    /**
-     * Notify the host application that a find result is available.
-     *
-     * @param numberOfMatches How many matches have been found
-     * @param activeMatchOrdinal The ordinal of the currently selected match
-     * @param isDoneCounting Whether we have finished counting matches
-     */
-    public void onFindResultReceived(int numberOfMatches,
-        int activeMatchOrdinal, boolean isDoneCounting);
-}
diff --git a/core/java/android/webkit/HTML5Audio.java b/core/java/android/webkit/HTML5Audio.java
index 8e1f573..689884f 100644
--- a/core/java/android/webkit/HTML5Audio.java
+++ b/core/java/android/webkit/HTML5Audio.java
@@ -183,7 +183,7 @@
         resetMediaPlayer();
         mContext = webViewCore.getContext();
         mIsPrivateBrowsingEnabledGetter = new IsPrivateBrowsingEnabledGetter(
-                webViewCore.getContext().getMainLooper(), webViewCore.getWebView());
+                webViewCore.getContext().getMainLooper(), webViewCore.getWebViewClassic());
     }
 
     private void resetMediaPlayer() {
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
index 40c3778..5fa4bad 100644
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ b/core/java/android/webkit/HTML5VideoViewProxy.java
@@ -724,7 +724,7 @@
      * @return a new HTML5VideoViewProxy object.
      */
     public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore, int nativePtr) {
-        return new HTML5VideoViewProxy(webViewCore.getWebView(), nativePtr);
+        return new HTML5VideoViewProxy(webViewCore.getWebViewClassic(), nativePtr);
     }
 
     /* package */ WebViewClassic getWebView() {
diff --git a/core/java/android/webkit/WebSettingsClassic.java b/core/java/android/webkit/WebSettingsClassic.java
index 6850eea..c41bc00 100644
--- a/core/java/android/webkit/WebSettingsClassic.java
+++ b/core/java/android/webkit/WebSettingsClassic.java
@@ -121,9 +121,6 @@
     private boolean         mForceUserScalable = false;
 
     // AutoFill Profile data
-    /**
-     * @hide for now, pending API council approval.
-     */
     public static class AutoFillProfile {
         private int mUniqueId;
         private String mFullName;
@@ -644,7 +641,6 @@
     /**
      * Set the double-tap zoom of the page in percent. Default is 100.
      * @param doubleTapZoom A percent value for increasing or decreasing the double-tap zoom.
-     * @hide
      */
     public void setDoubleTapZoom(int doubleTapZoom) {
         if (mDoubleTapZoom != doubleTapZoom) {
@@ -656,7 +652,6 @@
     /**
      * Get the double-tap zoom of the page in percent.
      * @return A percent value describing the double-tap zoom.
-     * @hide
      */
     public int getDoubleTapZoom() {
         return mDoubleTapZoom;
@@ -1012,7 +1007,6 @@
     /**
      * Set the number of pages cached by the WebKit for the history navigation.
      * @param size A non-negative integer between 0 (no cache) and 20 (max).
-     * @hide
      */
     public synchronized void setPageCacheCapacity(int size) {
         if (size < 0) size = 0;
@@ -1108,7 +1102,6 @@
     /**
      * Tell the WebView to use Skia's hardware accelerated rendering path
      * @param flag True if the WebView should use Skia's hw-accel path
-     * @hide
      */
     public synchronized void setHardwareAccelSkiaEnabled(boolean flag) {
         if (mHardwareAccelSkia != flag) {
@@ -1119,7 +1112,6 @@
 
     /**
      * @return True if the WebView is using hardware accelerated skia
-     * @hide
      */
     public synchronized boolean getHardwareAccelSkiaEnabled() {
         return mHardwareAccelSkia;
@@ -1128,7 +1120,6 @@
     /**
      * Tell the WebView to show the visual indicator
      * @param flag True if the WebView should show the visual indicator
-     * @hide
      */
     public synchronized void setShowVisualIndicator(boolean flag) {
         if (mShowVisualIndicator != flag) {
@@ -1139,7 +1130,6 @@
 
     /**
      * @return True if the WebView is showing the visual indicator
-     * @hide
      */
     public synchronized boolean getShowVisualIndicator() {
         return mShowVisualIndicator;
@@ -1283,7 +1273,6 @@
      * @param flag True if the WebView should enable WebWorkers.
      * Note that this flag only affects V8. JSC does not have
      * an equivalent setting.
-     * @hide
      */
     public synchronized void setWorkersEnabled(boolean flag) {
         if (mWorkersEnabled != flag) {
@@ -1305,8 +1294,8 @@
 
     /**
      * Sets whether XSS Auditor is enabled.
+     * Only used by LayoutTestController.
      * @param flag Whether XSS Auditor should be enabled.
-     * @hide Only used by LayoutTestController.
      */
     public synchronized void setXSSAuditorEnabled(boolean flag) {
         if (mXSSAuditorEnabled != flag) {
@@ -1507,7 +1496,6 @@
      * of an HTML page to fit the screen. This conflicts with attempts by
      * the UI to zoom in and out of an image, so it is set false by default.
      * @param shrink Set true to let webkit shrink the standalone image to fit.
-     * {@hide}
      */
     public void setShrinksStandaloneImagesToFit(boolean shrink) {
         if (mShrinksStandaloneImagesToFit != shrink) {
@@ -1520,7 +1508,6 @@
      * Specify the maximum decoded image size. The default is
      * 2 megs for small memory devices and 8 megs for large memory devices.
      * @param size The maximum decoded size, or zero to set to the default.
-     * @hide
      */
     public void setMaximumDecodedImageSize(long size) {
         if (mMaximumDecodedImageSize != size) {
@@ -1562,7 +1549,6 @@
 
     /**
      * Returns whether the viewport metatag can disable zooming
-     * @hide
      */
     public boolean forceUserScalable() {
         return mForceUserScalable;
@@ -1571,7 +1557,6 @@
     /**
      * Sets whether viewport metatag can disable zooming.
      * @param flag Whether or not to forceably enable user scalable.
-     * @hide
      */
     public synchronized void setForceUserScalable(boolean flag) {
         mForceUserScalable = flag;
@@ -1584,9 +1569,6 @@
         }
     }
 
-    /**
-     * @hide
-     */
     public synchronized void setAutoFillEnabled(boolean enabled) {
         // AutoFill is always disabled in private browsing mode.
         boolean autoFillEnabled = enabled && !mPrivateBrowsingEnabled;
@@ -1596,16 +1578,10 @@
         }
     }
 
-    /**
-     * @hide
-     */
     public synchronized boolean getAutoFillEnabled() {
         return mAutoFillEnabled;
     }
 
-    /**
-     * @hide
-     */
     public synchronized void setAutoFillProfile(AutoFillProfile profile) {
         if (mAutoFillProfile != profile) {
             mAutoFillProfile = profile;
@@ -1613,9 +1589,6 @@
         }
     }
 
-    /**
-     * @hide
-     */
     public synchronized AutoFillProfile getAutoFillProfile() {
         return mAutoFillProfile;
     }
@@ -1633,18 +1606,12 @@
         }
     }
 
-    /**
-     * @hide
-     */
     public void setProperty(String key, String value) {
         if (mWebView.nativeSetProperty(key, value)) {
-            mWebView.contentInvalidateAll();
+            mWebView.invalidate();
         }
     }
 
-    /**
-     * @hide
-     */
     public String getProperty(String key) {
         return mWebView.nativeGetProperty(key);
     }
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 5e09416..d225594 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -21,6 +21,7 @@
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.Picture;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -311,6 +312,24 @@
     public static final String SCHEME_GEO = "geo:0,0?q=";
 
     /**
+     * Interface to listen for find results.
+     * @hide
+     */
+    public interface FindListener {
+        /**
+         * Notify the listener about progress made by a find operation.
+         *
+         * @param numberOfMatches How many matches have been found.
+         * @param activeMatchOrdinal The zero-based ordinal of the currently selected match.
+         * @param isDoneCounting Whether the find operation has actually completed. The listener
+         * may be notified multiple times while the operation is underway, and the numberOfMatches
+         * value should not be considered final unless isDoneCounting is true.
+         */
+        public void onFindResultReceived(int numberOfMatches, int activeMatchOrdinal,
+            boolean isDoneCounting);
+    }
+
+    /**
      * Interface to listen for new pictures as they change.
      * @deprecated This interface is now obsolete.
      */
@@ -1227,10 +1246,10 @@
     }
 
     /**
-     * Register the interface to be used when a find-on-page result has become
-     * available. This will replace the current handler.
+     * Register the listener to be notified as find-on-page operations progress.
+     * This will replace the current listener.
      *
-     * @param listener An implementation of FindListener
+     * @param listener An implementation of {@link WebView#FindListener}.
      * @hide
      */
     public void setFindListener(FindListener listener) {
@@ -1980,4 +1999,10 @@
     public void setBackgroundColor(int color) {
         mProvider.getViewDelegate().setBackgroundColor(color);
     }
+
+    @Override
+    public void setLayerType(int layerType, Paint paint) {
+        super.setLayerType(layerType, paint);
+        mProvider.getViewDelegate().setLayerType(layerType, paint);
+    }
 }
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index e5434ce..5ae2fe0 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -348,8 +348,6 @@
  *
  * @hide
  */
-// TODO: Remove duplicated API documentation and @hide from fields and methods, and
-// checkThread() call. (All left in for now to ease branch merging.)
 // TODO: Check if any WebView published API methods are called from within here, and if so
 // we should bounce the call out via the proxy to enable any sub-class to override it.
 @Widget
@@ -1255,6 +1253,7 @@
     static final int AUTOFILL_FORM                      = 148;
     static final int ANIMATE_TEXT_SCROLL                = 149;
     static final int EDIT_TEXT_SIZE_CHANGED             = 150;
+    static final int SHOW_CARET_HANDLE                  = 151;
 
     private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
     private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT;
@@ -1441,7 +1440,7 @@
     private PictureListener mPictureListener;
 
     // Used to notify listeners about find-on-page results.
-    private FindListener mFindListener;
+    private WebView.FindListener mFindListener;
 
     /**
      * Refer to {@link WebView#requestFocusNodeHref(Message)} for more information
@@ -1463,8 +1462,6 @@
      */
     @Override
     public void init(Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
-        checkThread();
-
         Context context = mContext;
 
         // Used by the chrome stack to find application paths
@@ -1496,8 +1493,7 @@
         mEditTextScroller = new Scroller(context);
     }
 
-    // === START: WebView Proxy binding ===
-    // Keep the webview proxy / SPI related stuff in this section, to minimize merge conflicts.
+    // WebViewProvider bindings
 
     static class Factory implements WebViewFactoryProvider,  WebViewFactoryProvider.Statics {
         @Override
@@ -1586,8 +1582,6 @@
         mWebViewPrivate.setScrollYRaw(mScrollY);
     }
 
-    // === END: WebView Proxy binding ===
-
     private static class TrustStorageListener extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -1968,7 +1962,6 @@
      */
     @Override
     public void setHorizontalScrollbarOverlay(boolean overlay) {
-        checkThread();
         mOverlayHorizontalScrollbar = overlay;
     }
 
@@ -1977,7 +1970,6 @@
      */
     @Override
     public void setVerticalScrollbarOverlay(boolean overlay) {
-        checkThread();
         mOverlayVerticalScrollbar = overlay;
     }
 
@@ -1986,7 +1978,6 @@
      */
     @Override
     public boolean overlayHorizontalScrollbar() {
-        checkThread();
         return mOverlayHorizontalScrollbar;
     }
 
@@ -1995,7 +1986,6 @@
      */
     @Override
     public boolean overlayVerticalScrollbar() {
-        checkThread();
         return mOverlayVerticalScrollbar;
     }
 
@@ -2021,7 +2011,6 @@
     /**
      * Returns the height (in pixels) of the embedded title bar (if any). Does not care about
      * scrolling
-     * @hide
      */
     protected int getTitleHeight() {
         if (mWebView instanceof TitleBarDelegate) {
@@ -2038,7 +2027,6 @@
     public int getVisibleTitleHeight() {
         // Actually, this method returns the height of the embedded title bar if one is set via the
         // hidden setEmbeddedTitleBar method.
-        checkThread();
         return getVisibleTitleHeightImpl();
     }
 
@@ -2084,7 +2072,6 @@
      */
     @Override
     public SslCertificate getCertificate() {
-        checkThread();
         return mCertificate;
     }
 
@@ -2093,7 +2080,6 @@
      */
     @Override
     public void setCertificate(SslCertificate certificate) {
-        checkThread();
         if (DebugFlags.WEB_VIEW) {
             Log.v(LOGTAG, "setCertificate=" + certificate);
         }
@@ -2110,7 +2096,6 @@
      */
     @Override
     public void savePassword(String host, String username, String password) {
-        checkThread();
         mDatabase.setUsernamePassword(host, username, password);
     }
 
@@ -2120,7 +2105,6 @@
     @Override
     public void setHttpAuthUsernamePassword(String host, String realm,
             String username, String password) {
-        checkThread();
         mDatabase.setHttpAuthUsernamePassword(host, realm, username, password);
     }
 
@@ -2129,7 +2113,6 @@
      */
     @Override
     public String[] getHttpAuthUsernamePassword(String host, String realm) {
-        checkThread();
         return mDatabase.getHttpAuthUsernamePassword(host, realm);
     }
 
@@ -2169,7 +2152,6 @@
      */
     @Override
     public void destroy() {
-        checkThread();
         destroyImpl();
     }
 
@@ -2202,7 +2184,6 @@
      */
     @Deprecated
     public static void enablePlatformNotifications() {
-        checkThread();
         synchronized (WebViewClassic.class) {
             sNotificationsEnabled = true;
             Context context = JniUtil.getContext();
@@ -2216,7 +2197,6 @@
      */
     @Deprecated
     public static void disablePlatformNotifications() {
-        checkThread();
         synchronized (WebViewClassic.class) {
             sNotificationsEnabled = false;
             Context context = JniUtil.getContext();
@@ -2230,10 +2210,9 @@
      *
      * @param flags JS engine flags in a String
      *
-     * @hide This is an implementation detail.
+     * This is an implementation detail.
      */
     public void setJsFlags(String flags) {
-        checkThread();
         mWebViewCore.sendMessage(EventHub.SET_JS_FLAGS, flags);
     }
 
@@ -2242,17 +2221,14 @@
      */
     @Override
     public void setNetworkAvailable(boolean networkUp) {
-        checkThread();
         mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE,
                 networkUp ? 1 : 0, 0);
     }
 
     /**
      * Inform WebView about the current network type.
-     * {@hide}
      */
     public void setNetworkType(String type, String subtype) {
-        checkThread();
         Map<String, String> map = new HashMap<String, String>();
         map.put("type", type);
         map.put("subtype", subtype);
@@ -2264,7 +2240,6 @@
      */
     @Override
     public WebBackForwardList saveState(Bundle outState) {
-        checkThread();
         if (outState == null) {
             return null;
         }
@@ -2316,7 +2291,6 @@
     @Override
     @Deprecated
     public boolean savePicture(Bundle b, final File dest) {
-        checkThread();
         if (dest == null || b == null) {
             return false;
         }
@@ -2378,7 +2352,6 @@
     @Override
     @Deprecated
     public boolean restorePicture(Bundle b, File src) {
-        checkThread();
         if (src == null || b == null) {
             return false;
         }
@@ -2424,7 +2397,6 @@
      * of WebView.
      * @param stream The {@link OutputStream} to save to
      * @return True if saved successfully
-     * @hide
      */
     public boolean saveViewState(OutputStream stream) {
         try {
@@ -2440,7 +2412,6 @@
      * {@link #saveViewState(OutputStream)} for more information.
      * @param stream The {@link InputStream} to load from
      * @return True if loaded successfully
-     * @hide
      */
     public boolean loadViewState(InputStream stream) {
         try {
@@ -2470,7 +2441,6 @@
      */
     @Override
     public WebBackForwardList restoreState(Bundle inState) {
-        checkThread();
         WebBackForwardList returnList = null;
         if (inState == null) {
             return returnList;
@@ -2527,7 +2497,6 @@
      */
     @Override
     public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
-        checkThread();
         loadUrlImpl(url, additionalHttpHeaders);
     }
 
@@ -2545,7 +2514,6 @@
      */
     @Override
     public void loadUrl(String url) {
-        checkThread();
         loadUrlImpl(url);
     }
 
@@ -2561,7 +2529,6 @@
      */
     @Override
     public void postUrl(String url, byte[] postData) {
-        checkThread();
         if (URLUtil.isNetworkUrl(url)) {
             switchOutDrawHistory();
             WebViewCore.PostUrlData arg = new WebViewCore.PostUrlData();
@@ -2579,7 +2546,6 @@
      */
     @Override
     public void loadData(String data, String mimeType, String encoding) {
-        checkThread();
         loadDataImpl(data, mimeType, encoding);
     }
 
@@ -2600,7 +2566,6 @@
     @Override
     public void loadDataWithBaseURL(String baseUrl, String data,
             String mimeType, String encoding, String historyUrl) {
-        checkThread();
 
         if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) {
             loadDataImpl(data, mimeType, encoding);
@@ -2622,7 +2587,6 @@
      */
     @Override
     public void saveWebArchive(String filename) {
-        checkThread();
         saveWebArchiveImpl(filename, false, null);
     }
 
@@ -2644,7 +2608,6 @@
      */
     @Override
     public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) {
-        checkThread();
         saveWebArchiveImpl(basename, autoname, callback);
     }
 
@@ -2659,7 +2622,6 @@
      */
     @Override
     public void stopLoading() {
-        checkThread();
         // TODO: should we clear all the messages in the queue before sending
         // STOP_LOADING?
         switchOutDrawHistory();
@@ -2671,7 +2633,6 @@
      */
     @Override
     public void reload() {
-        checkThread();
         clearHelpers();
         switchOutDrawHistory();
         mWebViewCore.sendMessage(EventHub.RELOAD);
@@ -2682,7 +2643,6 @@
      */
     @Override
     public boolean canGoBack() {
-        checkThread();
         WebBackForwardList l = mCallbackProxy.getBackForwardList();
         synchronized (l) {
             if (l.getClearPending()) {
@@ -2698,7 +2658,6 @@
      */
     @Override
     public void goBack() {
-        checkThread();
         goBackOrForwardImpl(-1);
     }
 
@@ -2707,7 +2666,6 @@
      */
     @Override
     public boolean canGoForward() {
-        checkThread();
         WebBackForwardList l = mCallbackProxy.getBackForwardList();
         synchronized (l) {
             if (l.getClearPending()) {
@@ -2723,7 +2681,6 @@
      */
     @Override
     public void goForward() {
-        checkThread();
         goBackOrForwardImpl(1);
     }
 
@@ -2732,7 +2689,6 @@
      */
     @Override
     public boolean canGoBackOrForward(int steps) {
-        checkThread();
         WebBackForwardList l = mCallbackProxy.getBackForwardList();
         synchronized (l) {
             if (l.getClearPending()) {
@@ -2749,7 +2705,6 @@
      */
     @Override
     public void goBackOrForward(int steps) {
-        checkThread();
         goBackOrForwardImpl(steps);
     }
 
@@ -2770,7 +2725,6 @@
      */
     @Override
     public boolean isPrivateBrowsingEnabled() {
-        checkThread();
         return getSettings().isPrivateBrowsingEnabled();
     }
 
@@ -2792,7 +2746,6 @@
      */
     @Override
     public boolean pageUp(boolean top) {
-        checkThread();
         if (mNativeClass == 0) {
             return false;
         }
@@ -2817,7 +2770,6 @@
      */
     @Override
     public boolean pageDown(boolean bottom) {
-        checkThread();
         if (mNativeClass == 0) {
             return false;
         }
@@ -2841,7 +2793,6 @@
      */
     @Override
     public void clearView() {
-        checkThread();
         mContentWidth = 0;
         mContentHeight = 0;
         setBaseLayer(0, null, false, false);
@@ -2853,7 +2804,6 @@
      */
     @Override
     public Picture capturePicture() {
-        checkThread();
         if (mNativeClass == 0) return null;
         Picture result = new Picture();
         nativeCopyBaseContentToPicture(result);
@@ -2865,7 +2815,6 @@
      */
     @Override
     public float getScale() {
-        checkThread();
         return mZoomManager.getScale();
     }
 
@@ -2883,7 +2832,6 @@
      */
     @Override
     public void setInitialScale(int scaleInPercent) {
-        checkThread();
         mZoomManager.setInitialScaleInPercent(scaleInPercent);
     }
 
@@ -2892,7 +2840,6 @@
      */
     @Override
     public void invokeZoomPicker() {
-        checkThread();
         if (!getSettings().supportZoom()) {
             Log.w(LOGTAG, "This WebView doesn't support zoom.");
             return;
@@ -2906,7 +2853,6 @@
      */
     @Override
     public HitTestResult getHitTestResult() {
-        checkThread();
         return mInitialHitTestResult;
     }
 
@@ -2942,7 +2888,6 @@
      */
     @Override
     public void requestFocusNodeHref(Message hrefMsg) {
-        checkThread();
         if (hrefMsg == null) {
             return;
         }
@@ -2965,7 +2910,6 @@
      */
     @Override
     public void requestImageRef(Message msg) {
-        checkThread();
         if (0 == mNativeClass) return; // client isn't initialized
         String url = mFocusedNode != null ? mFocusedNode.mImageUrl : null;
         Bundle data = msg.getData();
@@ -3019,7 +2963,6 @@
      * along with it vertically, while remaining in view horizontally. Pass
      * null to remove the title bar from the WebView, and return to drawing
      * the WebView normally without translating to account for the title bar.
-     * @hide
      */
     public void setEmbeddedTitleBar(View v) {
         if (mWebView instanceof TitleBarDelegate) {
@@ -3041,7 +2984,6 @@
      * Set where to render the embedded title bar
      * NO_GRAVITY at the top of the page
      * TOP        at the top of the screen
-     * @hide
      */
     public void setTitleBarGravity(int gravity) {
         mTitleGravity = gravity;
@@ -3421,7 +3363,6 @@
         return getViewHeight();
     }
 
-    /** @hide */
     @Override
     public void onDrawVerticalScrollBar(Canvas canvas,
                                            Drawable scrollBar,
@@ -3470,7 +3411,6 @@
      */
     @Override
     public String getUrl() {
-        checkThread();
         WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
         return h != null ? h.getUrl() : null;
     }
@@ -3480,7 +3420,6 @@
      */
     @Override
     public String getOriginalUrl() {
-        checkThread();
         WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
         return h != null ? h.getOriginalUrl() : null;
     }
@@ -3490,7 +3429,6 @@
      */
     @Override
     public String getTitle() {
-        checkThread();
         WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
         return h != null ? h.getTitle() : null;
     }
@@ -3500,7 +3438,6 @@
      */
     @Override
     public Bitmap getFavicon() {
-        checkThread();
         WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
         return h != null ? h.getFavicon() : null;
     }
@@ -3519,7 +3456,6 @@
      */
     @Override
     public int getProgress() {
-        checkThread();
         return mCallbackProxy.getProgress();
     }
 
@@ -3528,7 +3464,6 @@
      */
     @Override
     public int getContentHeight() {
-        checkThread();
         return mContentHeight;
     }
 
@@ -3540,9 +3475,6 @@
         return mContentWidth;
     }
 
-    /**
-     * @hide
-     */
     public int getPageBackgroundColor() {
         return nativeGetBackgroundColor();
     }
@@ -3552,7 +3484,6 @@
      */
     @Override
     public void pauseTimers() {
-        checkThread();
         mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS);
     }
 
@@ -3561,7 +3492,6 @@
      */
     @Override
     public void resumeTimers() {
-        checkThread();
         mWebViewCore.sendMessage(EventHub.RESUME_TIMERS);
     }
 
@@ -3570,7 +3500,6 @@
      */
     @Override
     public void onPause() {
-        checkThread();
         if (!mIsPaused) {
             mIsPaused = true;
             mWebViewCore.sendMessage(EventHub.ON_PAUSE);
@@ -3609,7 +3538,6 @@
      */
     @Override
     public void onResume() {
-        checkThread();
         if (mIsPaused) {
             mIsPaused = false;
             mWebViewCore.sendMessage(EventHub.ON_RESUME);
@@ -3641,7 +3569,6 @@
      */
     @Override
     public void freeMemory() {
-        checkThread();
         mWebViewCore.sendMessage(EventHub.FREE_MEMORY);
     }
 
@@ -3650,7 +3577,6 @@
      */
     @Override
     public void clearCache(boolean includeDiskFiles) {
-        checkThread();
         // Note: this really needs to be a static method as it clears cache for all
         // WebView. But we need mWebViewCore to send message to WebCore thread, so
         // we can't make this static.
@@ -3663,7 +3589,6 @@
      */
     @Override
     public void clearFormData() {
-        checkThread();
         if (mAutoCompletePopup != null) {
             mAutoCompletePopup.clearAdapter();
         }
@@ -3674,7 +3599,6 @@
      */
     @Override
     public void clearHistory() {
-        checkThread();
         mCallbackProxy.getBackForwardList().setClearPending();
         mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY);
     }
@@ -3684,7 +3608,6 @@
      */
     @Override
     public void clearSslPreferences() {
-        checkThread();
         mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE);
     }
 
@@ -3693,18 +3616,14 @@
      */
     @Override
     public WebBackForwardList copyBackForwardList() {
-        checkThread();
         return mCallbackProxy.getBackForwardList().clone();
     }
 
     /**
-     * Register the interface to be used when a find-on-page result has become
-     * available. This will replace the current handler.
-     *
-     * @param listener An implementation of FindListener
+     * See {@link WebView#setFindListener(WebView.FindListener)}.
+     * @hide
      */
-     public void setFindListener(FindListener listener) {
-         checkThread();
+     public void setFindListener(WebView.FindListener listener) {
          mFindListener = listener;
      }
 
@@ -3713,7 +3632,6 @@
      */
     @Override
     public void findNext(boolean forward) {
-        checkThread();
         if (0 == mNativeClass) return; // client isn't initialized
         mWebViewCore.sendMessage(EventHub.FIND_NEXT, forward ? 1 : 0);
     }
@@ -3726,15 +3644,11 @@
         return findAllBody(find, false);
     }
 
-    /**
-     * @hide
-     */
     public void findAllAsync(String find) {
         findAllBody(find, true);
     }
 
     private int findAllBody(String find, boolean isAsync) {
-        checkThread();
         if (0 == mNativeClass) return 0; // client isn't initialized
         mLastFind = find;
         if (find == null) return 0;
@@ -3771,7 +3685,6 @@
      * @return boolean True if the find dialog is shown, false otherwise.
      */
     public boolean showFindDialog(String text, boolean showIme) {
-        checkThread();
         FindActionModeCallback callback = new FindActionModeCallback(mContext);
         if (mWebView.getParent() == null || mWebView.startActionMode(callback) == null) {
             // Could not start the action mode, so end Find on page
@@ -3840,12 +3753,10 @@
      * @return the address, or if no address is found, return null.
      */
     public static String findAddress(String addr) {
-        checkThread();
         return findAddress(addr, false);
     }
 
     /**
-     * @hide
      * Return the first substring consisting of the address of a physical
      * location. Currently, only addresses in the United States are detected,
      * and consist of:
@@ -3875,7 +3786,6 @@
      */
     @Override
     public void clearMatches() {
-        checkThread();
         if (mNativeClass == 0)
             return;
         mWebViewCore.removeMessages(EventHub.FIND_ALL);
@@ -3905,7 +3815,6 @@
      */
     @Override
     public void documentHasImages(Message response) {
-        checkThread();
         if (response == null) {
             return;
         }
@@ -3914,8 +3823,6 @@
 
     /**
      * Request the scroller to abort any ongoing animation
-     *
-     * @hide
      */
     public void stopScroll() {
         mScroller.forceFinished(true);
@@ -4339,7 +4246,6 @@
      */
     @Override
     public void setWebViewClient(WebViewClient client) {
-        checkThread();
         mCallbackProxy.setWebViewClient(client);
     }
 
@@ -4347,7 +4253,7 @@
      * Gets the WebViewClient
      * @return the current WebViewClient instance.
      *
-     * @hide This is an implementation detail.
+     * This is an implementation detail.
      */
     public WebViewClient getWebViewClient() {
         return mCallbackProxy.getWebViewClient();
@@ -4358,7 +4264,6 @@
      */
     @Override
     public void setDownloadListener(DownloadListener listener) {
-        checkThread();
         mCallbackProxy.setDownloadListener(listener);
     }
 
@@ -4367,7 +4272,6 @@
      */
     @Override
     public void setWebChromeClient(WebChromeClient client) {
-        checkThread();
         mCallbackProxy.setWebChromeClient(client);
     }
 
@@ -4375,7 +4279,7 @@
      * Gets the chrome handler.
      * @return the current WebChromeClient instance.
      *
-     * @hide This is an implementation detail.
+     * This is an implementation detail.
      */
     public WebChromeClient getWebChromeClient() {
         return mCallbackProxy.getWebChromeClient();
@@ -4386,7 +4290,6 @@
      * WebBackForwardListClient for handling new items and changes in the
      * history index.
      * @param client An implementation of WebBackForwardListClient.
-     * {@hide}
      */
     public void setWebBackForwardListClient(WebBackForwardListClient client) {
         mCallbackProxy.setWebBackForwardListClient(client);
@@ -4394,7 +4297,6 @@
 
     /**
      * Gets the WebBackForwardListClient.
-     * {@hide}
      */
     public WebBackForwardListClient getWebBackForwardListClient() {
         return mCallbackProxy.getWebBackForwardListClient();
@@ -4406,21 +4308,14 @@
     @Override
     @Deprecated
     public void setPictureListener(PictureListener listener) {
-        checkThread();
         mPictureListener = listener;
     }
 
-    /**
-     * {@hide}
-     */
     /* FIXME: Debug only! Remove for SDK! */
     public void externalRepresentation(Message callback) {
         mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback);
     }
 
-    /**
-     * {@hide}
-     */
     /* FIXME: Debug only! Remove for SDK! */
     public void documentAsText(Message callback) {
         mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback);
@@ -4431,7 +4326,6 @@
      */
     @Override
     public void addJavascriptInterface(Object object, String name) {
-        checkThread();
         if (object == null) {
             return;
         }
@@ -4446,7 +4340,6 @@
      */
     @Override
     public void removeJavascriptInterface(String interfaceName) {
-        checkThread();
         if (mWebViewCore != null) {
             WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
             arg.mInterfaceName = interfaceName;
@@ -4461,7 +4354,6 @@
      */
     @Override
     public WebSettingsClassic getSettings() {
-        checkThread();
         return (mWebViewCore != null) ? mWebViewCore.getSettings() : null;
     }
 
@@ -4470,7 +4362,6 @@
      */
     @Deprecated
     public static synchronized PluginList getPluginList() {
-        checkThread();
         return new PluginList();
     }
 
@@ -4479,7 +4370,6 @@
      */
     @Deprecated
     public void refreshPlugins(boolean reloadOpenPages) {
-        checkThread();
     }
 
     //-------------------------------------------------------------------------
@@ -4783,7 +4673,7 @@
     /**
      * Select the word at the last click point.
      *
-     * @hide This is an implementation detail.
+     * This is an implementation detail.
      */
     public boolean selectText() {
         int x = viewToContentX(mLastTouchX + getScrollX());
@@ -4827,10 +4717,10 @@
         queueFull = nativeSetBaseLayer(mNativeClass, layer, invalRegion,
                                        showVisualIndicator, isPictureAfterFirstLayout);
 
-        if (layer == 0 || isPictureAfterFirstLayout) {
-            mWebViewCore.resumeWebKitDraw();
-        } else if (queueFull) {
+        if (queueFull) {
             mWebViewCore.pauseWebKitDraw();
+        } else {
+            mWebViewCore.resumeWebKitDraw();
         }
 
         if (mHTML5VideoViewProxy != null) {
@@ -4923,7 +4813,7 @@
      * startX, startY, endX, endY
      */
     private void getSelectionHandles(int[] handles) {
-        handles[0] = mSelectCursorBase.right;
+        handles[0] = mSelectCursorBase.left;
         handles[1] = mSelectCursorBase.bottom;
         handles[2] = mSelectCursorExtent.left;
         handles[3] = mSelectCursorExtent.bottom;
@@ -5134,7 +5024,7 @@
     /**
      * Dump the display tree to "/sdcard/displayTree.txt"
      *
-     * @hide debug only
+     * debug only
      */
     public void dumpDisplayTree() {
         nativeDumpDisplayTree(getUrl());
@@ -5144,7 +5034,7 @@
      * Dump the dom tree to adb shell if "toFile" is False, otherwise dump it to
      * "/sdcard/domTree.txt"
      *
-     * @hide debug only
+     * debug only
      */
     public void dumpDomTree(boolean toFile) {
         mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE, toFile ? 1 : 0, 0);
@@ -5154,7 +5044,7 @@
      * Dump the render tree to adb shell if "toFile" is False, otherwise dump it
      * to "/sdcard/renderTree.txt"
      *
-     * @hide debug only
+     * debug only
      */
     public void dumpRenderTree(boolean toFile) {
         mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE, toFile ? 1 : 0, 0);
@@ -5163,7 +5053,7 @@
     /**
      * Called by DRT on UI thread, need to proxy to WebCore thread.
      *
-     * @hide debug only
+     * debug only
      */
     public void useMockDeviceOrientation() {
         mWebViewCore.sendMessage(EventHub.USE_MOCK_DEVICE_ORIENTATION);
@@ -5172,7 +5062,7 @@
     /**
      * Called by DRT on WebCore thread.
      *
-     * @hide debug only
+     * debug only
      */
     public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
             boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
@@ -5468,9 +5358,7 @@
 
     private boolean setupWebkitSelect() {
         syncSelectionCursors();
-        if (mIsCaretSelection) {
-            showPasteWindow();
-        } else if (!startSelectActionMode()) {
+        if (!mIsCaretSelection && !startSelectActionMode()) {
             selectionDone();
             return false;
         }
@@ -5511,13 +5399,12 @@
     @Override
     @Deprecated
     public void emulateShiftHeld() {
-        checkThread();
     }
 
     /**
      * Select all of the text in this WebView.
      *
-     * @hide This is an implementation detail.
+     * This is an implementation detail.
      */
     public void selectAll() {
         mWebViewCore.sendMessage(EventHub.SELECT_ALL);
@@ -5539,7 +5426,6 @@
             if (!mIsCaretSelection) {
                 updateWebkitSelection();
             }
-            mIsCaretSelection = false;
             invalidate(); // redraw without selection
             mAutoScrollX = 0;
             mAutoScrollY = 0;
@@ -5550,7 +5436,7 @@
     /**
      * Copy the selection to the clipboard
      *
-     * @hide This is an implementation detail.
+     * This is an implementation detail.
      */
     public boolean copySelection() {
         boolean copiedSomething = false;
@@ -5577,7 +5463,7 @@
     /**
      * Cut the selected text into the clipboard
      *
-     * @hide This is an implementation detail
+     * This is an implementation detail
      */
     public void cutSelection() {
         copySelection();
@@ -5589,7 +5475,7 @@
     /**
      * Paste text from the clipboard to the cursor position.
      *
-     * @hide This is an implementation detail
+     * This is an implementation detail
      */
     public void pasteFromClipboard() {
         ClipboardManager cm = (ClipboardManager)mContext
@@ -5605,7 +5491,7 @@
     }
 
     /**
-     * @hide This is an implementation detail.
+     * This is an implementation detail.
      */
     public SearchBox getSearchBox() {
         if ((mWebViewCore == null) || (mWebViewCore.getBrowserFrame() == null)) {
@@ -5638,6 +5524,7 @@
         addAccessibilityApisToJavaScript();
 
         mTouchEventQueue.reset();
+        updateHwAccelerated();
     }
 
     @Override
@@ -5657,6 +5544,7 @@
         }
 
         removeAccessibilityApisFromJavaScript();
+        updateHwAccelerated();
     }
 
     @Override
@@ -5779,9 +5667,6 @@
                 mVisibleContentRect, getScale());
     }
 
-    /**
-     * @hide
-     */
     @Override
     public boolean setFrame(int left, int top, int right, int bottom) {
         boolean changed = mWebViewPrivate.super_setFrame(left, top, right, bottom);
@@ -6221,6 +6106,7 @@
                     ted.mReprocess = mDeferTouchProcess;
                     ted.mNativeLayer = mCurrentScrollingLayerId;
                     ted.mNativeLayerRect.set(mScrollingLayerRect);
+                    ted.mMotionEvent = MotionEvent.obtain(ev);
                     ted.mSequence = mTouchEventQueue.nextTouchSequence();
                     mTouchEventQueue.preQueueTouchEventData(ted);
                     mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
@@ -6375,8 +6261,15 @@
             case MotionEvent.ACTION_UP: {
                 mGestureDetector.onTouchEvent(ev);
                 if (mTouchInEditText && mConfirmMove) {
+                    stopTouch();
                     break; // We've been scrolling the edit text.
                 }
+                if (!mConfirmMove && mIsEditingText && mSelectionStarted &&
+                        mIsCaretSelection) {
+                    showPasteWindow();
+                    stopTouch();
+                    break;
+                }
                 // pass the touch events from UI thread to WebCore thread
                 if (shouldForwardTouchEvent()) {
                     TouchEventData ted = new TouchEventData();
@@ -6762,7 +6655,6 @@
             syncSelectionCursors();
             if (mIsCaretSelection) {
                 resetCaretTimer();
-                showPasteWindow();
             }
             invalidate();
         }
@@ -6854,7 +6746,6 @@
     private DrawData mLoadedPicture;
 
     public void setMapTrackballToArrowKeys(boolean setMap) {
-        checkThread();
         mMapTrackballToArrowKeys = setMap;
     }
 
@@ -7081,7 +6972,6 @@
     }
 
     public void flingScroll(int vx, int vy) {
-        checkThread();
         mScroller.fling(getScrollX(), getScrollY(), vx, vy, 0, computeMaxScrollX(), 0,
                 computeMaxScrollY(), mOverflingDistance, mOverflingDistance);
         invalidate();
@@ -7203,7 +7093,6 @@
     @Override
     @Deprecated
     public View getZoomControls() {
-        checkThread();
         if (!getSettings().supportZoom()) {
             Log.w(LOGTAG, "This WebView doesn't support zoom.");
             return null;
@@ -7232,7 +7121,6 @@
      */
     @Override
     public boolean canZoomIn() {
-        checkThread();
         return mZoomManager.canZoomIn();
     }
 
@@ -7241,7 +7129,6 @@
      */
     @Override
     public boolean canZoomOut() {
-        checkThread();
         return mZoomManager.canZoomOut();
     }
 
@@ -7250,7 +7137,6 @@
      */
     @Override
     public boolean zoomIn() {
-        checkThread();
         return mZoomManager.zoomIn();
     }
 
@@ -7259,7 +7145,6 @@
      */
     @Override
     public boolean zoomOut() {
-        checkThread();
         return mZoomManager.zoomOut();
     }
 
@@ -7296,10 +7181,6 @@
         }
     }
 
-    void sendPluginDrawMsg() {
-        mWebViewCore.sendMessage(EventHub.PLUGIN_SURFACE_READY);
-    }
-
     /*
      * Return true if the rect (e.g. plugin) is fully visible and maximized
      * inside the WebView.
@@ -7553,9 +7434,6 @@
         mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE, null, 1000);
     }
 
-    /**
-     * @hide
-     */
     public synchronized WebViewCore getWebViewCore() {
         return mWebViewCore;
     }
@@ -8525,6 +8403,14 @@
                     }
                     break;
 
+                case SHOW_CARET_HANDLE:
+                    if (!mSelectingText && mIsEditingText && mIsCaretSelection) {
+                        setupWebkitSelect();
+                        resetCaretTimer();
+                        showPasteWindow();
+                    }
+                    break;
+
                 default:
                     super.handleMessage(msg);
                     break;
@@ -8729,9 +8615,19 @@
         void onPageSwapOccurred(boolean notifyAnimationStarted);
     }
 
-    /** @hide Called by JNI when pages are swapped (only occurs with hardware
+    long mLastSwapTime;
+    double mAverageSwapFps;
+
+    /** Called by JNI when pages are swapped (only occurs with hardware
      * acceleration) */
     protected void pageSwapCallback(boolean notifyAnimationStarted) {
+        if (DebugFlags.MEASURE_PAGE_SWAP_FPS) {
+            long now = System.currentTimeMillis();
+            long diff = now - mLastSwapTime;
+            mAverageSwapFps = ((1000.0 / diff) + mAverageSwapFps) / 2;
+            Log.d(LOGTAG, "page swap fps: " + mAverageSwapFps);
+            mLastSwapTime = now;
+        }
         mWebViewCore.resumeWebKitDraw();
         if (notifyAnimationStarted) {
             mWebViewCore.sendMessage(EventHub.NOTIFY_ANIMATION_STARTED);
@@ -8793,7 +8689,12 @@
             Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" +
                     b.left+","+b.top+","+b.right+","+b.bottom+"}");
         }
-        invalidateContentRect(draw.mInvalRegion.getBounds());
+        Rect invalBounds = draw.mInvalRegion.getBounds();
+        if (!invalBounds.isEmpty()) {
+            invalidateContentRect(invalBounds);
+        } else {
+            mWebView.invalidate();
+        }
 
         if (mPictureListener != null) {
             mPictureListener.onNewPicture(getWebView(), capturePicture());
@@ -8824,13 +8725,20 @@
                 (data.mStart != data.mEnd ||
                 (mFieldPointer == nodePointer && mFieldPointer != 0))) {
             mIsCaretSelection = (data.mStart == data.mEnd);
-            if (!mSelectingText) {
-                setupWebkitSelect();
-            } else if (!mSelectionStarted) {
-                syncSelectionCursors();
-            }
-            if (mIsCaretSelection) {
-                resetCaretTimer();
+            if (mIsCaretSelection &&
+                    (mInputConnection == null ||
+                    mInputConnection.getEditable().length() == 0)) {
+                // There's no text, don't show caret handle.
+                selectionDone();
+            } else {
+                if (!mSelectingText) {
+                    setupWebkitSelect();
+                } else if (!mSelectionStarted) {
+                    syncSelectionCursors();
+                }
+                if (mIsCaretSelection) {
+                    resetCaretTimer();
+                }
             }
         } else {
             selectionDone();
@@ -9112,8 +9020,10 @@
                         // after the page regains focus.
                         mListBoxMessage = Message.obtain(null,
                                 EventHub.SINGLE_LISTBOX_CHOICE, (int) id, 0);
-                        mListBoxDialog.dismiss();
-                        mListBoxDialog = null;
+                        if (mListBoxDialog != null) {
+                            mListBoxDialog.dismiss();
+                            mListBoxDialog = null;
+                        }
                     }
                 });
                 if (mSelection != -1) {
@@ -9288,7 +9198,7 @@
      * view-specific zoom, scroll offset, or other changes. It does not draw
      * any view-specific chrome, such as progress or URL bars.
      *
-     * @hide only needs to be accessible to Browser and testing
+     * only needs to be accessible to Browser and testing
      */
     public void drawPage(Canvas canvas) {
         calcOurContentVisibleRectF(mVisibleContentRect);
@@ -9298,7 +9208,7 @@
     /**
      * Enable the communication b/t the webView and VideoViewProxy
      *
-     * @hide only used by the Browser
+     * only used by the Browser
      */
     public void setHTML5VideoViewProxy(HTML5VideoViewProxy proxy) {
         mHTML5VideoViewProxy = proxy;
@@ -9308,7 +9218,7 @@
      * Set the time to wait between passing touches to WebCore. See also the
      * TOUCH_SENT_INTERVAL member for further discussion.
      *
-     * @hide This is only used by the DRT test application.
+     * This is only used by the DRT test application.
      */
     public void setTouchInterval(int interval) {
         mCurrentTouchInterval = interval;
@@ -9335,34 +9245,46 @@
         return mViewManager;
     }
 
-    private static void checkThread() {
-        if (Looper.myLooper() != Looper.getMainLooper()) {
-            Throwable throwable = new Throwable(
-                    "Warning: A WebView method was called on thread '" +
-                    Thread.currentThread().getName() + "'. " +
-                    "All WebView methods must be called on the UI thread. " +
-                    "Future versions of WebView may not support use on other threads.");
-            Log.w(LOGTAG, Log.getStackTraceString(throwable));
-            StrictMode.onWebViewMethodCalledOnWrongThread(throwable);
-        }
-    }
-
-    /** @hide send content invalidate */
+    /** send content invalidate */
     protected void contentInvalidateAll() {
         if (mWebViewCore != null && !mBlockWebkitViewMessages) {
             mWebViewCore.sendMessage(EventHub.CONTENT_INVALIDATE_ALL);
         }
     }
 
-    /** @hide discard all textures from tiles. Used in Profiled WebView */
+    /** discard all textures from tiles. Used in Profiled WebView */
     public void discardAllTextures() {
         nativeDiscardAllTextures();
     }
 
+    @Override
+    public void setLayerType(int layerType, Paint paint) {
+        updateHwAccelerated();
+    }
+
+    private void updateHwAccelerated() {
+        if (mNativeClass == 0) {
+            return;
+        }
+        boolean hwAccelerated = false;
+        if (mWebView.isHardwareAccelerated()
+                && mWebView.getLayerType() != View.LAYER_TYPE_SOFTWARE) {
+            hwAccelerated = true;
+        }
+        int result = nativeSetHwAccelerated(mNativeClass, hwAccelerated);
+        if (mWebViewCore == null || mBlockWebkitViewMessages) {
+            return;
+        }
+        if (result == 1) {
+            // Sync layers
+            mWebViewCore.layersDraw();
+        }
+    }
+
     /**
      * Begin collecting per-tile profiling data
      *
-     * @hide only used by profiling tests
+     * only used by profiling tests
      */
     public void tileProfilingStart() {
         nativeTileProfilingStart();
@@ -9370,29 +9292,29 @@
     /**
      * Return per-tile profiling data
      *
-     * @hide only used by profiling tests
+     * only used by profiling tests
      */
     public float tileProfilingStop() {
         return nativeTileProfilingStop();
     }
 
-    /** @hide only used by profiling tests */
+    /** only used by profiling tests */
     public void tileProfilingClear() {
         nativeTileProfilingClear();
     }
-    /** @hide only used by profiling tests */
+    /** only used by profiling tests */
     public int tileProfilingNumFrames() {
         return nativeTileProfilingNumFrames();
     }
-    /** @hide only used by profiling tests */
+    /** only used by profiling tests */
     public int tileProfilingNumTilesInFrame(int frame) {
         return nativeTileProfilingNumTilesInFrame(frame);
     }
-    /** @hide only used by profiling tests */
+    /** only used by profiling tests */
     public int tileProfilingGetInt(int frame, int tile, String key) {
         return nativeTileProfilingGetInt(frame, tile, key);
     }
-    /** @hide only used by profiling tests */
+    /** only used by profiling tests */
     public float tileProfilingGetFloat(int frame, int tile, String key) {
         return nativeTileProfilingGetFloat(frame, tile, key);
     }
@@ -9480,4 +9402,6 @@
     private static native boolean nativeIsBaseFirst(int instance);
     private static native void nativeMapLayerRect(int instance, int layerId,
             Rect rect);
+    // Returns 1 if a layer sync is needed, else 0
+    private static native int nativeSetHwAccelerated(int instance, boolean hwAccelerated);
 }
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index d784b08..afb2992 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -71,9 +71,8 @@
      * WebViewCore always executes in the same thread as the native webkit.
      */
 
-    // The WebView that corresponds to this WebViewCore.
-    // TODO: rename this field (and its getter) to mWebViewClassic or  mWebViewImpl.
-    private WebViewClassic mWebView;
+    // The WebViewClassic that corresponds to this WebViewCore.
+    private WebViewClassic mWebViewClassic;
     // Proxy for handling callbacks from native code
     private final CallbackProxy mCallbackProxy;
     // Settings object for maintaining all settings
@@ -149,7 +148,7 @@
             Map<String, Object> javascriptInterfaces) {
         // No need to assign this in the WebCore thread.
         mCallbackProxy = proxy;
-        mWebView = w;
+        mWebViewClassic = w;
         mJavascriptInterfaces = javascriptInterfaces;
         // This context object is used to initialize the WebViewCore during
         // subwindow creation.
@@ -182,7 +181,7 @@
         // ready.
         mEventHub = new EventHub();
         // Create a WebSettings object for maintaining all settings
-        mSettings = new WebSettingsClassic(mContext, mWebView);
+        mSettings = new WebSettingsClassic(mContext, mWebViewClassic);
         // The WebIconDatabase needs to be initialized within the UI thread so
         // just request the instance here.
         WebIconDatabase.getInstance();
@@ -235,8 +234,8 @@
 
         // Send a message back to WebView to tell it that we have set up the
         // WebCore thread.
-        if (mWebView != null) {
-            Message.obtain(mWebView.mPrivateHandler,
+        if (mWebViewClassic != null) {
+            Message.obtain(mWebViewClassic.mPrivateHandler,
                     WebViewClassic.WEBCORE_INITIALIZED_MSG_ID,
                     mNativeClass, 0).sendToTarget();
         }
@@ -331,8 +330,8 @@
      * @param nodePointer The node which just blurred.
      */
     private void formDidBlur(int nodePointer) {
-        if (mWebView == null) return;
-        Message.obtain(mWebView.mPrivateHandler, WebViewClassic.FORM_DID_BLUR,
+        if (mWebViewClassic == null) return;
+        Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.FORM_DID_BLUR,
                 nodePointer, 0).sendToTarget();
     }
 
@@ -340,8 +339,8 @@
      * Called by JNI when the focus node changed.
      */
     private void focusNodeChanged(int nodePointer, WebKitHitTest hitTest) {
-        if (mWebView == null) return;
-        mWebView.mPrivateHandler.obtainMessage(WebViewClassic.FOCUS_NODE_CHANGED,
+        if (mWebViewClassic == null) return;
+        mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.FOCUS_NODE_CHANGED,
                 nodePointer, 0, hitTest).sendToTarget();
     }
 
@@ -349,8 +348,8 @@
      * Called by JNI to advance focus to the next view.
      */
     private void chromeTakeFocus(int webkitDirection) {
-        if (mWebView == null) return;
-        Message m = mWebView.mPrivateHandler.obtainMessage(
+        if (mWebViewClassic == null) return;
+        Message m = mWebViewClassic.mPrivateHandler.obtainMessage(
                 WebViewClassic.TAKE_FOCUS);
         m.arg1 = mapDirection(webkitDirection);
         m.sendToTarget();
@@ -561,8 +560,8 @@
      * Notify the webview that we want to display the video layer fullscreen.
      */
     protected void enterFullscreenForVideoLayer(int layerId, String url) {
-        if (mWebView == null) return;
-        Message message = Message.obtain(mWebView.mPrivateHandler,
+        if (mWebViewClassic == null) return;
+        Message message = Message.obtain(mWebViewClassic.mPrivateHandler,
                        WebViewClassic.ENTER_FULLSCREEN_VIDEO, layerId, 0);
         message.obj = url;
         message.sendToTarget();
@@ -573,8 +572,8 @@
      * This is called through JNI by webcore.
      */
     protected void exitFullscreenVideo() {
-        if (mWebView == null) return;
-        Message message = Message.obtain(mWebView.mPrivateHandler,
+        if (mWebViewClassic == null) return;
+        Message message = Message.obtain(mWebViewClassic.mPrivateHandler,
                        WebViewClassic.EXIT_FULLSCREEN_VIDEO);
         message.sendToTarget();
     }
@@ -1256,7 +1255,7 @@
                         return;
                     }
 
-                    if (mWebView == null || mNativeClass == 0) {
+                    if (mWebViewClassic == null || mNativeClass == 0) {
                         if (DebugFlags.WEB_VIEW_CORE) {
                             Log.w(LOGTAG, "Rejecting message " + msg.what
                                     + " because we are destroyed");
@@ -1294,7 +1293,7 @@
                                 mBrowserFrame = null;
                                 mSettings.onDestroyed();
                                 mNativeClass = 0;
-                                mWebView = null;
+                                mWebViewClassic = null;
                             }
                             break;
 
@@ -1537,7 +1536,7 @@
                                     yArray, count, ted.mActionIndex,
                                     ted.mMetaState);
                             Message.obtain(
-                                    mWebView.mPrivateHandler,
+                                    mWebViewClassic.mPrivateHandler,
                                     WebViewClassic.PREVENT_TOUCH_ID,
                                     ted.mAction,
                                     ted.mNativeResult ? 1 : 0,
@@ -1607,7 +1606,7 @@
                             String modifiedSelectionString =
                                 nativeModifySelection(mNativeClass, msg.arg1,
                                         msg.arg2);
-                            mWebView.mPrivateHandler.obtainMessage(
+                            mWebViewClassic.mPrivateHandler.obtainMessage(
                                     WebViewClassic.SELECTION_STRING_CHANGED,
                                     modifiedSelectionString).sendToTarget();
                             break;
@@ -1653,7 +1652,7 @@
                                 (WebViewClassic.SaveWebArchiveMessage)msg.obj;
                             saveMessage.mResultFile =
                                 saveWebArchive(saveMessage.mBasename, saveMessage.mAutoname);
-                            mWebView.mPrivateHandler.obtainMessage(
+                            mWebViewClassic.mPrivateHandler.obtainMessage(
                                 WebViewClassic.SAVE_WEBARCHIVE_FINISHED, saveMessage).sendToTarget();
                             break;
 
@@ -1666,7 +1665,7 @@
 
                         case SPLIT_PICTURE_SET:
                             nativeSplitContent(mNativeClass, msg.arg1);
-                            mWebView.mPrivateHandler.obtainMessage(
+                            mWebViewClassic.mPrivateHandler.obtainMessage(
                                     WebViewClassic.REPLACE_BASE_CONTENT, msg.arg1, 0);
                             mSplitPictureIsScheduled = false;
                             break;
@@ -1714,7 +1713,7 @@
                                         d.mNativeLayer, d.mNativeLayerRect);
                             }
                             WebKitHitTest hit = performHitTest(d.mX, d.mY, d.mSlop, true);
-                            mWebView.mPrivateHandler.obtainMessage(
+                            mWebViewClassic.mPrivateHandler.obtainMessage(
                                     WebViewClassic.HIT_TEST_RESULT, hit)
                                     .sendToTarget();
                             break;
@@ -1725,8 +1724,8 @@
 
                         case AUTOFILL_FORM:
                             nativeAutoFillForm(mNativeClass, msg.arg1);
-                            mWebView.mPrivateHandler.obtainMessage(WebViewClassic.AUTOFILL_COMPLETE, null)
-                                    .sendToTarget();
+                            mWebViewClassic.mPrivateHandler.obtainMessage(
+                                    WebViewClassic.AUTOFILL_COMPLETE, null).sendToTarget();
                             break;
 
                         case EXECUTE_JS:
@@ -1734,7 +1733,8 @@
                                 if (DebugFlags.WEB_VIEW_CORE) {
                                     Log.d(LOGTAG, "Executing JS : " + msg.obj);
                                 }
-                                mBrowserFrame.stringByEvaluatingJavaScriptFromString((String) msg.obj);
+                                mBrowserFrame.stringByEvaluatingJavaScriptFromString(
+                                        (String) msg.obj);
                             }
                             break;
                         case SCROLL_LAYER:
@@ -1756,7 +1756,8 @@
                                     handles[0], handles[1], handles[2],
                                     handles[3]);
                             if (copiedText != null) {
-                                mWebView.mPrivateHandler.obtainMessage(WebViewClassic.COPY_TO_CLIPBOARD, copiedText)
+                                mWebViewClassic.mPrivateHandler.obtainMessage(
+                                        WebViewClassic.COPY_TO_CLIPBOARD, copiedText)
                                         .sendToTarget();
                             }
                             break;
@@ -1777,7 +1778,10 @@
                         case SELECT_WORD_AT: {
                             int x = msg.arg1;
                             int y = msg.arg2;
-                            nativeSelectWordAt(mNativeClass, x, y);
+                            if (!nativeSelectWordAt(mNativeClass, x, y)) {
+                                mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.SHOW_CARET_HANDLE)
+                                    .sendToTarget();
+                            }
                             break;
                         }
                         case SELECT_ALL:
@@ -2028,7 +2032,7 @@
             if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
                     && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
                 if (canTakeFocusDirection != 0 && isDown) {
-                    Message m = mWebView.mPrivateHandler.obtainMessage(
+                    Message m = mWebViewClassic.mPrivateHandler.obtainMessage(
                             WebViewClassic.TAKE_FOCUS);
                     m.arg1 = canTakeFocusDirection;
                     m.sendToTarget();
@@ -2101,7 +2105,8 @@
                 width = mViewportWidth;
             } else {
                 // For mobile web site.
-                width = Math.round(mWebView.getViewWidth() / mWebView.getDefaultZoomScale());
+                width = Math.round(mWebViewClassic.getViewWidth() /
+                        mWebViewClassic.getDefaultZoomScale());
             }
         }
         return width;
@@ -2193,8 +2198,8 @@
             // If anything more complex than position has been touched, let's do a full draw
             webkitDraw();
         }
-        mWebView.mPrivateHandler.removeMessages(WebViewClassic.INVAL_RECT_MSG_ID);
-        mWebView.mPrivateHandler.sendMessageAtFrontOfQueue(mWebView.mPrivateHandler
+        mWebViewClassic.mPrivateHandler.removeMessages(WebViewClassic.INVAL_RECT_MSG_ID);
+        mWebViewClassic.mPrivateHandler.sendMessageAtFrontOfQueue(mWebViewClassic.mPrivateHandler
                 .obtainMessage(WebViewClassic.INVAL_RECT_MSG_ID));
     }
 
@@ -2234,7 +2239,7 @@
         draw.mBaseLayer = nativeRecordContent(mNativeClass, draw.mInvalRegion,
                 draw.mContentSize);
         if (draw.mBaseLayer == 0) {
-            if (mWebView != null && !mWebView.isPaused()) {
+            if (mWebViewClassic != null && !mWebViewClassic.isPaused()) {
                 if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort, resending draw message");
                 mEventHub.sendMessageDelayed(Message.obtain(null, EventHub.WEBKIT_DRAW), 10);
             } else {
@@ -2247,7 +2252,7 @@
     }
 
     private void webkitDraw(DrawData draw) {
-        if (mWebView != null) {
+        if (mWebViewClassic != null) {
             draw.mFocusSizeChanged = nativeFocusBoundsChanged(mNativeClass);
             draw.mViewSize = new Point(mCurrentViewWidth, mCurrentViewHeight);
             if (mSettings.getUseWideViewPort()) {
@@ -2266,7 +2271,8 @@
                 mFirstLayoutForNonStandardLoad = false;
             }
             if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID");
-            Message.obtain(mWebView.mPrivateHandler,
+            pauseWebKitDraw();
+            Message.obtain(mWebViewClassic.mPrivateHandler,
                     WebViewClassic.NEW_PICTURE_MSG_ID, draw).sendToTarget();
         }
     }
@@ -2356,7 +2362,7 @@
     // called from JNI or WebView thread
     /* package */ void contentDraw() {
         synchronized (this) {
-            if (mWebView == null || mBrowserFrame == null) {
+            if (mWebViewClassic == null || mBrowserFrame == null) {
                 // We were destroyed
                 return;
             }
@@ -2394,8 +2400,8 @@
             mRestoredY = y;
             return;
         }
-        if (mWebView != null) {
-            Message msg = Message.obtain(mWebView.mPrivateHandler,
+        if (mWebViewClassic != null) {
+            Message msg = Message.obtain(mWebViewClassic.mPrivateHandler,
                     WebViewClassic.SCROLL_TO_MSG_ID, animate ? 1 : 0,
                     onlyIfImeIsShowing ? 1 : 0, new Point(x, y));
             if (mDrawIsScheduled) {
@@ -2417,8 +2423,8 @@
         in WebView since it (and its thread) know the current scale factor.
      */
     private void sendViewInvalidate(int left, int top, int right, int bottom) {
-        if (mWebView != null) {
-            Message.obtain(mWebView.mPrivateHandler,
+        if (mWebViewClassic != null) {
+            Message.obtain(mWebViewClassic.mPrivateHandler,
                            WebViewClassic.INVAL_RECT_MSG_ID,
                            new Rect(left, top, right, bottom)).sendToTarget();
         }
@@ -2433,10 +2439,20 @@
         mRepaintScheduled = false;
     }
 
-    // Gets the WebView corresponding to this WebViewCore. Note that the
-    // WebView object must only be used on the UI thread.
-    /* package */ WebViewClassic getWebView() {
-        return mWebView;
+    // Gets the WebViewClassic corresponding to this WebViewCore. Note that the
+    // WebViewClassic object must only be used on the UI thread.
+    /* package */ WebViewClassic getWebViewClassic() {
+        return mWebViewClassic;
+    }
+
+    // Called by JNI
+    private WebView getWebView() {
+        return mWebViewClassic.getWebView();
+    }
+
+    // Called by JNI
+    private void sendPluginDrawMsg() {
+        sendMessage(EventHub.PLUGIN_SURFACE_READY);
     }
 
     private native void setViewportSettingsFromNative(int nativeClass);
@@ -2449,7 +2465,7 @@
 
         mBrowserFrame.didFirstLayout();
 
-        if (mWebView == null) return;
+        if (mWebViewClassic == null) return;
 
         boolean updateViewState = standardLoad || mIsRestored;
         setupViewport(updateViewState);
@@ -2457,11 +2473,11 @@
         // be called after the WebView updates its state. If updateRestoreState
         // is false, start to draw now as it is ready.
         if (!updateViewState) {
-            mWebView.mViewManager.postReadyToDrawAll();
+            mWebViewClassic.mViewManager.postReadyToDrawAll();
         }
 
         // remove the touch highlight when moving to a new page
-        mWebView.mPrivateHandler.sendEmptyMessage(
+        mWebViewClassic.mPrivateHandler.sendEmptyMessage(
                 WebViewClassic.HIT_TEST_RESULT);
 
         // reset the scroll position, the restored offset and scales
@@ -2477,7 +2493,7 @@
     }
 
     private void setupViewport(boolean updateViewState) {
-        if (mWebView == null || mSettings == null) {
+        if (mWebViewClassic == null || mSettings == null) {
             // We've been destroyed or are being destroyed, return early
             return;
         }
@@ -2525,8 +2541,8 @@
             adjust = (float) mContext.getResources().getDisplayMetrics().densityDpi
                     / mViewportDensityDpi;
         }
-        if (adjust != mWebView.getDefaultZoomScale()) {
-            Message.obtain(mWebView.mPrivateHandler,
+        if (adjust != mWebViewClassic.getDefaultZoomScale()) {
+            Message.obtain(mWebViewClassic.mPrivateHandler,
                     WebViewClassic.UPDATE_ZOOM_DENSITY, adjust).sendToTarget();
         }
         int defaultScale = (int) (adjust * 100);
@@ -2577,7 +2593,7 @@
             // for non-mobile site, we don't need minPrefWidth, set it as 0
             viewState.mScrollX = 0;
             viewState.mShouldStartScrolledRight = false;
-            Message.obtain(mWebView.mPrivateHandler,
+            Message.obtain(mWebViewClassic.mPrivateHandler,
                     WebViewClassic.UPDATE_ZOOM_RANGE, viewState).sendToTarget();
             return;
         }
@@ -2591,7 +2607,7 @@
             // this may happen when WebView just starts. This is not perfect as
             // we call WebView method from WebCore thread. But not perfect
             // reference is better than no reference.
-            webViewWidth = mWebView.getViewWidth();
+            webViewWidth = mWebViewClassic.getViewWidth();
             viewportWidth = (int) (webViewWidth / adjust);
             if (viewportWidth == 0) {
                 if (DebugFlags.WEB_VIEW_CORE) {
@@ -2640,17 +2656,17 @@
             }
         }
 
-        if (mWebView.mHeightCanMeasure) {
+        if (mWebViewClassic.mHeightCanMeasure) {
             // Trick to ensure that the Picture has the exact height for the
             // content by forcing to layout with 0 height after the page is
             // ready, which is indicated by didFirstLayout. This is essential to
             // get rid of the white space in the GMail which uses WebView for
             // message view.
-            mWebView.mLastHeightSent = 0;
+            mWebViewClassic.mLastHeightSent = 0;
             // Send a negative scale to indicate that WebCore should reuse
             // the current scale
             WebViewClassic.ViewSizeData data = new WebViewClassic.ViewSizeData();
-            data.mWidth = mWebView.mLastWidthSent;
+            data.mWidth = mWebViewClassic.mLastWidthSent;
             data.mHeight = 0;
             // if mHeightCanMeasure is true, getUseWideViewPort() can't be
             // true. It is safe to use mWidth for mTextWrapWidth.
@@ -2671,7 +2687,7 @@
             if (viewportWidth == 0) {
                 // Trick to ensure VIEW_SIZE_CHANGED will be sent from WebView
                 // to WebViewCore
-                mWebView.mLastWidthSent = 0;
+                mWebViewClassic.mLastWidthSent = 0;
             } else {
                 WebViewClassic.ViewSizeData data = new WebViewClassic.ViewSizeData();
                 // mViewScale as 0 means it is in zoom overview mode. So we don't
@@ -2699,7 +2715,7 @@
                     if (mSettings.isNarrowColumnLayout()) {
                         // In case of automatic text reflow in fixed view port mode.
                         mInitialViewState.mTextWrapScale =
-                                mWebView.computeReadingLevelScale(data.mScale);
+                                mWebViewClassic.computeReadingLevelScale(data.mScale);
                     }
                 } else {
                     // Scale is given such as when page is restored, use it.
@@ -2720,7 +2736,7 @@
                 // are calling a WebView method from the WebCore thread. But this is preferable
                 // to syncing an incorrect height.
                 data.mHeight = mCurrentViewHeight == 0 ?
-                        Math.round(mWebView.getViewHeight() / data.mScale)
+                        Math.round(mWebViewClassic.getViewHeight() / data.mScale)
                         : Math.round((float) mCurrentViewHeight * data.mWidth / viewportWidth);
                 data.mTextWrapWidth = Math.round(webViewWidth
                         / mInitialViewState.mTextWrapScale);
@@ -2749,8 +2765,8 @@
 
     // called by JNI
     private void needTouchEvents(boolean need) {
-        if (mWebView != null) {
-            Message.obtain(mWebView.mPrivateHandler,
+        if (mWebViewClassic != null) {
+            Message.obtain(mWebViewClassic.mPrivateHandler,
                     WebViewClassic.WEBCORE_NEED_TOUCH_EVENTS, need ? 1 : 0, 0)
                     .sendToTarget();
         }
@@ -2759,8 +2775,8 @@
     // called by JNI
     private void updateTextfield(int ptr, boolean changeToPassword,
             String text, int textGeneration) {
-        if (mWebView != null) {
-            Message msg = Message.obtain(mWebView.mPrivateHandler,
+        if (mWebViewClassic != null) {
+            Message msg = Message.obtain(mWebViewClassic.mPrivateHandler,
                     WebViewClassic.UPDATE_TEXTFIELD_TEXT_MSG_ID, ptr,
                     textGeneration, text);
             msg.getData().putBoolean("password", changeToPassword);
@@ -2771,8 +2787,8 @@
     // called by JNI
     private void updateTextSelection(int pointer, int start, int end,
             int textGeneration, int selectionPtr) {
-        if (mWebView != null) {
-            Message.obtain(mWebView.mPrivateHandler,
+        if (mWebViewClassic != null) {
+            Message.obtain(mWebViewClassic.mPrivateHandler,
                 WebViewClassic.UPDATE_TEXT_SELECTION_MSG_ID, pointer, textGeneration,
                 new TextSelectionData(start, end, selectionPtr)).sendToTarget();
         }
@@ -2781,10 +2797,10 @@
     // called by JNI
     private void updateTextSizeAndScroll(int pointer, int width, int height,
             int scrollX, int scrollY) {
-        if (mWebView != null) {
+        if (mWebViewClassic != null) {
             Rect rect = new Rect(-scrollX, -scrollY, width - scrollX,
                     height - scrollY);
-            Message.obtain(mWebView.mPrivateHandler,
+            Message.obtain(mWebViewClassic.mPrivateHandler,
                     WebViewClassic.EDIT_TEXT_SIZE_CHANGED, pointer, 0, rect)
                     .sendToTarget();
         }
@@ -2792,20 +2808,20 @@
 
     // called by JNI
     private void clearTextEntry() {
-        if (mWebView == null) return;
-        Message.obtain(mWebView.mPrivateHandler,
+        if (mWebViewClassic == null) return;
+        Message.obtain(mWebViewClassic.mPrivateHandler,
                 WebViewClassic.CLEAR_TEXT_ENTRY).sendToTarget();
     }
 
     // called by JNI
     private void initEditField(int start, int end, int selectionPtr,
             TextFieldInitData initData) {
-        if (mWebView == null) {
+        if (mWebViewClassic == null) {
             return;
         }
-        Message.obtain(mWebView.mPrivateHandler,
+        Message.obtain(mWebViewClassic.mPrivateHandler,
                 WebViewClassic.INIT_EDIT_FIELD, initData).sendToTarget();
-        Message.obtain(mWebView.mPrivateHandler,
+        Message.obtain(mWebViewClassic.mPrivateHandler,
                 WebViewClassic.REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID,
                 initData.mFieldPointer, 0,
                 new TextSelectionData(start, end, selectionPtr))
@@ -2815,10 +2831,10 @@
     // called by JNI
     private void updateMatchCount(int matchIndex, int matchCount,
         String findText) {
-        if (mWebView == null) {
+        if (mWebViewClassic == null) {
             return;
         }
-        Message.obtain(mWebView.mPrivateHandler,
+        Message.obtain(mWebViewClassic.mPrivateHandler,
                 WebViewClassic.UPDATE_MATCH_COUNT, matchIndex, matchCount,
                 findText).sendToTarget();
     }
@@ -2842,32 +2858,32 @@
     // called by JNI
     private void requestListBox(String[] array, int[] enabledArray,
             int[] selectedArray) {
-        if (mWebView != null) {
-            mWebView.requestListBox(array, enabledArray, selectedArray);
+        if (mWebViewClassic != null) {
+            mWebViewClassic.requestListBox(array, enabledArray, selectedArray);
         }
     }
 
     // called by JNI
     private void requestListBox(String[] array, int[] enabledArray,
             int selection) {
-        if (mWebView != null) {
-            mWebView.requestListBox(array, enabledArray, selection);
+        if (mWebViewClassic != null) {
+            mWebViewClassic.requestListBox(array, enabledArray, selection);
         }
 
     }
 
     // called by JNI
     private void requestKeyboard(boolean showKeyboard) {
-        if (mWebView != null) {
-            Message.obtain(mWebView.mPrivateHandler,
+        if (mWebViewClassic != null) {
+            Message.obtain(mWebViewClassic.mPrivateHandler,
                     WebViewClassic.REQUEST_KEYBOARD, showKeyboard ? 1 : 0, 0)
                     .sendToTarget();
         }
     }
 
     private void setWebTextViewAutoFillable(int queryId, String preview) {
-        if (mWebView != null) {
-            Message.obtain(mWebView.mPrivateHandler, WebViewClassic.SET_AUTOFILLABLE,
+        if (mWebViewClassic != null) {
+            Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.SET_AUTOFILLABLE,
                     new AutoFillData(queryId, preview))
                     .sendToTarget();
         }
@@ -2879,8 +2895,9 @@
 
     // called by JNI
     private void keepScreenOn(boolean screenOn) {
-        if (mWebView != null) {
-            Message message = mWebView.mPrivateHandler.obtainMessage(WebViewClassic.SCREEN_ON);
+        if (mWebViewClassic != null) {
+            Message message = mWebViewClassic.mPrivateHandler.obtainMessage(
+                    WebViewClassic.SCREEN_ON);
             message.arg1 = screenOn ? 1 : 0;
             message.sendToTarget();
         }
@@ -2889,7 +2906,7 @@
     // called by JNI
     private Class<?> getPluginClass(String libName, String clsName) {
 
-        if (mWebView == null) {
+        if (mWebViewClassic == null) {
             return null;
         }
 
@@ -2916,11 +2933,12 @@
     // called by JNI. PluginWidget function to launch a full-screen view using a
     // View object provided by the plugin class.
     private void showFullScreenPlugin(ViewManager.ChildView childView, int orientation, int npp) {
-        if (mWebView == null) {
+        if (mWebViewClassic == null) {
             return;
         }
 
-        Message message = mWebView.mPrivateHandler.obtainMessage(WebViewClassic.SHOW_FULLSCREEN);
+        Message message = mWebViewClassic.mPrivateHandler.obtainMessage(
+                WebViewClassic.SHOW_FULLSCREEN);
         message.obj = childView.mView;
         message.arg1 = orientation;
         message.arg2 = npp;
@@ -2929,15 +2947,15 @@
 
     // called by JNI
     private void hideFullScreenPlugin() {
-        if (mWebView == null) {
+        if (mWebViewClassic == null) {
             return;
         }
-        mWebView.mPrivateHandler.obtainMessage(WebViewClassic.HIDE_FULLSCREEN)
+        mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.HIDE_FULLSCREEN)
                 .sendToTarget();
     }
 
     private ViewManager.ChildView createSurface(View pluginView) {
-        if (mWebView == null) {
+        if (mWebViewClassic == null) {
             return null;
         }
 
@@ -2952,7 +2970,7 @@
         if(pluginView instanceof SurfaceView)
             ((SurfaceView)pluginView).setZOrderOnTop(true);
 
-        ViewManager.ChildView view = mWebView.mViewManager.createView();
+        ViewManager.ChildView view = mWebViewClassic.mViewManager.createView();
         view.mView = pluginView;
         return view;
     }
@@ -2992,7 +3010,7 @@
     private void showRect(int left, int top, int width, int height,
             int contentWidth, int contentHeight, float xPercentInDoc,
             float xPercentInView, float yPercentInDoc, float yPercentInView) {
-        if (mWebView != null) {
+        if (mWebViewClassic != null) {
             ShowRectData data = new ShowRectData();
             data.mLeft = left;
             data.mTop = top;
@@ -3004,26 +3022,26 @@
             data.mXPercentInView = xPercentInView;
             data.mYPercentInDoc = yPercentInDoc;
             data.mYPercentInView = yPercentInView;
-            Message.obtain(mWebView.mPrivateHandler, WebViewClassic.SHOW_RECT_MSG_ID,
+            Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.SHOW_RECT_MSG_ID,
                     data).sendToTarget();
         }
     }
 
     // called by JNI
     private void centerFitRect(int x, int y, int width, int height) {
-        if (mWebView == null) {
+        if (mWebViewClassic == null) {
             return;
         }
-        mWebView.mPrivateHandler.obtainMessage(WebViewClassic.CENTER_FIT_RECT,
+        mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.CENTER_FIT_RECT,
                 new Rect(x, y, x + width, y + height)).sendToTarget();
     }
 
     // called by JNI
     private void setScrollbarModes(int hMode, int vMode) {
-        if (mWebView == null) {
+        if (mWebViewClassic == null) {
             return;
         }
-        mWebView.mPrivateHandler.obtainMessage(WebViewClassic.SET_SCROLLBAR_MODES,
+        mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.SET_SCROLLBAR_MODES,
                 hMode, vMode).sendToTarget();
     }
 
@@ -3106,7 +3124,7 @@
     private native void nativeSelectText(int nativeClass,
             int startX, int startY, int endX, int endY);
     private native void nativeClearTextSelection(int nativeClass);
-    private native void nativeSelectWordAt(int nativeClass, int x, int y);
+    private native boolean nativeSelectWordAt(int nativeClass, int x, int y);
     private native void nativeSelectAll(int nativeClass);
 
     private static native void nativeCertTrustChanged();
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index 757a619..6c35f19 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -144,7 +144,6 @@
                         null);
             }
         }
-        mDatabase.enableWriteAheadLogging();
 
         // mDatabase should not be null,
         // the only case is RequestAPI test has problem to create db
@@ -163,11 +162,6 @@
                 mDatabase.endTransaction();
             }
         }
-
-        // use per table Mutex lock, turn off database lock, this
-        // improves performance as database's ReentrantLock is
-        // expansive
-        mDatabase.setLockingEnabled(false);
     }
 
     private static void upgradeDatabase() {
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 9016fbc..f049198 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -19,6 +19,7 @@
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.Picture;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -191,7 +192,7 @@
 
     public WebBackForwardList copyBackForwardList();
 
-    public void setFindListener(FindListener listener);
+    public void setFindListener(WebView.FindListener listener);
 
     public void findNext(boolean forward);
 
@@ -337,6 +338,8 @@
         public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate);
 
         public void setBackgroundColor(int color);
+
+        public void setLayerType(int layerType, Paint paint);
     }
 
     interface ScrollDelegate {
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index e36afa3..ca5648a 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -133,6 +133,16 @@
     }
 
     /**
+     * Return the drawable used to represent the scroll thumb - the component that
+     * the user can drag back and forth indicating the current value by its position.
+     *
+     * @return The current thumb drawable
+     */
+    public Drawable getThumb() {
+        return mThumb;
+    }
+
+    /**
      * @see #setThumbOffset(int)
      */
     public int getThumbOffset() {
diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java
index 5096227..aea029b 100644
--- a/core/java/android/widget/AdapterViewFlipper.java
+++ b/core/java/android/widget/AdapterViewFlipper.java
@@ -127,13 +127,29 @@
     }
 
     /**
-     * How long to wait before flipping to the next view
+     * Returns the flip interval, in milliseconds.
      *
-     * @param milliseconds
-     *            time in milliseconds
+     * @return the flip interval in milliseconds
+     *
+     * @see #setFlipInterval(int)
+     *
+     * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval
      */
-    public void setFlipInterval(int milliseconds) {
-        mFlipInterval = milliseconds;
+    public int getFlipInterval() {
+        return mFlipInterval;
+    }
+
+    /**
+     * How long to wait before flipping to the next view.
+     *
+     * @param flipInterval flip interval in milliseconds
+     *
+     * @see #getFlipInterval()
+     *
+     * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval
+     */
+    public void setFlipInterval(int flipInterval) {
+        mFlipInterval = flipInterval;
     }
 
     /**
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index fd93980..c5066b6 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -29,8 +29,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
+import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
@@ -280,9 +280,7 @@
         reorderSpinners();
 
         // set content descriptions
-        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
-            setContentDescriptions();
-        }
+        setContentDescriptions();
     }
 
     /**
@@ -717,20 +715,27 @@
 
     private void setContentDescriptions() {
         // Day
-        String text = mContext.getString(R.string.date_picker_increment_day_button);
-        mDaySpinner.findViewById(R.id.increment).setContentDescription(text);
-        text = mContext.getString(R.string.date_picker_decrement_day_button);
-        mDaySpinner.findViewById(R.id.decrement).setContentDescription(text);
+        trySetContentDescription(mDaySpinner, R.id.increment,
+                R.string.date_picker_increment_day_button);
+        trySetContentDescription(mDaySpinner, R.id.decrement,
+                R.string.date_picker_decrement_day_button);
         // Month
-        text = mContext.getString(R.string.date_picker_increment_month_button);
-        mMonthSpinner.findViewById(R.id.increment).setContentDescription(text);
-        text = mContext.getString(R.string.date_picker_decrement_month_button);
-        mMonthSpinner.findViewById(R.id.decrement).setContentDescription(text);
+        trySetContentDescription(mMonthSpinner, R.id.increment,
+                R.string.date_picker_increment_month_button);
+        trySetContentDescription(mMonthSpinner, R.id.decrement,
+                R.string.date_picker_decrement_month_button);
         // Year
-        text = mContext.getString(R.string.date_picker_increment_year_button);
-        mYearSpinner.findViewById(R.id.increment).setContentDescription(text);
-        text = mContext.getString(R.string.date_picker_decrement_year_button);
-        mYearSpinner.findViewById(R.id.decrement).setContentDescription(text);
+        trySetContentDescription(mYearSpinner, R.id.increment,
+                R.string.date_picker_increment_year_button);
+        trySetContentDescription(mYearSpinner, R.id.decrement,
+                R.string.date_picker_decrement_year_button);
+    }
+
+    private void trySetContentDescription(View root, int viewId, int contDescResId) {
+        View target = root.findViewById(viewId);
+        if (target != null) {
+            target.setContentDescription(mContext.getString(contDescResId));
+        }
     }
 
     private void updateInputState() {
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 739bcce..0f1dab5 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -1908,7 +1908,8 @@
     }
 
     /**
-     * Describes how the child views are horizontally aligned. Defaults to Gravity.LEFT
+     * Set the gravity for this grid. Gravity describes how the child views
+     * are horizontally aligned. Defaults to Gravity.LEFT
      *
      * @param gravity the gravity to apply to this grid's children
      *
@@ -1922,6 +1923,17 @@
     }
 
     /**
+     * Describes how the child views are horizontally aligned. Defaults to Gravity.LEFT
+     *
+     * @return the gravity that will be applied to this grid's children
+     *
+     * @attr ref android.R.styleable#GridView_gravity
+     */
+    public int getGravity() {
+        return mGravity;
+    }
+
+    /**
      * Set the amount of horizontal (x) spacing to place between each item
      * in the grid.
      *
@@ -1937,6 +1949,44 @@
         }
     }
 
+    /**
+     * Returns the amount of horizontal spacing currently used between each item in the grid.
+     *
+     * <p>This is only accurate for the current layout. If {@link #setHorizontalSpacing(int)}
+     * has been called but layout is not yet complete, this method may return a stale value.
+     * To get the horizontal spacing that was explicitly requested use
+     * {@link #getRequestedHorizontalSpacing()}.</p>
+     *
+     * @return Current horizontal spacing between each item in pixels
+     *
+     * @see #setHorizontalSpacing(int)
+     * @see #getRequestedHorizontalSpacing()
+     *
+     * @attr ref android.R.styleable#GridView_horizontalSpacing
+     */
+    public int getHorizontalSpacing() {
+        return mHorizontalSpacing;
+    }
+
+    /**
+     * Returns the requested amount of horizontal spacing between each item in the grid.
+     *
+     * <p>The value returned may have been supplied during inflation as part of a style,
+     * the default GridView style, or by a call to {@link #setHorizontalSpacing(int)}.
+     * If layout is not yet complete or if GridView calculated a different horizontal spacing
+     * from what was requested, this may return a different value from
+     * {@link #getHorizontalSpacing()}.</p>
+     *
+     * @return The currently requested horizontal spacing between items, in pixels
+     *
+     * @see #setHorizontalSpacing(int)
+     * @see #getHorizontalSpacing()
+     *
+     * @attr ref android.R.styleable#GridView_horizontalSpacing
+     */
+    public int getRequestedHorizontalSpacing() {
+        return mRequestedHorizontalSpacing;
+    }
 
     /**
      * Set the amount of vertical (y) spacing to place between each item
@@ -1945,6 +1995,8 @@
      * @param verticalSpacing The amount of vertical space between items,
      * in pixels.
      *
+     * @see #getVerticalSpacing()
+     *
      * @attr ref android.R.styleable#GridView_verticalSpacing
      */
     public void setVerticalSpacing(int verticalSpacing) {
@@ -1955,6 +2007,19 @@
     }
 
     /**
+     * Returns the amount of vertical spacing between each item in the grid.
+     *
+     * @return The vertical spacing between items in pixels
+     *
+     * @see #setVerticalSpacing(int)
+     *
+     * @attr ref android.R.styleable#GridView_verticalSpacing
+     */
+    public int getVerticalSpacing() {
+        return mVerticalSpacing;
+    }
+
+    /**
      * Control how items are stretched to fill their space.
      *
      * @param stretchMode Either {@link #NO_STRETCH},
@@ -1988,6 +2053,39 @@
     }
 
     /**
+     * Return the width of a column in the grid.
+     *
+     * <p>This may not be valid yet if a layout is pending.</p>
+     *
+     * @return The column width in pixels
+     *
+     * @see #setColumnWidth(int)
+     * @see #getRequestedColumnWidth()
+     *
+     * @attr ref android.R.styleable#GridView_columnWidth
+     */
+    public int getColumnWidth() {
+        return mColumnWidth;
+    }
+
+    /**
+     * Return the requested width of a column in the grid.
+     *
+     * <p>This may not be the actual column width used. Use {@link #getColumnWidth()}
+     * to retrieve the current real width of a column.</p>
+     *
+     * @return The requested column width in pixels
+     *
+     * @see #setColumnWidth(int)
+     * @see #getColumnWidth()
+     *
+     * @attr ref android.R.styleable#GridView_columnWidth
+     */
+    public int getRequestedColumnWidth() {
+        return mRequestedColumnWidth;
+    }
+
+    /**
      * Set the number of columns in the grid
      *
      * @param numColumns The desired number of columns.
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 0db6ef2..4e13ea1 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -715,6 +715,7 @@
         } else {
             super.scrollTo(scrollX, scrollY);
         }
+        
         awakenScrollBars();
     }
 
@@ -1204,10 +1205,9 @@
                 }
             }
 
-            awakenScrollBars();
-
-            // Keep on drawing until the animation has finished.
-            postInvalidate();
+            if (!awakenScrollBars()) {
+                invalidate();
+            }
         }
     }
 
@@ -1414,7 +1414,7 @@
     /**
      * Return true if child is a descendant of parent, (or equal to the parent).
      */
-    private boolean isViewDescendantOf(View child, View parent) {
+    private static boolean isViewDescendantOf(View child, View parent) {
         if (child == parent) {
             return true;
         }
@@ -1524,7 +1524,7 @@
         }
     }
 
-    private int clamp(int n, int my, int child) {
+    private static int clamp(int n, int my, int child) {
         if (my >= child || n < 0) {
             return 0;
         }
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 3001ea1..b1a75e1 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -223,11 +223,28 @@
     }
 
     /**
+     * True when ImageView is adjusting its bounds
+     * to preserve the aspect ratio of its drawable
+     *
+     * @return whether to adjust the bounds of this view
+     * to presrve the original aspect ratio of the drawable
+     *
+     * @see #setAdjustViewBounds(boolean)
+     *
+     * @attr ref android.R.styleable#ImageView_adjustViewBounds
+     */
+    public boolean getAdjustViewBounds() {
+        return mAdjustViewBounds;
+    }
+
+    /**
      * Set this to true if you want the ImageView to adjust its bounds
      * to preserve the aspect ratio of its drawable.
      * @param adjustViewBounds Whether to adjust the bounds of this view
      * to presrve the original aspect ratio of the drawable
      * 
+     * @see #getAdjustViewBounds()
+     *
      * @attr ref android.R.styleable#ImageView_adjustViewBounds
      */
     @android.view.RemotableViewMethod
@@ -237,7 +254,20 @@
             setScaleType(ScaleType.FIT_CENTER);
         }
     }
-    
+
+    /**
+     * The maximum width of this view.
+     *
+     * @return The maximum width of this view
+     *
+     * @see #setMaxWidth(int)
+     *
+     * @attr ref android.R.styleable#ImageView_maxWidth
+     */
+    public int getMaxWidth() {
+        return mMaxWidth;
+    }
+
     /**
      * An optional argument to supply a maximum width for this view. Only valid if
      * {@link #setAdjustViewBounds(boolean)} has been set to true. To set an image to be a maximum
@@ -253,14 +283,29 @@
      * </p>
      * 
      * @param maxWidth maximum width for this view
-     * 
+     *
+     * @see #getMaxWidth()
+     *
      * @attr ref android.R.styleable#ImageView_maxWidth
      */
     @android.view.RemotableViewMethod
     public void setMaxWidth(int maxWidth) {
         mMaxWidth = maxWidth;
     }
-    
+
+    /**
+     * The maximum height of this view.
+     *
+     * @return The maximum height of this view
+     *
+     * @see #setMaxHeight(int)
+     *
+     * @attr ref android.R.styleable#ImageView_maxHeight
+     */
+    public int getMaxHeight() {
+        return mMaxHeight;
+    }
+
     /**
      * An optional argument to supply a maximum height for this view. Only valid if
      * {@link #setAdjustViewBounds(boolean)} has been set to true. To set an image to be a
@@ -276,7 +321,9 @@
      * </p>
      * 
      * @param maxHeight maximum height for this view
-     * 
+     *
+     * @see #getMaxHeight()
+     *
      * @attr ref android.R.styleable#ImageView_maxHeight
      */
     @android.view.RemotableViewMethod
@@ -522,7 +569,37 @@
             invalidate();
         }
     }
-    
+
+    /**
+     * Return whether this ImageView crops to padding.
+     *
+     * @return whether this ImageView crops to padding
+     *
+     * @see #setCropToPadding(boolean)
+     *
+     * @attr ref android.R.styleable#ImageView_cropToPadding
+     */
+    public boolean getCropToPadding() {
+        return mCropToPadding;
+    }
+
+    /**
+     * Sets whether this ImageView will crop to padding.
+     *
+     * @param cropToPadding whether this ImageView will crop to padding
+     *
+     * @see #getCropToPadding()
+     *
+     * @attr ref android.R.styleable#ImageView_cropToPadding
+     */
+    public void setCropToPadding(boolean cropToPadding) {
+        if (mCropToPadding != cropToPadding) {
+            mCropToPadding = cropToPadding;
+            requestLayout();
+            invalidate();
+        }
+    }
+
     private void resolveUri() {
         if (mDrawable != null) {
             return;
@@ -997,11 +1074,24 @@
     public final void clearColorFilter() {
         setColorFilter(null);
     }
-    
+
+    /**
+     * Returns the active color filter for this ImageView.
+     *
+     * @return the active color filter for this ImageView
+     *
+     * @see #setColorFilter(android.graphics.ColorFilter)
+     */
+    public ColorFilter getColorFilter() {
+        return mColorFilter;
+    }
+
     /**
      * Apply an arbitrary colorfilter to the image.
      *
      * @param cf the colorfilter to apply (may be null)
+     *
+     * @see #getColorFilter()
      */
     public void setColorFilter(ColorFilter cf) {
         if (mColorFilter != cf) {
@@ -1012,6 +1102,37 @@
         }
     }
 
+    /**
+     * Returns the alpha that will be applied to the drawable of this ImageView.
+     *
+     * @return the alpha that will be applied to the drawable of this ImageView
+     *
+     * @see #setImageAlpha(int)
+     */
+    public int getImageAlpha() {
+        return mAlpha;
+    }
+
+    /**
+     * Sets the alpha value that should be applied to the image.
+     *
+     * @param alpha the alpha value that should be applied to the image
+     *
+     * @see #getImageAlpha()
+     */
+    @RemotableViewMethod
+    public void setImageAlpha(int alpha) {
+        setAlpha(alpha);
+    }
+
+    /**
+     * Sets the alpha value that should be applied to the image.
+     *
+     * @param alpha the alpha value that should be applied to the image
+     *
+     * @deprecated use #setImageAlpha(int) instead
+     */
+    @Deprecated
     @RemotableViewMethod
     public void setAlpha(int alpha) {
         alpha &= 0xFF;          // keep it legal
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 3335da0..4e56cd6 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -16,10 +16,6 @@
 
 package android.widget;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.annotation.Widget;
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -48,22 +44,41 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
 import android.view.animation.DecelerateInterpolator;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.R;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
 /**
  * A widget that enables the user to select a number form a predefined range.
- * The widget presents an input field and up and down buttons for selecting the
- * current value. Pressing/long-pressing the up and down buttons increments and
- * decrements the current value respectively. Touching the input field shows a
- * scroll wheel, which when touched allows direct edit
- * of the current value. Sliding gestures up or down hide the buttons and the
- * input filed, show and rotate the scroll wheel. Flinging is
- * also supported. The widget enables mapping from positions to strings such
- * that, instead of the position index, the corresponding string is displayed.
+ * There are two flavors of this widget and which one is presented to the user
+ * depends on the current theme.
+ * <ul>
+ * <li>
+ * If the current theme is derived from {@link android.R.style#Theme} the widget
+ * presents the current value as an editable input field with an increment button
+ * above and a decrement button below. Long pressing the buttons allows for a quick
+ * change of the current value. Tapping on the input field allows to type in
+ * a desired value.
+ * </li>
+ * <li>
+ * If the current theme is derived from {@link android.R.style#Theme_Holo} or
+ * {@link android.R.style#Theme_Holo_Light} the widget presents the current
+ * value as an editable input field with a lesser value above and a greater
+ * value below. Tapping on the lesser or greater value selects it by animating
+ * the number axis up or down to make the chosen value current. Flinging up
+ * or down allows for multiple increments or decrements of the current value.
+ * Long pressing on the lesser and greater values also allows for a quick change
+ * of the current value. Tapping on the current value allows to type in a
+ * desired value.
+ * </li>
+ * </ul>
  * <p>
  * For an example of using this widget, see {@link android.widget.TimePicker}.
  * </p>
@@ -74,7 +89,7 @@
     /**
      * The number of items show in the selector wheel.
      */
-    public static final int SELECTOR_WHEEL_ITEM_COUNT = 5;
+    private static final int SELECTOR_WHEEL_ITEM_COUNT = 3;
 
     /**
      * The default update interval during long press.
@@ -84,7 +99,7 @@
     /**
      * The index of the middle selector item.
      */
-    private static final int SELECTOR_MIDDLE_ITEM_INDEX = 2;
+    private static final int SELECTOR_MIDDLE_ITEM_INDEX = SELECTOR_WHEEL_ITEM_COUNT / 2;
 
     /**
      * The coefficient by which to adjust (divide) the max fling velocity.
@@ -97,19 +112,12 @@
     private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800;
 
     /**
-     * The duration of scrolling to the next/previous value while changing
-     * the current value by one, i.e. increment or decrement.
+     * The duration of scrolling to the next/previous value while changing the
+     * current value by one, i.e. increment or decrement.
      */
     private static final int CHANGE_CURRENT_BY_ONE_SCROLL_DURATION = 300;
 
     /**
-     * The the delay for showing the input controls after a single tap on the
-     * input text.
-     */
-    private static final int SHOW_INPUT_CONTROLS_DELAY_MILLIS = ViewConfiguration
-            .getDoubleTapTimeout();
-
-    /**
      * The strength of fading in the top and bottom while drawing the selector.
      */
     private static final float TOP_AND_BOTTOM_FADING_EDGE_STRENGTH = 0.9f;
@@ -120,56 +128,31 @@
     private static final int UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT = 2;
 
     /**
-     * In this state the selector wheel is not shown.
+     * The default unscaled distance between the selection dividers.
      */
-    private static final int SELECTOR_WHEEL_STATE_NONE = 0;
+    private static final int UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE = 48;
 
     /**
-     * In this state the selector wheel is small.
+     * The default unscaled minimal distance for a swipe to be considered a fling.
      */
-    private static final int SELECTOR_WHEEL_STATE_SMALL = 1;
+    private static final int UNSCALED_DEFAULT_MIN_FLING_DISTANCE = 150;
 
     /**
-     * In this state the selector wheel is large.
+     * Coefficient for adjusting touch scroll distance.
      */
-    private static final int SELECTOR_WHEEL_STATE_LARGE = 2;
+    private static final float TOUCH_SCROLL_DECELERATION_COEFFICIENT = 2.5f;
 
     /**
-     * The alpha of the selector wheel when it is bright.
+     * The resource id for the default layout.
      */
-    private static final int SELECTOR_WHEEL_BRIGHT_ALPHA = 255;
-
-    /**
-     * The alpha of the selector wheel when it is dimmed.
-     */
-    private static final int SELECTOR_WHEEL_DIM_ALPHA = 60;
-
-    /**
-     * The alpha for the increment/decrement button when it is transparent.
-     */
-    private static final int BUTTON_ALPHA_TRANSPARENT = 0;
-
-    /**
-     * The alpha for the increment/decrement button when it is opaque.
-     */
-    private static final int BUTTON_ALPHA_OPAQUE = 1;
-
-    /**
-     * The property for setting the selector paint.
-     */
-    private static final String PROPERTY_SELECTOR_PAINT_ALPHA = "selectorPaintAlpha";
-
-    /**
-     * The property for setting the increment/decrement button alpha.
-     */
-    private static final String PROPERTY_BUTTON_ALPHA = "alpha";
+    private static final int DEFAULT_LAYOUT_RESOURCE_ID = R.layout.number_picker;
 
     /**
      * The numbers accepted by the input text's {@link Filter}
      */
     private static final char[] DIGIT_CHARACTERS = new char[] {
             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
-    };
+     };
 
     /**
      * Constant for unspecified size.
@@ -215,6 +198,11 @@
     private final EditText mInputText;
 
     /**
+     * The distance between the two selection dividers.
+     */
+    private final int mSelectionDividersDistance;
+
+    /**
      * The min height of this widget.
      */
     private final int mMinHeight;
@@ -245,6 +233,11 @@
     private final int mTextSize;
 
     /**
+     * The minimal distance for a swipe to be considered a fling.
+     */
+    private final int mMinFlingDistance;
+
+    /**
      * The height of the gap between text elements if the selector wheel.
      */
     private int mSelectorTextGapHeight;
@@ -297,10 +290,7 @@
     /**
      * The selector indices whose value are show by the selector.
      */
-    private final int[] mSelectorIndices = new int[] {
-            Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE,
-            Integer.MIN_VALUE
-    };
+    private final int[] mSelectorIndices = new int[SELECTOR_WHEEL_ITEM_COUNT];
 
     /**
      * The {@link Paint} for drawing the selector.
@@ -343,25 +333,15 @@
     private SetSelectionCommand mSetSelectionCommand;
 
     /**
-     * Handle to the reusable command for adjusting the scroller.
-     */
-    private AdjustScrollerCommand mAdjustScrollerCommand;
-
-    /**
      * Handle to the reusable command for changing the current value from long
      * press by one.
      */
     private ChangeCurrentByOneFromLongPressCommand mChangeCurrentByOneFromLongPressCommand;
 
     /**
-     * {@link Animator} for showing the up/down arrows.
+     * Command for beginning an edit of the current value via IME on long press.
      */
-    private final AnimatorSet mShowInputControlsAnimator;
-
-    /**
-     * {@link Animator} for dimming the selector wheel.
-     */
-    private final Animator mDimSelectorWheelAnimator;
+    private BeginSoftInputOnLongPressCommand mBeginSoftInputOnLongPressCommand;
 
     /**
      * The Y position of the last down event.
@@ -369,24 +349,14 @@
     private float mLastDownEventY;
 
     /**
-     * The Y position of the last motion event.
+     * The time of the last down event.
      */
-    private float mLastMotionEventY;
+    private long mLastDownEventTime;
 
     /**
-     * Flag if to check for double tap and potentially start edit.
+     * The Y position of the last down or move event.
      */
-    private boolean mCheckBeginEditOnUpEvent;
-
-    /**
-     * Flag if to adjust the selector wheel on next up event.
-     */
-    private boolean mAdjustScrollerOnUpEvent;
-
-    /**
-     * The state of the selector wheel.
-     */
-    private int mSelectorWheelState;
+    private float mLastDownOrMoveEventY;
 
     /**
      * Determines speed during touch scrolling.
@@ -419,9 +389,9 @@
     private final int mSolidColor;
 
     /**
-     * Flag indicating if this widget supports flinging.
+     * Flag whether this widget has a selector wheel.
      */
-    private final boolean mFlingable;
+    private final boolean mHasSelectorWheel;
 
     /**
      * Divider for showing item to be selected while scrolling
@@ -434,29 +404,40 @@
     private final int mSelectionDividerHeight;
 
     /**
-     * Reusable {@link Rect} instance.
-     */
-    private final Rect mTempRect = new Rect();
-
-    /**
      * The current scroll state of the number picker.
      */
     private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
 
     /**
-     * The duration of the animation for showing the input controls.
+     * Flag whether to ignore move events - we ignore such when we show in IME
+     * to prevent the content from scrolling.
      */
-    private final long mShowInputControlsAnimimationDuration;
+    private boolean mIngonreMoveEvents;
 
     /**
-     * Flag whether the scoll wheel and the fading edges have been initialized.
+     * Flag whether to show soft input on tap.
      */
-    private boolean mScrollWheelAndFadingEdgesInitialized;
+    private boolean mShowSoftInputOnTap;
 
     /**
-     * The time of the last up event.
+     * The top of the top selection divider.
      */
-    private long mLastUpEventTimeMillis;
+    private int mTopSelectionDividerTop;
+
+    /**
+     * The bottom of the bottom selection divider.
+     */
+    private int mBottomSelectionDividerBottom;
+
+    /**
+     * The virtual id of the last hovered child.
+     */
+    private int mLastHoveredChildVirtualViewId;
+
+    /**
+     * Provider to report to clients the semantic structure of this widget.
+     */
+    private AccessibilityNodeProviderImpl mAccessibilityNodeProvider;
 
     /**
      * Interface to listen for changes of the current value.
@@ -484,7 +465,7 @@
         public static int SCROLL_STATE_IDLE = 0;
 
         /**
-         * The user is scrolling using touch, and their finger is still on the screen.
+         * The user is scrolling using touch, and his finger is still on the screen.
          */
         public static int SCROLL_STATE_TOUCH_SCROLL = 1;
 
@@ -549,58 +530,78 @@
         super(context, attrs, defStyle);
 
         // process style attributes
-        TypedArray attributesArray = context.obtainStyledAttributes(attrs,
-                R.styleable.NumberPicker, defStyle, 0);
+        TypedArray attributesArray = context.obtainStyledAttributes(
+                attrs, R.styleable.NumberPicker, defStyle, 0);
+        final int layoutResId = attributesArray.getResourceId(
+                R.styleable.NumberPicker_internalLayout, DEFAULT_LAYOUT_RESOURCE_ID);
+
+        mHasSelectorWheel = (layoutResId != DEFAULT_LAYOUT_RESOURCE_ID);
+
         mSolidColor = attributesArray.getColor(R.styleable.NumberPicker_solidColor, 0);
-        mFlingable = attributesArray.getBoolean(R.styleable.NumberPicker_flingable, true);
+
         mSelectionDivider = attributesArray.getDrawable(R.styleable.NumberPicker_selectionDivider);
-        int defSelectionDividerHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT,
+
+        final int defSelectionDividerHeight = (int) TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT,
                 getResources().getDisplayMetrics());
         mSelectionDividerHeight = attributesArray.getDimensionPixelSize(
                 R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight);
+
+        final int defSelectionDividerDistance = (int) TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE,
+                getResources().getDisplayMetrics());
+        mSelectionDividersDistance = attributesArray.getDimensionPixelSize(
+                R.styleable.NumberPicker_selectionDividersDistance, defSelectionDividerDistance);
+
+        final int defMinFlingDistance = (int) TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_MIN_FLING_DISTANCE,
+                getResources().getDisplayMetrics());
+        mMinFlingDistance = attributesArray.getDimensionPixelSize(
+                R.styleable.NumberPicker_minFlingDistance, defMinFlingDistance);
+
         mMinHeight = attributesArray.getDimensionPixelSize(
                 R.styleable.NumberPicker_internalMinHeight, SIZE_UNSPECIFIED);
+
         mMaxHeight = attributesArray.getDimensionPixelSize(
                 R.styleable.NumberPicker_internalMaxHeight, SIZE_UNSPECIFIED);
         if (mMinHeight != SIZE_UNSPECIFIED && mMaxHeight != SIZE_UNSPECIFIED
                 && mMinHeight > mMaxHeight) {
             throw new IllegalArgumentException("minHeight > maxHeight");
         }
-        mMinWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_internalMinWidth,
-                SIZE_UNSPECIFIED);
-        mMaxWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_internalMaxWidth,
-                SIZE_UNSPECIFIED);
+
+        mMinWidth = attributesArray.getDimensionPixelSize(
+                R.styleable.NumberPicker_internalMinWidth, SIZE_UNSPECIFIED);
+
+        mMaxWidth = attributesArray.getDimensionPixelSize(
+                R.styleable.NumberPicker_internalMaxWidth, SIZE_UNSPECIFIED);
         if (mMinWidth != SIZE_UNSPECIFIED && mMaxWidth != SIZE_UNSPECIFIED
                 && mMinWidth > mMaxWidth) {
             throw new IllegalArgumentException("minWidth > maxWidth");
         }
-        mComputeMaxWidth = (mMaxWidth == Integer.MAX_VALUE);
-        attributesArray.recycle();
 
-        mShowInputControlsAnimimationDuration = getResources().getInteger(
-                R.integer.config_longAnimTime);
+        mComputeMaxWidth = (mMaxWidth == Integer.MAX_VALUE);
+
+        attributesArray.recycle();
 
         // By default Linearlayout that we extend is not drawn. This is
         // its draw() method is not called but dispatchDraw() is called
         // directly (see ViewGroup.drawChild()). However, this class uses
         // the fading edge effect implemented by View and we need our
         // draw() method to be called. Therefore, we declare we will draw.
-        setWillNotDraw(false);
-        setSelectorWheelState(SELECTOR_WHEEL_STATE_NONE);
+        setWillNotDraw(!mHasSelectorWheel);
 
         LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
                 Context.LAYOUT_INFLATER_SERVICE);
-        inflater.inflate(R.layout.number_picker, this, true);
+        inflater.inflate(layoutResId, this, true);
 
         OnClickListener onClickListener = new OnClickListener() {
             public void onClick(View v) {
                 hideSoftInput();
                 mInputText.clearFocus();
                 if (v.getId() == R.id.increment) {
-                    changeCurrentByOne(true);
+                    changeValueByOne(true);
                 } else {
-                    changeCurrentByOne(false);
+                    changeValueByOne(false);
                 }
             }
         };
@@ -610,23 +611,31 @@
                 hideSoftInput();
                 mInputText.clearFocus();
                 if (v.getId() == R.id.increment) {
-                    postChangeCurrentByOneFromLongPress(true);
+                    postChangeCurrentByOneFromLongPress(true, 0);
                 } else {
-                    postChangeCurrentByOneFromLongPress(false);
+                    postChangeCurrentByOneFromLongPress(false, 0);
                 }
                 return true;
             }
         };
 
         // increment button
-        mIncrementButton = (ImageButton) findViewById(R.id.increment);
-        mIncrementButton.setOnClickListener(onClickListener);
-        mIncrementButton.setOnLongClickListener(onLongClickListener);
+        if (!mHasSelectorWheel) {
+            mIncrementButton = (ImageButton) findViewById(R.id.increment);
+            mIncrementButton.setOnClickListener(onClickListener);
+            mIncrementButton.setOnLongClickListener(onLongClickListener);
+        } else {
+            mIncrementButton = null;
+        }
 
         // decrement button
-        mDecrementButton = (ImageButton) findViewById(R.id.decrement);
-        mDecrementButton.setOnClickListener(onClickListener);
-        mDecrementButton.setOnLongClickListener(onLongClickListener);
+        if (!mHasSelectorWheel) {
+            mDecrementButton = (ImageButton) findViewById(R.id.decrement);
+            mDecrementButton.setOnClickListener(onClickListener);
+            mDecrementButton.setOnLongClickListener(onLongClickListener);
+        } else {
+            mDecrementButton = null;
+        }
 
         // input text
         mInputText = (EditText) findViewById(R.id.numberpicker_input);
@@ -648,7 +657,6 @@
         mInputText.setImeOptions(EditorInfo.IME_ACTION_DONE);
 
         // initialize constants
-        mTouchSlop = ViewConfiguration.getTapTimeout();
         ViewConfiguration configuration = ViewConfiguration.get(context);
         mTouchSlop = configuration.getScaledTouchSlop();
         mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
@@ -667,69 +675,22 @@
         paint.setColor(color);
         mSelectorWheelPaint = paint;
 
-        // create the animator for showing the input controls
-        mDimSelectorWheelAnimator = ObjectAnimator.ofInt(this, PROPERTY_SELECTOR_PAINT_ALPHA,
-                SELECTOR_WHEEL_BRIGHT_ALPHA, SELECTOR_WHEEL_DIM_ALPHA);
-        final ObjectAnimator showIncrementButton = ObjectAnimator.ofFloat(mIncrementButton,
-                PROPERTY_BUTTON_ALPHA, BUTTON_ALPHA_TRANSPARENT, BUTTON_ALPHA_OPAQUE);
-        final ObjectAnimator showDecrementButton = ObjectAnimator.ofFloat(mDecrementButton,
-                PROPERTY_BUTTON_ALPHA, BUTTON_ALPHA_TRANSPARENT, BUTTON_ALPHA_OPAQUE);
-        mShowInputControlsAnimator = new AnimatorSet();
-        mShowInputControlsAnimator.playTogether(mDimSelectorWheelAnimator, showIncrementButton,
-                showDecrementButton);
-        mShowInputControlsAnimator.addListener(new AnimatorListenerAdapter() {
-            private boolean mCanceled = false;
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (!mCanceled) {
-                    // if canceled => we still want the wheel drawn
-                    setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL);
-                }
-                mCanceled = false;
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                if (mShowInputControlsAnimator.isRunning()) {
-                    mCanceled = true;
-                }
-            }
-        });
-
         // create the fling and adjust scrollers
         mFlingScroller = new Scroller(getContext(), null, true);
         mAdjustScroller = new Scroller(getContext(), new DecelerateInterpolator(2.5f));
 
         updateInputTextView();
-        updateIncrementAndDecrementButtonsVisibilityState();
-
-        if (mFlingable) {
-           if (isInEditMode()) {
-               setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL);
-           } else {
-                // Start with shown selector wheel and hidden controls. When made
-                // visible hide the selector and fade-in the controls to suggest
-                // fling interaction.
-                setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE);
-                hideInputControls();
-           }
-        }
     }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        if (!mHasSelectorWheel) {
+            super.onLayout(changed, left, top, right, bottom);
+            return;
+        }
         final int msrdWdth = getMeasuredWidth();
         final int msrdHght = getMeasuredHeight();
 
-        // Increment button at the top.
-        final int inctBtnMsrdWdth = mIncrementButton.getMeasuredWidth();
-        final int incrBtnLeft = (msrdWdth - inctBtnMsrdWdth) / 2;
-        final int incrBtnTop = 0;
-        final int incrBtnRight = incrBtnLeft + inctBtnMsrdWdth;
-        final int incrBtnBottom = incrBtnTop + mIncrementButton.getMeasuredHeight();
-        mIncrementButton.layout(incrBtnLeft, incrBtnTop, incrBtnRight, incrBtnBottom);
-
         // Input text centered horizontally.
         final int inptTxtMsrdWdth = mInputText.getMeasuredWidth();
         final int inptTxtMsrdHght = mInputText.getMeasuredHeight();
@@ -739,24 +700,23 @@
         final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;
         mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);
 
-        // Decrement button at the top.
-        final int decrBtnMsrdWdth = mIncrementButton.getMeasuredWidth();
-        final int decrBtnLeft = (msrdWdth - decrBtnMsrdWdth) / 2;
-        final int decrBtnTop = msrdHght - mDecrementButton.getMeasuredHeight();
-        final int decrBtnRight = decrBtnLeft + decrBtnMsrdWdth;
-        final int decrBtnBottom = msrdHght;
-        mDecrementButton.layout(decrBtnLeft, decrBtnTop, decrBtnRight, decrBtnBottom);
-
-        if (!mScrollWheelAndFadingEdgesInitialized) {
-            mScrollWheelAndFadingEdgesInitialized = true;
+        if (changed) {
             // need to do all this when we know our size
             initializeSelectorWheel();
             initializeFadingEdges();
+            mTopSelectionDividerTop = (getHeight() - mSelectionDividersDistance) / 2
+                    - mSelectionDividerHeight;
+            mBottomSelectionDividerBottom = mTopSelectionDividerTop + 2 * mSelectionDividerHeight
+                    + mSelectionDividersDistance;
         }
     }
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (!mHasSelectorWheel) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            return;
+        }
         // Try greedily to fit the max width and height.
         final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec, mMaxWidth);
         final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMaxHeight);
@@ -769,120 +729,143 @@
         setMeasuredDimension(widthSize, heightSize);
     }
 
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent event) {
-        if (!isEnabled() || !mFlingable) {
-            return false;
-        }
-        switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-                mLastMotionEventY = mLastDownEventY = event.getY();
-                removeAllCallbacks();
-                mShowInputControlsAnimator.cancel();
-                mDimSelectorWheelAnimator.cancel();
-                mCheckBeginEditOnUpEvent = false;
-                mAdjustScrollerOnUpEvent = true;
-                if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
-                    mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA);
-                    boolean scrollersFinished = mFlingScroller.isFinished()
-                            && mAdjustScroller.isFinished();
-                    if (!scrollersFinished) {
-                        mFlingScroller.forceFinished(true);
-                        mAdjustScroller.forceFinished(true);
-                        onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
-                    }
-                    mCheckBeginEditOnUpEvent = scrollersFinished;
-                    mAdjustScrollerOnUpEvent = true;
-                    hideSoftInput();
-                    hideInputControls();
-                    return true;
+    /**
+     * Move to the final position of a scroller. Ensures to force finish the scroller
+     * and if it is not at its final position a scroll of the selector wheel is
+     * performed to fast forward to the final position.
+     *
+     * @param scroller The scroller to whose final position to get.
+     * @return True of the a move was performed, i.e. the scroller was not in final position.
+     */
+    private boolean moveToFinalScrollerPosition(Scroller scroller) {
+        scroller.forceFinished(true);
+        int amountToScroll = scroller.getFinalY() - scroller.getCurrY();
+        int futureScrollOffset = (mCurrentScrollOffset + amountToScroll) % mSelectorElementHeight;
+        int overshootAdjustment = mInitialScrollOffset - futureScrollOffset;
+        if (overshootAdjustment != 0) {
+            if (Math.abs(overshootAdjustment) > mSelectorElementHeight / 2) {
+                if (overshootAdjustment > 0) {
+                    overshootAdjustment -= mSelectorElementHeight;
+                } else {
+                    overshootAdjustment += mSelectorElementHeight;
                 }
-                if (isEventInVisibleViewHitRect(event, mIncrementButton)
-                        || isEventInVisibleViewHitRect(event, mDecrementButton)) {
-                    return false;
-                }
-                mAdjustScrollerOnUpEvent = false;
-                setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE);
-                hideSoftInput();
-                hideInputControls();
-                return true;
-            case MotionEvent.ACTION_MOVE:
-                float currentMoveY = event.getY();
-                int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
-                if (deltaDownY > mTouchSlop) {
-                    mCheckBeginEditOnUpEvent = false;
-                    onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
-                    setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE);
-                    hideSoftInput();
-                    hideInputControls();
-                    return true;
-                }
-                break;
+            }
+            amountToScroll += overshootAdjustment;
+            scrollBy(0, amountToScroll);
+            return true;
         }
         return false;
     }
 
     @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        if (!isEnabled()) {
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        if (!mHasSelectorWheel || !isEnabled()) {
+            return false;
+        }
+        final int action = event.getActionMasked();
+        switch (action) {
+            case MotionEvent.ACTION_DOWN: {
+                removeAllCallbacks();
+                mInputText.setVisibility(View.INVISIBLE);
+                mLastDownOrMoveEventY = mLastDownEventY = event.getY();
+                mLastDownEventTime = event.getEventTime();
+                mIngonreMoveEvents = false;
+                mShowSoftInputOnTap = false;
+                if (!mFlingScroller.isFinished()) {
+                    mFlingScroller.forceFinished(true);
+                    mAdjustScroller.forceFinished(true);
+                    onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
+                } else if (!mAdjustScroller.isFinished()) {
+                    mFlingScroller.forceFinished(true);
+                    mAdjustScroller.forceFinished(true);
+                } else if (mLastDownEventY < mTopSelectionDividerTop) {
+                    hideSoftInput();
+                    postChangeCurrentByOneFromLongPress(
+                            false, ViewConfiguration.getLongPressTimeout());
+                } else if (mLastDownEventY > mBottomSelectionDividerBottom) {
+                    hideSoftInput();
+                    postChangeCurrentByOneFromLongPress(
+                            true, ViewConfiguration.getLongPressTimeout());
+                } else {
+                    mShowSoftInputOnTap = true;
+                    postBeginSoftInputOnLongPressCommand();
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (!isEnabled() || !mHasSelectorWheel) {
             return false;
         }
         if (mVelocityTracker == null) {
             mVelocityTracker = VelocityTracker.obtain();
         }
-        mVelocityTracker.addMovement(ev);
-        int action = ev.getActionMasked();
+        mVelocityTracker.addMovement(event);
+        int action = event.getActionMasked();
         switch (action) {
-            case MotionEvent.ACTION_MOVE:
-                float currentMoveY = ev.getY();
-                if (mCheckBeginEditOnUpEvent
-                        || mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
+            case MotionEvent.ACTION_MOVE: {
+                if (mIngonreMoveEvents) {
+                    break;
+                }
+                float currentMoveY = event.getY();
+                if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
                     int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
                     if (deltaDownY > mTouchSlop) {
-                        mCheckBeginEditOnUpEvent = false;
+                        removeAllCallbacks();
                         onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
                     }
+                } else {
+                    int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY)
+                            / TOUCH_SCROLL_DECELERATION_COEFFICIENT);
+                    scrollBy(0, deltaMoveY);
+                    invalidate();
                 }
-                int deltaMoveY = (int) (currentMoveY - mLastMotionEventY);
-                scrollBy(0, deltaMoveY);
-                invalidate();
-                mLastMotionEventY = currentMoveY;
-                break;
-            case MotionEvent.ACTION_UP:
-                if (mCheckBeginEditOnUpEvent) {
-                    mCheckBeginEditOnUpEvent = false;
-                    final long deltaTapTimeMillis = ev.getEventTime() - mLastUpEventTimeMillis;
-                    if (deltaTapTimeMillis < ViewConfiguration.getDoubleTapTimeout()) {
-                        setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL);
-                        showInputControls(mShowInputControlsAnimimationDuration);
-                        mInputText.requestFocus();
-                        InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
-                        if (inputMethodManager != null) {
-                            inputMethodManager.showSoftInput(mInputText, 0);
-                        }
-                        mLastUpEventTimeMillis = ev.getEventTime();
-                        return true;
-                    }
-                }
+                mLastDownOrMoveEventY = currentMoveY;
+            } break;
+            case MotionEvent.ACTION_UP: {
+                removeBeginSoftInputCommand();
+                removeChangeCurrentByOneFromLongPress();
                 VelocityTracker velocityTracker = mVelocityTracker;
                 velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
                 int initialVelocity = (int) velocityTracker.getYVelocity();
                 if (Math.abs(initialVelocity) > mMinimumFlingVelocity) {
-                    fling(initialVelocity);
+                    int deltaMove = (int) (event.getY() - mLastDownEventY);
+                    int absDeltaMoveY = Math.abs(deltaMove);
+                    if (absDeltaMoveY > mMinFlingDistance) {
+                        fling(initialVelocity);
+                    } else {
+                        changeValueByOne(deltaMove < 0);
+                    }
                     onScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
                 } else {
-                    if (mAdjustScrollerOnUpEvent) {
-                        if (mFlingScroller.isFinished() && mAdjustScroller.isFinished()) {
-                            postAdjustScrollerCommand(0);
+                    int eventY = (int) event.getY();
+                    int deltaMoveY = (int) Math.abs(eventY - mLastDownEventY);
+                    long deltaTime = event.getEventTime() - mLastDownEventTime;
+                    if (deltaMoveY <= mTouchSlop && deltaTime < ViewConfiguration.getTapTimeout()) {
+                        if (mShowSoftInputOnTap) {
+                            mShowSoftInputOnTap = false;
+                            showSoftInput();
+                        } else {
+                            int selectorIndexOffset = (eventY / mSelectorElementHeight)
+                                    - SELECTOR_MIDDLE_ITEM_INDEX;
+                            if (selectorIndexOffset > 0) {
+                                changeValueByOne(true);
+                            } else if (selectorIndexOffset < 0) {
+                                changeValueByOne(false);
+                            }
                         }
                     } else {
-                        postAdjustScrollerCommand(SHOW_INPUT_CONTROLS_DELAY_MILLIS);
+                        ensureScrollWheelAdjusted();
                     }
+                    onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
                 }
                 mVelocityTracker.recycle();
                 mVelocityTracker = null;
-                mLastUpEventTimeMillis = ev.getEventTime();
-                break;
+            } break;
         }
         return true;
     }
@@ -891,12 +874,6 @@
     public boolean dispatchTouchEvent(MotionEvent event) {
         final int action = event.getActionMasked();
         switch (action) {
-            case MotionEvent.ACTION_MOVE:
-                if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
-                    removeAllCallbacks();
-                    forceCompleteChangeCurrentByOneViaScroll();
-                }
-                break;
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP:
                 removeAllCallbacks();
@@ -907,27 +884,75 @@
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
-        int keyCode = event.getKeyCode();
-        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
-            removeAllCallbacks();
+        final int keyCode = event.getKeyCode();
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+            case KeyEvent.KEYCODE_ENTER:
+                removeAllCallbacks();
+                break;
         }
         return super.dispatchKeyEvent(event);
     }
 
     @Override
     public boolean dispatchTrackballEvent(MotionEvent event) {
-        int action = event.getActionMasked();
-        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
-            removeAllCallbacks();
+        final int action = event.getActionMasked();
+        switch (action) {
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                removeAllCallbacks();
+                break;
         }
         return super.dispatchTrackballEvent(event);
     }
 
     @Override
-    public void computeScroll() {
-        if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) {
-            return;
+    protected boolean dispatchHoverEvent(MotionEvent event) {
+        if (!mHasSelectorWheel) {
+            return super.dispatchHoverEvent(event);
         }
+        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            final int eventY = (int) event.getY();
+            final int hoveredVirtualViewId;
+            if (eventY < mTopSelectionDividerTop) {
+                hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_DECREMENT;
+            } else if (eventY > mBottomSelectionDividerBottom) {
+                hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INCREMENT;
+            } else {
+                hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INPUT;
+            }
+            final int action = event.getActionMasked();
+            AccessibilityNodeProviderImpl provider =
+                (AccessibilityNodeProviderImpl) getAccessibilityNodeProvider();
+            switch (action) {
+                case MotionEvent.ACTION_HOVER_ENTER: {
+                    provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
+                            AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+                    mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
+                } break;
+                case MotionEvent.ACTION_HOVER_MOVE: {
+                    if (mLastHoveredChildVirtualViewId != hoveredVirtualViewId
+                            && mLastHoveredChildVirtualViewId != View.NO_ID) {
+                        provider.sendAccessibilityEventForVirtualView(
+                                mLastHoveredChildVirtualViewId,
+                                AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+                        provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
+                                AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+                        mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
+                    }
+                } break;
+                case MotionEvent.ACTION_HOVER_EXIT: {
+                    provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
+                            AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+                    mLastHoveredChildVirtualViewId = View.NO_ID;
+                } break;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void computeScroll() {
         Scroller scroller = mFlingScroller;
         if (scroller.isFinished()) {
             scroller = mAdjustScroller;
@@ -952,16 +977,17 @@
     @Override
     public void setEnabled(boolean enabled) {
         super.setEnabled(enabled);
-        mIncrementButton.setEnabled(enabled);
-        mDecrementButton.setEnabled(enabled);
+        if (!mHasSelectorWheel) {
+            mIncrementButton.setEnabled(enabled);
+        }
+        if (!mHasSelectorWheel) {
+            mDecrementButton.setEnabled(enabled);
+        }
         mInputText.setEnabled(enabled);
     }
 
     @Override
     public void scrollBy(int x, int y) {
-        if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) {
-            return;
-        }
         int[] selectorIndices = mSelectorIndices;
         if (!mWrapSelectorWheel && y > 0
                 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
@@ -977,7 +1003,7 @@
         while (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight) {
             mCurrentScrollOffset -= mSelectorElementHeight;
             decrementSelectorIndices(selectorIndices);
-            changeCurrent(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX]);
+            setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);
             if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
                 mCurrentScrollOffset = mInitialScrollOffset;
             }
@@ -985,7 +1011,7 @@
         while (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight) {
             mCurrentScrollOffset += mSelectorElementHeight;
             incrementSelectorIndices(selectorIndices);
-            changeCurrent(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX]);
+            setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);
             if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) {
                 mCurrentScrollOffset = mInitialScrollOffset;
             }
@@ -1024,8 +1050,7 @@
      *
      * @param formatter The formatter object. If formatter is <code>null</code>,
      *            {@link String#valueOf(int)} will be used.
-     *
-     * @see #setDisplayedValues(String[])
+     *@see #setDisplayedValues(String[])
      */
     public void setFormatter(Formatter formatter) {
         if (formatter == mFormatter) {
@@ -1068,26 +1093,35 @@
         if (mValue == value) {
             return;
         }
-        if (value < mMinValue) {
-            value = mWrapSelectorWheel ? mMaxValue : mMinValue;
-        }
-        if (value > mMaxValue) {
-            value = mWrapSelectorWheel ? mMinValue : mMaxValue;
-        }
-        mValue = value;
+        setValueInternal(value, false);
         initializeSelectorWheelIndices();
-        updateInputTextView();
-        updateIncrementAndDecrementButtonsVisibilityState();
         invalidate();
     }
 
     /**
-     * Hides the soft input of it is active for the input text.
+     * Shows the soft input for its input text.
+     */
+    private void showSoftInput() {
+        InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
+        if (inputMethodManager != null) {
+            if (mHasSelectorWheel) {
+                mInputText.setVisibility(View.VISIBLE);
+            }
+            mInputText.requestFocus();
+            inputMethodManager.showSoftInput(mInputText, 0);
+        }
+    }
+
+    /**
+     * Hides the soft input if it is active for the input text.
      */
     private void hideSoftInput() {
         InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
         if (inputMethodManager != null && inputMethodManager.isActive(mInputText)) {
             inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
+            if (mHasSelectorWheel) {
+                mInputText.setVisibility(View.INVISIBLE);
+            }
         }
     }
 
@@ -1151,23 +1185,22 @@
      * wrap around the {@link NumberPicker#getMinValue()} and
      * {@link NumberPicker#getMaxValue()} values.
      * <p>
-     * By default if the range (max - min) is more than five (the number of
-     * items shown on the selector wheel) the selector wheel wrapping is
-     * enabled.
+     * By default if the range (max - min) is more than the number of items shown
+     * on the selector wheel the selector wheel wrapping is enabled.
      * </p>
      * <p>
-     * <strong>Note:</strong> If the number of items, i.e. the range
-     * ({@link #getMaxValue()} - {@link #getMinValue()}) is less than
-     * {@link #SELECTOR_WHEEL_ITEM_COUNT}, the selector wheel will not
-     * wrap. Hence, in such a case calling this method is a NOP.
+     * <strong>Note:</strong> If the number of items, i.e. the range (
+     * {@link #getMaxValue()} - {@link #getMinValue()}) is less than
+     * the number of items shown on the selector wheel, the selector wheel will
+     * not wrap. Hence, in such a case calling this method is a NOP.
      * </p>
+     *
      * @param wrapSelectorWheel Whether to wrap.
      */
     public void setWrapSelectorWheel(boolean wrapSelectorWheel) {
         final boolean wrappingAllowed = (mMaxValue - mMinValue) >= mSelectorIndices.length;
         if ((!wrapSelectorWheel || wrappingAllowed) && wrapSelectorWheel != mWrapSelectorWheel) {
             mWrapSelectorWheel = wrapSelectorWheel;
-            updateIncrementAndDecrementButtonsVisibilityState();
         }
     }
 
@@ -1224,6 +1257,7 @@
         initializeSelectorWheelIndices();
         updateInputTextView();
         tryComputeMaxWidth();
+        invalidate();
     }
 
     /**
@@ -1256,6 +1290,7 @@
         initializeSelectorWheelIndices();
         updateInputTextView();
         tryComputeMaxWidth();
+        invalidate();
     }
 
     /**
@@ -1300,102 +1335,49 @@
     }
 
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        // make sure we show the controls only the very
-        // first time the user sees this widget
-        if (mFlingable && !isInEditMode()) {
-            // animate a bit slower the very first time
-            showInputControls(mShowInputControlsAnimimationDuration * 2);
-        }
-    }
-
-    @Override
     protected void onDetachedFromWindow() {
         removeAllCallbacks();
     }
 
     @Override
-    protected void dispatchDraw(Canvas canvas) {
-        // There is a good reason for doing this. See comments in draw().
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        // Dispatch draw to our children only if we are not currently running
-        // the animation for simultaneously dimming the scroll wheel and
-        // showing in the buttons. This class takes advantage of the View
-        // implementation of fading edges effect to draw the selector wheel.
-        // However, in View.draw(), the fading is applied after all the children
-        // have been drawn and we do not want this fading to be applied to the
-        // buttons. Therefore, we draw our children after we have completed
-        // drawing ourselves.
-        super.draw(canvas);
-
-        // Draw our children if we are not showing the selector wheel of fading
-        // it out
-        if (mShowInputControlsAnimator.isRunning()
-                || mSelectorWheelState != SELECTOR_WHEEL_STATE_LARGE) {
-            long drawTime = getDrawingTime();
-            for (int i = 0, count = getChildCount(); i < count; i++) {
-                View child = getChildAt(i);
-                if (!child.isShown()) {
-                    continue;
-                }
-                drawChild(canvas, getChildAt(i), drawTime);
-            }
-        }
-    }
-
-    @Override
     protected void onDraw(Canvas canvas) {
-        if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) {
+        if (!mHasSelectorWheel) {
+            super.onDraw(canvas);
             return;
         }
-
         float x = (mRight - mLeft) / 2;
         float y = mCurrentScrollOffset;
 
-        final int restoreCount = canvas.save();
-
-        if (mSelectorWheelState == SELECTOR_WHEEL_STATE_SMALL) {
-            Rect clipBounds = canvas.getClipBounds();
-            clipBounds.inset(0, mSelectorElementHeight);
-            canvas.clipRect(clipBounds);
-        }
-
         // draw the selector wheel
         int[] selectorIndices = mSelectorIndices;
         for (int i = 0; i < selectorIndices.length; i++) {
             int selectorIndex = selectorIndices[i];
             String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex);
-            // Do not draw the middle item if input is visible since the input is shown only
-            // if the wheel is static and it covers the middle item. Otherwise, if the user
-            // starts editing the text via the IME he may see a dimmed version of the old
-            // value intermixed with the new one.
+            // Do not draw the middle item if input is visible since the input
+            // is shown only if the wheel is static and it covers the middle
+            // item. Otherwise, if the user starts editing the text via the
+            // IME he may see a dimmed version of the old value intermixed
+            // with the new one.
             if (i != SELECTOR_MIDDLE_ITEM_INDEX || mInputText.getVisibility() != VISIBLE) {
                 canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint);
             }
             y += mSelectorElementHeight;
         }
 
-        // draw the selection dividers (only if scrolling and drawable specified)
+        // draw the selection dividers
         if (mSelectionDivider != null) {
             // draw the top divider
-            int topOfTopDivider =
-                (getHeight() - mSelectorElementHeight - mSelectionDividerHeight) / 2;
+            int topOfTopDivider = mTopSelectionDividerTop;
             int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight;
             mSelectionDivider.setBounds(0, topOfTopDivider, mRight, bottomOfTopDivider);
             mSelectionDivider.draw(canvas);
 
             // draw the bottom divider
-            int topOfBottomDivider =  topOfTopDivider + mSelectorElementHeight;
-            int bottomOfBottomDivider = bottomOfTopDivider + mSelectorElementHeight;
+            int bottomOfBottomDivider = mBottomSelectionDividerBottom;
+            int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerHeight;
             mSelectionDivider.setBounds(0, topOfBottomDivider, mRight, bottomOfBottomDivider);
             mSelectionDivider.draw(canvas);
         }
-
-        canvas.restoreToCount(restoreCount);
     }
 
     @Override
@@ -1408,12 +1390,20 @@
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
         event.setClassName(NumberPicker.class.getName());
+        event.setScrollable(true);
+        event.setScrollY((mMinValue + mValue) * mSelectorElementHeight);
+        event.setMaxScrollY((mMaxValue - mMinValue) * mSelectorElementHeight);
     }
 
     @Override
-    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
-        super.onInitializeAccessibilityNodeInfo(info);
-        info.setClassName(NumberPicker.class.getName());
+    public AccessibilityNodeProvider getAccessibilityNodeProvider() {
+        if (!mHasSelectorWheel) {
+            return super.getAccessibilityNodeProvider();
+        }
+        if (mAccessibilityNodeProvider == null) {
+            mAccessibilityNodeProvider = new AccessibilityNodeProviderImpl();
+        }
+        return mAccessibilityNodeProvider;
     }
 
     /**
@@ -1442,17 +1432,17 @@
     }
 
     /**
-     * Utility to reconcile a desired size and state, with constraints imposed by
-     * a MeasureSpec. Tries to respect the min size, unless a different size is
-     * imposed by the constraints.
+     * Utility to reconcile a desired size and state, with constraints imposed
+     * by a MeasureSpec. Tries to respect the min size, unless a different size
+     * is imposed by the constraints.
      *
      * @param minSize The minimal desired size.
      * @param measuredSize The currently measured size.
      * @param measureSpec The current measure spec.
      * @return The resolved size and state.
      */
-    private int resolveSizeAndStateRespectingMinSize(int minSize, int measuredSize,
-            int measureSpec) {
+    private int resolveSizeAndStateRespectingMinSize(
+            int minSize, int measuredSize, int measureSpec) {
         if (minSize != SIZE_UNSPECIFIED) {
             final int desiredWidth = Math.max(minSize, measuredSize);
             return resolveSizeAndState(desiredWidth, measureSpec, 0);
@@ -1462,8 +1452,8 @@
     }
 
     /**
-     * Resets the selector indices and clear the cached
-     * string representation of these indices.
+     * Resets the selector indices and clear the cached string representation of
+     * these indices.
      */
     private void initializeSelectorWheelIndices() {
         mSelectorIndexToStringCache.clear();
@@ -1480,39 +1470,44 @@
     }
 
     /**
-     * Sets the current value of this NumberPicker, and sets mPrevious to the
-     * previous value. If current is greater than mEnd less than mStart, the
-     * value of mCurrent is wrapped around. Subclasses can override this to
-     * change the wrapping behavior
+     * Sets the current value of this NumberPicker.
      *
-     * @param current the new value of the NumberPicker
+     * @param current The new value of the NumberPicker.
+     * @param notifyChange Whether to notify if the current value changed.
      */
-    private void changeCurrent(int current) {
+    private void setValueInternal(int current, boolean notifyChange) {
         if (mValue == current) {
             return;
         }
         // Wrap around the values if we go past the start or end
         if (mWrapSelectorWheel) {
             current = getWrappedSelectorIndex(current);
+        } else {
+            current = Math.max(current, mMinValue);
+            current = Math.min(current, mMaxValue);
         }
         int previous = mValue;
-        setValue(current);
-        notifyChange(previous, current);
+        mValue = current;
+        updateInputTextView();
+        if (notifyChange) {
+            notifyChange(previous, current);
+        }
     }
 
     /**
      * Changes the current value by one which is increment or
      * decrement based on the passes argument.
+     * decrement the current value.
      *
      * @param increment True to increment, false to decrement.
      */
-    private void changeCurrentByOne(boolean increment) {
-        if (mFlingable) {
-            mDimSelectorWheelAnimator.cancel();
+     private void changeValueByOne(boolean increment) {
+        if (mHasSelectorWheel) {
             mInputText.setVisibility(View.INVISIBLE);
-            mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA);
+            if (!moveToFinalScrollerPosition(mFlingScroller)) {
+                moveToFinalScrollerPosition(mAdjustScroller);
+            }
             mPreviousScrollerY = 0;
-            forceCompleteChangeCurrentByOneViaScroll();
             if (increment) {
                 mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight,
                         CHANGE_CURRENT_BY_ONE_SCROLL_DURATION);
@@ -1523,81 +1518,26 @@
             invalidate();
         } else {
             if (increment) {
-                changeCurrent(mValue + 1);
+                setValueInternal(mValue + 1, true);
             } else {
-                changeCurrent(mValue - 1);
+                setValueInternal(mValue - 1, true);
             }
         }
     }
 
-    /**
-     * Ensures that if we are in the process of changing the current value
-     * by one via scrolling the scroller gets to its final state and the
-     * value is updated.
-     */
-    private void forceCompleteChangeCurrentByOneViaScroll() {
-        Scroller scroller = mFlingScroller;
-        if (!scroller.isFinished()) {
-            final int yBeforeAbort = scroller.getCurrY();
-            scroller.abortAnimation();
-            final int yDelta = scroller.getCurrY() - yBeforeAbort;
-            scrollBy(0, yDelta);
-        }
-    }
-
-    /**
-     * Sets the <code>alpha</code> of the {@link Paint} for drawing the selector
-     * wheel.
-     */
-    @SuppressWarnings("unused")
-    // Called via reflection
-    private void setSelectorPaintAlpha(int alpha) {
-        mSelectorWheelPaint.setAlpha(alpha);
-        invalidate();
-    }
-
-    /**
-     * @return If the <code>event</code> is in the visible <code>view</code>.
-     */
-    private boolean isEventInVisibleViewHitRect(MotionEvent event, View view) {
-        if (view.getVisibility() == VISIBLE) {
-            view.getHitRect(mTempRect);
-            return mTempRect.contains((int) event.getX(), (int) event.getY());
-        }
-        return false;
-    }
-
-    /**
-     * Sets the <code>selectorWheelState</code>.
-     */
-    private void setSelectorWheelState(int selectorWheelState) {
-        mSelectorWheelState = selectorWheelState;
-        if (selectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
-            mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA);
-        }
-
-        if (mFlingable && selectorWheelState == SELECTOR_WHEEL_STATE_LARGE
-                && AccessibilityManager.getInstance(mContext).isEnabled()) {
-            AccessibilityManager.getInstance(mContext).interrupt();
-            String text = mContext.getString(R.string.number_picker_increment_scroll_action);
-            mInputText.setContentDescription(text);
-            mInputText.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
-            mInputText.setContentDescription(null);
-        }
-    }
-
     private void initializeSelectorWheel() {
         initializeSelectorWheelIndices();
         int[] selectorIndices = mSelectorIndices;
         int totalTextHeight = selectorIndices.length * mTextSize;
         float totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
-        float textGapCount = selectorIndices.length - 1;
+        float textGapCount = selectorIndices.length;
         mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f);
         mSelectorElementHeight = mTextSize + mSelectorTextGapHeight;
-        // Ensure that the middle item is positioned the same as the text in mInputText
+        // Ensure that the middle item is positioned the same as the text in
+        // mInputText
         int editTextTextPosition = mInputText.getBaseline() + mInputText.getTop();
-        mInitialScrollOffset = editTextTextPosition -
-                (mSelectorElementHeight * SELECTOR_MIDDLE_ITEM_INDEX);
+        mInitialScrollOffset = editTextTextPosition
+                - (mSelectorElementHeight * SELECTOR_MIDDLE_ITEM_INDEX);
         mCurrentScrollOffset = mInitialScrollOffset;
         updateInputTextView();
     }
@@ -1612,16 +1552,14 @@
      */
     private void onScrollerFinished(Scroller scroller) {
         if (scroller == mFlingScroller) {
-            if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) {
-                postAdjustScrollerCommand(0);
-                onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
-            } else {
+            if (!ensureScrollWheelAdjusted()) {
                 updateInputTextView();
-                fadeSelectorWheel(mShowInputControlsAnimimationDuration);
             }
+            onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
         } else {
-            updateInputTextView();
-            showInputControls(mShowInputControlsAnimimationDuration);
+            if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
+                updateInputTextView();
+            }
         }
     }
 
@@ -1654,56 +1592,6 @@
     }
 
     /**
-     * Hides the input controls which is the up/down arrows and the text field.
-     */
-    private void hideInputControls() {
-        mShowInputControlsAnimator.cancel();
-        mIncrementButton.setVisibility(INVISIBLE);
-        mDecrementButton.setVisibility(INVISIBLE);
-        mInputText.setVisibility(INVISIBLE);
-    }
-
-    /**
-     * Show the input controls by making them visible and animating the alpha
-     * property up/down arrows.
-     *
-     * @param animationDuration The duration of the animation.
-     */
-    private void showInputControls(long animationDuration) {
-        updateIncrementAndDecrementButtonsVisibilityState();
-        mInputText.setVisibility(VISIBLE);
-        mShowInputControlsAnimator.setDuration(animationDuration);
-        mShowInputControlsAnimator.start();
-    }
-
-    /**
-     * Fade the selector wheel via an animation.
-     *
-     * @param animationDuration The duration of the animation.
-     */
-    private void fadeSelectorWheel(long animationDuration) {
-        mInputText.setVisibility(VISIBLE);
-        mDimSelectorWheelAnimator.setDuration(animationDuration);
-        mDimSelectorWheelAnimator.start();
-    }
-
-    /**
-     * Updates the visibility state of the increment and decrement buttons.
-     */
-    private void updateIncrementAndDecrementButtonsVisibilityState() {
-        if (mWrapSelectorWheel || mValue < mMaxValue) {
-            mIncrementButton.setVisibility(VISIBLE);
-        } else {
-            mIncrementButton.setVisibility(INVISIBLE);
-        }
-        if (mWrapSelectorWheel || mValue > mMinValue) {
-            mDecrementButton.setVisibility(VISIBLE);
-        } else {
-            mDecrementButton.setVisibility(INVISIBLE);
-        }
-    }
-
-    /**
      * @return The wrapped index <code>selectorIndex</code> value.
      */
     private int getWrappedSelectorIndex(int selectorIndex) {
@@ -1749,8 +1637,7 @@
 
     /**
      * Ensures we have a cached string representation of the given <code>
-     * selectorIndex</code>
-     * to avoid multiple instantiations of the same string.
+     * selectorIndex</code> to avoid multiple instantiations of the same string.
      */
     private void ensureCachedScrollSelectorValue(int selectorIndex) {
         SparseArray<String> cache = mSelectorIndexToStringCache;
@@ -1783,7 +1670,7 @@
         } else {
             // Check the new value and ensure it's in range
             int current = getSelectedPos(str.toString());
-            changeCurrent(current);
+            setValueInternal(current, true);
         }
     }
 
@@ -1792,25 +1679,23 @@
      * the string corresponding to the index specified by the current value will
      * be returned. Otherwise, the formatter specified in {@link #setFormatter}
      * will be used to format the number.
+     *
+     * @return Whether the text was updated.
      */
-    private void updateInputTextView() {
+    private boolean updateInputTextView() {
         /*
          * If we don't have displayed values then use the current number else
          * find the correct value in the displayed values for the current
          * number.
          */
-        if (mDisplayedValues == null) {
-            mInputText.setText(formatNumber(mValue));
-        } else {
-            mInputText.setText(mDisplayedValues[mValue - mMinValue]);
+        String text = (mDisplayedValues == null) ? formatNumber(mValue)
+                : mDisplayedValues[mValue - mMinValue];
+        if (!TextUtils.isEmpty(text) && !text.equals(mInputText.getText().toString())) {
+            mInputText.setText(text);
+            return true;
         }
-        mInputText.setSelection(mInputText.getText().length());
 
-        if (mFlingable && AccessibilityManager.getInstance(mContext).isEnabled()) {
-            String text = mContext.getString(R.string.number_picker_increment_scroll_mode,
-                    mInputText.getText());
-            mInputText.setContentDescription(text);
-        }
+        return false;
     }
 
     /**
@@ -1828,14 +1713,45 @@
      *
      * @param increment Whether to increment or decrement the value.
      */
-    private void postChangeCurrentByOneFromLongPress(boolean increment) {
-        mInputText.clearFocus();
-        removeAllCallbacks();
+    private void postChangeCurrentByOneFromLongPress(boolean increment, long delayMillis) {
         if (mChangeCurrentByOneFromLongPressCommand == null) {
             mChangeCurrentByOneFromLongPressCommand = new ChangeCurrentByOneFromLongPressCommand();
+        } else {
+            removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
         }
-        mChangeCurrentByOneFromLongPressCommand.setIncrement(increment);
-        post(mChangeCurrentByOneFromLongPressCommand);
+        mChangeCurrentByOneFromLongPressCommand.setStep(increment);
+        postDelayed(mChangeCurrentByOneFromLongPressCommand, delayMillis);
+    }
+
+    /**
+     * Removes the command for changing the current value by one.
+     */
+    private void removeChangeCurrentByOneFromLongPress() {
+        if (mChangeCurrentByOneFromLongPressCommand != null) {
+            removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
+        }
+    }
+
+    /**
+     * Posts a command for beginning an edit of the current value via IME on
+     * long press.
+     */
+    private void postBeginSoftInputOnLongPressCommand() {
+        if (mBeginSoftInputOnLongPressCommand == null) {
+            mBeginSoftInputOnLongPressCommand = new BeginSoftInputOnLongPressCommand();
+        } else {
+            removeCallbacks(mBeginSoftInputOnLongPressCommand);
+        }
+        postDelayed(mBeginSoftInputOnLongPressCommand, ViewConfiguration.getLongPressTimeout());
+    }
+
+    /**
+     * Removes the command for beginning an edit of the current value via IME.
+     */
+    private void removeBeginSoftInputCommand() {
+        if (mBeginSoftInputOnLongPressCommand != null) {
+            removeCallbacks(mBeginSoftInputOnLongPressCommand);
+        }
     }
 
     /**
@@ -1845,12 +1761,12 @@
         if (mChangeCurrentByOneFromLongPressCommand != null) {
             removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
         }
-        if (mAdjustScrollerCommand != null) {
-            removeCallbacks(mAdjustScrollerCommand);
-        }
         if (mSetSelectionCommand != null) {
             removeCallbacks(mSetSelectionCommand);
         }
+        if (mBeginSoftInputOnLongPressCommand != null) {
+            removeCallbacks(mBeginSoftInputOnLongPressCommand);
+        }
     }
 
     /**
@@ -1888,8 +1804,7 @@
 
     /**
      * Posts an {@link SetSelectionCommand} from the given <code>selectionStart
-     * </code> to
-     * <code>selectionEnd</code>.
+     * </code> to <code>selectionEnd</code>.
      */
     private void postSetSelectionCommand(int selectionStart, int selectionEnd) {
         if (mSetSelectionCommand == null) {
@@ -1903,20 +1818,6 @@
     }
 
     /**
-     * Posts an {@link AdjustScrollerCommand} within the given <code>
-     * delayMillis</code>
-     * .
-     */
-    private void postAdjustScrollerCommand(int delayMillis) {
-        if (mAdjustScrollerCommand == null) {
-            mAdjustScrollerCommand = new AdjustScrollerCommand();
-        } else {
-            removeCallbacks(mAdjustScrollerCommand);
-        }
-        postDelayed(mAdjustScrollerCommand, delayMillis);
-    }
-
-    /**
      * Filter for accepting only valid indices or prefixes of the string
      * representation of valid indices.
      */
@@ -1934,8 +1835,8 @@
         }
 
         @Override
-        public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
-                int dstart, int dend) {
+        public CharSequence filter(
+                CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
             if (mDisplayedValues == null) {
                 CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
                 if (filtered == null) {
@@ -1981,6 +1882,27 @@
     }
 
     /**
+     * Ensures that the scroll wheel is adjusted i.e. there is no offset and the
+     * middle element is in the middle of the widget.
+     *
+     * @return Whether an adjustment has been made.
+     */
+    private boolean ensureScrollWheelAdjusted() {
+        // adjust to the closest value
+        int deltaY = mInitialScrollOffset - mCurrentScrollOffset;
+        if (deltaY != 0) {
+            mPreviousScrollerY = 0;
+            if (Math.abs(deltaY) > mSelectorElementHeight / 2) {
+                deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight;
+            }
+            mAdjustScroller.startScroll(0, 0, 0, deltaY, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
+            invalidate();
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Command for setting the input text selection.
      */
     class SetSelectionCommand implements Runnable {
@@ -1994,39 +1916,18 @@
     }
 
     /**
-     * Command for adjusting the scroller to show in its center the closest of
-     * the displayed items.
-     */
-    class AdjustScrollerCommand implements Runnable {
-        public void run() {
-            mPreviousScrollerY = 0;
-            if (mInitialScrollOffset == mCurrentScrollOffset) {
-                updateInputTextView();
-                showInputControls(mShowInputControlsAnimimationDuration);
-                return;
-            }
-            // adjust to the closest value
-            int deltaY = mInitialScrollOffset - mCurrentScrollOffset;
-            if (Math.abs(deltaY) > mSelectorElementHeight / 2) {
-                deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight;
-            }
-            mAdjustScroller.startScroll(0, 0, 0, deltaY, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
-            invalidate();
-        }
-    }
-
-    /**
      * Command for changing the current value from a long press by one.
      */
     class ChangeCurrentByOneFromLongPressCommand implements Runnable {
         private boolean mIncrement;
 
-        private void setIncrement(boolean increment) {
+        private void setStep(boolean increment) {
             mIncrement = increment;
         }
 
+        @Override
         public void run() {
-            changeCurrentByOne(mIncrement);
+            changeValueByOne(mIncrement);
             postDelayed(this, mLongPressUpdateInterval);
         }
     }
@@ -2048,4 +1949,248 @@
             }
         }
     }
+
+    /**
+     * Command for beginning soft input on long press.
+     */
+    class BeginSoftInputOnLongPressCommand implements Runnable {
+
+        @Override
+        public void run() {
+            showSoftInput();
+            mIngonreMoveEvents = true;
+        }
+    }
+
+    class AccessibilityNodeProviderImpl extends AccessibilityNodeProvider {
+        private static final int VIRTUAL_VIEW_ID_INCREMENT = 1;
+
+        private static final int VIRTUAL_VIEW_ID_INPUT = 2;
+
+        private static final int VIRTUAL_VIEW_ID_DECREMENT = 3;
+
+        private final Rect mTempRect = new Rect();
+
+        private final int[] mTempArray = new int[2];
+
+        @Override
+        public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
+            switch (virtualViewId) {
+                case View.NO_ID:
+                    return createAccessibilityNodeInfoForNumberPicker( mScrollX, mScrollY,
+                            mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop));
+                case VIRTUAL_VIEW_ID_DECREMENT:
+                    return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_DECREMENT,
+                            getVirtualDecrementButtonText(), mScrollX, mScrollY,
+                            mScrollX + (mRight - mLeft),
+                            mTopSelectionDividerTop + mSelectionDividerHeight);
+                case VIRTUAL_VIEW_ID_INPUT:
+                    return createAccessibiltyNodeInfoForInputText();
+                case VIRTUAL_VIEW_ID_INCREMENT:
+                    return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_INCREMENT,
+                            getVirtualIncrementButtonText(), mScrollX,
+                            mBottomSelectionDividerBottom - mSelectionDividerHeight,
+                            mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop));
+            }
+            return super.createAccessibilityNodeInfo(virtualViewId);
+        }
+
+        @Override
+        public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String searched,
+                int virtualViewId) {
+            if (TextUtils.isEmpty(searched)) {
+                return Collections.emptyList();
+            }
+            String searchedLowerCase = searched.toLowerCase();
+            List<AccessibilityNodeInfo> result = new ArrayList<AccessibilityNodeInfo>();
+            switch (virtualViewId) {
+                case View.NO_ID: {
+                    findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
+                            VIRTUAL_VIEW_ID_DECREMENT, result);
+                    findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
+                            VIRTUAL_VIEW_ID_INPUT, result);
+                    findAccessibilityNodeInfosByTextInChild(searchedLowerCase,
+                            VIRTUAL_VIEW_ID_INCREMENT, result);
+                    return result;
+                }
+                case VIRTUAL_VIEW_ID_DECREMENT:
+                case VIRTUAL_VIEW_ID_INCREMENT:
+                case VIRTUAL_VIEW_ID_INPUT: {
+                    findAccessibilityNodeInfosByTextInChild(searchedLowerCase, virtualViewId,
+                            result);
+                    return result;
+                }
+            }
+            return super.findAccessibilityNodeInfosByText(searched, virtualViewId);
+        }
+
+        @Override
+        public boolean performAccessibilityAction(int action, int virtualViewId) {
+            switch (virtualViewId) {
+                case VIRTUAL_VIEW_ID_INPUT: {
+                    switch (action) {
+                        case AccessibilityNodeInfo.ACTION_FOCUS: {
+                            if (!mInputText.isFocused()) {
+                                return mInputText.requestFocus();
+                            }
+                        } break;
+                        case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {
+                            if (mInputText.isFocused()) {
+                                mInputText.clearFocus();
+                                return true;
+                            }
+                        } break;
+                    }
+                } break;
+            }
+            return super.performAccessibilityAction(action, virtualViewId);
+        }
+
+        public void sendAccessibilityEventForVirtualView(int virtualViewId, int eventType) {
+            switch (virtualViewId) {
+                case VIRTUAL_VIEW_ID_DECREMENT: {
+                    sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
+                            getVirtualDecrementButtonText());
+                } break;
+                case VIRTUAL_VIEW_ID_INPUT: {
+                    sendAccessibilityEventForVirtualText(eventType);
+                } break;
+                case VIRTUAL_VIEW_ID_INCREMENT: {
+                    sendAccessibilityEventForVirtualButton(virtualViewId, eventType,
+                            getVirtualIncrementButtonText());
+                } break;
+            }
+        }
+
+        private void sendAccessibilityEventForVirtualText(int eventType) {
+            AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+            mInputText.onInitializeAccessibilityEvent(event);
+            mInputText.onPopulateAccessibilityEvent(event);
+            event.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
+            requestSendAccessibilityEvent(NumberPicker.this, event);
+        }
+
+        private void sendAccessibilityEventForVirtualButton(int virtualViewId, int eventType,
+                String text) {
+            AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+            event.setClassName(Button.class.getName());
+            event.setPackageName(mContext.getPackageName());
+            event.getText().add(text);
+            event.setEnabled(NumberPicker.this.isEnabled());
+            event.setSource(NumberPicker.this, virtualViewId);
+            requestSendAccessibilityEvent(NumberPicker.this, event);
+        }
+
+        private void findAccessibilityNodeInfosByTextInChild(String searchedLowerCase,
+                int virtualViewId, List<AccessibilityNodeInfo> outResult) {
+            switch (virtualViewId) {
+                case VIRTUAL_VIEW_ID_DECREMENT: {
+                    String text = getVirtualDecrementButtonText();
+                    if (!TextUtils.isEmpty(text)
+                            && text.toString().toLowerCase().contains(searchedLowerCase)) {
+                        outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT));
+                    }
+                } return;
+                case VIRTUAL_VIEW_ID_INPUT: {
+                    CharSequence text = mInputText.getText();
+                    if (!TextUtils.isEmpty(text) &&
+                            text.toString().toLowerCase().contains(searchedLowerCase)) {
+                        outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT));
+                        return;
+                    }
+                    CharSequence contentDesc = mInputText.getText();
+                    if (!TextUtils.isEmpty(contentDesc) &&
+                            contentDesc.toString().toLowerCase().contains(searchedLowerCase)) {
+                        outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT));
+                        return;
+                    }
+                } break;
+                case VIRTUAL_VIEW_ID_INCREMENT: {
+                    String text = getVirtualIncrementButtonText();
+                    if (!TextUtils.isEmpty(text)
+                            && text.toString().toLowerCase().contains(searchedLowerCase)) {
+                        outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT));
+                    }
+                } return;
+            }
+        }
+
+        private AccessibilityNodeInfo createAccessibiltyNodeInfoForInputText() {
+            AccessibilityNodeInfo info = mInputText.createAccessibilityNodeInfo();
+            info.setLongClickable(true);
+            info.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
+            return info;
+        }
+
+        private AccessibilityNodeInfo createAccessibilityNodeInfoForVirtualButton(int virtualViewId,
+                String text, int left, int top, int right, int bottom) {
+            AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+            info.setClassName(Button.class.getName());
+            info.setPackageName(mContext.getPackageName());
+            info.setSource(NumberPicker.this, virtualViewId);
+            info.setParent(NumberPicker.this);
+            info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_DECREMENT);
+            info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
+            info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INCREMENT);
+            info.setText(text);
+            info.setClickable(true);
+            info.setLongClickable(true);
+            info.setEnabled(NumberPicker.this.isEnabled());
+            Rect boundsInParent = mTempRect;
+            boundsInParent.set(left, top, right, bottom);
+            info.setBoundsInParent(boundsInParent);
+            Rect boundsInScreen = boundsInParent;
+            int[] locationOnScreen = mTempArray;
+            getLocationOnScreen(locationOnScreen);
+            boundsInScreen.offsetTo(0, 0);
+            boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
+            info.setBoundsInScreen(boundsInScreen);
+            return info;
+        }
+
+        private AccessibilityNodeInfo createAccessibilityNodeInfoForNumberPicker(int left, int top,
+                int right, int bottom) {
+            AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+            info.setClassName(Button.class.getName());
+            info.setPackageName(mContext.getPackageName());
+            info.setSource(NumberPicker.this);
+            info.setParent((View) getParent());
+            info.setEnabled(NumberPicker.this.isEnabled());
+            info.setScrollable(true);
+            Rect boundsInParent = mTempRect;
+            boundsInParent.set(left, top, right, bottom);
+            info.setBoundsInParent(boundsInParent);
+            Rect boundsInScreen = boundsInParent;
+            int[] locationOnScreen = mTempArray;
+            getLocationOnScreen(locationOnScreen);
+            boundsInScreen.offsetTo(0, 0);
+            boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
+            info.setBoundsInScreen(boundsInScreen);
+            return info;
+        }
+
+        private String getVirtualDecrementButtonText() {
+            int value = mValue - 1;
+            if (mWrapSelectorWheel) {
+                value = getWrappedSelectorIndex(value);
+            }
+            if (value >= mMinValue) {
+                return (mDisplayedValues == null) ? formatNumber(value)
+                        : mDisplayedValues[value - mMinValue];
+            }
+            return null;
+        }
+
+        private String getVirtualIncrementButtonText() {
+            int value = mValue + 1;
+            if (mWrapSelectorWheel) {
+                value = getWrappedSelectorIndex(value);
+            }
+            if (value <= mMaxValue) {
+                return (mDisplayedValues == null) ? formatNumber(value)
+                        : mDisplayedValues[value - mMinValue];
+            }
+            return null;
+        }
+    }
 }
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index 7f53ffd..f217c9c 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -190,6 +190,8 @@
      *
      * @see #check(int)
      * @see #clearCheck()
+     *
+     * @attr ref android.R.styleable#RadioGroup_checkedButton
      */
     public int getCheckedRadioButtonId() {
         return mCheckedId;
diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java
index e69577b..524d272 100644
--- a/core/java/android/widget/RatingBar.java
+++ b/core/java/android/widget/RatingBar.java
@@ -145,6 +145,8 @@
      * by the user).
      * 
      * @param isIndicator Whether it should be an indicator.
+     *
+     * @attr ref android.R.styleable#RatingBar_isIndicator
      */
     public void setIsIndicator(boolean isIndicator) {
         mIsUserSeekable = !isIndicator;
@@ -153,6 +155,8 @@
     
     /**
      * @return Whether this rating bar is only an indicator.
+     *
+     * @attr ref android.R.styleable#RatingBar_isIndicator
      */
     public boolean isIndicator() {
         return !mIsUserSeekable;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 55acb74..2f72e4a 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -965,6 +965,58 @@
     }
 
     /**
+     * Helper action to set compound drawables on a TextView. Supports relative
+     * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
+     */
+    private class TextViewDrawableAction extends Action {
+        public TextViewDrawableAction(int viewId, boolean isRelative, int d1, int d2, int d3, int d4) {
+            this.viewId = viewId;
+            this.isRelative = isRelative;
+            this.d1 = d1;
+            this.d2 = d2;
+            this.d3 = d3;
+            this.d4 = d4;
+        }
+
+        public TextViewDrawableAction(Parcel parcel) {
+            viewId = parcel.readInt();
+            isRelative = (parcel.readInt() != 0);
+            d1 = parcel.readInt();
+            d2 = parcel.readInt();
+            d3 = parcel.readInt();
+            d4 = parcel.readInt();
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            dest.writeInt(isRelative ? 1 : 0);
+            dest.writeInt(d1);
+            dest.writeInt(d2);
+            dest.writeInt(d3);
+            dest.writeInt(d4);
+        }
+
+        @Override
+        public void apply(View root, ViewGroup rootParent) {
+            final Context context = root.getContext();
+            final TextView target = (TextView) root.findViewById(viewId);
+            if (target == null) return;
+            if (isRelative) {
+                target.setCompoundDrawablesRelativeWithIntrinsicBounds(d1, d2, d3, d4);
+            } else {
+                target.setCompoundDrawablesWithIntrinsicBounds(d1, d2, d3, d4);
+            }
+        }
+
+        int viewId;
+        boolean isRelative = false;
+        int d1, d2, d3, d4;
+
+        public final static int TAG = 11;
+    }
+
+    /**
      * Simple class used to keep track of memory usage in a RemoteViews.
      *
      */
@@ -1043,6 +1095,9 @@
                 case SetRemoteViewsAdapterIntent.TAG:
                     mActions.add(new SetRemoteViewsAdapterIntent(parcel));
                     break;
+                case TextViewDrawableAction.TAG:
+                    mActions.add(new TextViewDrawableAction(parcel));
+                    break;
                 default:
                     throw new ActionException("Tag " + tag + " not found");
                 }
@@ -1195,6 +1250,35 @@
     }
     
     /**
+     * Equivalent to calling 
+     * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}.
+     *
+     * @param viewId The id of the view whose text should change
+     * @param left The id of a drawable to place to the left of the text, or 0
+     * @param top The id of a drawable to place above the text, or 0
+     * @param right The id of a drawable to place to the right of the text, or 0
+     * @param bottom The id of a drawable to place below the text, or 0 
+     */
+    public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) {
+        addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom));
+    }
+
+    /**
+     * Equivalent to calling {@link 
+     * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, int)}.
+     *
+     * @param viewId The id of the view whose text should change
+     * @param start The id of a drawable to place before the text (relative to the 
+     * layout direction), or 0
+     * @param top The id of a drawable to place above the text, or 0
+     * @param end The id of a drawable to place after the text, or 0
+     * @param bottom The id of a drawable to place below the text, or 0 
+     */
+    public void setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom) {
+        addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom));
+    }
+
+    /**
      * Equivalent to calling ImageView.setImageResource
      * 
      * @param viewId The id of the view whose drawable should change
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 25dd438..e0e3e93 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -642,8 +642,7 @@
                 break;
             case MotionEvent.ACTION_POINTER_DOWN: {
                 final int index = ev.getActionIndex();
-                final float y = ev.getY(index);
-                mLastMotionY = y;
+                mLastMotionY = ev.getY(index);
                 mActivePointerId = ev.getPointerId(index);
                 break;
             }
@@ -715,6 +714,7 @@
         } else {
             super.scrollTo(scrollX, scrollY);
         }
+
         awakenScrollBars();
     }
 
@@ -749,42 +749,6 @@
 
     /**
      * <p>
-     * Finds the next focusable component that fits in this View's bounds
-     * (excluding fading edges) pretending that this View's top is located at
-     * the parameter top.
-     * </p>
-     *
-     * @param topFocus           look for a candidate at the top of the bounds if topFocus is true,
-     *                           or at the bottom of the bounds if topFocus is false
-     * @param top                the top offset of the bounds in which a focusable must be
-     *                           found (the fading edge is assumed to start at this position)
-     * @param preferredFocusable the View that has highest priority and will be
-     *                           returned if it is within my bounds (null is valid)
-     * @return the next focusable component in the bounds or null if none can be found
-     */
-    private View findFocusableViewInMyBounds(final boolean topFocus,
-            final int top, View preferredFocusable) {
-        /*
-         * The fading edge's transparent side should be considered for focus
-         * since it's mostly visible, so we divide the actual fading edge length
-         * by 2.
-         */
-        final int fadingEdgeLength = getVerticalFadingEdgeLength() / 2;
-        final int topWithoutFadingEdge = top + fadingEdgeLength;
-        final int bottomWithoutFadingEdge = top + getHeight() - fadingEdgeLength;
-
-        if ((preferredFocusable != null)
-                && (preferredFocusable.getTop() < bottomWithoutFadingEdge)
-                && (preferredFocusable.getBottom() > topWithoutFadingEdge)) {
-            return preferredFocusable;
-        }
-
-        return findFocusableViewInBounds(topFocus, topWithoutFadingEdge,
-                bottomWithoutFadingEdge);
-    }
-
-    /**
-     * <p>
      * Finds the next focusable component that fits in the specified bounds.
      * </p>
      *
@@ -1208,10 +1172,10 @@
                 }
             }
 
-            awakenScrollBars();
-
-            // Keep on drawing until the animation has finished.
-            postInvalidate();
+            if (!awakenScrollBars()) {
+                // Keep on drawing until the animation has finished.
+                invalidate();
+            }
         } else {
             if (mFlingStrictSpan != null) {
                 mFlingStrictSpan.finish();
@@ -1438,7 +1402,7 @@
     /**
      * Return true if child is a descendant of parent, (or equal to the parent).
      */
-    private boolean isViewDescendantOf(View child, View parent) {
+    private static boolean isViewDescendantOf(View child, View parent) {
         if (child == parent) {
             return true;
         }
@@ -1462,8 +1426,6 @@
             mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
                     Math.max(0, bottom - height), 0, height/2);
 
-            final boolean movingDown = velocityY > 0;
-
             if (mFlingStrictSpan == null) {
                 mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
             }
@@ -1554,7 +1516,7 @@
         }
     }
 
-    private int clamp(int n, int my, int child) {
+    private static int clamp(int n, int my, int child) {
         if (my >= child || n < 0) {
             /* my >= child is this case:
              *                    |--------------- me ---------------|
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 9941c95..1f2410b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -1798,6 +1798,7 @@
      * @attr ref android.R.styleable#TextView_drawableRight
      * @attr ref android.R.styleable#TextView_drawableBottom
      */
+    @android.view.RemotableViewMethod
     public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
         final Resources resources = getContext().getResources();
         setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null,
@@ -1967,6 +1968,7 @@
      * @attr ref android.R.styleable#TextView_drawableEnd
      * @attr ref android.R.styleable#TextView_drawableBottom
      */
+    @android.view.RemotableViewMethod
     public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end,
             int bottom) {
         resetResolvedDrawables();
@@ -2042,6 +2044,7 @@
      *
      * @attr ref android.R.styleable#TextView_drawablePadding
      */
+    @android.view.RemotableViewMethod
     public void setCompoundDrawablePadding(int pad) {
         Drawables dr = mDrawables;
         if (pad == 0) {
@@ -7493,7 +7496,7 @@
      * Returns true, only while processing a touch gesture, if the initial
      * touch down event caused focus to move to the text view and as a result
      * its selection changed.  Only valid while processing the touch gesture
-     * of interest.
+     * of interest, in an editable text view.
      */
     public boolean didTouchFocusSelect() {
         return mEditor != null && getEditor().mTouchFocusSelected;
@@ -11755,7 +11758,7 @@
                         hardwareCanvas.onPostDraw();
                         blockDisplayList.end();
                         if (USE_DISPLAY_LIST_PROPERTIES) {
-                            blockDisplayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
+                            blockDisplayList.setLeftTopRightBottom(0, 0, width, height);
                         }
                     }
                 }
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 7eff1aa..bc88b62 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -532,21 +532,28 @@
 
     private void setContentDescriptions() {
         // Minute
-        String text = mContext.getString(R.string.time_picker_increment_minute_button);
-        mMinuteSpinner.findViewById(R.id.increment).setContentDescription(text);
-        text = mContext.getString(R.string.time_picker_decrement_minute_button);
-        mMinuteSpinner.findViewById(R.id.decrement).setContentDescription(text);
+        trySetContentDescription(mMinuteSpinner, R.id.increment,
+                R.string.time_picker_increment_minute_button);
+        trySetContentDescription(mMinuteSpinner, R.id.decrement,
+                R.string.time_picker_decrement_minute_button);
         // Hour
-        text = mContext.getString(R.string.time_picker_increment_hour_button);
-        mHourSpinner.findViewById(R.id.increment).setContentDescription(text);
-        text = mContext.getString(R.string.time_picker_decrement_hour_button);
-        mHourSpinner.findViewById(R.id.decrement).setContentDescription(text);
+        trySetContentDescription(mHourSpinner, R.id.increment,
+                R.string.time_picker_increment_hour_button);
+        trySetContentDescription(mHourSpinner, R.id.decrement,
+                R.string.time_picker_decrement_hour_button);
         // AM/PM
         if (mAmPmSpinner != null) {
-            text = mContext.getString(R.string.time_picker_increment_set_pm_button);
-            mAmPmSpinner.findViewById(R.id.increment).setContentDescription(text);
-            text = mContext.getString(R.string.time_picker_decrement_set_am_button);
-            mAmPmSpinner.findViewById(R.id.decrement).setContentDescription(text);
+            trySetContentDescription(mAmPmSpinner, R.id.increment,
+                    R.string.time_picker_increment_set_pm_button);
+            trySetContentDescription(mAmPmSpinner, R.id.decrement,
+                    R.string.time_picker_decrement_set_am_button);
+        }
+    }
+
+    private void trySetContentDescription(View root, int viewId, int contDescResId) {
+        View target = root.findViewById(viewId);
+        if (target != null) {
+            target.setContentDescription(mContext.getString(contDescResId));
         }
     }
 
diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
index ef7e651..3d46cdd 100644
--- a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
+++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
@@ -123,7 +123,7 @@
     private OnClickListener mSwitchOldListener = new OnClickListener() {
         public void onClick(View v) {
             try {
-                ActivityManagerNative.getDefault().moveTaskToFront(mCurTask, 0);
+                ActivityManagerNative.getDefault().moveTaskToFront(mCurTask, 0, null);
             } catch (RemoteException e) {
             }
             finish();
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 0563846..af722a8 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -33,14 +33,17 @@
 import android.os.Bundle;
 import android.os.PatternMatcher;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.LayoutInflater;
+import android.widget.AdapterView;
 import android.widget.BaseAdapter;
 import android.widget.CheckBox;
 import android.widget.CompoundButton;
 import android.widget.ImageView;
+import android.widget.ListView;
 import android.widget.TextView;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
@@ -122,6 +125,11 @@
         }
 
         setupAlert();
+
+        ListView lv = mAlert.getListView();
+        if (lv != null) {
+            lv.setOnItemLongClickListener(new ItemLongClickListener());
+        }
     }
 
     @Override
@@ -489,5 +497,18 @@
             mClearDefaultHint.setVisibility(View.GONE);
         }
     }
+
+    class ItemLongClickListener implements AdapterView.OnItemLongClickListener {
+
+        @Override
+        public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
+            ResolveInfo ri = mAdapter.resolveInfoForPosition(position);
+            Intent in = new Intent().setAction("android.settings.APPLICATION_DETAILS_SETTINGS")
+                    .setData(Uri.fromParts("package", ri.activityInfo.packageName, null));
+            startActivity(in);
+            return true;
+        }
+
+    }
 }
 
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
index e7c3948..eed3e67 100644
--- a/core/java/com/android/internal/backup/LocalTransport.java
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -166,10 +166,13 @@
         if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
 
         File packageDir = new File(mDataDir, packageInfo.packageName);
-        for (File f : packageDir.listFiles()) {
-            f.delete();
+        final File[] fileset = packageDir.listFiles();
+        if (fileset != null) {
+            for (File f : fileset) {
+                f.delete();
+            }
+            packageDir.delete();
         }
-        packageDir.delete();
         return BackupConstants.TRANSPORT_OK;
     }
 
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index da189f1..07496a7 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -44,10 +44,14 @@
  * <p>When a state machine is created <code>addState</code> is used to build the
  * hierarchy and <code>setInitialState</code> is used to identify which of these
  * is the initial state. After construction the programmer calls <code>start</code>
- * which initializes the state machine and calls <code>enter</code> for all of the initial
- * state's hierarchy, starting at its eldest parent. For example given the simple
- * state machine below after start is called mP1.enter will have been called and
- * then mS1.enter.</p>
+ * which initializes and starts the state machine. The first action the StateMachine
+ * is to the invoke <code>enter</code> for all of the initial state's hierarchy,
+ * starting at its eldest parent. The calls to enter will be done in the context
+ * of the StateMachines Handler not in the context of the call to start and they
+ * will be invoked before any messages are processed. For example, given the simple
+ * state machine below mP1.enter will be invoked and then mS1.enter. Finally,
+ * messages sent to the state machine will be processed by the current state,
+ * in our simple state machine below that would initially be mS1.processMessage.</p>
 <code>
         mP1
        /   \
@@ -621,8 +625,8 @@
         /** The debug flag */
         private boolean mDbg = false;
 
-        /** The quit object */
-        private static final Object mQuitObj = new Object();
+        /** The SmHandler object, identifies that message is internal */
+        private static final Object mSmHandlerObj = new Object();
 
         /** The current message */
         private Message mMsg;
@@ -726,19 +730,18 @@
             /** Save the current message */
             mMsg = msg;
 
-            /**
-             * Check that construction was completed
-             */
-            if (!mIsConstructionCompleted) {
-                Log.e(TAG, "The start method not called, ignore msg: " + msg);
-                return;
+            if (mIsConstructionCompleted) {
+                /** Normal path */
+                processMsg(msg);
+            } else if (!mIsConstructionCompleted &&
+                    (mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) {
+                /** Initial one time path. */
+                mIsConstructionCompleted = true;
+                invokeEnterMethods(0);
+            } else {
+                throw new RuntimeException("StateMachine.handleMessage: " +
+                            "The start method not called, received msg: " + msg);
             }
-
-            /**
-             * Process the message abiding by the hierarchical semantics
-             * and perform any requested transitions.
-             */
-            processMsg(msg);
             performTransitions();
 
             if (mDbg) Log.d(TAG, "handleMessage: X");
@@ -852,18 +855,8 @@
             mTempStateStack = new StateInfo[maxDepth];
             setupInitialStateStack();
 
-            /**
-             * Construction is complete call all enter methods
-             * starting at the first entry.
-             */
-            mIsConstructionCompleted = true;
-            mMsg = obtainMessage(SM_INIT_CMD);
-            invokeEnterMethods(0);
-
-            /**
-             * Perform any transitions requested by the enter methods
-             */
-            performTransitions();
+            /** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
+            sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
 
             if (mDbg) Log.d(TAG, "completeConstruction: X");
         }
@@ -1103,14 +1096,14 @@
 
         /** @see StateMachine#setInitialState(State) */
         private final void setInitialState(State initialState) {
-            if (mDbg) Log.d(TAG, "setInitialState: initialState" + initialState.getName());
+            if (mDbg) Log.d(TAG, "setInitialState: initialState=" + initialState.getName());
             mInitialState = initialState;
         }
 
         /** @see StateMachine#transitionTo(IState) */
         private final void transitionTo(IState destState) {
             mDestState = (State) destState;
-            if (mDbg) Log.d(TAG, "StateMachine.transitionTo EX destState" + mDestState.getName());
+            if (mDbg) Log.d(TAG, "transitionTo: destState=" + mDestState.getName());
         }
 
         /** @see StateMachine#deferMessage(Message) */
@@ -1127,12 +1120,12 @@
         /** @see StateMachine#deferMessage(Message) */
         private final void quit() {
             if (mDbg) Log.d(TAG, "quit:");
-            sendMessage(obtainMessage(SM_QUIT_CMD, mQuitObj));
+            sendMessage(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
         }
 
         /** @see StateMachine#isQuit(Message) */
         private final boolean isQuit(Message msg) {
-            return (msg.what == SM_QUIT_CMD) && (msg.obj == mQuitObj);
+            return (msg.what == SM_QUIT_CMD) && (msg.obj == mSmHandlerObj);
         }
 
         /** @see StateMachine#isDbg() */
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index acc3c1c..5a7d519 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -105,6 +105,12 @@
      */
     public static final int MIN_PATTERN_REGISTER_FAIL = MIN_LOCK_PATTERN_SIZE;
 
+    /**
+     * The bit in LOCK_BIOMETRIC_WEAK_FLAGS to be used to indicate whether liveliness should
+     * be used
+     */
+    public static final int FLAG_BIOMETRIC_WEAK_LIVELINESS = 0x1;
+
     private final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
     private final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
     private final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
@@ -878,6 +884,28 @@
     }
 
     /**
+     * Set whether biometric weak liveliness is enabled.
+     */
+    public void setBiometricWeakLivelinessEnabled(boolean enabled) {
+        long currentFlag = getLong(Settings.Secure.LOCK_BIOMETRIC_WEAK_FLAGS, 0L);
+        long newFlag;
+        if (enabled) {
+            newFlag = currentFlag | FLAG_BIOMETRIC_WEAK_LIVELINESS;
+        } else {
+            newFlag = currentFlag & ~FLAG_BIOMETRIC_WEAK_LIVELINESS;
+        }
+        setLong(Settings.Secure.LOCK_BIOMETRIC_WEAK_FLAGS, newFlag);
+    }
+
+    /**
+     * @return Whether the biometric weak liveliness is enabled.
+     */
+    public boolean isBiometricWeakLivelinessEnabled() {
+        long currentFlag = getLong(Settings.Secure.LOCK_BIOMETRIC_WEAK_FLAGS, 0L);
+        return ((currentFlag & FLAG_BIOMETRIC_WEAK_LIVELINESS) != 0);
+    }
+
+    /**
      * Set whether the lock pattern is enabled.
      */
     public void setLockPatternEnabled(boolean enabled) {
diff --git a/core/java/com/google/android/mms/pdu/PduPersister.java b/core/java/com/google/android/mms/pdu/PduPersister.java
index b04f890..7c937ed 100644
--- a/core/java/com/google/android/mms/pdu/PduPersister.java
+++ b/core/java/com/google/android/mms/pdu/PduPersister.java
@@ -20,6 +20,8 @@
 import com.google.android.mms.ContentType;
 import com.google.android.mms.InvalidHeaderValueException;
 import com.google.android.mms.MmsException;
+import com.google.android.mms.util.DownloadDrmHelper;
+import com.google.android.mms.util.DrmConvertSession;
 import com.google.android.mms.util.PduCache;
 import com.google.android.mms.util.PduCacheEntry;
 import com.google.android.mms.util.SqliteWrapper;
@@ -30,7 +32,11 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteException;
+import android.drm.DrmManagerClient;
 import android.net.Uri;
+import android.os.FileUtils;
+import android.provider.MediaStore;
 import android.provider.Telephony;
 import android.provider.Telephony.Mms;
 import android.provider.Telephony.MmsSms;
@@ -42,6 +48,7 @@
 import android.util.Log;
 
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -271,10 +278,12 @@
 
     private final Context mContext;
     private final ContentResolver mContentResolver;
+    private final DrmManagerClient mDrmManagerClient;
 
     private PduPersister(Context context) {
         mContext = context;
         mContentResolver = context.getContentResolver();
+        mDrmManagerClient = new DrmManagerClient(context);
      }
 
     /** Get(or create if not exist) an instance of PduPersister */
@@ -761,6 +770,9 @@
             throws MmsException {
         OutputStream os = null;
         InputStream is = null;
+        DrmConvertSession drmConvertSession = null;
+        Uri dataUri = null;
+        String path = null;
 
         try {
             byte[] data = part.getData();
@@ -773,9 +785,38 @@
                     throw new MmsException("unable to update " + uri.toString());
                 }
             } else {
+                boolean isDrm = DownloadDrmHelper.isDrmConvertNeeded(contentType);
+                if (isDrm) {
+                    if (uri != null) {
+                        try {
+                            path = convertUriToPath(mContext, uri);
+                            if (LOCAL_LOGV) {
+                                Log.v(TAG, "drm uri: " + uri + " path: " + path);
+                            }
+                            File f = new File(path);
+                            long len = f.length();
+                            if (LOCAL_LOGV) {
+                                Log.v(TAG, "drm path: " + path + " len: " + len);
+                            }
+                            if (len > 0) {
+                                // we're not going to re-persist and re-encrypt an already
+                                // converted drm file
+                                return;
+                            }
+                        } catch (Exception e) {
+                            Log.e(TAG, "Can't get file info for: " + part.getDataUri(), e);
+                        }
+                    }
+                    // We haven't converted the file yet, start the conversion
+                    drmConvertSession = DrmConvertSession.open(mContext, contentType);
+                    if (drmConvertSession == null) {
+                        throw new MmsException("Mimetype " + contentType +
+                                " can not be converted.");
+                    }
+                }
                 os = mContentResolver.openOutputStream(uri);
                 if (data == null) {
-                    Uri dataUri = part.getDataUri();
+                    dataUri = part.getDataUri();
                     if ((dataUri == null) || (dataUri == uri)) {
                         Log.w(TAG, "Can't find data for this part.");
                         return;
@@ -788,13 +829,32 @@
 
                     byte[] buffer = new byte[8192];
                     for (int len = 0; (len = is.read(buffer)) != -1; ) {
-                        os.write(buffer, 0, len);
+                        if (!isDrm) {
+                            os.write(buffer, 0, len);
+                        } else {
+                            byte[] convertedData = drmConvertSession.convert(buffer, len);
+                            if (convertedData != null) {
+                                os.write(convertedData, 0, convertedData.length);
+                            } else {
+                                throw new MmsException("Error converting drm data.");
+                            }
+                        }
                     }
                 } else {
                     if (LOCAL_LOGV) {
                         Log.v(TAG, "Saving data to: " + uri);
                     }
-                    os.write(data);
+                    if (!isDrm) {
+                        os.write(data);
+                    } else {
+                        dataUri = uri;
+                        byte[] convertedData = drmConvertSession.convert(data, data.length);
+                        if (convertedData != null) {
+                            os.write(convertedData, 0, convertedData.length);
+                        } else {
+                            throw new MmsException("Error converting drm data.");
+                        }
+                    }
                 }
             }
         } catch (FileNotFoundException e) {
@@ -818,9 +878,67 @@
                     Log.e(TAG, "IOException while closing: " + is, e);
                 } // Ignore
             }
+            if (drmConvertSession != null) {
+                drmConvertSession.close(path);
+
+                // Reset the permissions on the encrypted part file so everyone has only read
+                // permission.
+                File f = new File(path);
+                ContentValues values = new ContentValues(0);
+                SqliteWrapper.update(mContext, mContentResolver,
+                                     Uri.parse("content://mms/resetFilePerm/" + f.getName()),
+                                     values, null, null);
+            }
         }
     }
 
+    /**
+     * This method expects uri in the following format
+     *     content://media/<table_name>/<row_index> (or)
+     *     file://sdcard/test.mp4
+     *     http://test.com/test.mp4
+     *
+     * Here <table_name> shall be "video" or "audio" or "images"
+     * <row_index> the index of the content in given table
+     */
+    static public String convertUriToPath(Context context, Uri uri) {
+        String path = null;
+        if (null != uri) {
+            String scheme = uri.getScheme();
+            if (null == scheme || scheme.equals("") ||
+                    scheme.equals(ContentResolver.SCHEME_FILE)) {
+                path = uri.getPath();
+
+            } else if (scheme.equals("http")) {
+                path = uri.toString();
+
+            } else if (scheme.equals(ContentResolver.SCHEME_CONTENT)) {
+                String[] projection = new String[] {MediaStore.MediaColumns.DATA};
+                Cursor cursor = null;
+                try {
+                    cursor = context.getContentResolver().query(uri, projection, null,
+                            null, null);
+                    if (null == cursor || 0 == cursor.getCount() || !cursor.moveToFirst()) {
+                        throw new IllegalArgumentException("Given Uri could not be found" +
+                                " in media store");
+                    }
+                    int pathIndex = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
+                    path = cursor.getString(pathIndex);
+                } catch (SQLiteException e) {
+                    throw new IllegalArgumentException("Given Uri is not formatted in a way " +
+                            "so that it can be found in media store.");
+                } finally {
+                    if (null != cursor) {
+                        cursor.close();
+                    }
+                }
+            } else {
+                throw new IllegalArgumentException("Given Uri scheme is not supported");
+            }
+        }
+        return path;
+    }
+
     private void updateAddress(
             long msgId, int type, EncodedStringValue[] array) {
         // Delete old address information and then insert new ones.
diff --git a/core/java/com/google/android/mms/util/DownloadDrmHelper.java b/core/java/com/google/android/mms/util/DownloadDrmHelper.java
new file mode 100644
index 0000000..6852eca
--- /dev/null
+++ b/core/java/com/google/android/mms/util/DownloadDrmHelper.java
@@ -0,0 +1,111 @@
+/*
+ * 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.google.android.mms.util;
+
+import android.content.Context;
+import android.drm.DrmManagerClient;
+import android.util.Log;
+
+public class DownloadDrmHelper {
+    private static final String TAG = "DownloadDrmHelper";
+
+    /** The MIME type of special DRM files */
+    public static final String MIMETYPE_DRM_MESSAGE = "application/vnd.oma.drm.message";
+
+    /** The extensions of special DRM files */
+    public static final String EXTENSION_DRM_MESSAGE = ".dm";
+
+    public static final String EXTENSION_INTERNAL_FWDL = ".fl";
+
+    /**
+     * Checks if the Media Type is a DRM Media Type
+     *
+     * @param drmManagerClient A DrmManagerClient
+     * @param mimetype Media Type to check
+     * @return True if the Media Type is DRM else false
+     */
+    public static boolean isDrmMimeType(Context context, String mimetype) {
+        boolean result = false;
+        if (context != null) {
+            try {
+                DrmManagerClient drmClient = new DrmManagerClient(context);
+                if (drmClient != null && mimetype != null && mimetype.length() > 0) {
+                    result = drmClient.canHandle("", mimetype);
+                }
+            } catch (IllegalArgumentException e) {
+                Log.w(TAG,
+                        "DrmManagerClient instance could not be created, context is Illegal.");
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "DrmManagerClient didn't initialize properly.");
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Checks if the Media Type needs to be DRM converted
+     *
+     * @param mimetype Media type of the content
+     * @return True if convert is needed else false
+     */
+    public static boolean isDrmConvertNeeded(String mimetype) {
+        return MIMETYPE_DRM_MESSAGE.equals(mimetype);
+    }
+
+    /**
+     * Modifies the file extension for a DRM Forward Lock file NOTE: This
+     * function shouldn't be called if the file shouldn't be DRM converted
+     */
+    public static String modifyDrmFwLockFileExtension(String filename) {
+        if (filename != null) {
+            int extensionIndex;
+            extensionIndex = filename.lastIndexOf(".");
+            if (extensionIndex != -1) {
+                filename = filename.substring(0, extensionIndex);
+            }
+            filename = filename.concat(EXTENSION_INTERNAL_FWDL);
+        }
+        return filename;
+    }
+
+    /**
+     * Gets the original mime type of DRM protected content.
+     *
+     * @param context The context
+     * @param path Path to the file
+     * @param containingMime The current mime type of of the file i.e. the
+     *            containing mime type
+     * @return The original mime type of the file if DRM protected else the
+     *         currentMime
+     */
+    public static String getOriginalMimeType(Context context, String path, String containingMime) {
+        String result = containingMime;
+        DrmManagerClient drmClient = new DrmManagerClient(context);
+        try {
+            if (drmClient.canHandle(path, null)) {
+                result = drmClient.getOriginalMimeType(path);
+            }
+        } catch (IllegalArgumentException ex) {
+            Log.w(TAG,
+                    "Can't get original mime type since path is null or empty string.");
+        } catch (IllegalStateException ex) {
+            Log.w(TAG, "DrmManagerClient didn't initialize properly.");
+        }
+        return result;
+    }
+}
diff --git a/core/java/com/google/android/mms/util/DrmConvertSession.java b/core/java/com/google/android/mms/util/DrmConvertSession.java
new file mode 100644
index 0000000..2d8f274
--- /dev/null
+++ b/core/java/com/google/android/mms/util/DrmConvertSession.java
@@ -0,0 +1,172 @@
+/*
+ * 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.google.android.mms.util;
+
+import android.content.Context;
+import android.drm.DrmConvertedStatus;
+import android.drm.DrmManagerClient;
+import android.util.Log;
+import android.provider.Downloads;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+
+public class DrmConvertSession {
+    private DrmManagerClient mDrmClient;
+    private int mConvertSessionId;
+    private static final String TAG = "DrmConvertSession";
+
+    private DrmConvertSession(DrmManagerClient drmClient, int convertSessionId) {
+        mDrmClient = drmClient;
+        mConvertSessionId = convertSessionId;
+    }
+
+    /**
+     * Start of converting a file.
+     *
+     * @param context The context of the application running the convert session.
+     * @param mimeType Mimetype of content that shall be converted.
+     * @return A convert session or null in case an error occurs.
+     */
+    public static DrmConvertSession open(Context context, String mimeType) {
+        DrmManagerClient drmClient = null;
+        int convertSessionId = -1;
+        if (context != null && mimeType != null && !mimeType.equals("")) {
+            try {
+                drmClient = new DrmManagerClient(context);
+                try {
+                    convertSessionId = drmClient.openConvertSession(mimeType);
+                } catch (IllegalArgumentException e) {
+                    Log.w(TAG, "Conversion of Mimetype: " + mimeType
+                            + " is not supported.", e);
+                } catch (IllegalStateException e) {
+                    Log.w(TAG, "Could not access Open DrmFramework.", e);
+                }
+            } catch (IllegalArgumentException e) {
+                Log.w(TAG,
+                        "DrmManagerClient instance could not be created, context is Illegal.");
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "DrmManagerClient didn't initialize properly.");
+            }
+        }
+
+        if (drmClient == null || convertSessionId < 0) {
+            return null;
+        } else {
+            return new DrmConvertSession(drmClient, convertSessionId);
+        }
+    }
+    /**
+     * Convert a buffer of data to protected format.
+     *
+     * @param buffer Buffer filled with data to convert.
+     * @param size The number of bytes that shall be converted.
+     * @return A Buffer filled with converted data, if execution is ok, in all
+     *         other case null.
+     */
+    public byte [] convert(byte[] inBuffer, int size) {
+        byte[] result = null;
+        if (inBuffer != null) {
+            DrmConvertedStatus convertedStatus = null;
+            try {
+                if (size != inBuffer.length) {
+                    byte[] buf = new byte[size];
+                    System.arraycopy(inBuffer, 0, buf, 0, size);
+                    convertedStatus = mDrmClient.convertData(mConvertSessionId, buf);
+                } else {
+                    convertedStatus = mDrmClient.convertData(mConvertSessionId, inBuffer);
+                }
+
+                if (convertedStatus != null &&
+                        convertedStatus.statusCode == DrmConvertedStatus.STATUS_OK &&
+                        convertedStatus.convertedData != null) {
+                    result = convertedStatus.convertedData;
+                }
+            } catch (IllegalArgumentException e) {
+                Log.w(TAG, "Buffer with data to convert is illegal. Convertsession: "
+                        + mConvertSessionId, e);
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "Could not convert data. Convertsession: " +
+                        mConvertSessionId, e);
+            }
+        } else {
+            throw new IllegalArgumentException("Parameter inBuffer is null");
+        }
+        return result;
+    }
+
+    /**
+     * Ends a conversion session of a file.
+     *
+     * @param fileName The filename of the converted file.
+     * @return Downloads.Impl.STATUS_SUCCESS if execution is ok.
+     *         Downloads.Impl.STATUS_FILE_ERROR in case converted file can not
+     *         be accessed. Downloads.Impl.STATUS_NOT_ACCEPTABLE if a problem
+     *         occurs when accessing drm framework.
+     *         Downloads.Impl.STATUS_UNKNOWN_ERROR if a general error occurred.
+     */
+    public int close(String filename) {
+        DrmConvertedStatus convertedStatus = null;
+        int result = Downloads.Impl.STATUS_UNKNOWN_ERROR;
+        if (mDrmClient != null && mConvertSessionId >= 0) {
+            try {
+                convertedStatus = mDrmClient.closeConvertSession(mConvertSessionId);
+                if (convertedStatus == null ||
+                        convertedStatus.statusCode != DrmConvertedStatus.STATUS_OK ||
+                        convertedStatus.convertedData == null) {
+                    result = Downloads.Impl.STATUS_NOT_ACCEPTABLE;
+                } else {
+                    RandomAccessFile rndAccessFile = null;
+                    try {
+                        rndAccessFile = new RandomAccessFile(filename, "rw");
+                        rndAccessFile.seek(convertedStatus.offset);
+                        rndAccessFile.write(convertedStatus.convertedData);
+                        result = Downloads.Impl.STATUS_SUCCESS;
+                    } catch (FileNotFoundException e) {
+                        result = Downloads.Impl.STATUS_FILE_ERROR;
+                        Log.w(TAG, "File: " + filename + " could not be found.", e);
+                    } catch (IOException e) {
+                        result = Downloads.Impl.STATUS_FILE_ERROR;
+                        Log.w(TAG, "Could not access File: " + filename + " .", e);
+                    } catch (IllegalArgumentException e) {
+                        result = Downloads.Impl.STATUS_FILE_ERROR;
+                        Log.w(TAG, "Could not open file in mode: rw", e);
+                    } catch (SecurityException e) {
+                        Log.w(TAG, "Access to File: " + filename +
+                                " was denied denied by SecurityManager.", e);
+                    } finally {
+                        if (rndAccessFile != null) {
+                            try {
+                                rndAccessFile.close();
+                            } catch (IOException e) {
+                                result = Downloads.Impl.STATUS_FILE_ERROR;
+                                Log.w(TAG, "Failed to close File:" + filename
+                                        + ".", e);
+                            }
+                        }
+                    }
+                }
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "Could not close convertsession. Convertsession: " +
+                        mConvertSessionId, e);
+            }
+        }
+        return result;
+    }
+}
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 8e5db36..baf296d 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -21,12 +21,16 @@
 #include "jni.h"
 #include "GraphicsJNI.h"
 #include <nativehelper/JNIHelp.h>
+
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_graphics_SurfaceTexture.h>
-#include <cutils/properties.h>
+#include <gui/SurfaceTexture.h>
+
 #include <androidfw/ResourceTypes.h>
 
-#include <gui/SurfaceTexture.h>
+#include <private/hwui/DrawGlInfo.h>
+
+#include <cutils/properties.h>
 
 #include <SkBitmap.h>
 #include <SkCanvas.h>
@@ -196,7 +200,7 @@
 // Functor
 // ----------------------------------------------------------------------------
 
-static bool android_view_GLES20Canvas_callDrawGLFunction(JNIEnv* env, jobject clazz,
+static jint android_view_GLES20Canvas_callDrawGLFunction(JNIEnv* env, jobject clazz,
         OpenGLRenderer* renderer, Functor *functor) {
     android::uirenderer::Rect dirty;
     return renderer->callDrawGLFunction(functor, dirty);
@@ -682,16 +686,16 @@
     DisplayList::destroyDisplayListDeferred(displayList);
 }
 
-static bool android_view_GLES20Canvas_drawDisplayList(JNIEnv* env,
+static jint android_view_GLES20Canvas_drawDisplayList(JNIEnv* env,
         jobject clazz, OpenGLRenderer* renderer, DisplayList* displayList,
         jint width, jint height, jobject dirty, jint flags) {
     android::uirenderer::Rect bounds;
-    bool redraw = renderer->drawDisplayList(displayList, width, height, bounds, flags);
-    if (redraw && dirty != NULL) {
+    status_t status = renderer->drawDisplayList(displayList, width, height, bounds, flags);
+    if (status != DrawGlInfo::kStatusDone && dirty != NULL) {
         env->CallVoidMethod(dirty, gRectClassInfo.set,
                 int(bounds.left), int(bounds.top), int(bounds.right), int(bounds.bottom));
     }
-    return redraw;
+    return status;
 }
 
 static void android_view_GLES20Canvas_outputDisplayList(JNIEnv* env,
@@ -865,7 +869,7 @@
 
     { "nGetStencilSize",    "()I",             (void*) android_view_GLES20Canvas_getStencilSize },
 
-    { "nCallDrawGLFunction", "(II)Z",
+    { "nCallDrawGLFunction", "(II)I",
             (void*) android_view_GLES20Canvas_callDrawGLFunction },
 
     { "nSave",              "(II)I",           (void*) android_view_GLES20Canvas_save },
@@ -943,7 +947,7 @@
     { "nGetDisplayListSize",     "(I)I",       (void*) android_view_GLES20Canvas_getDisplayListSize },
     { "nSetDisplayListName",     "(ILjava/lang/String;)V",
                                                (void*) android_view_GLES20Canvas_setDisplayListName },
-    { "nDrawDisplayList",        "(IIIILandroid/graphics/Rect;I)Z",
+    { "nDrawDisplayList",        "(IIIILandroid/graphics/Rect;I)I",
                                                (void*) android_view_GLES20Canvas_drawDisplayList },
 
     { "nCreateDisplayListRenderer", "()I",     (void*) android_view_GLES20Canvas_createDisplayListRenderer },
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index afbcfc2..8d6fab4 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -20,8 +20,8 @@
 #include <android_runtime/AndroidRuntime.h>
 
 #include <utils/Log.h>
+#include <utils/ZipFileRO.h>
 #include <ScopedUtfChars.h>
-#include <androidfw/ZipFileRO.h>
 
 #include <zlib.h>
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d1e3642..5ae12b6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -60,6 +60,9 @@
     <protected-broadcast android:name="android.intent.action.REBOOT" />
     <protected-broadcast android:name="android.intent.action.DOCK_EVENT" />
     <protected-broadcast android:name="android.intent.action.MASTER_CLEAR_NOTIFICATION" />
+    <protected-broadcast android:name="android.intent.action.USER_ADDED" />
+    <protected-broadcast android:name="android.intent.action.USER_REMOVED" />
+    <protected-broadcast android:name="android.intent.action.USER_SWITCHED" />
 
     <protected-broadcast android:name="android.app.action.ENTER_CAR_MODE" />
     <protected-broadcast android:name="android.app.action.EXIT_CAR_MODE" />
@@ -1032,15 +1035,6 @@
         android:label="@string/permlab_clearAppCache"
         android:description="@string/permdesc_clearAppCache" />
 
-    <!-- Allows an application to read the low-level system log files.
-         Log entries can contain the user's private information,
-         which is why this permission is 'dangerous'. -->
-    <permission android:name="android.permission.READ_LOGS"
-        android:permissionGroup="android.permission-group.PERSONAL_INFO"
-        android:protectionLevel="dangerous"
-        android:label="@string/permlab_readLogs"
-        android:description="@string/permdesc_readLogs" />
-
     <!-- Allows an application to use any media decoder when decoding for playback
          @hide -->
     <permission android:name="android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK"
@@ -1076,6 +1070,15 @@
         android:label="@string/permlab_dump"
         android:description="@string/permdesc_dump" />
 
+    <!-- Allows an application to read the low-level system log files.
+         Log entries can contain the user's private information,
+         which is why this permission is not available to normal apps. -->
+    <permission android:name="android.permission.READ_LOGS"
+        android:permissionGroup="android.permission-group.DEVELOPMENT_TOOLS"
+        android:protectionLevel="signature|system|development"
+        android:label="@string/permlab_readLogs"
+        android:description="@string/permdesc_readLogs" />
+
     <!-- Configure an application for debugging. -->
     <permission android:name="android.permission.SET_DEBUG_APP"
         android:permissionGroup="android.permission-group.DEVELOPMENT_TOOLS"
diff --git a/core/res/res/drawable-hdpi/ic_facial_backup.png b/core/res/res/drawable-hdpi/ic_facial_backup.png
new file mode 100644
index 0000000..2956109
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_facial_backup.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/intro_bg.png b/core/res/res/drawable-hdpi/intro_bg.png
new file mode 100644
index 0000000..a758e7d
--- /dev/null
+++ b/core/res/res/drawable-hdpi/intro_bg.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_down_disabled_focused_holo_dark.png b/core/res/res/drawable-hdpi/numberpicker_down_disabled_focused_holo_dark.png
deleted file mode 100644
index 9584649..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_down_disabled_focused_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_down_disabled_focused_holo_light.png b/core/res/res/drawable-hdpi/numberpicker_down_disabled_focused_holo_light.png
deleted file mode 100644
index 5c37873..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_down_disabled_focused_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_down_disabled_holo_dark.png b/core/res/res/drawable-hdpi/numberpicker_down_disabled_holo_dark.png
deleted file mode 100644
index b5faf6f..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_down_disabled_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_down_disabled_holo_light.png b/core/res/res/drawable-hdpi/numberpicker_down_disabled_holo_light.png
deleted file mode 100644
index 041412b..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_down_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_down_focused_holo_dark.png b/core/res/res/drawable-hdpi/numberpicker_down_focused_holo_dark.png
deleted file mode 100644
index 5717bee..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_down_focused_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_down_focused_holo_light.png b/core/res/res/drawable-hdpi/numberpicker_down_focused_holo_light.png
deleted file mode 100644
index e874330..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_down_focused_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_down_longpressed_holo_dark.png b/core/res/res/drawable-hdpi/numberpicker_down_longpressed_holo_dark.png
deleted file mode 100644
index 96a6c8a..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_down_longpressed_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_down_longpressed_holo_light.png b/core/res/res/drawable-hdpi/numberpicker_down_longpressed_holo_light.png
deleted file mode 100644
index 96a6c8a..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_down_longpressed_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_down_normal_holo_dark.png b/core/res/res/drawable-hdpi/numberpicker_down_normal_holo_dark.png
deleted file mode 100644
index 4631d85..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_down_normal_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_down_normal_holo_light.png b/core/res/res/drawable-hdpi/numberpicker_down_normal_holo_light.png
deleted file mode 100644
index 39c7af4..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_down_normal_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_down_pressed_holo_dark.png b/core/res/res/drawable-hdpi/numberpicker_down_pressed_holo_dark.png
deleted file mode 100644
index 9c23a18..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_down_pressed_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_down_pressed_holo_light.png b/core/res/res/drawable-hdpi/numberpicker_down_pressed_holo_light.png
deleted file mode 100644
index 9c23a18..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_down_pressed_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_up_disabled_focused_holo_dark.png b/core/res/res/drawable-hdpi/numberpicker_up_disabled_focused_holo_dark.png
deleted file mode 100644
index 159913c..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_up_disabled_focused_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_up_disabled_focused_holo_light.png b/core/res/res/drawable-hdpi/numberpicker_up_disabled_focused_holo_light.png
deleted file mode 100644
index cfee4b7..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_up_disabled_focused_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_up_disabled_holo_dark.png b/core/res/res/drawable-hdpi/numberpicker_up_disabled_holo_dark.png
deleted file mode 100644
index e5f0430..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_up_disabled_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_up_disabled_holo_light.png b/core/res/res/drawable-hdpi/numberpicker_up_disabled_holo_light.png
deleted file mode 100644
index 7e4ec4a..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_up_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_up_focused_holo_dark.png b/core/res/res/drawable-hdpi/numberpicker_up_focused_holo_dark.png
deleted file mode 100644
index b06017e..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_up_focused_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_up_focused_holo_light.png b/core/res/res/drawable-hdpi/numberpicker_up_focused_holo_light.png
deleted file mode 100644
index a1000f8..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_up_focused_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_up_longpressed_holo_dark.png b/core/res/res/drawable-hdpi/numberpicker_up_longpressed_holo_dark.png
deleted file mode 100644
index b3d6706..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_up_longpressed_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_up_longpressed_holo_light.png b/core/res/res/drawable-hdpi/numberpicker_up_longpressed_holo_light.png
deleted file mode 100644
index b3d6706..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_up_longpressed_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_up_normal_holo_dark.png b/core/res/res/drawable-hdpi/numberpicker_up_normal_holo_dark.png
deleted file mode 100644
index 9ee35c7..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_up_normal_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_up_normal_holo_light.png b/core/res/res/drawable-hdpi/numberpicker_up_normal_holo_light.png
deleted file mode 100644
index 4da4fa7..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_up_normal_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_up_pressed_holo_dark.png b/core/res/res/drawable-hdpi/numberpicker_up_pressed_holo_dark.png
deleted file mode 100644
index 358a13f..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_up_pressed_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/numberpicker_up_pressed_holo_light.png b/core/res/res/drawable-hdpi/numberpicker_up_pressed_holo_light.png
deleted file mode 100644
index 358a13f..0000000
--- a/core/res/res/drawable-hdpi/numberpicker_up_pressed_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_facial_backup.png b/core/res/res/drawable-mdpi/ic_facial_backup.png
new file mode 100644
index 0000000..6ed1327
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_facial_backup.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/intro_bg.png b/core/res/res/drawable-mdpi/intro_bg.png
new file mode 100644
index 0000000..540da31
--- /dev/null
+++ b/core/res/res/drawable-mdpi/intro_bg.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_down_disabled_focused_holo_dark.png b/core/res/res/drawable-mdpi/numberpicker_down_disabled_focused_holo_dark.png
deleted file mode 100644
index 50f6e98..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_down_disabled_focused_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_down_disabled_focused_holo_light.png b/core/res/res/drawable-mdpi/numberpicker_down_disabled_focused_holo_light.png
deleted file mode 100644
index 67434f6..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_down_disabled_focused_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_down_disabled_holo_dark.png b/core/res/res/drawable-mdpi/numberpicker_down_disabled_holo_dark.png
deleted file mode 100644
index 9c2b833..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_down_disabled_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_down_disabled_holo_light.png b/core/res/res/drawable-mdpi/numberpicker_down_disabled_holo_light.png
deleted file mode 100644
index dcf2fb7..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_down_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_down_focused_holo_dark.png b/core/res/res/drawable-mdpi/numberpicker_down_focused_holo_dark.png
deleted file mode 100644
index b63c510d..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_down_focused_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_down_focused_holo_light.png b/core/res/res/drawable-mdpi/numberpicker_down_focused_holo_light.png
deleted file mode 100644
index 55312a1..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_down_focused_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_down_longpressed_holo_dark.png b/core/res/res/drawable-mdpi/numberpicker_down_longpressed_holo_dark.png
deleted file mode 100644
index 48e300c..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_down_longpressed_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_down_longpressed_holo_light.png b/core/res/res/drawable-mdpi/numberpicker_down_longpressed_holo_light.png
deleted file mode 100644
index 48e300c..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_down_longpressed_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_down_normal_holo_dark.png b/core/res/res/drawable-mdpi/numberpicker_down_normal_holo_dark.png
deleted file mode 100644
index 1558d3d..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_down_normal_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_down_normal_holo_light.png b/core/res/res/drawable-mdpi/numberpicker_down_normal_holo_light.png
deleted file mode 100644
index 6b6e7e1..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_down_normal_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_down_pressed_holo_dark.png b/core/res/res/drawable-mdpi/numberpicker_down_pressed_holo_dark.png
deleted file mode 100644
index eb16f8d..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_down_pressed_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_down_pressed_holo_light.png b/core/res/res/drawable-mdpi/numberpicker_down_pressed_holo_light.png
deleted file mode 100644
index eb16f8d..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_down_pressed_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_up_disabled_focused_holo_dark.png b/core/res/res/drawable-mdpi/numberpicker_up_disabled_focused_holo_dark.png
deleted file mode 100644
index 58a3b64..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_up_disabled_focused_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_up_disabled_focused_holo_light.png b/core/res/res/drawable-mdpi/numberpicker_up_disabled_focused_holo_light.png
deleted file mode 100644
index 382943b..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_up_disabled_focused_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_up_disabled_holo_dark.png b/core/res/res/drawable-mdpi/numberpicker_up_disabled_holo_dark.png
deleted file mode 100644
index cf856a1..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_up_disabled_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_up_disabled_holo_light.png b/core/res/res/drawable-mdpi/numberpicker_up_disabled_holo_light.png
deleted file mode 100644
index 6665953..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_up_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_up_focused_holo_dark.png b/core/res/res/drawable-mdpi/numberpicker_up_focused_holo_dark.png
deleted file mode 100644
index d63d797..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_up_focused_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_up_focused_holo_light.png b/core/res/res/drawable-mdpi/numberpicker_up_focused_holo_light.png
deleted file mode 100644
index 22b6dbd..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_up_focused_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_up_longpressed_holo_dark.png b/core/res/res/drawable-mdpi/numberpicker_up_longpressed_holo_dark.png
deleted file mode 100644
index 4bcce98..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_up_longpressed_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_up_longpressed_holo_light.png b/core/res/res/drawable-mdpi/numberpicker_up_longpressed_holo_light.png
deleted file mode 100644
index 4bcce98..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_up_longpressed_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_up_normal_holo_dark.png b/core/res/res/drawable-mdpi/numberpicker_up_normal_holo_dark.png
deleted file mode 100644
index 12ba823..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_up_normal_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_up_normal_holo_light.png b/core/res/res/drawable-mdpi/numberpicker_up_normal_holo_light.png
deleted file mode 100644
index d841f5a..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_up_normal_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_up_pressed_holo_dark.png b/core/res/res/drawable-mdpi/numberpicker_up_pressed_holo_dark.png
deleted file mode 100644
index bc5e3fa..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_up_pressed_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/numberpicker_up_pressed_holo_light.png b/core/res/res/drawable-mdpi/numberpicker_up_pressed_holo_light.png
deleted file mode 100644
index bc5e3fa..0000000
--- a/core/res/res/drawable-mdpi/numberpicker_up_pressed_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_facial_backup.png b/core/res/res/drawable-xhdpi/ic_facial_backup.png
new file mode 100644
index 0000000..942cf23
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_facial_backup.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/intro_bg.png b/core/res/res/drawable-xhdpi/intro_bg.png
new file mode 100644
index 0000000..00466c5
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/intro_bg.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_down_disabled_focused_holo_dark.png b/core/res/res/drawable-xhdpi/numberpicker_down_disabled_focused_holo_dark.png
deleted file mode 100644
index 111f57e..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_down_disabled_focused_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_down_disabled_focused_holo_light.png b/core/res/res/drawable-xhdpi/numberpicker_down_disabled_focused_holo_light.png
deleted file mode 100644
index d0ef05b..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_down_disabled_focused_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_down_disabled_holo_dark.png b/core/res/res/drawable-xhdpi/numberpicker_down_disabled_holo_dark.png
deleted file mode 100644
index ff21941..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_down_disabled_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_down_disabled_holo_light.png b/core/res/res/drawable-xhdpi/numberpicker_down_disabled_holo_light.png
deleted file mode 100644
index 3e9bdda..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_down_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_down_focused_holo_dark.png b/core/res/res/drawable-xhdpi/numberpicker_down_focused_holo_dark.png
deleted file mode 100644
index 0462fca..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_down_focused_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_down_focused_holo_light.png b/core/res/res/drawable-xhdpi/numberpicker_down_focused_holo_light.png
deleted file mode 100644
index a488e8e..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_down_focused_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_down_longpressed_holo_dark.png b/core/res/res/drawable-xhdpi/numberpicker_down_longpressed_holo_dark.png
deleted file mode 100644
index f61b076..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_down_longpressed_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_down_longpressed_holo_light.png b/core/res/res/drawable-xhdpi/numberpicker_down_longpressed_holo_light.png
deleted file mode 100644
index f61b076..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_down_longpressed_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_down_normal_holo_dark.png b/core/res/res/drawable-xhdpi/numberpicker_down_normal_holo_dark.png
deleted file mode 100644
index 211944e..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_down_normal_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_down_normal_holo_light.png b/core/res/res/drawable-xhdpi/numberpicker_down_normal_holo_light.png
deleted file mode 100644
index 12bc11a..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_down_normal_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_down_pressed_holo_dark.png b/core/res/res/drawable-xhdpi/numberpicker_down_pressed_holo_dark.png
deleted file mode 100644
index 635184c..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_down_pressed_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_down_pressed_holo_light.png b/core/res/res/drawable-xhdpi/numberpicker_down_pressed_holo_light.png
deleted file mode 100644
index 635184c..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_down_pressed_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_up_disabled_focused_holo_dark.png b/core/res/res/drawable-xhdpi/numberpicker_up_disabled_focused_holo_dark.png
deleted file mode 100644
index 470e569..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_up_disabled_focused_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_up_disabled_focused_holo_light.png b/core/res/res/drawable-xhdpi/numberpicker_up_disabled_focused_holo_light.png
deleted file mode 100644
index 16df74d..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_up_disabled_focused_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_up_disabled_holo_dark.png b/core/res/res/drawable-xhdpi/numberpicker_up_disabled_holo_dark.png
deleted file mode 100644
index edd4c04..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_up_disabled_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_up_disabled_holo_light.png b/core/res/res/drawable-xhdpi/numberpicker_up_disabled_holo_light.png
deleted file mode 100644
index d8f459a..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_up_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_up_focused_holo_dark.png b/core/res/res/drawable-xhdpi/numberpicker_up_focused_holo_dark.png
deleted file mode 100644
index 08bf241..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_up_focused_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_up_focused_holo_light.png b/core/res/res/drawable-xhdpi/numberpicker_up_focused_holo_light.png
deleted file mode 100644
index b2c40f1..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_up_focused_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_up_longpressed_holo_dark.png b/core/res/res/drawable-xhdpi/numberpicker_up_longpressed_holo_dark.png
deleted file mode 100644
index f4f7331..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_up_longpressed_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_up_longpressed_holo_light.png b/core/res/res/drawable-xhdpi/numberpicker_up_longpressed_holo_light.png
deleted file mode 100644
index f4f7331..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_up_longpressed_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_up_normal_holo_dark.png b/core/res/res/drawable-xhdpi/numberpicker_up_normal_holo_dark.png
deleted file mode 100644
index 83650b1..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_up_normal_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_up_normal_holo_light.png b/core/res/res/drawable-xhdpi/numberpicker_up_normal_holo_light.png
deleted file mode 100644
index 78085d3..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_up_normal_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_up_pressed_holo_dark.png b/core/res/res/drawable-xhdpi/numberpicker_up_pressed_holo_dark.png
deleted file mode 100644
index b8f6849..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_up_pressed_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/numberpicker_up_pressed_holo_light.png b/core/res/res/drawable-xhdpi/numberpicker_up_pressed_holo_light.png
deleted file mode 100644
index b8f6849..0000000
--- a/core/res/res/drawable-xhdpi/numberpicker_up_pressed_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/numberpicker_down_btn_holo_dark.xml b/core/res/res/drawable/numberpicker_down_btn_holo_dark.xml
deleted file mode 100644
index dd6332d..0000000
--- a/core/res/res/drawable/numberpicker_down_btn_holo_dark.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item android:state_pressed="false"
-          android:state_enabled="true"
-          android:state_focused="false"
-          android:drawable="@drawable/numberpicker_down_normal_holo_dark" />
-
-    <item android:state_pressed="true"
-          android:state_enabled="true"
-          android:drawable="@drawable/numberpicker_down_pressed_holo_dark" />
-
-    <item android:state_pressed="false"
-          android:state_enabled="true"
-          android:state_focused="true"
-          android:drawable="@drawable/numberpicker_down_focused_holo_dark" />
-
-    <item android:state_pressed="false"
-          android:state_enabled="false"
-          android:state_focused="false"
-          android:drawable="@drawable/numberpicker_down_disabled_holo_dark" />
-
-    <item android:state_pressed="false"
-          android:state_enabled="false"
-          android:state_focused="true"
-          android:drawable="@drawable/numberpicker_down_disabled_focused_holo_dark" />
-
-</selector>
diff --git a/core/res/res/drawable/numberpicker_down_btn_holo_light.xml b/core/res/res/drawable/numberpicker_down_btn_holo_light.xml
deleted file mode 100644
index 565fd86..0000000
--- a/core/res/res/drawable/numberpicker_down_btn_holo_light.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item android:state_pressed="false"
-          android:state_enabled="true"
-          android:state_focused="false"
-          android:drawable="@drawable/numberpicker_down_normal_holo_light" />
-
-    <item android:state_pressed="true"
-          android:state_enabled="true"
-          android:drawable="@drawable/numberpicker_down_pressed_holo_light" />
-
-    <item android:state_pressed="false"
-          android:state_enabled="true"
-          android:state_focused="true"
-          android:drawable="@drawable/numberpicker_down_focused_holo_light" />
-
-    <item android:state_pressed="false"
-          android:state_enabled="false"
-          android:state_focused="false"
-          android:drawable="@drawable/numberpicker_down_disabled_holo_light" />
-
-    <item android:state_pressed="false"
-          android:state_enabled="false"
-          android:state_focused="true"
-          android:drawable="@drawable/numberpicker_down_disabled_focused_holo_light" />
-
-</selector>
diff --git a/core/res/res/drawable/numberpicker_up_btn_holo_dark.xml b/core/res/res/drawable/numberpicker_up_btn_holo_dark.xml
deleted file mode 100644
index 7af3ee4..0000000
--- a/core/res/res/drawable/numberpicker_up_btn_holo_dark.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item android:state_pressed="false"
-          android:state_enabled="true"
-          android:state_focused="false"
-          android:drawable="@drawable/numberpicker_up_normal_holo_dark" />
-
-    <item android:state_pressed="true"
-          android:state_enabled="true"
-          android:drawable="@drawable/numberpicker_up_pressed_holo_dark" />
-
-    <item android:state_pressed="false"
-          android:state_enabled="true"
-          android:state_focused="true"
-          android:drawable="@drawable/numberpicker_up_focused_holo_dark" />
-
-    <item android:state_pressed="false"
-          android:state_enabled="false"
-          android:state_focused="false"
-          android:drawable="@drawable/numberpicker_up_disabled_holo_dark" />
-
-    <item android:state_pressed="false"
-          android:state_enabled="false"
-          android:state_focused="true"
-          android:drawable="@drawable/numberpicker_up_disabled_focused_holo_dark" />
-
-</selector>
diff --git a/core/res/res/drawable/numberpicker_up_btn_holo_light.xml b/core/res/res/drawable/numberpicker_up_btn_holo_light.xml
deleted file mode 100644
index cbcbb07..0000000
--- a/core/res/res/drawable/numberpicker_up_btn_holo_light.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item android:state_pressed="false"
-          android:state_enabled="true"
-          android:state_focused="false"
-          android:drawable="@drawable/numberpicker_up_normal_holo_light" />
-
-    <item android:state_pressed="true"
-          android:state_enabled="true"
-          android:drawable="@drawable/numberpicker_up_pressed_holo_light" />
-
-    <item android:state_pressed="false"
-          android:state_enabled="true"
-          android:state_focused="true"
-          android:drawable="@drawable/numberpicker_up_focused_holo_light" />
-
-    <item android:state_pressed="false"
-          android:state_enabled="false"
-          android:state_focused="false"
-          android:drawable="@drawable/numberpicker_up_focused_holo_light" />
-
-    <item android:state_pressed="false"
-          android:state_enabled="false"
-          android:state_focused="true"
-          android:drawable="@drawable/numberpicker_up_disabled_focused_holo_light" />
-
-</selector>
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_password_landscape.xml b/core/res/res/layout-sw600dp/keyguard_screen_password_landscape.xml
index 43d0a2c..2495b67 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_password_landscape.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_password_landscape.xml
@@ -153,13 +153,32 @@
         </LinearLayout>
 
         <!-- Area to overlay FaceLock -->
-        <View android:id="@+id/faceLockAreaView"
+        <RelativeLayout
+            android:id="@+id/faceLockAreaView"
             android:visibility="invisible"
             android:layout_width="512dip"
             android:layout_height="512dip"
             android:layout_centerInParent="true"
-            android:background="@color/facelock_color_background"
-        />
+            android:background="@drawable/intro_bg">
+
+            <View
+               android:id="@+id/spotlightMask"
+               android:layout_width="match_parent"
+               android:layout_height="match_parent"
+               android:background="@color/facelock_spotlight_mask"
+            />
+
+            <ImageView
+                android:id="@+id/cancel_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:padding="5dip"
+                android:layout_alignParentTop="true"
+                android:layout_alignParentRight="true"
+                android:src="@drawable/ic_facial_backup"
+            />
+
+        </RelativeLayout>
 
     </RelativeLayout>
 
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_password_portrait.xml b/core/res/res/layout-sw600dp/keyguard_screen_password_portrait.xml
index 8c02888..fb62eb6 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_password_portrait.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_password_portrait.xml
@@ -156,13 +156,32 @@
         </LinearLayout>
 
         <!-- Area to overlay FaceLock -->
-        <View android:id="@+id/faceLockAreaView"
+        <RelativeLayout
+            android:id="@+id/faceLockAreaView"
             android:visibility="invisible"
             android:layout_width="512dip"
             android:layout_height="512dip"
             android:layout_centerInParent="true"
-            android:background="@color/facelock_color_background"
-        />
+            android:background="@drawable/intro_bg">
+
+            <View
+               android:id="@+id/spotlightMask"
+               android:layout_width="match_parent"
+               android:layout_height="match_parent"
+               android:background="@color/facelock_spotlight_mask"
+            />
+
+            <ImageView
+                android:id="@+id/cancel_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:padding="5dip"
+                android:layout_alignParentTop="true"
+                android:layout_alignParentRight="true"
+                android:src="@drawable/ic_facial_backup"
+            />
+
+        </RelativeLayout>
 
     </RelativeLayout>
 
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_unlock_landscape.xml b/core/res/res/layout-sw600dp/keyguard_screen_unlock_landscape.xml
index 802ef82..53ecbac 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_unlock_landscape.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_unlock_landscape.xml
@@ -124,13 +124,32 @@
         </RelativeLayout>
 
         <!-- Area to overlay FaceLock -->
-        <View android:id="@+id/faceLockAreaView"
+        <RelativeLayout
+            android:id="@+id/faceLockAreaView"
             android:visibility="invisible"
             android:layout_width="512dip"
             android:layout_height="512dip"
             android:layout_centerInParent="true"
-            android:background="@color/facelock_color_background"
-        />
+            android:background="@drawable/intro_bg">
+
+            <View
+               android:id="@+id/spotlightMask"
+               android:layout_width="match_parent"
+               android:layout_height="match_parent"
+               android:background="@color/facelock_spotlight_mask"
+            />
+
+            <ImageView
+                android:id="@+id/cancel_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:padding="5dip"
+                android:layout_alignParentTop="true"
+                android:layout_alignParentRight="true"
+                android:src="@drawable/ic_facial_backup"
+            />
+
+        </RelativeLayout>
 
     </RelativeLayout>
 
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_unlock_portrait.xml b/core/res/res/layout-sw600dp/keyguard_screen_unlock_portrait.xml
index 40f2492..577668f 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_unlock_portrait.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_unlock_portrait.xml
@@ -119,13 +119,32 @@
         </RelativeLayout>
 
         <!-- Area to overlay FaceLock -->
-        <View android:id="@+id/faceLockAreaView"
+        <RelativeLayout
+            android:id="@+id/faceLockAreaView"
             android:visibility="invisible"
             android:layout_width="512dip"
             android:layout_height="512dip"
             android:layout_centerInParent="true"
-            android:background="@color/facelock_color_background"
-        />
+            android:background="@drawable/intro_bg">
+
+            <View
+               android:id="@+id/spotlightMask"
+               android:layout_width="match_parent"
+               android:layout_height="match_parent"
+               android:background="@color/facelock_spotlight_mask"
+            />
+
+            <ImageView
+                android:id="@+id/cancel_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:padding="5dip"
+                android:layout_alignParentTop="true"
+                android:layout_alignParentRight="true"
+                android:src="@drawable/ic_facial_backup"
+            />
+
+        </RelativeLayout>
 
     </RelativeLayout>
 
diff --git a/core/res/res/layout/keyguard_screen_password_landscape.xml b/core/res/res/layout/keyguard_screen_password_landscape.xml
index 66d7fd7..3bea16c 100644
--- a/core/res/res/layout/keyguard_screen_password_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_password_landscape.xml
@@ -208,7 +208,8 @@
         />
 
     <!-- Area to overlay FaceLock -->
-    <TextView android:id="@+id/faceLockAreaView"
+    <RelativeLayout
+        android:id="@+id/faceLockAreaView"
         android:visibility="invisible"
         android:layout_row="0"
         android:layout_column="2"
@@ -217,7 +218,25 @@
         android:layout_gravity="fill"
         android:layout_width="0dip"
         android:layout_height="0dip"
-        android:background="@color/facelock_color_background"
-    />
+        android:background="@drawable/intro_bg">
+
+        <View
+            android:id="@+id/spotlightMask"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="@color/facelock_spotlight_mask"
+        />
+
+        <ImageView
+            android:id="@+id/cancel_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="5dip"
+            android:layout_alignParentTop="true"
+            android:layout_alignParentRight="true"
+            android:src="@drawable/ic_facial_backup"
+        />
+
+    </RelativeLayout>
 
 </GridLayout>
diff --git a/core/res/res/layout/keyguard_screen_password_portrait.xml b/core/res/res/layout/keyguard_screen_password_portrait.xml
index 426b2f7..6b409aa 100644
--- a/core/res/res/layout/keyguard_screen_password_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_password_portrait.xml
@@ -199,7 +199,8 @@
         />
 
     <!-- Area to overlay FaceLock -->
-    <TextView android:id="@+id/faceLockAreaView"
+    <RelativeLayout
+        android:id="@+id/faceLockAreaView"
         android:visibility="invisible"
         android:layout_row="3"
         android:layout_column="0"
@@ -208,7 +209,25 @@
         android:layout_gravity="fill"
         android:layout_width="0dip"
         android:layout_height="0dip"
-        android:background="@color/facelock_color_background"
-    />
+        android:background="@drawable/intro_bg">
+
+        <View
+            android:id="@+id/spotlightMask"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="@color/facelock_spotlight_mask"
+        />
+
+        <ImageView
+            android:id="@+id/cancel_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="5dip"
+            android:layout_alignParentTop="true"
+            android:layout_alignParentRight="true"
+            android:src="@drawable/ic_facial_backup"
+        />
+
+     </RelativeLayout>
 
 </GridLayout>
diff --git a/core/res/res/layout/keyguard_screen_unlock_landscape.xml b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
index 1038657..7f9aaa0 100644
--- a/core/res/res/layout/keyguard_screen_unlock_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
@@ -161,7 +161,8 @@
         />
 
     <!-- Area to overlay FaceLock -->
-    <TextView android:id="@+id/faceLockAreaView"
+    <RelativeLayout
+        android:id="@+id/faceLockAreaView"
         android:visibility="invisible"
         android:layout_row="0"
         android:layout_column="1"
@@ -171,7 +172,25 @@
         android:layout_marginLeft="8dip"
         android:layout_width="0dip"
         android:layout_height="0dip"
-        android:background="@color/facelock_color_background"
-    />
+        android:background="@drawable/intro_bg">
+
+        <View
+            android:id="@+id/spotlightMask"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="@color/facelock_spotlight_mask"
+        />
+
+        <ImageView
+            android:id="@+id/cancel_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="5dip"
+            android:layout_alignParentTop="true"
+            android:layout_alignParentRight="true"
+            android:src="@drawable/ic_facial_backup"
+        />
+
+    </RelativeLayout>
 
 </GridLayout>
diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
index 336faa1..9a2e024 100644
--- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
@@ -170,7 +170,8 @@
         />
 
     <!-- Area to overlay FaceLock -->
-    <TextView android:id="@+id/faceLockAreaView"
+    <RelativeLayout
+        android:id="@+id/faceLockAreaView"
         android:visibility="invisible"
         android:layout_row="4"
         android:layout_column="0"
@@ -181,7 +182,25 @@
         android:layout_marginBottom="8dip"
         android:layout_width="0dip"
         android:layout_height="0dip"
-        android:background="@color/facelock_color_background"
-    />
+        android:background="@drawable/intro_bg">
+
+        <View
+            android:id="@+id/spotlightMask"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="@color/facelock_spotlight_mask"
+        />
+
+        <ImageView
+            android:id="@+id/cancel_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="5dip"
+            android:layout_alignParentTop="true"
+            android:layout_alignParentRight="true"
+            android:src="@drawable/ic_facial_backup"
+        />
+
+    </RelativeLayout>
 
 </GridLayout>
diff --git a/core/res/res/layout/notification_intruder_content.xml b/core/res/res/layout/notification_intruder_content.xml
new file mode 100644
index 0000000..7f37032
--- /dev/null
+++ b/core/res/res/layout/notification_intruder_content.xml
@@ -0,0 +1,74 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:padding="4dp"
+    >
+    <ImageView android:id="@+id/icon"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:scaleType="center"
+        android:padding="4dp"
+        />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="40dp"
+        android:layout_marginLeft="40dp"
+        android:orientation="vertical"
+        >
+        <TextView android:id="@+id/title"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            />
+        <TextView android:id="@+id/text"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:layout_marginTop="-4dp"
+            android:singleLine="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"
+            />
+    </LinearLayout>
+    <LinearLayout
+        android:id="@+id/actions"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="48dp"
+        android:orientation="horizontal"
+        android:visibility="gone"
+        >
+        <Button
+            style="?android:attr/buttonBarButtonStyle"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title"
+            android:id="@+id/action0"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:visibility="gone"
+            />
+        <Button
+            style="?android:attr/buttonBarButtonStyle"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title"
+            android:id="@+id/action1"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:visibility="gone"
+            />
+        <Button
+            style="?android:attr/buttonBarButtonStyle"
+            android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title"
+            android:id="@+id/action2"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:visibility="gone"
+            />
+    </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/number_picker.xml b/core/res/res/layout/number_picker.xml
index 2967696..b7e7ae1 100644
--- a/core/res/res/layout/number_picker.xml
+++ b/core/res/res/layout/number_picker.xml
@@ -22,19 +22,26 @@
     <ImageButton android:id="@+id/increment"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
-        style="?android:attr/numberPickerUpButtonStyle"
+        android:background="@android:drawable/numberpicker_up_btn"
+        android:paddingTop="22dip"
+        android:paddingBottom="22dip"
         android:contentDescription="@string/number_picker_increment_button" />
 
-    <view class="android.widget.NumberPicker$CustomEditText"
+    <EditText
         android:id="@+id/numberpicker_input"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
-        style="?android:attr/numberPickerInputTextStyle" />
+        android:textAppearance="@style/TextAppearance.Large.Inverse.NumberPickerInputText"
+        android:gravity="center"
+        android:singleLine="true"
+        android:background="@drawable/numberpicker_input" />
 
     <ImageButton android:id="@+id/decrement"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
-        style="?android:attr/numberPickerDownButtonStyle"
+        android:background="@android:drawable/numberpicker_down_btn"
+        android:paddingTop="22dip"
+        android:paddingBottom="22dip"
         android:contentDescription="@string/number_picker_decrement_button" />
 
 </merge>
diff --git a/core/res/res/layout/number_picker_with_selector_wheel.xml b/core/res/res/layout/number_picker_with_selector_wheel.xml
new file mode 100644
index 0000000..c8fa0f7
--- /dev/null
+++ b/core/res/res/layout/number_picker_with_selector_wheel.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <view class="android.widget.NumberPicker$CustomEditText"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:id="@+id/numberpicker_input"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:singleLine="true"
+        android:background="@null" />
+
+</merge>
diff --git a/core/res/res/layout/preference_child_holo.xml b/core/res/res/layout/preference_child_holo.xml
index 3c6a12e..4167862 100644
--- a/core/res/res/layout/preference_child_holo.xml
+++ b/core/res/res/layout/preference_child_holo.xml
@@ -60,7 +60,7 @@
             android:layout_alignLeft="@android:id/title"
             android:textAppearance="?android:attr/textAppearanceSmall"
             android:textColor="?android:attr/textColorSecondary"
-            android:maxLines="4" />
+            android:maxLines="10" />
 
     </RelativeLayout>
 
diff --git a/core/res/res/layout/preference_holo.xml b/core/res/res/layout/preference_holo.xml
index 48f483d..e574219 100644
--- a/core/res/res/layout/preference_holo.xml
+++ b/core/res/res/layout/preference_holo.xml
@@ -63,7 +63,7 @@
             android:layout_alignLeft="@android:id/title"
             android:textAppearance="?android:attr/textAppearanceSmall"
             android:textColor="?android:attr/textColorSecondary"
-            android:maxLines="4" />
+            android:maxLines="10" />
 
     </RelativeLayout>
 
diff --git a/core/res/res/layout/preference_information_holo.xml b/core/res/res/layout/preference_information_holo.xml
index e29ed03..5e03cfb 100644
--- a/core/res/res/layout/preference_information_holo.xml
+++ b/core/res/res/layout/preference_information_holo.xml
@@ -61,7 +61,7 @@
             android:layout_alignLeft="@android:id/title"
             android:textAppearance="?android:attr/textAppearanceSmall"
             android:textColor="?android:attr/textColorSecondary"
-            android:maxLines="2" />
+            android:maxLines="10" />
 
     </RelativeLayout>
 
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index fd9eff3..f020f5d 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -335,7 +335,7 @@
     <string name="permlab_writeContacts" msgid="644616215860933284">"skryf kontakdata"</string>
     <string name="permdesc_writeContacts" product="tablet" msgid="988969759110632978">"Laat die program toe om die kontakdata (adresse) wat op jou tablet gestoor is, te verander. Kwaadwillige programme kan dit dalk gebruik om jou kontakdata uit te vee of te verander."</string>
     <string name="permdesc_writeContacts" product="default" msgid="5075164818647934067">"Laat die program toe om die kontakdata (adresse) wat op jou foon gestoor is, te verander. Kwaadwillige programme kan dit dalk gebruik om jou kontakdata uit te vee of te verander."</string>
-    <string name="permlab_readCallLog" msgid="3478133184624102739">"Lees oproeprekord"</string>
+    <string name="permlab_readCallLog" msgid="3478133184624102739">"lees oproeprekord"</string>
     <string name="permdesc_readCallLog" product="tablet" msgid="3995157599976515002">"Laat die program toe om jou tablet se oproeprekord, insluitende data oor inkomende en uitgaande oproepe, te lees. Kwaadwillige programme kan dit gebruik om jou data na ander mense te stuur."</string>
     <string name="permdesc_readCallLog" product="default" msgid="3452017559804750758">"Laat die program toe om jou foon se oproeprekord, insluitende data oor inkomende en uitgaande oproepe, te lees. Kwaadwillige programme kan dit gebruik om jou data na ander mense te stuur."</string>
     <string name="permlab_writeCallLog" msgid="8552045664743499354">"skryf oproeprekord"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index f1379f7..0481a67 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -342,9 +342,9 @@
     <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"ስለ ገቢ እና ወጪ ጥሪዎችን ውሂብ ጨምሮ፣ የጡባዊተኮህን ምዝግብ ማስታወሻ ለመቀየር ለመተግበሪያው ይፈቅዳል። ይሄንን ተንኮል አዘል መተግበሪያዎች የስልክህን ምዝግብ ማስታወሻ ለመሰረዝ ወይም ለመለወጥ ሊጠቀሙበት ይችላሉ።"</string>
     <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"ስለ ገቢ እና ወጪ ጥሪዎችን ውሂብ ጨምሮ፣ የስልክህን ምዝግብ ማስታወሻ ለመቀየር ለመተግበሪያው ይፈቅዳል። ይሄንን ተንኮል አዘል መተግበሪያዎች የስልክህን ምዝግብ ማስታወሻ ለመሰረዝ ወይም ለመለወጥ ሊጠቀሙበት ይችላሉ።"</string>
     <string name="permlab_readProfile" msgid="6824681438529842282">"የመገለጫ ውሂብዎን ያንብቡ"</string>
-    <string name="permdesc_readProfile" product="default" msgid="94520753797630679">"ልክ እንደ አንተ ስም እና የዕውቂያ መረጃ ፣ ባንተ መሳሪያ ወስጥ የተከማቹ የግል መገለጫ መረጃ ለማንበብ ለመተግበሪያው ይፈቅዳሉ፡፡ይሄም ማለት ሌሎች መተግበሪያዎች ሊለዩህ ይችላሉ እና ለሌሎች የመገለጫ መረጃህን ይልካሉ፡፡"</string>
+    <string name="permdesc_readProfile" product="default" msgid="94520753797630679">"ልክ እንደ አንተ ስም እና የዕውቂያ መረጃ ፣ በአንተ መሳሪያ ወስጥ የተከማቹ የግል መገለጫ መረጃ ለማንበብ ለመተግበሪያው ይፈቅዳሉ፡፡ይሄም ማለት ሌሎች መተግበሪያዎች ሊለዩህ ይችላሉ እና ለሌሎች የመገለጫ መረጃህን ይልካሉ፡፡"</string>
     <string name="permlab_writeProfile" msgid="4679878325177177400">"የአርስዎ መገለጫ ውሂብ ላይ ይፃፉ"</string>
-    <string name="permdesc_writeProfile" product="default" msgid="4637366723793045603">"ልክ እንደ አንተ ስም እና የዕውቂያ መረጃ ፣ ባንተ መሳሪያ ወስጥ የተከማቹ የግል መገለጫ መረጃ ለመለወጥ ወይም ለማከል ለመተግበሪያው ይፈቅዳሉ፡፡ይሄም ማለት ሌሎች መተግበሪያዎች ሊለዩህ ይችላሉ እና ለሌሎች የመገለጫ መረጃህን ይልካሉ፡፡"</string>
+    <string name="permdesc_writeProfile" product="default" msgid="4637366723793045603">"ልክ እንደ አንተ ስም እና የዕውቂያ መረጃ ፣ በአንተ መሳሪያ ወስጥ የተከማቹ የግል መገለጫ መረጃ ለመለወጥ ወይም ለማከል ለመተግበሪያው ይፈቅዳሉ፡፡ይሄም ማለት ሌሎች መተግበሪያዎች ሊለዩህ ይችላሉ እና ለሌሎች የመገለጫ መረጃህን ይልካሉ፡፡"</string>
     <string name="permlab_readSocialStream" product="default" msgid="1268920956152419170">"የአንተን ማህበራዊ የውይይት ክፍሎች አንብብ"</string>
     <string name="permdesc_readSocialStream" product="default" msgid="3419050808547335320">" ከአንተ ጓደኞች ማህበራዊ ዝማኔዎችን እንዲደርስባቸው እና እንዲያመሳስል ለመተግበሪያውይፈቅዳሉ፡፡ ተንኮል አዘል መተግበሪያዎች ይህን መዳረሻ ባንተና በጓደኞችህ መካከል በማህበራዊ አውታረመረቦች ያሉ የግል ተግባቦቶችን ለመዳረስ ሊጠቀሙበት ይችላሉ፡፡"</string>
     <string name="permlab_writeSocialStream" product="default" msgid="3504179222493235645">"ወደ የአንተ  ማህበራዊ የውይይት ክፍሎች ጻፍ"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 8125ee3..d2a4cc1 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -916,8 +916,8 @@
     <string name="no" msgid="5141531044935541497">"Zrušit"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Upozornění"</string>
     <string name="loading" msgid="7933681260296021180">"Načítání..."</string>
-    <string name="capital_on" msgid="1544682755514494298">"ZAPNUTO"</string>
-    <string name="capital_off" msgid="6815870386972805832">"VYPNUTO"</string>
+    <string name="capital_on" msgid="1544682755514494298">"I"</string>
+    <string name="capital_off" msgid="6815870386972805832">"O"</string>
     <string name="whichApplication" msgid="4533185947064773386">"Dokončit akci pomocí aplikace"</string>
     <string name="alwaysUse" msgid="4583018368000610438">"Použít jako výchozí nastavení pro tuto činnost."</string>
     <string name="clearDefaultHintMsg" msgid="3252584689512077257">"Výchozí nastavení vymažete v části Nastavení systému &gt; Aplikace &gt; Stažené."</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 0b14edb..b139e5e 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -507,8 +507,8 @@
     <string name="permdesc_writeDictionary" msgid="8185385716255065291">"Tillader, at appen kan skrive nye ord i brugerordbogen."</string>
     <string name="permlab_sdcardRead" product="nosdcard" msgid="4086221374639183281">"læse USB-lagerets indhold"</string>
     <string name="permlab_sdcardRead" product="default" msgid="8537875151845139539">"læse indholdet af SD-kortet"</string>
-    <string name="permdesc_sdcardRead" product="nosdcard" msgid="1055302898999352339">"Tillader, at app\'en læser indhold på USB-lager."</string>
-    <string name="permdesc_sdcardRead" product="default" msgid="7947792373570683542">"Tillader, at app\'en læser indholdet af SD-kortet."</string>
+    <string name="permdesc_sdcardRead" product="nosdcard" msgid="1055302898999352339">"Tillader, at appen læser indhold på USB-lageret."</string>
+    <string name="permdesc_sdcardRead" product="default" msgid="7947792373570683542">"Tillader, at appen læser indholdet af SD-kortet."</string>
     <string name="permlab_sdcardWrite" product="nosdcard" msgid="85430876310764752">"rette/slette i USB-lager"</string>
     <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"ret/slet indholdet på SD-kortet"</string>
     <string name="permdesc_sdcardWrite" product="nosdcard" msgid="6175406299445710888">"Lader appen skrive til USB."</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 5ef9483a..8761ca9 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -336,11 +336,11 @@
     <string name="permdesc_writeContacts" product="tablet" msgid="988969759110632978">"Permite que la aplicación modifique la información de contacto (dirección) almacenada en tu tableta. Las aplicaciones maliciosas pueden utilizar este permiso para borrar o modificar tu información de contacto."</string>
     <string name="permdesc_writeContacts" product="default" msgid="5075164818647934067">"Permite que la aplicación modifique la información de contacto (dirección) almacenada en tu dispositivo. Las aplicaciones maliciosas pueden utilizar este permiso para borrar o modificar tu información de contacto."</string>
     <string name="permlab_readCallLog" msgid="3478133184624102739">"leer el registro de llamadas"</string>
-    <string name="permdesc_readCallLog" product="tablet" msgid="3995157599976515002">"Permite que la aplicación lea el registro de llamadas del tablet, incluidos datos sobre llamadas entrantes y salientes. Las aplicaciones malintencionadas pueden usar este permiso para enviar tus datos a otros usuarios."</string>
-    <string name="permdesc_readCallLog" product="default" msgid="3452017559804750758">"Permite que la aplicación lea el registro de llamadas del teléfono, incluidos datos sobre llamadas entrantes y salientes. Las aplicaciones malintencionadas pueden usar este permiso para enviar tus datos a otros usuarios."</string>
+    <string name="permdesc_readCallLog" product="tablet" msgid="3995157599976515002">"Permite que la aplicación lea el registro de llamadas de la tableta, incluidos los datos sobre llamadas entrantes y salientes. Las aplicaciones malintencionadas pueden usar este permiso para enviar tus datos a otros usuarios."</string>
+    <string name="permdesc_readCallLog" product="default" msgid="3452017559804750758">"Permite que la aplicación lea el registro de llamadas del dispositivo, incluidos los datos sobre llamadas entrantes y salientes. Las aplicaciones malintencionadas pueden usar este permiso para enviar tus datos a otros usuarios."</string>
     <string name="permlab_writeCallLog" msgid="8552045664743499354">"escribir en el registro de llamadas"</string>
-    <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"Permite que la aplicación modifique el registro de llamadas del tablet, incluidos datos sobre llamadas entrantes y salientes. Las aplicaciones malintencionadas pueden usar este permiso para borrar o modificar el registro de llamadas."</string>
-    <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"Permite que la aplicación modifique el registro de llamadas del teléfono, incluidos datos sobre llamadas entrantes y salientes. Las aplicaciones malintencionadas pueden usar este permiso para borrar o modificar el registro de llamadas."</string>
+    <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"Permite que la aplicación modifique el registro de llamadas de la tableta, incluidos los datos sobre llamadas entrantes y salientes. Las aplicaciones malintencionadas pueden usar este permiso para borrar o modificar el registro de llamadas."</string>
+    <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"Permite que la aplicación modifique el registro de llamadas del dispositivo, incluidos los datos sobre llamadas entrantes y salientes. Las aplicaciones malintencionadas pueden usar este permiso para borrar o modificar el registro de llamadas."</string>
     <string name="permlab_readProfile" msgid="6824681438529842282">"Leer tus datos de perfil"</string>
     <string name="permdesc_readProfile" product="default" msgid="94520753797630679">"Permite que la aplicación lea la información de perfil almacenada en tu dispositivo, como tu nombre e información de contacto. Esto significa que la aplicación puede identificarte y enviar tu información de perfil a otros."</string>
     <string name="permlab_writeProfile" msgid="4679878325177177400">"Escrib. en datos de tu perfil"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 5a536e6..7ee47e7 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -338,7 +338,7 @@
     <string name="permlab_readCallLog" msgid="3478133184624102739">"خواندن گزارش تماس"</string>
     <string name="permdesc_readCallLog" product="tablet" msgid="3995157599976515002">"به برنامه اجازه می‌دهد گزارشات تماس رایانه لوحی شما، از جمله داده‌هایی درمورد تماس‎های ورودی و خروجی را بخواند. برنامه‌های مخرب ممکن است از این ویژگی برای ارسال داده‌های شما به دیگران استفاده کنند."</string>
     <string name="permdesc_readCallLog" product="default" msgid="3452017559804750758">"به برنامه اجازه می‌دهد گزارشات تماس تلفنی شما، از جمله داده‌هایی درمورد تماس‎های ورودی و خروجی را بخواند. برنامه‌های مخرب ممکن است از این ویژگی برای ارسال داده‌های شما به دیگران استفاده کنند."</string>
-    <string name="permlab_writeCallLog" msgid="8552045664743499354">"نوشتن در گزارش تماس"</string>
+    <string name="permlab_writeCallLog" msgid="8552045664743499354">"نوشتن گزارش تماس"</string>
     <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"به برنامه اجازه می‌دهد گزارشات تماس رایانه لوحی شما، از جمله داده‌هایی درمورد تماس‎های ورودی و خروجی را تغییر دهد. برنامه‌های مخرب ممکن است از این ویژگی برای پاک کردن یا تغییر گزارش تماس شما استفاده کنند."</string>
     <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"به برنامه اجازه می‌دهد گزارشات تماس تلفنی شما، از جمله داده‌هایی درمورد تماس‎های ورودی و خروجی را تغییر دهد. برنامه‌های مخرب ممکن است از این ویژگی برای پاک کردن یا تغییر گزارش تماس شما استفاده کنند."</string>
     <string name="permlab_readProfile" msgid="6824681438529842282">"خواندن داده‌های نمایه شما"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 88fe721..d044a70 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -507,7 +507,7 @@
     <string name="permdesc_writeDictionary" msgid="8185385716255065291">"Permet à l\'application d\'enregistrer de nouveaux mots dans le dictionnaire personnel de l\'utilisateur."</string>
     <string name="permlab_sdcardRead" product="nosdcard" msgid="4086221374639183281">"lire contenu de la mémoire USB"</string>
     <string name="permlab_sdcardRead" product="default" msgid="8537875151845139539">"lire le contenu de la carte SD"</string>
-    <string name="permdesc_sdcardRead" product="nosdcard" msgid="1055302898999352339">"Permet à l\'appli de lire contenu mémoire USB."</string>
+    <string name="permdesc_sdcardRead" product="nosdcard" msgid="1055302898999352339">"Permet à l\'appli de lire contenu de mémoire USB."</string>
     <string name="permdesc_sdcardRead" product="default" msgid="7947792373570683542">"Permet à l\'application de lire le contenu de la carte SD."</string>
     <string name="permlab_sdcardWrite" product="nosdcard" msgid="85430876310764752">"Modifier/Supprimer contenu mémoire USB"</string>
     <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"modifier/supprimer le contenu de la carte SD"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index d2b4f04..55a298e 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -108,7 +108,7 @@
     <string name="fcComplete" msgid="3118848230966886575">"Kode fitur selesai."</string>
     <string name="fcError" msgid="3327560126588500777">"Masalah sambungan atau kode fitur tidak valid."</string>
     <string name="httpErrorOk" msgid="1191919378083472204">"OK"</string>
-    <string name="httpError" msgid="7956392511146698522">"Terjadi galat jaringan."</string>
+    <string name="httpError" msgid="7956392511146698522">"Terjadi kesalahan jaringan."</string>
     <string name="httpErrorLookup" msgid="4711687456111963163">"Tidak dapat menemukan URL."</string>
     <string name="httpErrorUnsupportedAuthScheme" msgid="6299980280442076799">"Skema autentikasi situs tidak didukung."</string>
     <string name="httpErrorAuth" msgid="1435065629438044534">"Tidak dapat mengautentikasi."</string>
@@ -123,7 +123,7 @@
     <string name="httpErrorFile" msgid="2170788515052558676">"Tidak dapat mengakses file."</string>
     <string name="httpErrorFileNotFound" msgid="6203856612042655084">"Tidak dapat menemukan file yang diminta."</string>
     <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Terlalu banyak permintaan yang diproses. Coba lagi nanti."</string>
-    <string name="notification_title" msgid="8967710025036163822">"Galat saat masuk untuk <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
+    <string name="notification_title" msgid="8967710025036163822">"Kesalahan saat masuk untuk <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
     <string name="contentServiceSync" msgid="8353523060269335667">"Sinkron"</string>
     <string name="contentServiceSyncNotificationTitle" msgid="397743349191901458">"Sinkron"</string>
     <string name="contentServiceTooManyDeletesNotificationDesc" msgid="8100981435080696431">"Terlalu banyak <xliff:g id="CONTENT_TYPE">%s</xliff:g> penghapusan."</string>
@@ -396,7 +396,7 @@
     <string name="permdesc_asec_destroy" msgid="7218749286145526537">"Mengizinkan apl merusak penyimpanan internal."</string>
     <string name="permlab_asec_mount_unmount" msgid="8877998101944999386">"memasang/melepas penyimpanan internal"</string>
     <string name="permdesc_asec_mount_unmount" msgid="3451360114902490929">"Mengizinkan apl memasang/melepas penyimpanan internal."</string>
-    <string name="permlab_asec_rename" msgid="7496633954080472417">"ubah nama penyimpanan internal"</string>
+    <string name="permlab_asec_rename" msgid="7496633954080472417">"ganti nama penyimpanan internal"</string>
     <string name="permdesc_asec_rename" msgid="1794757588472127675">"Mengizinkan apl mengganti nama penyimpanan internal."</string>
     <string name="permlab_vibrate" msgid="7768356019980849603">"mengontrol penggetar"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"Mengizinkan aplikasi untuk mengendalikan vibrator."</string>
@@ -1061,7 +1061,7 @@
     <string name="candidates_style" msgid="4333913089637062257"><u>"calon"</u></string>
     <string name="ext_media_checking_notification_title" product="nosdcard" msgid="3449816005351468560">"Menyiapkan penyimpanan USB"</string>
     <string name="ext_media_checking_notification_title" product="default" msgid="5457603418970994050">"Menyiapkan kartu SD"</string>
-    <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Memeriksa galat."</string>
+    <string name="ext_media_checking_notification_message" msgid="8287319882926737053">"Memeriksa kesalahan."</string>
     <string name="ext_media_nofs_notification_title" product="nosdcard" msgid="7788040745686229307">"Penyimpanan USB kosong"</string>
     <string name="ext_media_nofs_notification_title" product="default" msgid="780477838241212997">"Kartu SD kosong"</string>
     <string name="ext_media_nofs_notification_message" product="nosdcard" msgid="7840121067427269500">"Penyimpanan USB kosong atau sistem berkasnya tidak didukung."</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 40a4d01..f51f232 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -130,7 +130,7 @@
     <string name="low_memory" product="tablet" msgid="6494019234102154896">"שטח האחסון של הטבלט מלא. מחק קבצים כדי לפנות מקום."</string>
     <string name="low_memory" product="default" msgid="3475999286680000541">"שטח האחסון של הטלפון מלא. מחק חלק מהקבצים כדי לפנות שטח."</string>
     <string name="me" msgid="6545696007631404292">"אני"</string>
-    <string name="power_dialog" product="tablet" msgid="8545351420865202853">"אפשרויות טבלט"</string>
+    <string name="power_dialog" product="tablet" msgid="8545351420865202853">"אפשרויות טאבלט"</string>
     <string name="power_dialog" product="default" msgid="1319919075463988638">"אפשרויות טלפון"</string>
     <string name="silent_mode" msgid="7167703389802618663">"מצב שקט"</string>
     <string name="turn_on_radio" msgid="3912793092339962371">"הפעל חיבור אלחוטי"</string>
@@ -146,7 +146,7 @@
     <string name="shutdown_confirm_question" msgid="2906544768881136183">"האם ברצונך לבצע כיבוי?"</string>
     <string name="recent_tasks_title" msgid="3691764623638127888">"נוצרו לאחרונה"</string>
     <string name="no_recent_tasks" msgid="8794906658732193473">"אין יישומים אחרונים"</string>
-    <string name="global_actions" product="tablet" msgid="408477140088053665">"אפשרויות טבלט"</string>
+    <string name="global_actions" product="tablet" msgid="408477140088053665">"אפשרויות טאבלט"</string>
     <string name="global_actions" product="default" msgid="2406416831541615258">"אפשרויות טלפון"</string>
     <string name="global_action_lock" msgid="2844945191792119712">"נעילת מסך"</string>
     <string name="global_action_power_off" msgid="4471879440839879722">"כיבוי"</string>
@@ -376,7 +376,7 @@
     <string name="permdesc_recordAudio" msgid="2387462233976248635">"מאפשר ליישום לגשת לנתיב להקלטת אודיו."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"צלם תמונות וסרטונים"</string>
     <string name="permdesc_camera" msgid="1507407407002492176">"מאפשר ליישום לצלם תמונות וסרטוני וידאו עם המצלמה. הגדרה זו מאפשרת ליישום לאסוף תמונות שהמצלמה קולטת בכל עת."</string>
-    <string name="permlab_brick" product="tablet" msgid="2961292205764488304">"השבת טבלט לצמיתות"</string>
+    <string name="permlab_brick" product="tablet" msgid="2961292205764488304">"השבת טאבלט לצמיתות"</string>
     <string name="permlab_brick" product="default" msgid="8337817093326370537">"השבת טלפון לצמיתות"</string>
     <string name="permdesc_brick" product="tablet" msgid="4334818808001699530">"מאפשר ליישום להשבית את הטבלט כולו לצמיתות. זו הרשאה מסוכנת מאוד."</string>
     <string name="permdesc_brick" product="default" msgid="5788903297627283099">"מאפשר ליישום להשבית את הטלפון כולו לצמיתות. זו הרשאה מסוכנת מאוד."</string>
@@ -412,7 +412,7 @@
     <string name="permdesc_callPhone" msgid="6396463004110544744">"מאפשר ליישום להתקשר למספרי טלפון ללא התערבותך. יישומים זדוניים עלולים לגרום לחיובי שיחות בלתי צפויים בחשבון הטלפון שלך. לתשומת לבך, הרשאה זו לא מאפשרת ליישום להתקשר למספרי חירום."</string>
     <string name="permlab_callPrivileged" msgid="4198349211108497879">"התקשר ישירות לכל מספר טלפון"</string>
     <string name="permdesc_callPrivileged" msgid="1689024901509996810">"מאפשר ליישום להתקשר לכל מספר טלפון שהוא, כולל מספרי חירום, ללא התערבותך. יישומים זדוניים עלולים לבצע שיחות מיותרות ולא חוקיות לשירותי חירום."</string>
-    <string name="permlab_performCdmaProvisioning" product="tablet" msgid="4842576994144604821">"הפעל ישירות התקנת טבלט מסוג CDMA"</string>
+    <string name="permlab_performCdmaProvisioning" product="tablet" msgid="4842576994144604821">"הפעל ישירות התקנת טאבלט מסוג CDMA"</string>
     <string name="permlab_performCdmaProvisioning" product="default" msgid="5604848095315421425">"הפעל ישירות הגדרה של טלפון CDMA"</string>
     <string name="permdesc_performCdmaProvisioning" msgid="1994193538802314186">"מאפשר ליישום להפעיל הקצאת CDMA. יישומים זדוניים עלולים להפעיל הקצאת CDMA ללא צורך."</string>
     <string name="permlab_locationUpdates" msgid="7785408253364335740">"שלוט בהתראות על עדכון מיקום"</string>
@@ -429,7 +429,7 @@
     <string name="permlab_wakeLock" product="default" msgid="573480187941496130">"מניעת מעבר הטלפון למצב שינה"</string>
     <string name="permdesc_wakeLock" product="tablet" msgid="7311319824400447868">"מאפשר ליישום למנוע מהטבלט לעבור למצב שינה."</string>
     <string name="permdesc_wakeLock" product="default" msgid="8559100677372928754">"מאפשר ליישום למנוע מהטלפון לעבור למצב שינה."</string>
-    <string name="permlab_devicePower" product="tablet" msgid="2787034722616350417">"הפעלה או כיבוי של טבלט"</string>
+    <string name="permlab_devicePower" product="tablet" msgid="2787034722616350417">"הפעלה או כיבוי של טאבלט"</string>
     <string name="permlab_devicePower" product="default" msgid="4928622470980943206">"הפעל או כבה את הטלפון"</string>
     <string name="permdesc_devicePower" product="tablet" msgid="6689862878984631831">"מאפשר ליישום להפעיל או לכבות את הטבלט."</string>
     <string name="permdesc_devicePower" product="default" msgid="6037057348463131032">"מאפשר ליישום להפעיל או לכבות את הטלפון."</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 87f8f8d..363832d 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -336,11 +336,11 @@
     <string name="permdesc_writeContacts" product="tablet" msgid="988969759110632978">"Membenarkan apl untuk mengubah suai data kenalan (alamat) yang disimpan pada tablet anda. Apl hasad boleh menggunakannya untuk memadam atau mengubah suai data kenalan anda."</string>
     <string name="permdesc_writeContacts" product="default" msgid="5075164818647934067">"Membenarkan apl untuk mengubah suai data kenalan (alamat) yang disimpan pada telefon anda. Apl hasad boleh menggunakannya untuk memadam atau mengubah suai data kenalan anda."</string>
     <string name="permlab_readCallLog" msgid="3478133184624102739">"baca log panggilan"</string>
-    <string name="permdesc_readCallLog" product="tablet" msgid="3995157599976515002">"Membenarkan apl membaca log panggilan tablet anda, termasuk data tentang panggilan masuk dan keluar. Apl hasad boleh menggunakan ini untuk menghantar data anda kepada orang lain."</string>
-    <string name="permdesc_readCallLog" product="default" msgid="3452017559804750758">"Membenarkan apl membaca log panggilan telefon anda, termasuk data tentang panggilan masuk dan keluar. Apl hasad boleh menggunakan ini untuk menghantar data anda kepada orang lain."</string>
+    <string name="permdesc_readCallLog" product="tablet" msgid="3995157599976515002">"Membenarkan apl membaca log panggilan tablet anda, termasuk data tentang panggilan masuk dan keluar. Apl hasad boleh menggunakannya untuk menghantar data anda kepada orang lain."</string>
+    <string name="permdesc_readCallLog" product="default" msgid="3452017559804750758">"Membenarkan apl membaca log panggilan telefon anda, termasuk data tentang panggilan masuk dan keluar. Apl hasad boleh menggunakannya untuk menghantar data anda kepada orang lain."</string>
     <string name="permlab_writeCallLog" msgid="8552045664743499354">"tulis log panggilan"</string>
-    <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"Membenarkan apl untuk mengubah suai panggilan tablet anda, termasuk data tentang panggilan masuk dan keluar. Apl hasad boleh menggunakan ini untuk memadam atau mengubah suai log panggilan anda."</string>
-    <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"Membenarkan apl untuk mengubah suai panggilan telefon anda, termasuk data tentang panggilan masuk dan keluar. Apl hasad boleh menggunakan ini untuk memadam atau mengubah suai log panggilan anda."</string>
+    <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"Membenarkan apl untuk mengubah suai panggilan tablet anda, termasuk data tentang panggilan masuk dan keluar. Apl hasad boleh menggunakannya untuk memadam atau mengubah suai log panggilan anda."</string>
+    <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"Membenarkan apl untuk mengubah suai panggilan telefon anda, termasuk data tentang panggilan masuk dan keluar. Apl hasad boleh menggunakannya untuk memadam atau mengubah suai log panggilan anda."</string>
     <string name="permlab_readProfile" msgid="6824681438529842282">"baca data profil anda"</string>
     <string name="permdesc_readProfile" product="default" msgid="94520753797630679">"Membenarkan apl untuk membaca maklumat profil peribadi yang disimpan dalam peranti anda, seperti nama dan maklumat kenalan anda. Ini bermakna apl lain boleh mengenal pasti anda dan menghantar maklumat profil anda kepada orang lain."</string>
     <string name="permlab_writeProfile" msgid="4679878325177177400">"tulis ke data profil anda"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index fec439d..384fdac 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -507,7 +507,7 @@
     <string name="permdesc_writeDictionary" msgid="8185385716255065291">"Lar appen skrive nye ord i brukerordlisten."</string>
     <string name="permlab_sdcardRead" product="nosdcard" msgid="4086221374639183281">"lese innhold på USB-lagr."</string>
     <string name="permlab_sdcardRead" product="default" msgid="8537875151845139539">"lese innhold på SD-kortet"</string>
-    <string name="permdesc_sdcardRead" product="nosdcard" msgid="1055302898999352339">"Lar appen lese innhold på USB-lagringsenheten."</string>
+    <string name="permdesc_sdcardRead" product="nosdcard" msgid="1055302898999352339">"Lar appen lese innhold på USB-lager."</string>
     <string name="permdesc_sdcardRead" product="default" msgid="7947792373570683542">"Lar appen lese innhold på SD-kortet."</string>
     <string name="permlab_sdcardWrite" product="nosdcard" msgid="85430876310764752">"endre/slette innh. i USB-lagr."</string>
     <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"redigere/slette innhold på minnekort"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index eee0d80..6559fc5 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -743,8 +743,7 @@
     <string name="double_tap_toast" msgid="4595046515400268881">"Tips! Dubbelknacka om du vill zooma in eller ut."</string>
     <string name="autofill_this_form" msgid="4616758841157816676">"Autofyll"</string>
     <string name="setup_autofill" msgid="7103495070180590814">"Ange Autofyll"</string>
-    <!-- no translation found for autofill_address_name_separator (6350145154779706772) -->
-    <skip />
+    <string name="autofill_address_name_separator" msgid="6350145154779706772">" "</string>
     <string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
     <string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
     <string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 3b870cf..39d2aba 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -335,12 +335,12 @@
     <string name="permlab_writeContacts" msgid="644616215860933284">"andika data ya anwani"</string>
     <string name="permdesc_writeContacts" product="tablet" msgid="988969759110632978">"Inaruhusu programu kurekebisha data ya mwasiliani(anwani) iliyohifadhiwa kwenye kompyuta yako ki. programu hasidi zinaweza tumia hii kufuta au kurekebisha data yako ya mwasiliani."</string>
     <string name="permdesc_writeContacts" product="default" msgid="5075164818647934067">"Inaruhusu programu kurekebisha data ya mwasiliani(anwani) iliyohifadhiwa kwenye simu yako. Programu hasidi zinaweza kutumia hii kufuta au kurekebisha data yako ya mwasiliani."</string>
-    <string name="permlab_readCallLog" msgid="3478133184624102739">"soma kumbukumbu ya simu"</string>
-    <string name="permdesc_readCallLog" product="tablet" msgid="3995157599976515002">"Huruhusu programu kusoma kumbukumbu ya kompyuta kibao yako, ikiwa ni pamoja na simu zinazoingia na kutoka. Huenda programu hasidi zikatumia hii ili kutuma data yako kwa watu wengine."</string>
-    <string name="permdesc_readCallLog" product="default" msgid="3452017559804750758">"Huruhusu programu kusoma kumbukumbu ya simu yako, ikiwa ni pamoja na simu zinazoingia na kutoka. Huenda programu hasidi zikatumia hii ili kutuma data yako kwa watu wengine."</string>
-    <string name="permlab_writeCallLog" msgid="8552045664743499354">"andika kumbukumbu ya simu"</string>
-    <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"Huruhusu programu kusoma kumbukumbu ya kompyuta kibao yako, ikiwa ni pamoja na simu zinazoingia na kutoka. Huenda programu hasidi zikatumia hii ili kufuta au kurekebisha kumbukumbu ya simu yako."</string>
-    <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"Huruhusu programu kusoma kumbukumbu ya simu yako, ikiwa ni pamoja na simu zinazoingia na kutoka. Huenda programu hasidi zikatumia hii ili kufuta au kurekebisha kumbukumbu ya simu yako."</string>
+    <string name="permlab_readCallLog" msgid="3478133184624102739">"soma rajisi ya simu"</string>
+    <string name="permdesc_readCallLog" product="tablet" msgid="3995157599976515002">"Huruhusu programu kusoma rajisi ya simu ya kompyuta kibao yako, ikiwa ni pamoja na simu zinazoingia na kutoka. Huenda programu hasidi zikatumia hii ili kutuma data yako kwa watu wengine."</string>
+    <string name="permdesc_readCallLog" product="default" msgid="3452017559804750758">"Huruhusu programu kusoma rajisi ya simu yako, ikiwa ni pamoja na data kuhusu simu zinazoingia na kutoka. Huenda programu hasidi zikatumia hii ili kutuma data yako kwa watu wengine."</string>
+    <string name="permlab_writeCallLog" msgid="8552045664743499354">"andika rajisi ya simu"</string>
+    <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"Huruhusu programu kurekebisha rajisi ya kompyuta kibao yako, ikiwa ni pamoja na simu zinazoingia na kutoka. Huenda programu hasidi zikatumia hii ili kufuta au kurekebisha rajisi ya simu yako."</string>
+    <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"Huruhusu programu kurekebisha rajisi ya simu yako, ikiwa ni pamoja na simu zinazoingia na kutoka. Huenda programu hasidi zikatumia hii ili kufuta au kurekebisha rajisi ya simu yako."</string>
     <string name="permlab_readProfile" msgid="6824681438529842282">"soma data ya maelezo yako mafupi"</string>
     <string name="permdesc_readProfile" product="default" msgid="94520753797630679">"Inaruhusu programu kusoma maelezo mafupi ya kibinafsi yaliyohifadhiwa kwenye kifaa chako, kama vile jina lako na taarifa ya kuwasiliana. Hii ina maanisha programu inaweza kukutambua na kutuma taarifa yako fupi ya kibinafsi kwa wengine."</string>
     <string name="permlab_writeProfile" msgid="4679878325177177400">"andika kwenye data ya maelezo yako mafupi"</string>
diff --git a/core/res/res/values-sw600dp/config.xml b/core/res/res/values-sw600dp/config.xml
index 7fa7658..49c8893 100644
--- a/core/res/res/values-sw600dp/config.xml
+++ b/core/res/res/values-sw600dp/config.xml
@@ -21,7 +21,7 @@
      for different hardware and product builds. -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- see comment in values/config.xml -->
-    <integer name="config_longPressOnPowerBehavior">2</integer>
+    <integer name="config_longPressOnPowerBehavior">1</integer>
 
     <!-- Enable lockscreen rotation -->
     <bool name="config_enableLockScreenRotation">true</bool>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 0542f8e..4b59923 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -336,11 +336,11 @@
     <string name="permdesc_writeContacts" product="tablet" msgid="988969759110632978">"อนุญาตให้แอปพลิเคชันแก้ไขข้อมูลการติดต่อ (ที่อยู่) ที่เก็บไว้ในแท็บเล็ตของคุณ แอปพลิเคชันที่เป็นอันตรายอาจใช้การอนุญาตนี้ลบหรือแก้ไขข้อมูลการติดต่อของคุณ"</string>
     <string name="permdesc_writeContacts" product="default" msgid="5075164818647934067">"อนุญาตให้แอปพลิเคชันแก้ไขข้อมูลการติดต่อ (ที่อยู่) ที่เก็บไว้ในโทรศัพท์ของคุณ แอปพลิเคชันที่เป็นอันตรายอาจใช้การอนุญาตนี้ลบหรือแก้ไขข้อมูลการติดต่อของคุณ"</string>
     <string name="permlab_readCallLog" msgid="3478133184624102739">"อ่านประวัติการโทร"</string>
-    <string name="permdesc_readCallLog" product="tablet" msgid="3995157599976515002">"อนุญาตให้แอปพลิเคชันอ่านประวัติการโทรของแท็บเล็ตของคุณ รวมถึงข้อมูลเกี่ยวกับสายเรียกเข้าและการโทรออก แอปพลิเคชันที่เป็นอันตรายอาจใช้สิ่งนี้เพื่อส่งข้อมูลของคุณให้กับบุคคลอื่น"</string>
-    <string name="permdesc_readCallLog" product="default" msgid="3452017559804750758">"อนุญาตให้แอปพลิเคชันอ่านประวัติการโทรของโทรศัพท์ของคุณ รวมถึงข้อมูลเกี่ยวกับสายเรียกเข้าและการโทรออก แอปพลิเคชันที่เป็นอันตรายอาจใช้สิ่งนี้เพื่อส่งข้อมูลของคุณให้กับบุคคลอื่น"</string>
+    <string name="permdesc_readCallLog" product="tablet" msgid="3995157599976515002">"อนุญาตให้แอปอ่านประวัติการโทรจากแท็บเล็ตของคุณ รวมถึงข้อมูลเกี่ยวกับสายเรียกเข้าและการโทรออก แอปที่เป็นอันตรายอาจใช้สิ่งนี้เพื่อส่งข้อมูลของคุณให้กับบุคคลอื่น"</string>
+    <string name="permdesc_readCallLog" product="default" msgid="3452017559804750758">"อนุญาตให้แอปอ่านประวัติการโทรจากโทรศัพท์ของคุณ รวมถึงข้อมูลเกี่ยวกับสายเรียกเข้าและการโทรออก แอปที่เป็นอันตรายอาจใช้สิ่งนี้เพื่อส่งข้อมูลของคุณให้กับบุคคลอื่น"</string>
     <string name="permlab_writeCallLog" msgid="8552045664743499354">"เขียนประวัติการโทร"</string>
-    <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"อนุญาตให้แอปพลิเคชันแก้ไขประวัติการโทรของแท็บเล็ตของคุณ รวมถึงข้อมูลเกี่ยวกับสายเรียกเข้าและการโทรออก แอปพลิเคชันที่เป็นอันตรายอาจใช้สิ่งนี้เพื่อลบหรือแก้ไขประวัติการโทรของคุณ"</string>
-    <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"อนุญาตให้แอปพลิเคชันแก้ไขประวัติการโทรของโทรศัพท์ของคุณ รวมถึงข้อมูลเกี่ยวกับสายเรียกเข้าและการโทรออก แอปพลิเคชันที่เป็นอันตรายอาจใช้สิ่งนี้เพื่อลบหรือแก้ไขประวัติการโทรของคุณ"</string>
+    <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"อนุญาตให้แอปแก้ไขประวัติการโทรจากแท็บเล็ตของคุณ รวมถึงข้อมูลเกี่ยวกับสายเรียกเข้าและการโทรออก แอปที่เป็นอันตรายอาจใช้สิ่งนี้เพื่อลบหรือแก้ไขประวัติการโทรของคุณ"</string>
+    <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"อนุญาตให้แอปแก้ไขประวัติการโทรจากโทรศัพท์ของคุณ รวมถึงข้อมูลเกี่ยวกับสายเรียกเข้าและการโทรออก แอปที่เป็นอันตรายอาจใช้สิ่งนี้เพื่อลบหรือแก้ไขประวัติการโทรของคุณ"</string>
     <string name="permlab_readProfile" msgid="6824681438529842282">"อ่านข้อมูลโปรไฟล์ของคุณ"</string>
     <string name="permdesc_readProfile" product="default" msgid="94520753797630679">"อนุญาตให้แอปพลิเคชันอ่านข้อมูลโปรไฟล์ส่วนบุคคลที่เก็บไว้บนอุปกรณ์ของคุณ เช่น ชื่อและข้อมูลติดต่อ ซึ่งหมายความว่าแอปพลิเคชันจะสามารถระบุตัวตนของคุณและส่งข้อมูลโปรไฟล์ของคุณแก่ผู้อื่นได้"</string>
     <string name="permlab_writeProfile" msgid="4679878325177177400">"เขียนลงในข้อมูลโปรไฟล์ของคุณ"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 81d0ec6..d7a1e43 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -145,7 +145,7 @@
     <string name="shutdown_confirm" product="default" msgid="649792175242821353">"您的手机会关机。"</string>
     <string name="shutdown_confirm_question" msgid="2906544768881136183">"您要关机吗?"</string>
     <string name="recent_tasks_title" msgid="3691764623638127888">"近期任务"</string>
-    <string name="no_recent_tasks" msgid="8794906658732193473">"最近没有运行任何应用程序"</string>
+    <string name="no_recent_tasks" msgid="8794906658732193473">"最近没有运行任何应用"</string>
     <string name="global_actions" product="tablet" msgid="408477140088053665">"平板电脑选项"</string>
     <string name="global_actions" product="default" msgid="2406416831541615258">"手机选项"</string>
     <string name="global_action_lock" msgid="2844945191792119712">"屏幕锁定"</string>
@@ -179,162 +179,162 @@
     <string name="permgrouplab_systemTools" msgid="4652191644082714048">"系统工具"</string>
     <string name="permgroupdesc_systemTools" msgid="8162102602190734305">"对系统进行低级访问和控制。"</string>
     <string name="permgrouplab_developmentTools" msgid="3446164584710596513">"开发工具"</string>
-    <string name="permgroupdesc_developmentTools" msgid="7058828032358142018">"只有应用程序开发人员才需要的功能。"</string>
+    <string name="permgroupdesc_developmentTools" msgid="7058828032358142018">"只有应用开发人员才需要的功能。"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"存储"</string>
     <string name="permgroupdesc_storage" product="nosdcard" msgid="7442318502446874999">"访问 USB 存储设备。"</string>
     <string name="permgroupdesc_storage" product="default" msgid="9203302214915355774">"访问 SD 卡。"</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"停用或修改状态栏"</string>
-    <string name="permdesc_statusBar" msgid="8434669549504290975">"允许应用程序停用状态栏或者增删系统图标。"</string>
+    <string name="permdesc_statusBar" msgid="8434669549504290975">"允许应用停用状态栏或者增删系统图标。"</string>
     <string name="permlab_statusBarService" msgid="7247281911387931485">"状态栏"</string>
-    <string name="permdesc_statusBarService" msgid="716113660795976060">"允许以状态栏形式显示应用程序。"</string>
+    <string name="permdesc_statusBarService" msgid="716113660795976060">"允许以状态栏形式显示应用。"</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"展开/收拢状态栏"</string>
-    <string name="permdesc_expandStatusBar" msgid="6917549437129401132">"允许应用程序展开或折叠状态栏。"</string>
+    <string name="permdesc_expandStatusBar" msgid="6917549437129401132">"允许应用展开或折叠状态栏。"</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"拦截外拨电话"</string>
-    <string name="permdesc_processOutgoingCalls" msgid="1152111671618301044">"允许应用程序处理外拨通话并更改要拨打的号码。恶意应用程序可能会监视、重定向或阻止外拨通话。"</string>
+    <string name="permdesc_processOutgoingCalls" msgid="1152111671618301044">"允许应用处理外拨通话并更改要拨打的号码。恶意应用可能会监视、重定向或阻止外拨通话。"</string>
     <string name="permlab_receiveSms" msgid="2697628268086208535">"接收短信"</string>
-    <string name="permdesc_receiveSms" msgid="8107887121893611793">"允许应用程序接收和处理短信。恶意应用程序可能会监视您的短信,或删除短信而不向您显示。"</string>
+    <string name="permdesc_receiveSms" msgid="8107887121893611793">"允许应用接收和处理短信。恶意应用可能会监视您的短信,或删除短信而不向您显示。"</string>
     <string name="permlab_receiveMms" msgid="8894700916188083287">"接收彩信"</string>
-    <string name="permdesc_receiveMms" msgid="1424805308566612086">"允许应用程序接收和处理彩信。恶意应用程序可能会监视您的短信,或删除短信而不向您显示。"</string>
+    <string name="permdesc_receiveMms" msgid="1424805308566612086">"允许应用接收和处理彩信。恶意应用可能会监视您的短信,或删除短信而不向您显示。"</string>
     <string name="permlab_receiveEmergencyBroadcast" msgid="1803477660846288089">"接收紧急广播"</string>
-    <string name="permdesc_receiveEmergencyBroadcast" msgid="848524070262431974">"允许应用程序接收和处理紧急广播消息。此权限仅适用于系统应用程序。"</string>
+    <string name="permdesc_receiveEmergencyBroadcast" msgid="848524070262431974">"允许应用接收和处理紧急广播消息。此权限仅适用于系统应用。"</string>
     <string name="permlab_sendSms" msgid="5600830612147671529">"发送短信"</string>
-    <string name="permdesc_sendSms" msgid="906546667507626156">"允许应用程序发送短信。恶意应用程序可能会未经您的确认而发送短信,由此产生相关费用。"</string>
+    <string name="permdesc_sendSms" msgid="906546667507626156">"允许应用发送短信。恶意应用可能会未经您的确认而发送短信,由此产生相关费用。"</string>
     <string name="permlab_sendSmsNoConfirmation" msgid="4781483105951730228">"不经确认直接发送短信"</string>
-    <string name="permdesc_sendSmsNoConfirmation" msgid="3437759207020400204">"允许应用程序发送短信。恶意应用程序可能会未经您的确认而发送短信,由此产生相关费用。"</string>
+    <string name="permdesc_sendSmsNoConfirmation" msgid="3437759207020400204">"允许应用发送短信。恶意应用可能会未经您的确认而发送短信,由此产生相关费用。"</string>
     <string name="permlab_readSms" msgid="4085333708122372256">"读取短信或彩信"</string>
-    <string name="permdesc_readSms" product="tablet" msgid="2341692916884515613">"允许应用程序读取您的平板电脑或 SIM 卡上存储的短信。恶意应用程序可能会读取您的机密短信。"</string>
-    <string name="permdesc_readSms" product="default" msgid="5653850482025875493">"允许应用程序读取您的手机或 SIM 卡上存储的短信。恶意应用程序可能借此读取您的机密短信。"</string>
+    <string name="permdesc_readSms" product="tablet" msgid="2341692916884515613">"允许应用读取您的平板电脑或 SIM 卡上存储的短信。恶意应用可能会读取您的机密短信。"</string>
+    <string name="permdesc_readSms" product="default" msgid="5653850482025875493">"允许应用读取您的手机或 SIM 卡上存储的短信。恶意应用可能借此读取您的机密短信。"</string>
     <string name="permlab_writeSms" msgid="6881122575154940744">"编辑短信或彩信"</string>
-    <string name="permdesc_writeSms" product="tablet" msgid="5160413947794501538">"允许应用程序对平板电脑或 SIM 卡上存储的短信执行写入操作。恶意应用程序可能会删除您的短信。"</string>
-    <string name="permdesc_writeSms" product="default" msgid="7268668709052328567">"允许应用程序对手机或 SIM 卡上存储的短信执行写入操作。恶意应用程序可能会删除您的短信。"</string>
+    <string name="permdesc_writeSms" product="tablet" msgid="5160413947794501538">"允许应用对平板电脑或 SIM 卡上存储的短信执行写入操作。恶意应用可能会删除您的短信。"</string>
+    <string name="permdesc_writeSms" product="default" msgid="7268668709052328567">"允许应用对手机或 SIM 卡上存储的短信执行写入操作。恶意应用可能会删除您的短信。"</string>
     <string name="permlab_receiveWapPush" msgid="8258226427716551388">"接收 WAP"</string>
-    <string name="permdesc_receiveWapPush" msgid="7983455145335316872">"允许应用程序接收和处理 WAP 短信。恶意应用程序可能会监视您的短信,或删除短信而不向您显示。"</string>
-    <string name="permlab_getTasks" msgid="6466095396623933906">"检索正在运行的应用程序"</string>
-    <string name="permdesc_getTasks" msgid="6608159250520381359">"允许应用程序针对近期运行的和当前正在运行的任务检索相关信息。恶意应用程序可能会发现有关其他应用程序的私密信息。"</string>
-    <string name="permlab_reorderTasks" msgid="2018575526934422779">"对正在运行的应用程序重新排序"</string>
-    <string name="permdesc_reorderTasks" msgid="4175137612205663399">"允许应用程序将任务移动到前台和后台。恶意应用程序可能会不受您的控制,强行让自己处于前台。"</string>
-    <string name="permlab_removeTasks" msgid="6821513401870377403">"停止正在运行的应用程序"</string>
-    <string name="permdesc_removeTasks" msgid="1394714352062635493">"允许该应用程序删除任务并终止这些任务的应用程序。恶意应用程序可以籍此影响其他应用程序的行为。"</string>
+    <string name="permdesc_receiveWapPush" msgid="7983455145335316872">"允许应用接收和处理 WAP 短信。恶意应用可能会监视您的短信,或删除短信而不向您显示。"</string>
+    <string name="permlab_getTasks" msgid="6466095396623933906">"检索正在运行的应用"</string>
+    <string name="permdesc_getTasks" msgid="6608159250520381359">"允许应用针对近期运行的和当前正在运行的任务检索相关信息。恶意应用可能会发现有关其他应用的私密信息。"</string>
+    <string name="permlab_reorderTasks" msgid="2018575526934422779">"对正在运行的应用重新排序"</string>
+    <string name="permdesc_reorderTasks" msgid="4175137612205663399">"允许应用将任务移动到前台和后台。恶意应用可能会不受您的控制,强行让自己处于前台。"</string>
+    <string name="permlab_removeTasks" msgid="6821513401870377403">"停止正在运行的应用"</string>
+    <string name="permdesc_removeTasks" msgid="1394714352062635493">"允许该应用删除任务并终止这些任务的应用。恶意应用可以籍此影响其他应用的行为。"</string>
     <string name="permlab_setScreenCompatibility" msgid="6975387118861842061">"设置屏幕兼容性"</string>
-    <string name="permdesc_setScreenCompatibility" msgid="692043618693917374">"允许该应用程序控制其他应用程序的屏幕兼容模式。恶意应用程序可以籍此影响其他应用程序的行为。"</string>
-    <string name="permlab_setDebugApp" msgid="3022107198686584052">"启用应用程序调试"</string>
-    <string name="permdesc_setDebugApp" msgid="4474512416299013256">"允许该应用程序对其他应用程序启用调试。恶意应用程序可以籍此终止其他的应用程序。"</string>
+    <string name="permdesc_setScreenCompatibility" msgid="692043618693917374">"允许该应用控制其他应用的屏幕兼容模式。恶意应用可以籍此影响其他应用的行为。"</string>
+    <string name="permlab_setDebugApp" msgid="3022107198686584052">"启用应用调试"</string>
+    <string name="permdesc_setDebugApp" msgid="4474512416299013256">"允许该应用对其他应用启用调试。恶意应用可以籍此终止其他的应用。"</string>
     <string name="permlab_changeConfiguration" msgid="8214475779521218295">"更改用户界面设置"</string>
-    <string name="permdesc_changeConfiguration" msgid="4372223873154296076">"允许该应用程序更改当前配置,例如语言区域或整体的字体大小。"</string>
+    <string name="permdesc_changeConfiguration" msgid="4372223873154296076">"允许该应用更改当前配置,例如语言区域或整体的字体大小。"</string>
     <string name="permlab_enableCarMode" msgid="5684504058192921098">"启用车载模式"</string>
-    <string name="permdesc_enableCarMode" msgid="4853187425751419467">"允许应用程序启用车载模式。"</string>
+    <string name="permdesc_enableCarMode" msgid="4853187425751419467">"允许应用启用车载模式。"</string>
     <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"结束后台进程"</string>
-    <string name="permdesc_killBackgroundProcesses" msgid="931129103262126617">"允许应用程序在内存空间充足的情况下终止其他应用程序的后台进程。"</string>
-    <string name="permlab_forceStopPackages" msgid="2329627428832067700">"强行停止其他应用程序"</string>
-    <string name="permdesc_forceStopPackages" msgid="5253157296183940812">"允许应用程序强行停止其他应用程序。"</string>
-    <string name="permlab_forceBack" msgid="652935204072584616">"强制应用程序关闭"</string>
-    <string name="permdesc_forceBack" msgid="3892295830419513623">"允许应用程序强制关闭任何前台活动并返回。普通应用程序绝不需要此权限。"</string>
+    <string name="permdesc_killBackgroundProcesses" msgid="931129103262126617">"允许应用在内存空间充足的情况下终止其他应用的后台进程。"</string>
+    <string name="permlab_forceStopPackages" msgid="2329627428832067700">"强行停止其他应用"</string>
+    <string name="permdesc_forceStopPackages" msgid="5253157296183940812">"允许应用强行停止其他应用。"</string>
+    <string name="permlab_forceBack" msgid="652935204072584616">"强制应用关闭"</string>
+    <string name="permdesc_forceBack" msgid="3892295830419513623">"允许应用强制关闭任何前台活动并返回。普通应用绝不需要此权限。"</string>
     <string name="permlab_dump" msgid="1681799862438954752">"检索系统内部状态"</string>
-    <string name="permdesc_dump" msgid="1778299088692290329">"允许应用程序检索系统的内部状态。恶意应用程序可能会检索一般情况下绝不需要检索的多种私人信息和安全信息。"</string>
+    <string name="permdesc_dump" msgid="1778299088692290329">"允许应用检索系统的内部状态。恶意应用可能会检索一般情况下绝不需要检索的多种私人信息和安全信息。"</string>
     <string name="permlab_retrieve_window_content" msgid="8022588608994589938">"检索屏幕内容"</string>
-    <string name="permdesc_retrieve_window_content" msgid="3193269069469700265">"允许应用程序检索活动窗口的内容。恶意应用程序可能会检索整个窗口的内容,并检查其中除密码以外的所有文字。"</string>
+    <string name="permdesc_retrieve_window_content" msgid="3193269069469700265">"允许应用检索活动窗口的内容。恶意应用可能会检索整个窗口的内容,并检查其中除密码以外的所有文字。"</string>
     <string name="permlab_shutdown" msgid="7185747824038909016">"部分关机"</string>
     <string name="permdesc_shutdown" msgid="7046500838746291775">"使活动管理器进入关闭状态。不执行彻底关机。"</string>
-    <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"禁止切换应用程序"</string>
-    <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"阻止用户切换到其他应用程序。"</string>
-    <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"监控所有应用程序的启动"</string>
-    <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"允许应用程序监视和控制系统是如何启动活动的。恶意应用程序可能会完全破坏系统。此权限只有在进行开发时才需要,正常使用情况下绝不需要。"</string>
+    <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"禁止切换应用"</string>
+    <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"阻止用户切换到其他应用。"</string>
+    <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"监控所有应用的启动"</string>
+    <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"允许应用监视和控制系统是如何启动活动的。恶意应用可能会完全破坏系统。此权限只有在进行开发时才需要,正常使用情况下绝不需要。"</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"发送包删除的广播"</string>
-    <string name="permdesc_broadcastPackageRemoved" msgid="6621901216207931089">"允许应用程序广播一条有关已删除了应用程序包的通知。恶意应用程序可能借此终止其他任何正在运行的应用程序。"</string>
+    <string name="permdesc_broadcastPackageRemoved" msgid="6621901216207931089">"允许应用广播一条有关已删除了应用包的通知。恶意应用可能借此终止其他任何正在运行的应用。"</string>
     <string name="permlab_broadcastSmsReceived" msgid="5689095009030336593">"发送短信收到的广播"</string>
-    <string name="permdesc_broadcastSmsReceived" msgid="4152037720034365492">"允许应用程序广播一条有关已收到短信的通知。恶意应用程序可能借此伪造接到的短信。"</string>
+    <string name="permdesc_broadcastSmsReceived" msgid="4152037720034365492">"允许应用广播一条有关已收到短信的通知。恶意应用可能借此伪造接到的短信。"</string>
     <string name="permlab_broadcastWapPush" msgid="3145347413028582371">"发送 WAP-PUSH 收到的广播"</string>
-    <string name="permdesc_broadcastWapPush" msgid="4783402525039442729">"允许应用程序广播一条有关已收到 WAP PUSH 短信的通知。恶意应用程序可能借此伪造短信接收,或在后台将任意网页的内容替换为恶意内容。"</string>
+    <string name="permdesc_broadcastWapPush" msgid="4783402525039442729">"允许应用广播一条有关已收到 WAP PUSH 短信的通知。恶意应用可能借此伪造短信接收,或在后台将任意网页的内容替换为恶意内容。"</string>
     <string name="permlab_setProcessLimit" msgid="2451873664363662666">"限制运行的进程个数"</string>
-    <string name="permdesc_setProcessLimit" msgid="7318061314040879542">"允许应用程序控制将运行的进程数上限。普通应用程序绝不需要此权限。"</string>
-    <string name="permlab_setAlwaysFinish" msgid="238828158465736054">"关闭所有后台应用程序"</string>
-    <string name="permdesc_setAlwaysFinish" msgid="7471310652868841499">"允许应用程序控制活动在转入后台后是否立即结束。普通应用程序绝不需要此权限。"</string>
+    <string name="permdesc_setProcessLimit" msgid="7318061314040879542">"允许应用控制将运行的进程数上限。普通应用绝不需要此权限。"</string>
+    <string name="permlab_setAlwaysFinish" msgid="238828158465736054">"关闭所有后台应用"</string>
+    <string name="permdesc_setAlwaysFinish" msgid="7471310652868841499">"允许应用控制活动在转入后台后是否立即结束。普通应用绝不需要此权限。"</string>
     <string name="permlab_batteryStats" msgid="7863923071360031652">"修改电池统计信息"</string>
-    <string name="permdesc_batteryStats" msgid="6835186932305744068">"允许应用程序修改收集到的电池统计信息。普通应用程序不能使用此权限。"</string>
+    <string name="permdesc_batteryStats" msgid="6835186932305744068">"允许应用修改收集到的电池统计信息。普通应用不能使用此权限。"</string>
     <string name="permlab_backup" msgid="470013022865453920">"控制系统备份和还原"</string>
-    <string name="permdesc_backup" msgid="6912230525140589891">"允许应用程序控制系统的备份和还原机制。普通应用程序不能使用此权限。"</string>
+    <string name="permdesc_backup" msgid="6912230525140589891">"允许应用控制系统的备份和还原机制。普通应用不能使用此权限。"</string>
     <string name="permlab_confirm_full_backup" msgid="5557071325804469102">"确认完整备份或恢复操作"</string>
-    <string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"允许应用程序启动完整备份确认用户界面。不用于任何应用程序。"</string>
+    <string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"允许应用启动完整备份确认用户界面。不用于任何应用。"</string>
     <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"显示未授权的窗口"</string>
-    <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"允许该应用程序创建供内部系统用户界面使用的窗口。普通应用程序不应使用此权限。"</string>
+    <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"允许该应用创建供内部系统用户界面使用的窗口。普通应用不应使用此权限。"</string>
     <string name="permlab_systemAlertWindow" msgid="3372321942941168324">"显示系统级警报"</string>
-    <string name="permdesc_systemAlertWindow" msgid="8507863469978066409">"允许应用程序显示系统警告窗口。恶意应用程序可能会控制整个屏幕。"</string>
+    <string name="permdesc_systemAlertWindow" msgid="8507863469978066409">"允许应用显示系统警告窗口。恶意应用可能会控制整个屏幕。"</string>
     <string name="permlab_setAnimationScale" msgid="2805103241153907174">"修改全局动画速度"</string>
-    <string name="permdesc_setAnimationScale" msgid="7690063428924343571">"允许该应用程序随时更改全局动画速度(加快或减慢)。"</string>
-    <string name="permlab_manageAppTokens" msgid="1286505717050121370">"管理应用程序令牌"</string>
-    <string name="permdesc_manageAppTokens" msgid="8043431713014395671">"允许应用程序绕过其正常的 Z 排序创建和管理自己的令牌。普通应用程序绝不需要此权限。"</string>
+    <string name="permdesc_setAnimationScale" msgid="7690063428924343571">"允许该应用随时更改全局动画速度(加快或减慢)。"</string>
+    <string name="permlab_manageAppTokens" msgid="1286505717050121370">"管理应用令牌"</string>
+    <string name="permdesc_manageAppTokens" msgid="8043431713014395671">"允许应用绕过其正常的 Z 排序创建和管理自己的令牌。普通应用绝不需要此权限。"</string>
     <string name="permlab_injectEvents" msgid="1378746584023586600">"按键和控制按钮"</string>
-    <string name="permdesc_injectEvents" product="tablet" msgid="206352565599968632">"允许应用程序将自身的输入活动(例如按键操作等)提供给其他应用程序。恶意应用程序可能借此控制平板电脑。"</string>
-    <string name="permdesc_injectEvents" product="default" msgid="653128057572326253">"允许应用程序将自身的输入活动(例如按键操作等)提供给其他应用程序。恶意应用程序可能借此控制手机。"</string>
+    <string name="permdesc_injectEvents" product="tablet" msgid="206352565599968632">"允许应用将自身的输入活动(例如按键操作等)提供给其他应用。恶意应用可能借此控制平板电脑。"</string>
+    <string name="permdesc_injectEvents" product="default" msgid="653128057572326253">"允许应用将自身的输入活动(例如按键操作等)提供给其他应用。恶意应用可能借此控制手机。"</string>
     <string name="permlab_readInputState" msgid="469428900041249234">"记录您键入的内容和执行的操作"</string>
-    <string name="permdesc_readInputState" msgid="8387754901688728043">"允许应用程序记录您所按的键,包括与其他应用程序进行交互(如输入密码)时按的键。普通应用程序绝不需要此权限。"</string>
+    <string name="permdesc_readInputState" msgid="8387754901688728043">"允许应用记录您所按的键,包括与其他应用进行交互(如输入密码)时按的键。普通应用绝不需要此权限。"</string>
     <string name="permlab_bindInputMethod" msgid="3360064620230515776">"绑定至输入法"</string>
-    <string name="permdesc_bindInputMethod" msgid="3250440322807286331">"允许用户绑定至输入法的顶级接口。普通应用程序绝不需要此权限。"</string>
+    <string name="permdesc_bindInputMethod" msgid="3250440322807286331">"允许用户绑定至输入法的顶级接口。普通应用绝不需要此权限。"</string>
     <string name="permlab_bindTextService" msgid="7358378401915287938">"绑定至文字服务"</string>
-    <string name="permdesc_bindTextService" msgid="8151968910973998670">"允许用户绑定至文字服务(如 SpellCheckerService)的顶级接口。普通应用程序绝不需要此权限。"</string>
+    <string name="permdesc_bindTextService" msgid="8151968910973998670">"允许用户绑定至文字服务(如 SpellCheckerService)的顶级接口。普通应用绝不需要此权限。"</string>
     <string name="permlab_bindVpnService" msgid="4708596021161473255">"绑定到 VPN 服务"</string>
-    <string name="permdesc_bindVpnService" msgid="2067845564581693905">"允许用户绑定到 VPN 服务的顶级接口。普通应用程序从不需要此权限。"</string>
+    <string name="permdesc_bindVpnService" msgid="2067845564581693905">"允许用户绑定到 VPN 服务的顶级接口。普通应用从不需要此权限。"</string>
     <string name="permlab_bindWallpaper" msgid="8716400279937856462">"绑定到壁纸"</string>
-    <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"允许用户绑定到壁纸的顶级接口。普通应用程序绝不需要此权限。"</string>
+    <string name="permdesc_bindWallpaper" msgid="7108428692595491668">"允许用户绑定到壁纸的顶级接口。普通应用绝不需要此权限。"</string>
     <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"绑定到窗口小部件服务"</string>
-    <string name="permdesc_bindRemoteViews" msgid="4717987810137692572">"允许用户绑定到窗口小部件服务的顶级接口。普通应用程序绝不需要此权限。"</string>
+    <string name="permdesc_bindRemoteViews" msgid="4717987810137692572">"允许用户绑定到窗口小部件服务的顶级接口。普通应用绝不需要此权限。"</string>
     <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"与设备管理器交互"</string>
-    <string name="permdesc_bindDeviceAdmin" msgid="569715419543907930">"允许用户将意向发送给设备管理员。普通应用程序绝不需要此权限。"</string>
+    <string name="permdesc_bindDeviceAdmin" msgid="569715419543907930">"允许用户将意向发送给设备管理员。普通应用绝不需要此权限。"</string>
     <string name="permlab_setOrientation" msgid="3365947717163866844">"更改屏幕显示方向"</string>
-    <string name="permdesc_setOrientation" msgid="3046126619316671476">"允许应用程序随时更改屏幕的旋转状态。普通应用程序绝不需要此权限。"</string>
+    <string name="permdesc_setOrientation" msgid="3046126619316671476">"允许应用随时更改屏幕的旋转状态。普通应用绝不需要此权限。"</string>
     <string name="permlab_setPointerSpeed" msgid="9175371613322562934">"更改指针速度"</string>
-    <string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"允许应用程序随时更改鼠标或触控板指针速度。普通应用程序绝不需要此权限。"</string>
-    <string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"向应用程序发送 Linux 信号"</string>
-    <string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"允许应用程序请求将提供的信号发送给所有持续的进程。"</string>
-    <string name="permlab_persistentActivity" msgid="8841113627955563938">"让应用程序始终运行"</string>
-    <string name="permdesc_persistentActivity" msgid="4909910271316074418">"允许应用程序持续保留其自身的某些组件,这样系统就无法将其用于其他应用程序。"</string>
-    <string name="permlab_deletePackages" msgid="184385129537705938">"删除应用程序"</string>
-    <string name="permdesc_deletePackages" msgid="7411480275167205081">"允许应用程序删除 Android 程序包。恶意应用程序可能借此删除重要的应用程序。"</string>
-    <string name="permlab_clearAppUserData" msgid="274109191845842756">"删除其他应用程序的数据"</string>
-    <string name="permdesc_clearAppUserData" msgid="4625323684125459488">"允许应用程序清除用户数据。"</string>
-    <string name="permlab_deleteCacheFiles" msgid="3128665571837408675">"删除其他应用程序的缓存"</string>
-    <string name="permdesc_deleteCacheFiles" msgid="3812998599006730196">"允许应用程序删除缓存文件。"</string>
-    <string name="permlab_getPackageSize" msgid="7472921768357981986">"计算应用程序存储空间"</string>
-    <string name="permdesc_getPackageSize" msgid="3921068154420738296">"允许应用程序检索其代码、数据和缓存大小"</string>
-    <string name="permlab_installPackages" msgid="2199128482820306924">"直接安装应用程序"</string>
-    <string name="permdesc_installPackages" msgid="5628530972548071284">"允许应用程序安装新的或更新的 Andr​​oid 程序包。恶意应用程序可能借此添加具有任意权限的新应用程序。"</string>
-    <string name="permlab_clearAppCache" msgid="7487279391723526815">"删除所有应用程序缓存数据"</string>
-    <string name="permdesc_clearAppCache" product="tablet" msgid="3523396284474042284">"允许应用程序删除应用程序缓存目录中的文件,从而释放平板电脑的存储空间。对系统进程的访问权限通常受到很大的限制。"</string>
-    <string name="permdesc_clearAppCache" product="default" msgid="5067988373366292186">"允许应用程序删除应用程序缓存目录中的文件,从而释放手机的存储空间。对系统进程的访问权限通常受到很大的限制。"</string>
-    <string name="permlab_movePackage" msgid="3289890271645921411">"移动应用程序资源"</string>
-    <string name="permdesc_movePackage" msgid="319562217778244524">"允许应用程序在内部与外部媒体之间移动应用程序资源。"</string>
+    <string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"允许应用随时更改鼠标或触控板指针速度。普通应用绝不需要此权限。"</string>
+    <string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"向应用发送 Linux 信号"</string>
+    <string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"允许应用请求将提供的信号发送给所有持续的进程。"</string>
+    <string name="permlab_persistentActivity" msgid="8841113627955563938">"让应用始终运行"</string>
+    <string name="permdesc_persistentActivity" msgid="4909910271316074418">"允许应用持续保留其自身的某些组件,这样系统就无法将其用于其他应用。"</string>
+    <string name="permlab_deletePackages" msgid="184385129537705938">"删除应用"</string>
+    <string name="permdesc_deletePackages" msgid="7411480275167205081">"允许应用删除 Android 程序包。恶意应用可能借此删除重要的应用。"</string>
+    <string name="permlab_clearAppUserData" msgid="274109191845842756">"删除其他应用的数据"</string>
+    <string name="permdesc_clearAppUserData" msgid="4625323684125459488">"允许应用清除用户数据。"</string>
+    <string name="permlab_deleteCacheFiles" msgid="3128665571837408675">"删除其他应用的缓存"</string>
+    <string name="permdesc_deleteCacheFiles" msgid="3812998599006730196">"允许应用删除缓存文件。"</string>
+    <string name="permlab_getPackageSize" msgid="7472921768357981986">"计算应用存储空间"</string>
+    <string name="permdesc_getPackageSize" msgid="3921068154420738296">"允许应用检索其代码、数据和缓存大小"</string>
+    <string name="permlab_installPackages" msgid="2199128482820306924">"直接安装应用"</string>
+    <string name="permdesc_installPackages" msgid="5628530972548071284">"允许应用安装新的或更新的 Andr​​oid 程序包。恶意应用可能借此添加具有任意权限的新应用。"</string>
+    <string name="permlab_clearAppCache" msgid="7487279391723526815">"删除所有应用缓存数据"</string>
+    <string name="permdesc_clearAppCache" product="tablet" msgid="3523396284474042284">"允许应用删除应用缓存目录中的文件,从而释放平板电脑的存储空间。对系统进程的访问权限通常受到很大的限制。"</string>
+    <string name="permdesc_clearAppCache" product="default" msgid="5067988373366292186">"允许应用删除应用缓存目录中的文件,从而释放手机的存储空间。对系统进程的访问权限通常受到很大的限制。"</string>
+    <string name="permlab_movePackage" msgid="3289890271645921411">"移动应用资源"</string>
+    <string name="permdesc_movePackage" msgid="319562217778244524">"允许应用在内部与外部媒体之间移动应用资源。"</string>
     <string name="permlab_readLogs" msgid="6615778543198967614">"查阅敏感日志数据"</string>
-    <string name="permdesc_readLogs" product="tablet" msgid="82061313293455151">"允许应用程序从系统的各种日志文件中读取信息。这样,应用程序就可以发现关于您平板电脑使用情况的一般信息,其中可能包含个人信息或隐私信息。"</string>
-    <string name="permdesc_readLogs" product="default" msgid="2063438140241560443">"允许应用程序从系统的各个日志文件中读取信息。这样,应用程序就可以发现关于您手机使用情况的一般信息,其中可能包含个人信息或隐私信息。"</string>
+    <string name="permdesc_readLogs" product="tablet" msgid="82061313293455151">"允许应用从系统的各种日志文件中读取信息。这样,应用就可以发现关于您平板电脑使用情况的一般信息,其中可能包含个人信息或隐私信息。"</string>
+    <string name="permdesc_readLogs" product="default" msgid="2063438140241560443">"允许应用从系统的各个日志文件中读取信息。这样,应用就可以发现关于您手机使用情况的一般信息,其中可能包含个人信息或隐私信息。"</string>
     <string name="permlab_anyCodecForPlayback" msgid="715805555823881818">"使用任何媒体解码器进行播放"</string>
-    <string name="permdesc_anyCodecForPlayback" msgid="8283912488433189010">"允许该应用程序使用任何已安装的媒体解码器进行解码,以便播放媒体。"</string>
+    <string name="permdesc_anyCodecForPlayback" msgid="8283912488433189010">"允许该应用使用任何已安装的媒体解码器进行解码,以便播放媒体。"</string>
     <string name="permlab_diagnostic" msgid="8076743953908000342">"读取/写入诊断所拥有的资源"</string>
-    <string name="permdesc_diagnostic" msgid="6608295692002452283">"允许应用程序读取/写入诊断组拥有的所有资源(例如 /dev 中的文件)。这可能会影响系统的稳定性和安全性。此权限仅供制造商或运营商诊断硬件方面的问题时使用。"</string>
-    <string name="permlab_changeComponentState" msgid="6335576775711095931">"启用或停用应用程序组件"</string>
-    <string name="permdesc_changeComponentState" product="tablet" msgid="8887435740982237294">"允许应用程序启用或停用其他应用程序的组件。恶意应用程序可能借此停用重要的平板电脑功能。请务必谨慎使用此权限,因为这可能导致某些应用程序组件处于无法使用、不一致或不稳定的状态。"</string>
-    <string name="permdesc_changeComponentState" product="default" msgid="1827232484416505615">"允许应用程序启用或停用其他应用程序的组件。恶意应用程序可能借此停用重要的手机功能。请务必谨慎使用此权限,因为这可能导致某些应用程序组件进入无法使用、不一致或不稳定的状态。"</string>
+    <string name="permdesc_diagnostic" msgid="6608295692002452283">"允许应用读取/写入诊断组拥有的所有资源(例如 /dev 中的文件)。这可能会影响系统的稳定性和安全性。此权限仅供制造商或运营商诊断硬件方面的问题时使用。"</string>
+    <string name="permlab_changeComponentState" msgid="6335576775711095931">"启用或停用应用组件"</string>
+    <string name="permdesc_changeComponentState" product="tablet" msgid="8887435740982237294">"允许应用启用或停用其他应用的组件。恶意应用可能借此停用重要的平板电脑功能。请务必谨慎使用此权限,因为这可能导致某些应用组件处于无法使用、不一致或不稳定的状态。"</string>
+    <string name="permdesc_changeComponentState" product="default" msgid="1827232484416505615">"允许应用启用或停用其他应用的组件。恶意应用可能借此停用重要的手机功能。请务必谨慎使用此权限,因为这可能导致某些应用组件进入无法使用、不一致或不稳定的状态。"</string>
     <string name="permlab_grantRevokePermissions" msgid="4627315351093508795">"授予或撤消权限"</string>
     <string name="permdesc_grantRevokePermissions" msgid="4088642654085850662">"允许应用针对自身或其他应用授予或撤消特定权限。恶意应用可能会借此访问您未授权它们访问的功能。"</string>
-    <string name="permlab_setPreferredApplications" msgid="8463181628695396391">"设置首选应用程序"</string>
-    <string name="permdesc_setPreferredApplications" msgid="4973986762241783712">"允许应用程序修改您的首选应用程序。恶意应用程序可能会在后台更改运行的应用程序,欺骗您现有的应用程序,以收集您的私人数据。"</string>
+    <string name="permlab_setPreferredApplications" msgid="8463181628695396391">"设置首选应用"</string>
+    <string name="permdesc_setPreferredApplications" msgid="4973986762241783712">"允许应用修改您的首选应用。恶意应用可能会在后台更改运行的应用,欺骗您现有的应用,以收集您的私人数据。"</string>
     <string name="permlab_writeSettings" msgid="1365523497395143704">"修改全局系统设置"</string>
-    <string name="permdesc_writeSettings" msgid="7775723441558907181">"允许应用程序修改系统的设置数据。恶意应用程序可能会破坏您的系统配置。"</string>
+    <string name="permdesc_writeSettings" msgid="7775723441558907181">"允许应用修改系统的设置数据。恶意应用可能会破坏您的系统配置。"</string>
     <string name="permlab_writeSecureSettings" msgid="204676251876718288">"修改安全系统设置"</string>
-    <string name="permdesc_writeSecureSettings" msgid="8159535613020137391">"允许应用程序修改系统的安全设置数据。普通应用程序不能使用此权限。"</string>
+    <string name="permdesc_writeSecureSettings" msgid="8159535613020137391">"允许应用修改系统的安全设置数据。普通应用不能使用此权限。"</string>
     <string name="permlab_writeGservices" msgid="2149426664226152185">"修改 Google 服务地图"</string>
-    <string name="permdesc_writeGservices" msgid="1287309437638380229">"允许应用程序修改 Google 服务地图。普通应用程序不能使用此权限。"</string>
+    <string name="permdesc_writeGservices" msgid="1287309437638380229">"允许应用修改 Google 服务地图。普通应用不能使用此权限。"</string>
     <string name="permlab_receiveBootCompleted" msgid="7776779842866993377">"开机时自动启动"</string>
-    <string name="permdesc_receiveBootCompleted" product="tablet" msgid="7390304664116880704">"允许应用程序在系统完成引导后立即自动启动。这样可能会延长平板电脑的启动时间,并允许应用程序始终运行,从而导致平板电脑总体运行速度减慢。"</string>
-    <string name="permdesc_receiveBootCompleted" product="default" msgid="513950589102617504">"允许应用程序在系统完成引导后立即自动启动。这样可能会延长手机的启动时间,并允许应用程序始终运行,从而导致手机总体运行速度减慢。"</string>
+    <string name="permdesc_receiveBootCompleted" product="tablet" msgid="7390304664116880704">"允许应用在系统完成引导后立即自动启动。这样可能会延长平板电脑的启动时间,并允许应用始终运行,从而导致平板电脑总体运行速度减慢。"</string>
+    <string name="permdesc_receiveBootCompleted" product="default" msgid="513950589102617504">"允许应用在系统完成引导后立即自动启动。这样可能会延长手机的启动时间,并允许应用始终运行,从而导致手机总体运行速度减慢。"</string>
     <string name="permlab_broadcastSticky" msgid="7919126372606881614">"发送持久广播"</string>
-    <string name="permdesc_broadcastSticky" product="tablet" msgid="1181582512022829259">"允许应用程序发送持久广播,此类广播在结束后仍会保留。恶意应用程序可能会导致平板电脑使用过多内存,从而使速度变慢或变得不稳定。"</string>
-    <string name="permdesc_broadcastSticky" product="default" msgid="3287869131621514325">"允许应用程序发送持久广播,此类广播在结束后仍会保留。恶意应用程序可能会导致手机使用过多内存,从而使速度变慢或变得不稳定。"</string>
+    <string name="permdesc_broadcastSticky" product="tablet" msgid="1181582512022829259">"允许应用发送持久广播,此类广播在结束后仍会保留。恶意应用可能会导致平板电脑使用过多内存,从而使速度变慢或变得不稳定。"</string>
+    <string name="permdesc_broadcastSticky" product="default" msgid="3287869131621514325">"允许应用发送持久广播,此类广播在结束后仍会保留。恶意应用可能会导致手机使用过多内存,从而使速度变慢或变得不稳定。"</string>
     <string name="permlab_readContacts" msgid="6219652189510218240">"读取联系人数据"</string>
-    <string name="permdesc_readContacts" product="tablet" msgid="4028657556924039119">"允许应用程序读取您平板电脑上存储的所有联系人(地址)数据。恶意应用程序可能借此将您的数据发送给其他人。"</string>
-    <string name="permdesc_readContacts" product="default" msgid="2032222056456498547">"允许应用程序读取您手机上存储的所有联系人(地址)数据。恶意应用程序可能借此将您的数据发送给其他人。"</string>
+    <string name="permdesc_readContacts" product="tablet" msgid="4028657556924039119">"允许应用读取您平板电脑上存储的所有联系人(地址)数据。恶意应用可能借此将您的数据发送给其他人。"</string>
+    <string name="permdesc_readContacts" product="default" msgid="2032222056456498547">"允许应用读取您手机上存储的所有联系人(地址)数据。恶意应用可能借此将您的数据发送给其他人。"</string>
     <string name="permlab_writeContacts" msgid="644616215860933284">"写入联系数据"</string>
-    <string name="permdesc_writeContacts" product="tablet" msgid="988969759110632978">"允许应用程序读取您平板电脑上存储的联系人(地址)数据。恶意应用程序可能借此清除或修改您的联系人数据。"</string>
-    <string name="permdesc_writeContacts" product="default" msgid="5075164818647934067">"允许应用程序修改您手机上存储的联系人(地址)数据。恶意应用程序可能借此清除或修改您的联系人数据。"</string>
+    <string name="permdesc_writeContacts" product="tablet" msgid="988969759110632978">"允许应用读取您平板电脑上存储的联系人(地址)数据。恶意应用可能借此清除或修改您的联系人数据。"</string>
+    <string name="permdesc_writeContacts" product="default" msgid="5075164818647934067">"允许应用修改您手机上存储的联系人(地址)数据。恶意应用可能借此清除或修改您的联系人数据。"</string>
     <string name="permlab_readCallLog" msgid="3478133184624102739">"读取通话记录"</string>
     <string name="permdesc_readCallLog" product="tablet" msgid="3995157599976515002">"允许该应用读取平板电脑的通话记录,包括有关来电和外拨电话的数据。恶意应用可能会借此将您的数据发送给他人。"</string>
     <string name="permdesc_readCallLog" product="default" msgid="3452017559804750758">"允许该应用读取手机的通话记录,包括有关来电和外拨电话的数据。恶意应用可能会借此将您的数据发送给他人。"</string>
@@ -342,189 +342,189 @@
     <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"允许该应用修改平板电脑的通话记录,包括有关来电和外拨电话的数据。恶意应用可能会借此清除或修改您的通话记录。"</string>
     <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"允许该应用修改手机的通话记录,包括有关来电和外拨电话的数据。恶意应用可能会借此清除或修改您的通话记录。"</string>
     <string name="permlab_readProfile" msgid="6824681438529842282">"读取您的个人资料数据"</string>
-    <string name="permdesc_readProfile" product="default" msgid="94520753797630679">"允许应用程序读取您设备上存储的个人资料信息,例如您的姓名和联系信息。这意味着应用程序可以识别您的身份,并将您的个人资料信息发送给他人。"</string>
+    <string name="permdesc_readProfile" product="default" msgid="94520753797630679">"允许应用读取您设备上存储的个人资料信息,例如您的姓名和联系信息。这意味着应用可以识别您的身份,并将您的个人资料信息发送给他人。"</string>
     <string name="permlab_writeProfile" msgid="4679878325177177400">"写入到您的个人资料数据"</string>
-    <string name="permdesc_writeProfile" product="default" msgid="4637366723793045603">"允许应用程序更改或添加您设备上存储的个人资料信息,例如您的姓名和联系信息。这意味着其他应用程序可以识别您的身份,并将您的个人资料信息发送给他人。"</string>
+    <string name="permdesc_writeProfile" product="default" msgid="4637366723793045603">"允许应用更改或添加您设备上存储的个人资料信息,例如您的姓名和联系信息。这意味着其他应用可以识别您的身份,并将您的个人资料信息发送给他人。"</string>
     <string name="permlab_readSocialStream" product="default" msgid="1268920956152419170">"读取您的社交视频流"</string>
-    <string name="permdesc_readSocialStream" product="default" msgid="3419050808547335320">"允许应用程序访问并同步您和好友的社交最新动态。恶意应用程序可能借此读取您与社交网络上的好友之间的私人交流信息。"</string>
+    <string name="permdesc_readSocialStream" product="default" msgid="3419050808547335320">"允许应用访问并同步您和好友的社交最新动态。恶意应用可能借此读取您与社交网络上的好友之间的私人交流信息。"</string>
     <string name="permlab_writeSocialStream" product="default" msgid="3504179222493235645">"写入您的社交视频流"</string>
-    <string name="permdesc_writeSocialStream" product="default" msgid="3496277176955721451">"允许应用程序显示好友的社交最新动态。恶意应用程序可能借此冒充您的某位好友,诱骗您泄露密码或其他机密信息。"</string>
+    <string name="permdesc_writeSocialStream" product="default" msgid="3496277176955721451">"允许应用显示好友的社交最新动态。恶意应用可能借此冒充您的某位好友,诱骗您泄露密码或其他机密信息。"</string>
     <string name="permlab_readCalendar" msgid="5972727560257612398">"读取日历活动和机密信息"</string>
-    <string name="permdesc_readCalendar" product="tablet" msgid="2338414551004122687">"允许应用程序读取您平板电脑上存储的所有日历活动,包括朋友或同事的活动。恶意应用程序可能会在所有者不知情的情况下,从这些日历活动中提取个人信息。"</string>
-    <string name="permdesc_readCalendar" product="default" msgid="5693933067751827753">"允许应用程序读取您手机上存储的所有日历活动,包括朋友或同事的活动。恶意应用程序可能会在所有者不知情的情况下,从这些日历活动中提取个人信息。"</string>
+    <string name="permdesc_readCalendar" product="tablet" msgid="2338414551004122687">"允许应用读取您平板电脑上存储的所有日历活动,包括朋友或同事的活动。恶意应用可能会在所有者不知情的情况下,从这些日历活动中提取个人信息。"</string>
+    <string name="permdesc_readCalendar" product="default" msgid="5693933067751827753">"允许应用读取您手机上存储的所有日历活动,包括朋友或同事的活动。恶意应用可能会在所有者不知情的情况下,从这些日历活动中提取个人信息。"</string>
     <string name="permlab_writeCalendar" msgid="8438874755193825647">"添加或修改日历活动,并在所有者不知情的情况下向邀请对象发送电子邮件"</string>
-    <string name="permdesc_writeCalendar" msgid="2243771395254848873">"允许应用程序以日历所有者的身份发送活动邀请,并添加、删除和更改您可在自己设备上修改的活动,包括朋友或同事的活动。恶意应用程序可能会冒充日历所有者发送垃圾邮件,在所有者不知情的情况下修改活动,或者添加虚假活动。"</string>
+    <string name="permdesc_writeCalendar" msgid="2243771395254848873">"允许应用以日历所有者的身份发送活动邀请,并添加、删除和更改您可在自己设备上修改的活动,包括朋友或同事的活动。恶意应用可能会冒充日历所有者发送垃圾邮件,在所有者不知情的情况下修改活动,或者添加虚假活动。"</string>
     <string name="permlab_accessMockLocation" msgid="8688334974036823330">"使用模拟地点来源进行测试"</string>
-    <string name="permdesc_accessMockLocation" msgid="7577931556422993949">"允许应用程序创建模拟位置源以进行测试。恶意应用程序可能借此覆盖由真实位置源(如 GPS 或网络服务提供商)返回的位置和/或状态信息。"</string>
+    <string name="permdesc_accessMockLocation" msgid="7577931556422993949">"允许应用创建模拟位置源以进行测试。恶意应用可能借此覆盖由真实位置源(如 GPS 或网络服务提供商)返回的位置和/或状态信息。"</string>
     <string name="permlab_accessLocationExtraCommands" msgid="2836308076720553837">"获取额外的位置信息提供程序命令"</string>
-    <string name="permdesc_accessLocationExtraCommands" msgid="6737736970602176133">"允许应用程序访问额外的位置提供程序命令。恶意应用程序可能借此干扰 GPS 或其他位置源的运行。"</string>
+    <string name="permdesc_accessLocationExtraCommands" msgid="6737736970602176133">"允许应用访问额外的位置提供程序命令。恶意应用可能借此干扰 GPS 或其他位置源的运行。"</string>
     <string name="permlab_installLocationProvider" msgid="6578101199825193873">"允许安装位置信息提供程序"</string>
-    <string name="permdesc_installLocationProvider" msgid="1742577679350078373">"创建模拟位置源以进行测试。恶意应用程序可能借此覆盖由真实位置源(如 GPS 或网络服务提供商)返回的位置和/或状态信息,或者监视您的位置并将信息提供给外部源。"</string>
+    <string name="permdesc_installLocationProvider" msgid="1742577679350078373">"创建模拟位置源以进行测试。恶意应用可能借此覆盖由真实位置源(如 GPS 或网络服务提供商)返回的位置和/或状态信息,或者监视您的位置并将信息提供给外部源。"</string>
     <string name="permlab_accessFineLocation" msgid="8116127007541369477">"精准的(GPS)位置"</string>
-    <string name="permdesc_accessFineLocation" product="tablet" msgid="5326423948268164934">"访问精准的位置源,例如平板电脑上的全球定位系统(如果有)。恶意应用程序可能借此确定您所处的位置,并可能消耗额外的电池电量。"</string>
-    <string name="permdesc_accessFineLocation" product="default" msgid="7130267914433890869">"访问手机上的精准位置源,例如全球定位系统(如果有)。恶意应用程序可能借此确定您所处的位置,并可能消耗额外的电池电量。"</string>
+    <string name="permdesc_accessFineLocation" product="tablet" msgid="5326423948268164934">"访问精准的位置源,例如平板电脑上的全球定位系统(如果有)。恶意应用可能借此确定您所处的位置,并可能消耗额外的电池电量。"</string>
+    <string name="permdesc_accessFineLocation" product="default" msgid="7130267914433890869">"访问手机上的精准位置源,例如全球定位系统(如果有)。恶意应用可能借此确定您所处的位置,并可能消耗额外的电池电量。"</string>
     <string name="permlab_accessCoarseLocation" msgid="4642255009181975828">"(基于网络的)粗略位置"</string>
-    <string name="permdesc_accessCoarseLocation" product="tablet" msgid="5460726396318105483">"访问粗略的位置源(例如蜂窝网络数据库)以确定平板电脑的大体位置(如果可以)。恶意应用程序可能借此确定您所处的大体位置。"</string>
-    <string name="permdesc_accessCoarseLocation" product="default" msgid="8900795778057579522">"访问粗略的位置源(例如蜂窝网络数据库)以确定手机的大体位置(如果可以)。恶意应用程序可能借此确定您所处的大体位置。"</string>
+    <string name="permdesc_accessCoarseLocation" product="tablet" msgid="5460726396318105483">"访问粗略的位置源(例如蜂窝网络数据库)以确定平板电脑的大体位置(如果可以)。恶意应用可能借此确定您所处的大体位置。"</string>
+    <string name="permdesc_accessCoarseLocation" product="default" msgid="8900795778057579522">"访问粗略的位置源(例如蜂窝网络数据库)以确定手机的大体位置(如果可以)。恶意应用可能借此确定您所处的大体位置。"</string>
     <string name="permlab_accessSurfaceFlinger" msgid="2363969641792388947">"访问 SurfaceFlinger"</string>
-    <string name="permdesc_accessSurfaceFlinger" msgid="1041619516733293551">"允许应用程序使用 SurfaceFlinger 低级功能。"</string>
+    <string name="permdesc_accessSurfaceFlinger" msgid="1041619516733293551">"允许应用使用 SurfaceFlinger 低级功能。"</string>
     <string name="permlab_readFrameBuffer" msgid="6690504248178498136">"读取帧缓冲区"</string>
-    <string name="permdesc_readFrameBuffer" msgid="4937405521809454680">"允许应用程序读取帧缓冲区的内容。"</string>
+    <string name="permdesc_readFrameBuffer" msgid="4937405521809454680">"允许应用读取帧缓冲区的内容。"</string>
     <string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"更改您的音频设置"</string>
-    <string name="permdesc_modifyAudioSettings" msgid="7343951185408396919">"允许应用程序修改全局音频设置,如音量和路由。"</string>
+    <string name="permdesc_modifyAudioSettings" msgid="7343951185408396919">"允许应用修改全局音频设置,如音量和路由。"</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"录音"</string>
-    <string name="permdesc_recordAudio" msgid="2387462233976248635">"允许应用程序访问录音路径。"</string>
+    <string name="permdesc_recordAudio" msgid="2387462233976248635">"允许应用访问录音路径。"</string>
     <string name="permlab_camera" msgid="3616391919559751192">"拍摄照片和视频"</string>
-    <string name="permdesc_camera" msgid="1507407407002492176">"允许应用程序使用相机拍摄照片和视频。该权限可让应用程序随时收集相机的取景。"</string>
+    <string name="permdesc_camera" msgid="1507407407002492176">"允许应用使用相机拍摄照片和视频。该权限可让应用随时收集相机的取景。"</string>
     <string name="permlab_brick" product="tablet" msgid="2961292205764488304">"永久停用平板电脑"</string>
     <string name="permlab_brick" product="default" msgid="8337817093326370537">"永久停用手机"</string>
-    <string name="permdesc_brick" product="tablet" msgid="4334818808001699530">"允许应用程序永久停用整个平板电脑,但这样非常危险。"</string>
-    <string name="permdesc_brick" product="default" msgid="5788903297627283099">"允许应用程序永久停用整个手机,但这非常危险。"</string>
+    <string name="permdesc_brick" product="tablet" msgid="4334818808001699530">"允许应用永久停用整个平板电脑,但这样非常危险。"</string>
+    <string name="permdesc_brick" product="default" msgid="5788903297627283099">"允许应用永久停用整个手机,但这非常危险。"</string>
     <string name="permlab_reboot" product="tablet" msgid="3436634972561795002">"强行重新启动平板电脑"</string>
     <string name="permlab_reboot" product="default" msgid="2898560872462638242">"强行重新启动手机"</string>
-    <string name="permdesc_reboot" product="tablet" msgid="8172056180063700741">"允许应用程序强行重新启动平板电脑。"</string>
-    <string name="permdesc_reboot" product="default" msgid="5326008124289989969">"允许应用程序强行重新启动手机。"</string>
+    <string name="permdesc_reboot" product="tablet" msgid="8172056180063700741">"允许应用强行重新启动平板电脑。"</string>
+    <string name="permdesc_reboot" product="default" msgid="5326008124289989969">"允许应用强行重新启动手机。"</string>
     <string name="permlab_mount_unmount_filesystems" msgid="1761023272170956541">"装载和卸载文件系统"</string>
-    <string name="permdesc_mount_unmount_filesystems" msgid="1829290701658992347">"允许应用程序装载和卸载可移动存储设备的文件系统。"</string>
+    <string name="permdesc_mount_unmount_filesystems" msgid="1829290701658992347">"允许应用装载和卸载可移动存储设备的文件系统。"</string>
     <string name="permlab_mount_format_filesystems" msgid="5523285143576718981">"格式化外部存储设备"</string>
-    <string name="permdesc_mount_format_filesystems" msgid="8784268246779198627">"允许应用程序格式化可移动存储设备。"</string>
+    <string name="permdesc_mount_format_filesystems" msgid="8784268246779198627">"允许应用格式化可移动存储设备。"</string>
     <string name="permlab_asec_access" msgid="3411338632002193846">"获取有关内部存储设备的信息"</string>
-    <string name="permdesc_asec_access" msgid="3094563844593878548">"允许应用程序获取有关内存设备的信息。"</string>
+    <string name="permdesc_asec_access" msgid="3094563844593878548">"允许应用获取有关内存设备的信息。"</string>
     <string name="permlab_asec_create" msgid="6414757234789336327">"创建内部存储设备"</string>
-    <string name="permdesc_asec_create" msgid="4558869273585856876">"允许应用程序创建内存设备。"</string>
+    <string name="permdesc_asec_create" msgid="4558869273585856876">"允许应用创建内存设备。"</string>
     <string name="permlab_asec_destroy" msgid="526928328301618022">"清除内部存储设备"</string>
-    <string name="permdesc_asec_destroy" msgid="7218749286145526537">"允许应用程序清除内存设备。"</string>
+    <string name="permdesc_asec_destroy" msgid="7218749286145526537">"允许应用清除内存设备。"</string>
     <string name="permlab_asec_mount_unmount" msgid="8877998101944999386">"装载/卸载内存设备"</string>
-    <string name="permdesc_asec_mount_unmount" msgid="3451360114902490929">"允许应用程序装载/卸载内存设备。"</string>
+    <string name="permdesc_asec_mount_unmount" msgid="3451360114902490929">"允许应用装载/卸载内存设备。"</string>
     <string name="permlab_asec_rename" msgid="7496633954080472417">"重命名内部存储设备"</string>
-    <string name="permdesc_asec_rename" msgid="1794757588472127675">"允许应用程序重命名内存设备。"</string>
+    <string name="permdesc_asec_rename" msgid="1794757588472127675">"允许应用重命名内存设备。"</string>
     <string name="permlab_vibrate" msgid="7768356019980849603">"控制振动器"</string>
-    <string name="permdesc_vibrate" msgid="6284989245902300945">"允许应用程序控制振动器。"</string>
+    <string name="permdesc_vibrate" msgid="6284989245902300945">"允许应用控制振动器。"</string>
     <string name="permlab_flashlight" msgid="2155920810121984215">"控制闪光灯"</string>
-    <string name="permdesc_flashlight" msgid="6522284794568368310">"允许应用程序控制闪光灯。"</string>
+    <string name="permdesc_flashlight" msgid="6522284794568368310">"允许应用控制闪光灯。"</string>
     <string name="permlab_manageUsb" msgid="1113453430645402723">"管理 USB 设备的偏好设置和权限"</string>
-    <string name="permdesc_manageUsb" msgid="7776155430218239833">"允许应用程序管理针对 USB 设备的偏好设置和权限。"</string>
+    <string name="permdesc_manageUsb" msgid="7776155430218239833">"允许应用管理针对 USB 设备的偏好设置和权限。"</string>
     <string name="permlab_accessMtp" msgid="4953468676795917042">"应用 MTP 协议"</string>
     <string name="permdesc_accessMtp" msgid="6532961200486791570">"允许访问内核 MTP 驱动程序,以便应用 MTP USB 协议。"</string>
     <string name="permlab_hardware_test" msgid="4148290860400659146">"测试硬件"</string>
-    <string name="permdesc_hardware_test" msgid="6597964191208016605">"允许应用程序控制各种外围设备以进行硬件测试。"</string>
+    <string name="permdesc_hardware_test" msgid="6597964191208016605">"允许应用控制各种外围设备以进行硬件测试。"</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"直接拨打电话号码"</string>
-    <string name="permdesc_callPhone" msgid="6396463004110544744">"允许应用程序在没有您干预的情况下呼叫电话号码。恶意应用程序可能会产生意料之外的话费。请注意,此权限不允许应用程序呼叫紧急电话。"</string>
+    <string name="permdesc_callPhone" msgid="6396463004110544744">"允许应用在没有您干预的情况下呼叫电话号码。恶意应用可能会产生意料之外的话费。请注意,此权限不允许应用呼叫紧急电话。"</string>
     <string name="permlab_callPrivileged" msgid="4198349211108497879">"直接呼叫任何电话号码"</string>
-    <string name="permdesc_callPrivileged" msgid="1689024901509996810">"允许应用程序在没有您干预的情况下呼叫任何电话号码,包括紧急呼叫号码。恶意应用程序可能会向紧急服务进行多余以及非法呼叫。"</string>
+    <string name="permdesc_callPrivileged" msgid="1689024901509996810">"允许应用在没有您干预的情况下呼叫任何电话号码,包括紧急呼叫号码。恶意应用可能会向紧急服务进行多余以及非法呼叫。"</string>
     <string name="permlab_performCdmaProvisioning" product="tablet" msgid="4842576994144604821">"直接启动 CDMA 平板电脑设置"</string>
     <string name="permlab_performCdmaProvisioning" product="default" msgid="5604848095315421425">"直接启动 CDMA 电话设置"</string>
-    <string name="permdesc_performCdmaProvisioning" msgid="1994193538802314186">"允许应用程序启动 CDMA 配置。恶意应用程序可能会无端启动 CDMA 配置。"</string>
+    <string name="permdesc_performCdmaProvisioning" msgid="1994193538802314186">"允许应用启动 CDMA 配置。恶意应用可能会无端启动 CDMA 配置。"</string>
     <string name="permlab_locationUpdates" msgid="7785408253364335740">"控制位置更新通知"</string>
-    <string name="permdesc_locationUpdates" msgid="1120741557891438876">"允许应用程序启用/停用来自无线装置的位置更新通知。普通应用程序不能使用此权限。"</string>
+    <string name="permdesc_locationUpdates" msgid="1120741557891438876">"允许应用启用/停用来自无线装置的位置更新通知。普通应用不能使用此权限。"</string>
     <string name="permlab_checkinProperties" msgid="7855259461268734914">"访问检入属性"</string>
-    <string name="permdesc_checkinProperties" msgid="4024526968630194128">"允许应用程序对登记服务上传的属性拥有读取/写入权限。普通应用程序不能使用此权限。"</string>
+    <string name="permdesc_checkinProperties" msgid="4024526968630194128">"允许应用对登记服务上传的属性拥有读取/写入权限。普通应用不能使用此权限。"</string>
     <string name="permlab_bindGadget" msgid="776905339015863471">"选择窗口小部件"</string>
-    <string name="permdesc_bindGadget" msgid="8261326938599049290">"允许应用程序告知系统哪些窗口小部件可供哪个应用程序使用。拥有此权限的应用程序可向其他应用程序授予对个人资料的访问权限。普通应用程序不能使用此权限。"</string>
+    <string name="permdesc_bindGadget" msgid="8261326938599049290">"允许应用告知系统哪些窗口小部件可供哪个应用使用。拥有此权限的应用可向其他应用授予对个人资料的访问权限。普通应用不能使用此权限。"</string>
     <string name="permlab_modifyPhoneState" msgid="8423923777659292228">"修改手机状态"</string>
-    <string name="permdesc_modifyPhoneState" msgid="1029877529007686732">"允许应用程序控制设备的电话功能。拥有此权限的应用程序可在不通知您的情况下执行切换网络、开关手机无线装置等此类操作。"</string>
+    <string name="permdesc_modifyPhoneState" msgid="1029877529007686732">"允许应用控制设备的电话功能。拥有此权限的应用可在不通知您的情况下执行切换网络、开关手机无线装置等此类操作。"</string>
     <string name="permlab_readPhoneState" msgid="2326172951448691631">"读取手机状态和身份"</string>
-    <string name="permdesc_readPhoneState" msgid="5127767618743602782">"允许应用程序访问设备的电话功能。拥有此权限的应用程序可以确定本机的电话号码和序列号、通话是否有效、呼叫的号码等。"</string>
+    <string name="permdesc_readPhoneState" msgid="5127767618743602782">"允许应用访问设备的电话功能。拥有此权限的应用可以确定本机的电话号码和序列号、通话是否有效、呼叫的号码等。"</string>
     <string name="permlab_wakeLock" product="tablet" msgid="1531731435011495015">"阻止平板电脑进入休眠状态"</string>
     <string name="permlab_wakeLock" product="default" msgid="573480187941496130">"防止手机休眠"</string>
-    <string name="permdesc_wakeLock" product="tablet" msgid="7311319824400447868">"允许应用程序阻止平板电脑进入休眠状态。"</string>
-    <string name="permdesc_wakeLock" product="default" msgid="8559100677372928754">"允许应用程序阻止手机进入休眠状态。"</string>
+    <string name="permdesc_wakeLock" product="tablet" msgid="7311319824400447868">"允许应用阻止平板电脑进入休眠状态。"</string>
+    <string name="permdesc_wakeLock" product="default" msgid="8559100677372928754">"允许应用阻止手机进入休眠状态。"</string>
     <string name="permlab_devicePower" product="tablet" msgid="2787034722616350417">"打开或关闭平板电脑"</string>
     <string name="permlab_devicePower" product="default" msgid="4928622470980943206">"开机或关机"</string>
-    <string name="permdesc_devicePower" product="tablet" msgid="6689862878984631831">"允许应用程序打开或关闭平板电脑。"</string>
-    <string name="permdesc_devicePower" product="default" msgid="6037057348463131032">"允许应用程序打开或关闭手机。"</string>
+    <string name="permdesc_devicePower" product="tablet" msgid="6689862878984631831">"允许应用打开或关闭平板电脑。"</string>
+    <string name="permdesc_devicePower" product="default" msgid="6037057348463131032">"允许应用打开或关闭手机。"</string>
     <string name="permlab_factoryTest" msgid="3715225492696416187">"在出厂测试模式下运行"</string>
     <string name="permdesc_factoryTest" product="tablet" msgid="3952059318359653091">"作为低级制造商测试运行,从而允许对平板电脑硬件进行完全访问。此权限仅当平板电脑在制造商测试模式下运行时才可用。"</string>
     <string name="permdesc_factoryTest" product="default" msgid="8136644990319244802">"作为一项低级制造商测试来运行,从而允许对手机硬件进行完全访问。此权限仅当手机在制造商测试模式下运行时才可用。。"</string>
     <string name="permlab_setWallpaper" msgid="6627192333373465143">"设置壁纸"</string>
-    <string name="permdesc_setWallpaper" msgid="7373447920977624745">"允许应用程序设置系统壁纸。"</string>
+    <string name="permdesc_setWallpaper" msgid="7373447920977624745">"允许应用设置系统壁纸。"</string>
     <string name="permlab_setWallpaperHints" msgid="3600721069353106851">"设置有关壁纸大小的提示"</string>
-    <string name="permdesc_setWallpaperHints" msgid="8235784384223730091">"允许应用程序设置有关系统壁纸大小的提示。"</string>
+    <string name="permdesc_setWallpaperHints" msgid="8235784384223730091">"允许应用设置有关系统壁纸大小的提示。"</string>
     <string name="permlab_masterClear" msgid="2315750423139697397">"将系统恢复为出厂设置"</string>
-    <string name="permdesc_masterClear" msgid="3665380492633910226">"允许应用程序将系统完全重置为其出厂设置,这会清除所有数据、配置和已安装的应用程序。"</string>
+    <string name="permdesc_masterClear" msgid="3665380492633910226">"允许应用将系统完全重置为其出厂设置,这会清除所有数据、配置和已安装的应用。"</string>
     <string name="permlab_setTime" msgid="2021614829591775646">"设置时间"</string>
-    <string name="permdesc_setTime" product="tablet" msgid="1896341438151152881">"允许应用程序更改平板电脑的时间。"</string>
-    <string name="permdesc_setTime" product="default" msgid="1855702730738020">"允许应用程序更改手机的时间。"</string>
+    <string name="permdesc_setTime" product="tablet" msgid="1896341438151152881">"允许应用更改平板电脑的时间。"</string>
+    <string name="permdesc_setTime" product="default" msgid="1855702730738020">"允许应用更改手机的时间。"</string>
     <string name="permlab_setTimeZone" msgid="2945079801013077340">"设置时区"</string>
-    <string name="permdesc_setTimeZone" product="tablet" msgid="1676983712315827645">"允许应用程序更改平板电脑的时区。"</string>
-    <string name="permdesc_setTimeZone" product="default" msgid="4499943488436633398">"允许应用程序更改手机的时区。"</string>
+    <string name="permdesc_setTimeZone" product="tablet" msgid="1676983712315827645">"允许应用更改平板电脑的时区。"</string>
+    <string name="permdesc_setTimeZone" product="default" msgid="4499943488436633398">"允许应用更改手机的时区。"</string>
     <string name="permlab_accountManagerService" msgid="4829262349691386986">"作为 AccountManagerService"</string>
-    <string name="permdesc_accountManagerService" msgid="1948455552333615954">"允许该应用程序调用 AccountAuthenticators。"</string>
+    <string name="permdesc_accountManagerService" msgid="1948455552333615954">"允许该应用调用 AccountAuthenticators。"</string>
     <string name="permlab_getAccounts" msgid="4549918644233460103">"发现已知帐户"</string>
-    <string name="permdesc_getAccounts" product="tablet" msgid="3238360555257773358">"允许应用程序获取平板电脑已知的帐户列表。"</string>
-    <string name="permdesc_getAccounts" product="default" msgid="2735689364629830348">"允许应用程序获取手机已知的帐户列表。"</string>
+    <string name="permdesc_getAccounts" product="tablet" msgid="3238360555257773358">"允许应用获取平板电脑已知的帐户列表。"</string>
+    <string name="permdesc_getAccounts" product="default" msgid="2735689364629830348">"允许应用获取手机已知的帐户列表。"</string>
     <string name="permlab_authenticateAccounts" msgid="3940505577982882450">"作为帐户身份验证程序"</string>
-    <string name="permdesc_authenticateAccounts" msgid="5472124296908977260">"允许应用程序使用 AccountManager 的帐户身份验证程序功能,包括创建帐户以及获取和设置其密码。"</string>
+    <string name="permdesc_authenticateAccounts" msgid="5472124296908977260">"允许应用使用 AccountManager 的帐户身份验证程序功能,包括创建帐户以及获取和设置其密码。"</string>
     <string name="permlab_manageAccounts" msgid="4440380488312204365">"管理帐户列表"</string>
-    <string name="permdesc_manageAccounts" msgid="8698295625488292506">"允许应用程序执行添加帐户、删除帐户、删除帐户密码等操作。"</string>
+    <string name="permdesc_manageAccounts" msgid="8698295625488292506">"允许应用执行添加帐户、删除帐户、删除帐户密码等操作。"</string>
     <string name="permlab_useCredentials" msgid="6401886092818819856">"使用帐户的身份验证凭据"</string>
-    <string name="permdesc_useCredentials" msgid="7984227147403346422">"允许应用程序请求身份验证令牌。"</string>
+    <string name="permdesc_useCredentials" msgid="7984227147403346422">"允许应用请求身份验证令牌。"</string>
     <string name="permlab_accessNetworkState" msgid="6865575199464405769">"查看网络状态"</string>
-    <string name="permdesc_accessNetworkState" msgid="479772796952547198">"允许应用程序查看所有网络的状态。"</string>
+    <string name="permdesc_accessNetworkState" msgid="479772796952547198">"允许应用查看所有网络的状态。"</string>
     <string name="permlab_createNetworkSockets" msgid="9121633680349549585">"完全的互联网访问权限"</string>
-    <string name="permdesc_createNetworkSockets" msgid="5963922297444265950">"允许应用程序创建网络套接字。"</string>
+    <string name="permdesc_createNetworkSockets" msgid="5963922297444265950">"允许应用创建网络套接字。"</string>
     <string name="permlab_writeApnSettings" msgid="505660159675751896">"更改/拦截网络设置和流量"</string>
-    <string name="permdesc_writeApnSettings" msgid="5333798886412714193">"允许应用程序更改网络设置,并拦截和检查所有网络流量,例如更改任意 APN 的代理和端口。恶意应用程序可能会在您不知情的情况下监视、重定向或修改网络数据包。"</string>
+    <string name="permdesc_writeApnSettings" msgid="5333798886412714193">"允许应用更改网络设置,并拦截和检查所有网络流量,例如更改任意 APN 的代理和端口。恶意应用可能会在您不知情的情况下监视、重定向或修改网络数据包。"</string>
     <string name="permlab_changeNetworkState" msgid="958884291454327309">"更改网络连接性"</string>
-    <string name="permdesc_changeNetworkState" msgid="6789123912476416214">"允许应用程序更改网络连接的状态。"</string>
+    <string name="permdesc_changeNetworkState" msgid="6789123912476416214">"允许应用更改网络连接的状态。"</string>
     <string name="permlab_changeTetherState" msgid="5952584964373017960">"更改网络共享连接"</string>
-    <string name="permdesc_changeTetherState" msgid="1524441344412319780">"允许应用程序更改绑定网络连接的状态。"</string>
+    <string name="permdesc_changeTetherState" msgid="1524441344412319780">"允许应用更改绑定网络连接的状态。"</string>
     <string name="permlab_changeBackgroundDataSetting" msgid="1400666012671648741">"更改后台数据使用设置"</string>
-    <string name="permdesc_changeBackgroundDataSetting" msgid="5347729578468744379">"允许应用程序更改后台数据使用设置。"</string>
+    <string name="permdesc_changeBackgroundDataSetting" msgid="5347729578468744379">"允许应用更改后台数据使用设置。"</string>
     <string name="permlab_accessWifiState" msgid="8100926650211034400">"查看 Wi-Fi 状态"</string>
-    <string name="permdesc_accessWifiState" msgid="7770452658226256831">"允许应用程序查看有关 Wi-Fi 状态的信息。"</string>
+    <string name="permdesc_accessWifiState" msgid="7770452658226256831">"允许应用查看有关 Wi-Fi 状态的信息。"</string>
     <string name="permlab_changeWifiState" msgid="7280632711057112137">"更改 Wi-Fi 状态"</string>
-    <string name="permdesc_changeWifiState" msgid="7399961004537946240">"允许应用程序与 Wi-Fi 接入点建立和断开连接,并对配置的 Wi-Fi 网络进行更改。"</string>
+    <string name="permdesc_changeWifiState" msgid="7399961004537946240">"允许应用与 Wi-Fi 接入点建立和断开连接,并对配置的 Wi-Fi 网络进行更改。"</string>
     <string name="permlab_changeWifiMulticastState" msgid="1368253871483254784">"允许接收 Wi-Fi 多播"</string>
-    <string name="permdesc_changeWifiMulticastState" msgid="7633598524564320817">"允许应用程序接收并非直接发送至您设备的数据包。在查找附近提供的服务时,此权限很有用。这种操作所耗电量大于非多播模式。"</string>
+    <string name="permdesc_changeWifiMulticastState" msgid="7633598524564320817">"允许应用接收并非直接发送至您设备的数据包。在查找附近提供的服务时,此权限很有用。这种操作所耗电量大于非多播模式。"</string>
     <string name="permlab_bluetoothAdmin" msgid="3606576270792236062">"蓝牙管理"</string>
-    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"允许应用程序配置本地蓝牙平板电脑,以及发现远程设备并进行配对。"</string>
-    <string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"允许应用程序配置本地蓝牙手机,以及发现远程设备并进行配对。"</string>
+    <string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"允许应用配置本地蓝牙平板电脑,以及发现远程设备并进行配对。"</string>
+    <string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"允许应用配置本地蓝牙手机,以及发现远程设备并进行配对。"</string>
     <string name="permlab_accessWimaxState" msgid="1232061307208861588">"查看 WiMAX 状态"</string>
-    <string name="permdesc_accessWimaxState" msgid="5914958077555177749">"允许该应用程序查看有关 WiMAX 状态的信息。"</string>
+    <string name="permdesc_accessWimaxState" msgid="5914958077555177749">"允许该应用查看有关 WiMAX 状态的信息。"</string>
     <string name="permlab_changeWimaxState" msgid="2405042267131496579">"更改 WiMAX 状态"</string>
-    <string name="permdesc_changeWimaxState" msgid="3328853825006455912">"允许该应用程序连接到 WiMAX 网络以及从 WiMAX 网络断开连接。"</string>
+    <string name="permdesc_changeWimaxState" msgid="3328853825006455912">"允许该应用连接到 WiMAX 网络以及从 WiMAX 网络断开连接。"</string>
     <string name="permlab_bluetooth" msgid="8361038707857018732">"创建蓝牙连接"</string>
-    <string name="permdesc_bluetooth" product="tablet" msgid="7007851048416363446">"允许应用程序查看本地蓝牙平板电脑的配置,以及建立和接受与配对设备的连接。"</string>
-    <string name="permdesc_bluetooth" product="default" msgid="31846362767164948">"允许应用程序查看本地蓝牙手机的配置,以及建立和接受与配对设备的连接。"</string>
+    <string name="permdesc_bluetooth" product="tablet" msgid="7007851048416363446">"允许应用查看本地蓝牙平板电脑的配置,以及建立和接受与配对设备的连接。"</string>
+    <string name="permdesc_bluetooth" product="default" msgid="31846362767164948">"允许应用查看本地蓝牙手机的配置,以及建立和接受与配对设备的连接。"</string>
     <string name="permlab_nfc" msgid="4423351274757876953">"控制近距离通信"</string>
-    <string name="permdesc_nfc" msgid="7120611819401789907">"允许应用程序与近距离无线通信 (NFC) 标记、卡和阅读器进行通信。"</string>
+    <string name="permdesc_nfc" msgid="7120611819401789907">"允许应用与近距离无线通信 (NFC) 标记、卡和阅读器进行通信。"</string>
     <string name="permlab_disableKeyguard" msgid="4977406164311535092">"停用键锁"</string>
-    <string name="permdesc_disableKeyguard" msgid="6231611286892232626">"允许应用程序停用键锁和任何关联的密码安全设置。例如,在手机上接听电话时停用键锁,在通话结束后重新启用键锁。"</string>
+    <string name="permdesc_disableKeyguard" msgid="6231611286892232626">"允许应用停用键锁和任何关联的密码安全设置。例如,在手机上接听电话时停用键锁,在通话结束后重新启用键锁。"</string>
     <string name="permlab_readSyncSettings" msgid="6201810008230503052">"读取同步设置"</string>
-    <string name="permdesc_readSyncSettings" msgid="5464056785274229278">"允许应用程序读取同步设置,例如是否对“联系人”应用程序启用同步功能。"</string>
+    <string name="permdesc_readSyncSettings" msgid="5464056785274229278">"允许应用读取同步设置,例如是否对“联系人”应用启用同步功能。"</string>
     <string name="permlab_writeSyncSettings" msgid="6297138566442486462">"写入同步设置"</string>
-    <string name="permdesc_writeSyncSettings" msgid="1466056564502117130">"允许应用程序修改同步设置,例如是否对“联系人”应用程序启用同步功能。"</string>
+    <string name="permdesc_writeSyncSettings" msgid="1466056564502117130">"允许应用修改同步设置,例如是否对“联系人”应用启用同步功能。"</string>
     <string name="permlab_readSyncStats" msgid="7396577451360202448">"读取同步统计信息"</string>
-    <string name="permdesc_readSyncStats" msgid="3801971839939951678">"允许应用程序读取同步统计信息;例如,已发生的同步历史记录。"</string>
+    <string name="permdesc_readSyncStats" msgid="3801971839939951678">"允许应用读取同步统计信息;例如,已发生的同步历史记录。"</string>
     <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"读取订阅的供稿"</string>
-    <string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"允许应用程序获取有关当前同步的 Feed 的详情。"</string>
+    <string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"允许应用获取有关当前同步的 Feed 的详情。"</string>
     <string name="permlab_subscribedFeedsWrite" msgid="9015246325408209296">"写入订阅的供稿"</string>
-    <string name="permdesc_subscribedFeedsWrite" msgid="6928930188826089413">"允许应用程序修改您当前同步的 Feed。恶意应用程序可能会更改您的同步 Feed。"</string>
+    <string name="permdesc_subscribedFeedsWrite" msgid="6928930188826089413">"允许应用修改您当前同步的 Feed。恶意应用可能会更改您的同步 Feed。"</string>
     <string name="permlab_readDictionary" msgid="8410247960433376352">"读取用户定义的词典"</string>
-    <string name="permdesc_readDictionary" msgid="8977815988329283705">"允许应用程序读取用户可能在用户词典中已存储的任意私有字词、名称和短语。"</string>
+    <string name="permdesc_readDictionary" msgid="8977815988329283705">"允许应用读取用户可能在用户词典中已存储的任意私有字词、名称和短语。"</string>
     <string name="permlab_writeDictionary" msgid="2296383164914812772">"写入用户定义的词典"</string>
-    <string name="permdesc_writeDictionary" msgid="8185385716255065291">"允许应用程序向用户词典中写入新词。"</string>
+    <string name="permdesc_writeDictionary" msgid="8185385716255065291">"允许应用向用户词典中写入新词。"</string>
     <string name="permlab_sdcardRead" product="nosdcard" msgid="4086221374639183281">"读取 USB 存储设备的内容"</string>
     <string name="permlab_sdcardRead" product="default" msgid="8537875151845139539">"读取 SD 卡的内容"</string>
     <string name="permdesc_sdcardRead" product="nosdcard" msgid="1055302898999352339">"允许应用读取 USB 存储设备的内容。"</string>
     <string name="permdesc_sdcardRead" product="default" msgid="7947792373570683542">"允许应用读取 SD 卡的内容。"</string>
     <string name="permlab_sdcardWrite" product="nosdcard" msgid="85430876310764752">"修改/删除 USB 存储设备内容"</string>
     <string name="permlab_sdcardWrite" product="default" msgid="8079403759001777291">"修改/删除 SD 卡中的内容"</string>
-    <string name="permdesc_sdcardWrite" product="nosdcard" msgid="6175406299445710888">"允许应用程序写入 USB 存储设备。"</string>
-    <string name="permdesc_sdcardWrite" product="default" msgid="4337417790936632090">"允许应用程序写入 SD 卡。"</string>
+    <string name="permdesc_sdcardWrite" product="nosdcard" msgid="6175406299445710888">"允许应用写入 USB 存储设备。"</string>
+    <string name="permdesc_sdcardWrite" product="default" msgid="4337417790936632090">"允许应用写入 SD 卡。"</string>
     <string name="permlab_mediaStorageWrite" product="default" msgid="6859839199706879015">"修改/删除内部媒体存储设备的内容"</string>
-    <string name="permdesc_mediaStorageWrite" product="default" msgid="8189160597698529185">"允许应用程序修改内部媒体存储设备的内容。"</string>
+    <string name="permdesc_mediaStorageWrite" product="default" msgid="8189160597698529185">"允许应用修改内部媒体存储设备的内容。"</string>
     <string name="permlab_cache_filesystem" msgid="5656487264819669824">"访问缓存文件系统"</string>
-    <string name="permdesc_cache_filesystem" msgid="5578967642265550955">"允许应用程序读取和写入缓存文件系统。"</string>
+    <string name="permdesc_cache_filesystem" msgid="5578967642265550955">"允许应用读取和写入缓存文件系统。"</string>
     <string name="permlab_use_sip" msgid="5986952362795870502">"拨打/接听互联网通话"</string>
-    <string name="permdesc_use_sip" msgid="4717632000062674294">"允许应用程序使用 SIP 服务拨打/接听互联网电话。"</string>
+    <string name="permdesc_use_sip" msgid="4717632000062674294">"允许应用使用 SIP 服务拨打/接听互联网电话。"</string>
     <string name="permlab_readNetworkUsageHistory" msgid="7862593283611493232">"读取网络使用情况历史记录"</string>
-    <string name="permdesc_readNetworkUsageHistory" msgid="7689060749819126472">"允许应用程序读取特定网络和应用程序的网络使用情况历史记录。"</string>
+    <string name="permdesc_readNetworkUsageHistory" msgid="7689060749819126472">"允许应用读取特定网络和应用的网络使用情况历史记录。"</string>
     <string name="permlab_manageNetworkPolicy" msgid="2562053592339859990">"管理网络政策"</string>
-    <string name="permdesc_manageNetworkPolicy" msgid="7537586771559370668">"允许应用程序管理网络政策和定义专门针对应用程序的规则。"</string>
+    <string name="permdesc_manageNetworkPolicy" msgid="7537586771559370668">"允许应用管理网络政策和定义专门针对应用的规则。"</string>
     <string name="permlab_modifyNetworkAccounting" msgid="5088217309088729650">"修改网络使用情况记录方式"</string>
-    <string name="permdesc_modifyNetworkAccounting" msgid="5443412866746198123">"允许该应用程序修改对于各应用程序的网络使用情况的统计方式。普通应用程序不应使用此权限。"</string>
+    <string name="permdesc_modifyNetworkAccounting" msgid="5443412866746198123">"允许该应用修改对于各应用的网络使用情况的统计方式。普通应用不应使用此权限。"</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"设置密码规则"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"控制屏幕解锁密码所允许的长度和字符。"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"监视屏幕解锁尝试次数"</string>
@@ -542,7 +542,7 @@
     <string name="policylab_expirePassword" msgid="885279151847254056">"设置锁定屏幕密码的有效期"</string>
     <string name="policydesc_expirePassword" msgid="1729725226314691591">"控制系统强制用户更改屏幕锁定密码的频率。"</string>
     <string name="policylab_encryptedStorage" msgid="8901326199909132915">"设置存储设备加密"</string>
-    <string name="policydesc_encryptedStorage" msgid="2637732115325316992">"要求对存储的应用程序数据进行加密。"</string>
+    <string name="policydesc_encryptedStorage" msgid="2637732115325316992">"要求对存储的应用数据进行加密。"</string>
     <string name="policylab_disableCamera" msgid="6395301023152297826">"停用相机"</string>
     <string name="policydesc_disableCamera" msgid="2306349042834754597">"禁止使用所有设备摄像头。"</string>
   <string-array name="phoneTypes">
@@ -760,20 +760,20 @@
     <string name="autofill_area" msgid="3547409050889952423">"区域"</string>
     <string name="autofill_emirate" msgid="2893880978835698818">"酋长国"</string>
     <string name="permlab_readHistoryBookmarks" msgid="1284843728203412135">"读取浏览器的历史记录和书签"</string>
-    <string name="permdesc_readHistoryBookmarks" msgid="4577476392604595921">"允许应用程序读取“浏览器”访问过的所有网址,以及“浏览器”的所有书签。"</string>
+    <string name="permdesc_readHistoryBookmarks" msgid="4577476392604595921">"允许应用读取“浏览器”访问过的所有网址,以及“浏览器”的所有书签。"</string>
     <string name="permlab_writeHistoryBookmarks" msgid="9009434109836280374">"写入浏览器的历史记录和书签"</string>
-    <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="1757103804824209530">"允许应用程序修改“浏览器”存储在平板电脑上的历史记录或书签。恶意应用程序可能借此删除或修改“浏览器”的数据。"</string>
-    <string name="permdesc_writeHistoryBookmarks" product="default" msgid="6693764355720719197">"允许应用程序修改“浏览器”存储在手机上的历史记录或书签。恶意应用程序可能借此删除或修改“浏览器”的数据。"</string>
+    <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="1757103804824209530">"允许应用修改“浏览器”存储在平板电脑上的历史记录或书签。恶意应用可能借此删除或修改“浏览器”的数据。"</string>
+    <string name="permdesc_writeHistoryBookmarks" product="default" msgid="6693764355720719197">"允许应用修改“浏览器”存储在手机上的历史记录或书签。恶意应用可能借此删除或修改“浏览器”的数据。"</string>
     <string name="permlab_setAlarm" msgid="5924401328803615165">"在闹钟中设置警报"</string>
-    <string name="permdesc_setAlarm" msgid="316392039157473848">"允许应用程序在已安装的闹钟应用程序中设置闹钟。有些闹钟应用程序可能无法实现此功能。"</string>
+    <string name="permdesc_setAlarm" msgid="316392039157473848">"允许应用在已安装的闹钟应用中设置闹钟。有些闹钟应用可能无法实现此功能。"</string>
     <string name="permlab_addVoicemail" msgid="5525660026090959044">"添加语音邮件"</string>
-    <string name="permdesc_addVoicemail" msgid="6604508651428252437">"允许应用程序向您的语音信箱收件箱添加邮件。"</string>
+    <string name="permdesc_addVoicemail" msgid="6604508651428252437">"允许应用向您的语音信箱收件箱添加邮件。"</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"修改“浏览器”地理位置的权限"</string>
-    <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"允许应用程序修改“浏览器”的地理位置权限。恶意应用程序可能借此向任意网站发送位置信息。"</string>
+    <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"允许应用修改“浏览器”的地理位置权限。恶意应用可能借此向任意网站发送位置信息。"</string>
     <string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"验证软件包"</string>
-    <string name="permdesc_packageVerificationAgent" msgid="8437590190990843381">"允许应用程序验证程序包是否可以安装。"</string>
+    <string name="permdesc_packageVerificationAgent" msgid="8437590190990843381">"允许应用验证程序包是否可以安装。"</string>
     <string name="permlab_bindPackageVerifier" msgid="4187786793360326654">"绑定到软件包验证程序"</string>
-    <string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"允许用户请求使用程序包验证程序。普通应用程序绝不需要此权限。"</string>
+    <string name="permdesc_bindPackageVerifier" msgid="3180741773233862126">"允许用户请求使用程序包验证程序。普通应用绝不需要此权限。"</string>
     <string name="permlab_serialPort" msgid="546083327654631076">"访问串行端口"</string>
     <string name="permdesc_serialPort" msgid="2991639985224598193">"允许持有人使用 SerialManager API 访问串行端口。"</string>
     <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"从外部访问内容提供程序"</string>
@@ -918,12 +918,12 @@
     <string name="loading" msgid="7933681260296021180">"正在加载..."</string>
     <string name="capital_on" msgid="1544682755514494298">"打开"</string>
     <string name="capital_off" msgid="6815870386972805832">"关闭"</string>
-    <string name="whichApplication" msgid="4533185947064773386">"选择要使用的应用程序:"</string>
+    <string name="whichApplication" msgid="4533185947064773386">"选择要使用的应用:"</string>
     <string name="alwaysUse" msgid="4583018368000610438">"设为默认选项。"</string>
-    <string name="clearDefaultHintMsg" msgid="3252584689512077257">"在“系统设置”&gt;“应用程序”&gt;“已下载”中清除默认设置。"</string>
+    <string name="clearDefaultHintMsg" msgid="3252584689512077257">"在“系统设置”&gt;“应用”&gt;“已下载”中清除默认设置。"</string>
     <string name="chooseActivity" msgid="7486876147751803333">"选择操作"</string>
-    <string name="chooseUsbActivity" msgid="6894748416073583509">"为 USB 设备选择一个应用程序"</string>
-    <string name="noApplications" msgid="2991814273936504689">"没有应用程序可执行此操作。"</string>
+    <string name="chooseUsbActivity" msgid="6894748416073583509">"为 USB 设备选择一个应用"</string>
+    <string name="noApplications" msgid="2991814273936504689">"没有应用可执行此操作。"</string>
     <string name="aerr_title" msgid="1905800560317137752"></string>
     <string name="aerr_application" msgid="932628488013092776">"很抱歉,“<xliff:g id="APPLICATION">%1$s</xliff:g>”已停止运行。"</string>
     <string name="aerr_process" msgid="4507058997035697579">"抱歉,进程“<xliff:g id="PROCESS">%1$s</xliff:g>”已停止运行。"</string>
@@ -936,26 +936,26 @@
     <string name="report" msgid="4060218260984795706">"报告"</string>
     <string name="wait" msgid="7147118217226317732">"等待"</string>
     <string name="webpage_unresponsive" msgid="3272758351138122503">"该网页已无响应。"\n\n"要将其关闭吗?"</string>
-    <string name="launch_warning_title" msgid="1547997780506713581">"应用程序已重定向"</string>
+    <string name="launch_warning_title" msgid="1547997780506713581">"应用已重定向"</string>
     <string name="launch_warning_replace" msgid="6202498949970281412">"<xliff:g id="APP_NAME">%1$s</xliff:g>目前正在运行。"</string>
     <string name="launch_warning_original" msgid="188102023021668683">"<xliff:g id="APP_NAME">%1$s</xliff:g>已启动。"</string>
     <string name="screen_compat_mode_scale" msgid="3202955667675944499">"缩放"</string>
     <string name="screen_compat_mode_show" msgid="4013878876486655892">"始终显示"</string>
-    <string name="screen_compat_mode_hint" msgid="1064524084543304459">"在“系统设置”&gt;“应用程序”&gt;“已下载”中重新启用此模式。"</string>
-    <string name="smv_application" msgid="3307209192155442829">"“<xliff:g id="APPLICATION">%1$s</xliff:g>”应用程序(<xliff:g id="PROCESS">%2$s</xliff:g> 进程)违反了自我强制执行的严格模式 (StrictMode) 政策。"</string>
+    <string name="screen_compat_mode_hint" msgid="1064524084543304459">"在“系统设置”&gt;“应用”&gt;“已下载”中重新启用此模式。"</string>
+    <string name="smv_application" msgid="3307209192155442829">"“<xliff:g id="APPLICATION">%1$s</xliff:g>”应用(<xliff:g id="PROCESS">%2$s</xliff:g> 进程)违反了自我强制执行的严格模式 (StrictMode) 政策。"</string>
     <string name="smv_process" msgid="5120397012047462446">"进程 <xliff:g id="PROCESS">%1$s</xliff:g> 违反了自我强制执行的严格模式 (StrictMode) 政策。"</string>
     <string name="android_upgrading_title" msgid="1584192285441405746">"Android 正在升级..."</string>
     <string name="android_upgrading_apk" msgid="7904042682111526169">"正在优化第 <xliff:g id="NUMBER_0">%1$d</xliff:g> 个(共 <xliff:g id="NUMBER_1">%2$d</xliff:g> 个)。"</string>
-    <string name="android_upgrading_starting_apps" msgid="451464516346926713">"正在启动应用程序。"</string>
+    <string name="android_upgrading_starting_apps" msgid="451464516346926713">"正在启动应用。"</string>
     <string name="android_upgrading_complete" msgid="1405954754112999229">"即将完成启动。"</string>
     <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g>正在运行"</string>
-    <string name="heavy_weight_notification_detail" msgid="1721681741617898865">"触摸可切换至应用程序"</string>
-    <string name="heavy_weight_switcher_title" msgid="7153167085403298169">"切换应用程序吗?"</string>
-    <string name="heavy_weight_switcher_text" msgid="7022631924534406403">"已有一个应用程序正在运行,要启动新的应用程序,您必须先停止该应用程序。"</string>
+    <string name="heavy_weight_notification_detail" msgid="1721681741617898865">"触摸可切换至应用"</string>
+    <string name="heavy_weight_switcher_title" msgid="7153167085403298169">"切换应用吗?"</string>
+    <string name="heavy_weight_switcher_text" msgid="7022631924534406403">"已有一个应用正在运行,要启动新的应用,您必须先停止该应用。"</string>
     <string name="old_app_action" msgid="493129172238566282">"返回至<xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="old_app_description" msgid="2082094275580358049">"不启动新的应用程序。"</string>
+    <string name="old_app_description" msgid="2082094275580358049">"不启动新的应用。"</string>
     <string name="new_app_action" msgid="5472756926945440706">"启动<xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
-    <string name="new_app_description" msgid="1932143598371537340">"停止旧的应用程序,但不保存。"</string>
+    <string name="new_app_description" msgid="1932143598371537340">"停止旧的应用,但不保存。"</string>
     <string name="sendText" msgid="5209874571959469142">"选择要对文字执行的操作"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"铃声音量"</string>
     <string name="volume_music" msgid="5421651157138628171">"媒体音量"</string>
@@ -1003,7 +1003,7 @@
     <string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"键入所需的 PIN:"</string>
     <string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
     <string name="select_character" msgid="3365550120617701745">"插入字符"</string>
-    <string name="sms_control_default_app_name" msgid="3058577482636640465">"未知应用程序"</string>
+    <string name="sms_control_default_app_name" msgid="3058577482636640465">"未知应用"</string>
     <string name="sms_control_title" msgid="7296612781128917719">"正在发送短信"</string>
     <string name="sms_control_message" msgid="4073755190243093924">"正在发送大量短信。触摸“确定”继续,或触摸“取消”停止发送。"</string>
     <string name="sms_control_yes" msgid="2532062172402615953">"确定"</string>
@@ -1039,7 +1039,7 @@
     <string name="usb_storage_stop_button_mount" msgid="7060218034900696029">"关闭 USB 存储设备"</string>
     <string name="usb_storage_stop_error_message" msgid="1970374898263063836">"关闭 USB 存储设备时遇到问题。请检查您是否已卸载了 USB 主设备,然后重试。"</string>
     <string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"打开 USB 存储设备"</string>
-    <string name="dlg_confirm_kill_storage_users_text" msgid="5100428757107469454">"如果您打开 USB 存储设备,您正在使用的某些应用程序将会停止,并且在您关闭 USB 存储设备前都将无法使用。"</string>
+    <string name="dlg_confirm_kill_storage_users_text" msgid="5100428757107469454">"如果您打开 USB 存储设备,您正在使用的某些应用将会停止,并且在您关闭 USB 存储设备前都将无法使用。"</string>
     <string name="dlg_error_title" msgid="7323658469626514207">"USB 操作失败"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"确定"</string>
     <string name="usb_mtp_notification_title" msgid="3699913097391550394">"作为媒体设备连接"</string>
@@ -1084,9 +1084,9 @@
     <string name="ext_media_nomedia_notification_message" product="default" msgid="3870120652983659641">"SD 卡已移除。请插入新的 SD 卡。"</string>
     <string name="activity_list_empty" msgid="1675388330786841066">"未找到匹配的活动。"</string>
     <string name="permlab_pkgUsageStats" msgid="8787352074326748892">"更新组件使用情况统计"</string>
-    <string name="permdesc_pkgUsageStats" msgid="1106612424254277630">"允许应用程序修改收集到的组件使用情况统计信息。普通应用程序不能使用此权限。"</string>
+    <string name="permdesc_pkgUsageStats" msgid="1106612424254277630">"允许应用修改收集到的组件使用情况统计信息。普通应用不能使用此权限。"</string>
     <string name="permlab_copyProtectedData" msgid="4341036311211406692">"复制内容"</string>
-    <string name="permdesc_copyProtectedData" msgid="4390697124288317831">"允许应用程序调用默认的容器服务,以便复制内容。普通应用程序不能使用此权限。"</string>
+    <string name="permdesc_copyProtectedData" msgid="4390697124288317831">"允许应用调用默认的容器服务,以便复制内容。普通应用不能使用此权限。"</string>
     <string name="tutorial_double_tap_to_zoom_message_short" msgid="4070433208160063538">"触摸两次可进行缩放控制"</string>
     <string name="gadget_host_error_inflating" msgid="4882004314906466162">"无法添加窗口小部件。"</string>
     <string name="ime_action_go" msgid="8320845651737369027">"开始"</string>
@@ -1098,13 +1098,13 @@
     <string name="ime_action_default" msgid="2840921885558045721">"执行"</string>
     <string name="dial_number_using" msgid="5789176425167573586">"拨打电话"\n"<xliff:g id="NUMBER">%s</xliff:g>"</string>
     <string name="create_contact_using" msgid="4947405226788104538">"创建电话号码为"\n"<xliff:g id="NUMBER">%s</xliff:g> 的联系人"</string>
-    <string name="grant_credentials_permission_message_header" msgid="2106103817937859662">"以下一个或多个应用程序请求获得相应权限,以便在当前和以后访问您的帐户。"</string>
+    <string name="grant_credentials_permission_message_header" msgid="2106103817937859662">"以下一个或多个应用请求获得相应权限,以便在当前和以后访问您的帐户。"</string>
     <string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"您是否同意此请求?"</string>
     <string name="grant_permissions_header_text" msgid="6874497408201826708">"访问权限请求"</string>
     <string name="allow" msgid="7225948811296386551">"允许"</string>
     <string name="deny" msgid="2081879885755434506">"拒绝"</string>
     <string name="permission_request_notification_title" msgid="6486759795926237907">"许可权限请求"</string>
-    <string name="permission_request_notification_with_subtitle" msgid="8530393139639560189">"应用程序对帐户 <xliff:g id="ACCOUNT">%s</xliff:g>"\n" 提出权限请求。"</string>
+    <string name="permission_request_notification_with_subtitle" msgid="8530393139639560189">"应用对帐户 <xliff:g id="ACCOUNT">%s</xliff:g>"\n" 提出权限请求。"</string>
     <string name="input_method_binding_label" msgid="1283557179944992649">"输入法"</string>
     <string name="sync_binding_label" msgid="3687969138375092423">"同步"</string>
     <string name="accessibility_binding_label" msgid="4148120742096474641">"辅助功能"</string>
@@ -1197,7 +1197,7 @@
     <string name="keyboardview_keycode_mode_change" msgid="4547387741906537519">"模式更改"</string>
     <string name="keyboardview_keycode_shift" msgid="2270748814315147690">"Shift"</string>
     <string name="keyboardview_keycode_enter" msgid="2985864015076059467">"Enter"</string>
-    <string name="activitychooserview_choose_application" msgid="2125168057199941199">"选择应用程序"</string>
+    <string name="activitychooserview_choose_application" msgid="2125168057199941199">"选择应用"</string>
     <string name="shareactionprovider_share_with" msgid="806688056141131819">"共享对象"</string>
     <string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"与“<xliff:g id="APPLICATION_NAME">%s</xliff:g>”共享"</string>
     <string name="content_description_sliding_handle" msgid="415975056159262248">"滑动手柄。触摸并按住。"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index e81e393..447af8a 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -336,11 +336,11 @@
     <string name="permdesc_writeContacts" product="tablet" msgid="988969759110632978">"允許應用程式修改平板電腦上儲存的聯絡人 (地址) 資料。請注意,惡意應用程式可能利用此功能清除或修改您的聯絡人資料。"</string>
     <string name="permdesc_writeContacts" product="default" msgid="5075164818647934067">"允許應用程式修改手機上儲存的聯絡人 (地址) 資料。請注意,惡意應用程式可能利用此功能清除或修改您的聯絡人資料。"</string>
     <string name="permlab_readCallLog" msgid="3478133184624102739">"讀取通話紀錄"</string>
-    <string name="permdesc_readCallLog" product="tablet" msgid="3995157599976515002">"允許應用程式讀取平板電腦的通話紀錄,包括來電和已撥電話相關資料。請注意,惡意應用程式可能利用此功能將您的資料傳送給他人。"</string>
-    <string name="permdesc_readCallLog" product="default" msgid="3452017559804750758">"允許應用程式讀取手機的通話紀錄,包括來電和已撥電話相關資料。請注意,惡意應用程式可能利用此功能將您的資料傳送給他人。"</string>
+    <string name="permdesc_readCallLog" product="tablet" msgid="3995157599976515002">"允許應用程式讀取平板電腦的通話紀錄,包括來電和已撥電話相關資料。請注意,惡意應用程式可能濫用此功能將您的資料傳送給他人。"</string>
+    <string name="permdesc_readCallLog" product="default" msgid="3452017559804750758">"允許應用程式讀取手機的通話紀錄,包括來電和已撥電話相關資料。請注意,惡意應用程式可能濫用此功能將您的資料傳送給他人。"</string>
     <string name="permlab_writeCallLog" msgid="8552045664743499354">"寫入通話紀錄"</string>
-    <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"允許應用程式修改平板電腦的通話紀錄,包括來電和已撥電話相關資料。請注意,惡意應用程式可能利用此功能刪除或修改您的通話記錄。"</string>
-    <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"允許應用程式修改手機的通話紀錄,包括來電和已撥電話相關資料。請注意,惡意應用程式可能利用此功能刪除或修改您的通話記錄。"</string>
+    <string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"允許應用程式修改平板電腦的通話紀錄,包括來電和已撥電話相關資料。請注意,惡意應用程式可能濫用此功能刪除或修改您的通話紀錄。"</string>
+    <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"允許應用程式修改手機的通話紀錄,包括來電和已撥電話相關資料。請注意,惡意應用程式可能濫用此功能刪除或修改您的通話紀錄。"</string>
     <string name="permlab_readProfile" msgid="6824681438529842282">"讀取您的個人資料"</string>
     <string name="permdesc_readProfile" product="default" msgid="94520753797630679">"允許應用程式讀取裝置上儲存的個人資料,例如您的姓名和聯絡資訊。這表示應用程式可以識別您的身分,並將您的個人資料傳送給他人。"</string>
     <string name="permlab_writeProfile" msgid="4679878325177177400">"寫入您的個人資料"</string>
@@ -743,8 +743,7 @@
     <string name="double_tap_toast" msgid="4595046515400268881">"提示:輕按兩下即可縮放。"</string>
     <string name="autofill_this_form" msgid="4616758841157816676">"自動填入功能"</string>
     <string name="setup_autofill" msgid="7103495070180590814">"設定自動填入功能"</string>
-    <!-- no translation found for autofill_address_name_separator (6350145154779706772) -->
-    <skip />
+    <string name="autofill_address_name_separator" msgid="6350145154779706772">" //*** Empty segment here as a separator ***//"</string>
     <string name="autofill_address_summary_name_format" msgid="3268041054899214945">"$1$2$3"</string>
     <string name="autofill_address_summary_separator" msgid="7483307893170324129">", "</string>
     <string name="autofill_address_summary_format" msgid="4874459455786827344">"$1$2$3"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index db908ca..428790f 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -589,12 +589,6 @@
 
         <!-- NumberPicker style. -->
         <attr name="numberPickerStyle" format="reference" />
-        <!-- NumberPicker up button style. -->
-        <attr name="numberPickerUpButtonStyle" format="reference" />
-        <!-- NumberPicker down button style. -->
-        <attr name="numberPickerDownButtonStyle" format="reference" />
-        <!-- NumberPicker input text style. -->
-        <attr name="numberPickerInputTextStyle" format="reference" />
 
         <!-- The CalendarView style. -->
         <attr name="calendarViewStyle" format="reference" />
@@ -3617,12 +3611,12 @@
     <declare-styleable name="NumberPicker">
         <!-- @hide Color for the solid color background if such for optimized rendering. -->
         <attr name="solidColor" format="color|reference" />
-        <!-- @hide Whether the number picker supports fligning. -->
-        <attr name="flingable" format="boolean" />
         <!-- @hide The divider for making the selection area. -->
         <attr name="selectionDivider" format="reference" />
         <!-- @hide The height of the selection divider. -->
         <attr name="selectionDividerHeight" format="dimension" />
+        <!-- @hide The distance between the two selection dividers. -->
+        <attr name="selectionDividersDistance" format="dimension" />
         <!-- @hide The min height of the NumberPicker. -->
         <attr name="internalMinHeight" format="dimension" />
         <!-- @hide The max height of the NumberPicker. -->
@@ -3631,6 +3625,10 @@
         <attr name="internalMinWidth" format="dimension" />
         <!-- @hide The max width of the NumberPicker. -->
         <attr name="internalMaxWidth" format="dimension" />
+        <!-- @hide The layout of the number picker. -->
+        <attr name="internalLayout" />
+        <!-- @hide The minimal move distance of a swipe to be considered a fling. -->
+       <attr name="minFlingDistance" format="dimension" />
     </declare-styleable>
 
     <declare-styleable name="TimePicker">
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index b1a4b42..5ff2c01 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -113,7 +113,7 @@
     <color name="lockscreen_owner_info">#ff9a9a9a</color>
 
     <!-- FaceLock -->
-    <color name="facelock_color_background">#000000</color>
+    <color name="facelock_spotlight_mask">#CC000000</color>
 
     <!-- For holo theme -->
       <drawable name="screen_background_holo_light">#fff3f3f3</drawable>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4d9b043a..4fde018 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -284,6 +284,10 @@
          point on the move. A value of 0 means no periodic scans will be used in the framework. -->
     <integer translatable="false" name="config_wifi_framework_scan_interval">300000</integer>
 
+    <!-- Wifi driver stop delay, in milliseconds.
+         Default value is 2 minutes. -->
+    <integer translatable="false" name="config_wifi_driver_stop_delay">120000</integer>
+
     <!-- Flag indicating whether the keyguard should be bypassed when
          the slider is open.  This can be set or unset depending how easily
          the slider can be opened (for example, in a pocket or purse). -->
@@ -304,6 +308,9 @@
     <!-- If this is true, the screen will fade off. -->
     <bool name="config_animateScreenLights">true</bool>
 
+    <!-- If this is true, key chords can be used to take a screenshot on the device. -->
+    <bool name="config_enableScreenshotChord">true</bool>
+
     <!-- If true, the screen can be rotated via the accelerometer in all 4
          rotations as the default behavior. -->
     <bool name="config_allowAllRotations">false</bool>
@@ -628,6 +635,10 @@
                cell broadcasting sms, and MMS. -->
     <bool name="config_sms_capable">true</bool>
 
+    <!-- Enable/disable default bluetooth profiles:
+        HSP_AG, ObexObjectPush, Audio, NAP -->
+    <bool name="config_bluetooth_default_profiles">true</bool>
+
     <!-- IP address of the dns server to use if nobody else suggests one -->
     <string name="config_default_dns_server" translatable="false">8.8.8.8</string>
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index ea1a70a..e1bc33b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -194,6 +194,10 @@
   <java-symbol type="id" name="zoomIn" />
   <java-symbol type="id" name="zoomMagnify" />
   <java-symbol type="id" name="zoomOut" />
+  <java-symbol type="id" name="actions" />
+  <java-symbol type="id" name="action0" />
+  <java-symbol type="id" name="action1" />
+  <java-symbol type="id" name="action2" />
 
   <java-symbol type="attr" name="actionModeShareDrawable" />
   <java-symbol type="attr" name="alertDialogCenterButtons" />
@@ -240,6 +244,8 @@
   <java-symbol type="bool" name="config_useMasterVolume" />
   <java-symbol type="bool" name="config_enableWallpaperService" />
   <java-symbol type="bool" name="config_sendAudioBecomingNoisy" />
+  <java-symbol type="bool" name="config_enableScreenshotChord" />
+  <java-symbol type="bool" name="config_bluetooth_default_profiles" />
 
   <java-symbol type="integer" name="config_cursorWindowSize" />
   <java-symbol type="integer" name="config_longPressOnPowerBehavior" />
@@ -251,6 +257,7 @@
   <java-symbol type="integer" name="db_journal_size_limit" />
   <java-symbol type="integer" name="db_wal_autocheckpoint" />
   <java-symbol type="integer" name="max_action_buttons" />
+  <java-symbol type="integer" name="config_wifi_driver_stop_delay" />
 
   <java-symbol type="color" name="tab_indicator_text_v4" />
 
@@ -1062,6 +1069,7 @@
   <java-symbol type="layout" name="zoom_container" />
   <java-symbol type="layout" name="zoom_controls" />
   <java-symbol type="layout" name="zoom_magnify" />
+  <java-symbol type="layout" name="notification_intruder_content" />
 
   <java-symbol type="anim" name="slide_in_child_bottom" />
   <java-symbol type="anim" name="slide_in_right" />
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index a51f3f9..288b8b2 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -521,10 +521,10 @@
     </style>
 
     <style name="Widget.NumberPicker">
+        <item name="android:internalLayout">@android:layout/number_picker</item>
         <item name="android:orientation">vertical</item>
         <item name="android:fadingEdge">vertical</item>
         <item name="android:fadingEdgeLength">50dip</item>
-        <item name="android:flingable">false</item>
     </style>
 
     <style name="Widget.TimePicker">
@@ -536,25 +536,6 @@
         <item name="android:calendarViewShown">false</item>
     </style>
 
-    <style name="Widget.ImageButton.NumberPickerUpButton">
-        <item name="android:background">@android:drawable/numberpicker_up_btn</item>
-        <item name="android:paddingTop">22dip</item>
-        <item name="android:paddingBottom">22dip</item>
-    </style>
-
-    <style name="Widget.ImageButton.NumberPickerDownButton">
-        <item name="android:background">@android:drawable/numberpicker_down_btn</item>
-        <item name="android:paddingTop">22dip</item>
-        <item name="android:paddingBottom">22dip</item>
-    </style>
-
-    <style name="Widget.EditText.NumberPickerInputText">
-        <item name="android:textAppearance">@style/TextAppearance.Large.Inverse.NumberPickerInputText</item>
-        <item name="android:gravity">center</item>
-        <item name="android:singleLine">true</item>
-        <item name="android:background">@drawable/numberpicker_input</item>
-    </style>
-
     <style name="Widget.AutoCompleteTextView" parent="Widget.EditText">
         <item name="android:completionHintView">@android:layout/simple_dropdown_hint</item>
         <item name="android:completionThreshold">2</item>
@@ -1656,12 +1637,14 @@
     </style>
 
     <style name="Widget.Holo.NumberPicker" parent="Widget.NumberPicker">
+        <item name="android:internalLayout">@android:layout/number_picker_with_selector_wheel</item>
         <item name="android:solidColor">@android:color/transparent</item>
-        <item name="android:flingable">true</item>
         <item name="android:selectionDivider">@android:drawable/numberpicker_selection_divider</item>
         <item name="android:selectionDividerHeight">2dip</item>
+        <item name="android:selectionDividersDistance">48dip</item>
         <item name="android:internalMinWidth">48dip</item>
         <item name="android:internalMaxHeight">200dip</item>
+        <item name="android:minFlingDistance">150dip</item>
     </style>
 
     <style name="Widget.Holo.TimePicker" parent="Widget.TimePicker">
@@ -1673,31 +1656,6 @@
         <item name="android:calendarViewShown">true</item>
     </style>
 
-    <style name="Widget.Holo.ImageButton.NumberPickerUpButton">
-        <item name="android:background">@null</item>
-        <item name="android:src">@android:drawable/numberpicker_up_btn_holo_dark</item>
-        <item name="android:paddingTop">16dip</item>
-        <item name="android:paddingBottom">22dip</item>
-    </style>
-
-    <style name="Widget.Holo.ImageButton.NumberPickerDownButton">
-        <item name="android:background">@null</item>
-        <item name="android:src">@android:drawable/numberpicker_down_btn_holo_dark</item>
-        <item name="android:paddingTop">22dip</item>
-        <item name="android:paddingBottom">16dip</item>
-    </style>
-
-    <style name="Widget.Holo.EditText.NumberPickerInputText">
-        <item name="android:paddingTop">13sp</item>
-        <item name="android:paddingBottom">13sp</item>
-        <item name="android:paddingLeft">2sp</item>
-        <item name="android:paddingRight">2sp</item>
-        <item name="android:gravity">center</item>
-        <item name="android:singleLine">true</item>
-        <item name="android:textSize">18sp</item>
-        <item name="android:background">@null</item>
-    </style>
-
     <style name="Widget.Holo.ActivityChooserView" parent="Widget.ActivityChooserView">
     </style>
 
@@ -2109,17 +2067,6 @@
     <style name="Widget.Holo.Light.DatePicker" parent="Widget.Holo.DatePicker">
     </style>
 
-    <style name="Widget.Holo.Light.ImageButton.NumberPickerUpButton" parent="Widget.Holo.ImageButton.NumberPickerUpButton">
-        <item name="android:src">@android:drawable/numberpicker_up_btn_holo_light</item>
-    </style>
-
-    <style name="Widget.Holo.Light.ImageButton.NumberPickerDownButton" parent="Widget.Holo.ImageButton.NumberPickerDownButton">
-        <item name="android:src">@android:drawable/numberpicker_down_btn_holo_light</item>
-    </style>
-
-    <style name="Widget.Holo.Light.EditText.NumberPickerInputText" parent="Widget.Holo.EditText.NumberPickerInputText">
-    </style>
-
     <style name="Widget.Holo.Light.ActivityChooserView" parent="Widget.Holo.ActivityChooserView">
         <item name="android:background">@android:drawable/ab_share_pack_holo_light</item>
     </style>
diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml
index 6419872..330e68c 100644
--- a/core/res/res/values/styles_device_defaults.xml
+++ b/core/res/res/values/styles_device_defaults.xml
@@ -200,9 +200,6 @@
     <style name="Widget.DeviceDefault.CompoundButton.Switch" parent="Widget.Holo.CompoundButton.Switch">
 
     </style>
-    <style name="Widget.DeviceDefault.EditText.NumberPickerInputText" parent="Widget.Holo.EditText.NumberPickerInputText">
-
-    </style>
     <style name="Widget.DeviceDefault.ExpandableListView.White" parent="Widget.Holo.ExpandableListView.White">
 
     </style>
@@ -212,12 +209,6 @@
     <style name="Widget.DeviceDefault.GestureOverlayView" parent="Widget.Holo.GestureOverlayView">
 
     </style>
-    <style name="Widget.DeviceDefault.ImageButton.NumberPickerDownButton" parent="Widget.Holo.ImageButton.NumberPickerDownButton">
-
-    </style>
-    <style name="Widget.DeviceDefault.ImageButton.NumberPickerUpButton" parent="Widget.Holo.ImageButton.NumberPickerUpButton">
-
-    </style>
     <style name="Widget.DeviceDefault.ImageWell" parent="Widget.Holo.ImageWell">
 
     </style>
@@ -465,9 +456,6 @@
     <style name="Widget.DeviceDefault.Light.DatePicker" parent="Widget.Holo.Light.DatePicker">
 
     </style>
-    <style name="Widget.DeviceDefault.Light.EditText.NumberPickerInputText" parent="Widget.Holo.Light.EditText.NumberPickerInputText">
-
-    </style>
     <style name="Widget.DeviceDefault.Light.ExpandableListView.White" parent="Widget.Holo.Light.ExpandableListView.White">
 
     </style>
@@ -477,12 +465,6 @@
     <style name="Widget.DeviceDefault.Light.GestureOverlayView" parent="Widget.Holo.Light.GestureOverlayView">
 
     </style>
-    <style name="Widget.DeviceDefault.Light.ImageButton.NumberPickerDownButton" parent="Widget.Holo.Light.ImageButton.NumberPickerDownButton">
-
-    </style>
-    <style name="Widget.DeviceDefault.Light.ImageButton.NumberPickerUpButton" parent="Widget.Holo.Light.ImageButton.NumberPickerUpButton">
-
-    </style>
     <style name="Widget.DeviceDefault.Light.ImageWell" parent="Widget.Holo.Light.ImageWell">
 
     </style>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 55438b2..7e06e24 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -345,10 +345,7 @@
         <!-- PreferenceFrameLayout attributes -->
         <item name="preferenceFrameLayoutStyle">@android:style/Widget.PreferenceFrameLayout</item>
 
-        <!-- NumberPicker styles-->
-        <item name="numberPickerUpButtonStyle">@style/Widget.ImageButton.NumberPickerUpButton</item>
-        <item name="numberPickerDownButtonStyle">@style/Widget.ImageButton.NumberPickerDownButton</item>
-        <item name="numberPickerInputTextStyle">@style/Widget.EditText.NumberPickerInputText</item>
+        <!-- NumberPicker style-->
         <item name="numberPickerStyle">@style/Widget.NumberPicker</item>
 
         <!-- CalendarView style-->
@@ -1141,10 +1138,7 @@
         <!-- PreferenceFrameLayout attributes -->
         <item name="preferenceFrameLayoutStyle">@android:style/Widget.Holo.PreferenceFrameLayout</item>
 
-        <!-- NumberPicker styles-->
-        <item name="numberPickerUpButtonStyle">@style/Widget.Holo.ImageButton.NumberPickerUpButton</item>
-        <item name="numberPickerDownButtonStyle">@style/Widget.Holo.ImageButton.NumberPickerDownButton</item>
-        <item name="numberPickerInputTextStyle">@style/Widget.Holo.EditText.NumberPickerInputText</item>
+        <!-- NumberPicker style-->
         <item name="numberPickerStyle">@style/Widget.Holo.NumberPicker</item>
 
         <!-- CalendarView style-->
@@ -1443,10 +1437,7 @@
 
         <item name="searchDialogTheme">@style/Theme.Holo.Light.SearchBar</item>
 
-        <!-- NumberPicker attributes and styles-->
-        <item name="numberPickerUpButtonStyle">@style/Widget.Holo.Light.ImageButton.NumberPickerUpButton</item>
-        <item name="numberPickerDownButtonStyle">@style/Widget.Holo.Light.ImageButton.NumberPickerDownButton</item>
-        <item name="numberPickerInputTextStyle">@style/Widget.Holo.Light.EditText.NumberPickerInputText</item>
+        <!-- NumberPicker style-->
         <item name="numberPickerStyle">@style/Widget.Holo.Light.NumberPicker</item>
 
         <!-- CalendarView style-->
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 7fd981c..ae9255a 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -179,10 +179,7 @@
         <!-- PreferenceFrameLayout attributes -->
         <item name="preferenceFrameLayoutStyle">@android:style/Widget.DeviceDefault.PreferenceFrameLayout</item>
 
-        <!-- NumberPicker styles-->
-        <item name="numberPickerUpButtonStyle">@style/Widget.DeviceDefault.ImageButton.NumberPickerUpButton</item>
-        <item name="numberPickerDownButtonStyle">@style/Widget.DeviceDefault.ImageButton.NumberPickerDownButton</item>
-        <item name="numberPickerInputTextStyle">@style/Widget.DeviceDefault.EditText.NumberPickerInputText</item>
+        <!-- NumberPicker style-->
         <item name="numberPickerStyle">@style/Widget.DeviceDefault.NumberPicker</item>
 
         <!-- CalendarView style-->
@@ -329,10 +326,7 @@
 
         <item name="searchDialogTheme">@style/Theme.DeviceDefault.Light.SearchBar</item>
 
-        <!-- NumberPicker attributes and styles-->
-        <item name="numberPickerUpButtonStyle">@style/Widget.DeviceDefault.Light.ImageButton.NumberPickerUpButton</item>
-        <item name="numberPickerDownButtonStyle">@style/Widget.DeviceDefault.Light.ImageButton.NumberPickerDownButton</item>
-        <item name="numberPickerInputTextStyle">@style/Widget.DeviceDefault.Light.EditText.NumberPickerInputText</item>
+        <!-- NumberPicker style -->
         <item name="numberPickerStyle">@style/Widget.DeviceDefault.Light.NumberPicker</item>
 
         <!-- CalendarView style-->
diff --git a/core/tests/coretests/src/android/content/pm/AppCacheTest.java b/core/tests/coretests/src/android/content/pm/AppCacheTest.java
index 2982816..0c31e2d 100755
--- a/core/tests/coretests/src/android/content/pm/AppCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/AppCacheTest.java
@@ -24,6 +24,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.StatFs;
+import android.os.UserId;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
@@ -674,7 +675,7 @@
             PackageDataObserver observer = new PackageDataObserver();
             //wait on observer
             synchronized(observer) {
-                getPm().clearApplicationUserData(packageName, observer);
+                getPm().clearApplicationUserData(packageName, observer, 0 /* TODO: Other users */);
                 long waitTime = 0;
                 while(!observer.isDone() || (waitTime > MAX_WAIT_TIME)) {
                     observer.wait(WAIT_TIME_INCR);
@@ -717,7 +718,8 @@
     
     File getDataDir() {
         try {
-            ApplicationInfo appInfo = getPm().getApplicationInfo(mContext.getPackageName(), 0);
+            ApplicationInfo appInfo = getPm().getApplicationInfo(mContext.getPackageName(), 0,
+                    UserId.myUserId());
             return new File(appInfo.dataDir);
         } catch (RemoteException e) {
             throw new RuntimeException("Pacakge manager dead", e);
@@ -746,7 +748,7 @@
     
     @LargeTest
     public void testClearApplicationUserDataNoObserver() throws Exception {
-        getPm().clearApplicationUserData(mContext.getPackageName(), null);
+        getPm().clearApplicationUserData(mContext.getPackageName(), null, UserId.myUserId());
         //sleep for 1 minute
         Thread.sleep(60*1000);
         //confirm files dont exist
diff --git a/core/tests/coretests/src/android/net/SSLTest.java b/core/tests/coretests/src/android/net/SSLTest.java
index 810ed0d..c573498 100644
--- a/core/tests/coretests/src/android/net/SSLTest.java
+++ b/core/tests/coretests/src/android/net/SSLTest.java
@@ -16,17 +16,16 @@
 
 package android.net;
 
-import android.net.SSLCertificateSocketFactory;
 import android.test.suitebuilder.annotation.Suppress;
-import junit.framework.TestCase;
-
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Socket;
+import java.util.Arrays;
+import junit.framework.TestCase;
 
-//This test relies on network resources.
-@Suppress
 public class SSLTest extends TestCase {
+    //This test relies on network resources.
+    @Suppress
     public void testCertificate() throws Exception {
         // test www.fortify.net/sslcheck.html
         Socket ssl = SSLCertificateSocketFactory.getDefault().createSocket("www.fortify.net",443);
@@ -49,4 +48,35 @@
 
         // System.out.println(new String(b));
     }
+
+    public void testStringsToNpnBytes() {
+        byte[] expected = {
+                6, 's', 'p', 'd', 'y', '/', '2',
+                8, 'h', 't', 't', 'p', '/', '1', '.', '1',
+        };
+        assertTrue(Arrays.equals(expected, SSLCertificateSocketFactory.toNpnProtocolsList(
+                new byte[] { 's', 'p', 'd', 'y', '/', '2' },
+                new byte[] { 'h', 't', 't', 'p', '/', '1', '.', '1' })));
+    }
+
+    public void testStringsToNpnBytesEmptyByteArray() {
+        try {
+            SSLCertificateSocketFactory.toNpnProtocolsList(new byte[0]);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testStringsToNpnBytesEmptyArray() {
+        byte[] expected = {};
+        assertTrue(Arrays.equals(expected, SSLCertificateSocketFactory.toNpnProtocolsList()));
+    }
+
+    public void testStringsToNpnBytesOversizedInput() {
+        try {
+            SSLCertificateSocketFactory.toNpnProtocolsList(new byte[256]);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
 }
diff --git a/data/etc/android.hardware.audio.low_latency.xml b/data/etc/android.hardware.audio.low_latency.xml
deleted file mode 100644
index 677ec1c..0000000
--- a/data/etc/android.hardware.audio.low_latency.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<!-- This is the feature indicating low-latency audio, as specified by the
-     CDD. ONLY devices that meet the CDD's requirements may declare this
-     feature. -->
-<permissions>
-    <feature name="android.hardware.audio.low_latency" />
-</permissions>
diff --git a/data/etc/android.hardware.bluetooth.xml b/data/etc/android.hardware.bluetooth.xml
deleted file mode 100644
index 4aa1744..0000000
--- a/data/etc/android.hardware.bluetooth.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-  
-          http://www.apache.org/licenses/LICENSE-2.0
-  
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<!-- Adds the feature indicating support for the Bluetooth API -->
-<permissions>
-    <feature name="android.hardware.bluetooth" />
-</permissions>
diff --git a/data/etc/android.hardware.camera.autofocus.xml b/data/etc/android.hardware.camera.autofocus.xml
deleted file mode 100644
index d6e2b90..0000000
--- a/data/etc/android.hardware.camera.autofocus.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- This is the standard set of features for an auto-focus camera. -->
-<permissions>
-    <feature name="android.hardware.camera" />
-    <feature name="android.hardware.camera.autofocus" />
-</permissions>
diff --git a/data/etc/android.hardware.camera.flash-autofocus.xml b/data/etc/android.hardware.camera.flash-autofocus.xml
deleted file mode 100644
index 55f1900..0000000
--- a/data/etc/android.hardware.camera.flash-autofocus.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- This is the standard set of features for a camera with a flash.  Note
-     that this currently requires having auto-focus as well. -->
-<permissions>
-    <feature name="android.hardware.camera" />
-    <feature name="android.hardware.camera.autofocus" />
-    <feature name="android.hardware.camera.flash" />
-</permissions>
diff --git a/data/etc/android.hardware.camera.front.xml b/data/etc/android.hardware.camera.front.xml
deleted file mode 100644
index a5a6998..0000000
--- a/data/etc/android.hardware.camera.front.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<!-- This is the standard set of features for a front facing camera. -->
-<permissions>
-    <feature name="android.hardware.camera.front" />
-</permissions>
diff --git a/data/etc/android.hardware.camera.xml b/data/etc/android.hardware.camera.xml
deleted file mode 100644
index 00a1ed7..0000000
--- a/data/etc/android.hardware.camera.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- This is the standard set of features for a non auto-focus camera. -->
-<permissions>
-    <feature name="android.hardware.camera" />
-</permissions>
diff --git a/data/etc/android.hardware.faketouch.multitouch.distinct.xml b/data/etc/android.hardware.faketouch.multitouch.distinct.xml
deleted file mode 100644
index b6e8d09..0000000
--- a/data/etc/android.hardware.faketouch.multitouch.distinct.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-  
-          http://www.apache.org/licenses/LICENSE-2.0
-  
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!-- This is the standard set of features for a indirect touch input device that supports
-     independently-trackable multiple-finger multitouch. -->
-<permissions>
-    <feature name="android.hardware.faketouch.multitouch" />
-    <feature name="android.hardware.faketouch.multitouch.distinct" />
-    <feature name="android.hardware.faketouch" />
-</permissions>
diff --git a/data/etc/android.hardware.faketouch.multitouch.jazzhand.xml b/data/etc/android.hardware.faketouch.multitouch.jazzhand.xml
deleted file mode 100644
index 7f0e70b..0000000
--- a/data/etc/android.hardware.faketouch.multitouch.jazzhand.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!-- This is the standard set of features for an indirect touch input device that supports
-     independently-trackable multiple-finger multitouch. -->
-<permissions>
-    <feature name="android.hardware.faketouch.multitouch" />
-    <feature name="android.hardware.faketouch.multitouch.distinct" />
-    <feature name="android.hardware.faketouch.multitouch.jazzhand" />
-    <feature name="android.hardware.faketouch" />
-</permissions>
diff --git a/data/etc/android.hardware.faketouch.xml b/data/etc/android.hardware.faketouch.xml
deleted file mode 100644
index cb99097..0000000
--- a/data/etc/android.hardware.faketouch.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-  
-          http://www.apache.org/licenses/LICENSE-2.0
-  
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!-- This is the standard set of features for a devices that does not have
-     a touch screen, but does have some other indirect input device such as
-     a track pad. -->
-<permissions>
-    <feature name="android.hardware.faketouch" />
-</permissions>
diff --git a/data/etc/android.hardware.location.gps.xml b/data/etc/android.hardware.location.gps.xml
deleted file mode 100644
index 72ab732..0000000
--- a/data/etc/android.hardware.location.gps.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- These are the location-related features for devices that include GPS. -->
-<permissions>
-    <feature name="android.hardware.location" />
-    <feature name="android.hardware.location.network" />
-    <feature name="android.hardware.location.gps" />
-</permissions>
diff --git a/data/etc/android.hardware.location.xml b/data/etc/android.hardware.location.xml
deleted file mode 100644
index ab901cf..0000000
--- a/data/etc/android.hardware.location.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- These are the location features for devices that do not include GPS. Note
-     that network location is required for all devices. -->
-<permissions>
-    <feature name="android.hardware.location" />
-    <feature name="android.hardware.location.network" />
-</permissions>
diff --git a/data/etc/android.hardware.nfc.xml b/data/etc/android.hardware.nfc.xml
deleted file mode 100644
index 81c4a84..0000000
--- a/data/etc/android.hardware.nfc.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<!-- This is the standard feature indicating that the device can communicate
-     using Near-Field Communications (NFC). -->
-<permissions>
-    <feature name="android.hardware.nfc" />
-</permissions>
diff --git a/data/etc/android.hardware.sensor.accelerometer.xml b/data/etc/android.hardware.sensor.accelerometer.xml
deleted file mode 100644
index 22f18b8..0000000
--- a/data/etc/android.hardware.sensor.accelerometer.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- Feature for devices with an accelerometer sensor. -->
-<permissions>
-    <feature name="android.hardware.sensor.accelerometer" />
-</permissions>
diff --git a/data/etc/android.hardware.sensor.compass.xml b/data/etc/android.hardware.sensor.compass.xml
deleted file mode 100644
index 963847d..0000000
--- a/data/etc/android.hardware.sensor.compass.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- Feature for devices with a compass. -->
-<permissions>
-    <feature name="android.hardware.sensor.compass" />
-</permissions>
diff --git a/data/etc/android.hardware.sensor.gyroscope.xml b/data/etc/android.hardware.sensor.gyroscope.xml
deleted file mode 100644
index fe79632..0000000
--- a/data/etc/android.hardware.sensor.gyroscope.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<!-- Feature for devices with gyroscope. -->
-<permissions>
-    <feature name="android.hardware.sensor.gyroscope" />
-</permissions>
diff --git a/data/etc/android.hardware.sensor.light.xml b/data/etc/android.hardware.sensor.light.xml
deleted file mode 100644
index 78b0fec..0000000
--- a/data/etc/android.hardware.sensor.light.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- Feature for devices with an ambient light sensor. -->
-<permissions>
-    <feature name="android.hardware.sensor.light" />
-</permissions>
diff --git a/data/etc/android.hardware.sensor.proximity.xml b/data/etc/android.hardware.sensor.proximity.xml
deleted file mode 100644
index d1948de..0000000
--- a/data/etc/android.hardware.sensor.proximity.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- Feature for devices with a proximity sensor. -->
-<permissions>
-    <feature name="android.hardware.sensor.proximity" />
-</permissions>
diff --git a/data/etc/android.hardware.telephony.cdma.xml b/data/etc/android.hardware.telephony.cdma.xml
deleted file mode 100644
index 082378d5..0000000
--- a/data/etc/android.hardware.telephony.cdma.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- This is the standard set of telephony features for a CDMA phone. -->
-<permissions>
-    <feature name="android.hardware.telephony" />
-    <feature name="android.hardware.telephony.cdma" />
-</permissions>
diff --git a/data/etc/android.hardware.telephony.gsm.xml b/data/etc/android.hardware.telephony.gsm.xml
deleted file mode 100644
index 7927fa8..0000000
--- a/data/etc/android.hardware.telephony.gsm.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- This is the standard set of telephony features for a GSM phone. -->
-<permissions>
-    <feature name="android.hardware.telephony" />
-    <feature name="android.hardware.telephony.gsm" />
-</permissions>
diff --git a/data/etc/android.hardware.touchscreen.multitouch.distinct.xml b/data/etc/android.hardware.touchscreen.multitouch.distinct.xml
deleted file mode 100644
index 35eeefd..0000000
--- a/data/etc/android.hardware.touchscreen.multitouch.distinct.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- This is the standard set of features for a touchscreen that supports
-     independently-trackable multiple-finger multitouch. -->
-<permissions>
-    <feature name="android.hardware.touchscreen" />
-    <feature name="android.hardware.touchscreen.multitouch" />
-    <feature name="android.hardware.touchscreen.multitouch.distinct" />
-    <feature name="android.hardware.faketouch" />
-</permissions>
diff --git a/data/etc/android.hardware.touchscreen.multitouch.jazzhand.xml b/data/etc/android.hardware.touchscreen.multitouch.jazzhand.xml
deleted file mode 100644
index ed6606d..0000000
--- a/data/etc/android.hardware.touchscreen.multitouch.jazzhand.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<!-- This is the standard set of features for a touchscreen that supports
-     independently-trackable multiple-finger multitouch. -->
-<permissions>
-    <feature name="android.hardware.touchscreen" />
-    <feature name="android.hardware.touchscreen.multitouch" />
-    <feature name="android.hardware.touchscreen.multitouch.distinct" />
-    <feature name="android.hardware.touchscreen.multitouch.jazzhand" />
-    <feature name="android.hardware.faketouch" />
-</permissions>
diff --git a/data/etc/android.hardware.touchscreen.multitouch.xml b/data/etc/android.hardware.touchscreen.multitouch.xml
deleted file mode 100644
index 1d59a27..0000000
--- a/data/etc/android.hardware.touchscreen.multitouch.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- This is the standard set of features for a touchscreen that supports
-     basic multitouch capable of gestures but not fully-indendent finger
-     tracking. -->
-<permissions>
-    <feature name="android.hardware.touchscreen" />
-    <feature name="android.hardware.touchscreen.multitouch" />
-    <feature name="android.hardware.faketouch" />
-</permissions>
diff --git a/data/etc/android.hardware.touchscreen.xml b/data/etc/android.hardware.touchscreen.xml
deleted file mode 100644
index 5b5ddf9..0000000
--- a/data/etc/android.hardware.touchscreen.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- This is the standard set of features for a touchscreen that does not
-     support multitouch. -->
-<permissions>
-    <feature name="android.hardware.touchscreen" />
-    <feature name="android.hardware.faketouch" />
-</permissions>
diff --git a/data/etc/android.hardware.usb.accessory.xml b/data/etc/android.hardware.usb.accessory.xml
deleted file mode 100644
index 80a0904..0000000
--- a/data/etc/android.hardware.usb.accessory.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!-- This is the standard feature indicating that the device supports USB accessories. -->
-<permissions>
-    <feature name="android.hardware.usb.accessory" />
-    <library name="com.android.future.usb.accessory"
-            file="/system/framework/com.android.future.usb.accessory.jar" />
-</permissions>
diff --git a/data/etc/android.hardware.usb.host.xml b/data/etc/android.hardware.usb.host.xml
deleted file mode 100644
index b0ca82c..0000000
--- a/data/etc/android.hardware.usb.host.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!-- This is the standard feature indicating that the device can communicate
-    with USB devices as the USB host. -->
-<permissions>
-    <feature name="android.hardware.usb.host" />
-</permissions>
diff --git a/data/etc/android.hardware.wifi.direct.xml b/data/etc/android.hardware.wifi.direct.xml
deleted file mode 100644
index 78cb474..0000000
--- a/data/etc/android.hardware.wifi.direct.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-  
-          http://www.apache.org/licenses/LICENSE-2.0
-  
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!-- This is the standard feature indicating that the device includes WiFi Direct. -->
-<permissions>
-    <feature name="android.hardware.wifi.direct" />
-</permissions>
diff --git a/data/etc/android.hardware.wifi.xml b/data/etc/android.hardware.wifi.xml
deleted file mode 100644
index 512570c..0000000
--- a/data/etc/android.hardware.wifi.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- This is the standard feature indicating that the device includes WiFi. -->
-<permissions>
-    <feature name="android.hardware.wifi" />
-</permissions>
diff --git a/data/etc/android.software.sip.voip.xml b/data/etc/android.software.sip.voip.xml
deleted file mode 100644
index edd06c1..0000000
--- a/data/etc/android.software.sip.voip.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- This is the standard set of features for devices that support SIP-based VoIP. -->
-<permissions>
-    <feature name="android.software.sip" />
-    <feature name="android.software.sip.voip" />
-</permissions>
diff --git a/data/etc/android.software.sip.xml b/data/etc/android.software.sip.xml
deleted file mode 100644
index d9fcaad..0000000
--- a/data/etc/android.software.sip.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- This is the standard set of features for devices that support the SIP API. -->
-<permissions>
-    <feature name="android.software.sip" />
-</permissions>
diff --git a/data/etc/handheld_core_hardware.xml b/data/etc/handheld_core_hardware.xml
deleted file mode 100644
index 9d2a0cb..0000000
--- a/data/etc/handheld_core_hardware.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 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.
--->
-
-<!-- These are the hardware components that all handheld devices
-     must include. Devices with optional hardware must also include extra
-     hardware files, per the comments below.
-
-     Handheld devices include phones, mobile Internet devices (MIDs),
-     Personal Media Players (PMPs), small tablets (7" or less), and similar
-     devices.
--->
-<permissions>
-    <feature name="android.hardware.camera" />
-    <feature name="android.hardware.location" />
-    <feature name="android.hardware.location.network" />
-    <feature name="android.hardware.sensor.compass" />
-    <feature name="android.hardware.sensor.accelerometer" />
-    <feature name="android.hardware.bluetooth" />
-    <feature name="android.hardware.touchscreen" />
-    <feature name="android.hardware.microphone" />
-    <feature name="android.hardware.screen.portrait" />
-    <feature name="android.hardware.screen.landscape" />
-    <!-- devices with GPS must include android.hardware.location.gps.xml -->
-    <!-- devices with an autofocus camera and/or flash must include either
-         android.hardware.camera.autofocus.xml or 
-         android.hardware.camera.autofocus-flash.xml -->
-    <!-- devices with a front facing camera must include
-         android.hardware.camera.front.xml -->
-    <!-- devices with WiFi must also include android.hardware.wifi.xml -->
-    <!-- devices that support multitouch must include the most appropriate one
-         of these files:
-
-         If only partial (non-independent) pointers are supported:
-         android.hardware.touchscreen.multitouch.xml
-
-         If up to 4 independently tracked pointers are supported:
-         include android.hardware.touchscreen.multitouch.distinct.xml
-
-         If 5 or more independently tracked pointers are supported:
-         include android.hardware.touchscreen.multitouch.jazzhand.xml
-
-         ONLY ONE of the above should be included. -->
-    <!-- devices with an ambient light sensor must also include
-         android.hardware.sensor.light.xml -->
-    <!-- devices with a proximity sensor must also include
-         android.hardware.sensor.proximity.xml -->
-    <!-- GSM phones must also include android.hardware.telephony.gsm.xml -->
-    <!-- CDMA phones must also include android.hardware.telephony.cdma.xml -->
-    <!-- Devices that have low-latency audio stacks suitable for apps like
-         VoIP may include android.hardware.audio.low_latency.xml. ONLY apps
-         that meet the requirements specified in the CDD may include this. -->
-</permissions>
diff --git a/data/etc/tablet_core_hardware.xml b/data/etc/tablet_core_hardware.xml
deleted file mode 100644
index bf29fe4..0000000
--- a/data/etc/tablet_core_hardware.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<!-- These are the hardware components that all handheld devices
-     must include. Devices with optional hardware must also include extra
-     hardware files, per the comments below.
-
-     Handheld devices include phones, mobile Internet devices (MIDs),
-     Personal Media Players (PMPs), small tablets (7" or less), and similar
-     devices.
--->
-<permissions>
-    <feature name="android.hardware.location" />
-    <feature name="android.hardware.location.network" />
-    <feature name="android.hardware.sensor.compass" />
-    <feature name="android.hardware.sensor.accelerometer" />
-    <feature name="android.hardware.bluetooth" />
-    <feature name="android.hardware.touchscreen" />
-    <feature name="android.hardware.touchscreen.multitouch" />
-    <feature name="android.hardware.touchscreen.multitouch.distinct" />
-    <feature name="android.hardware.microphone" />
-    <feature name="android.hardware.screen.portrait" />
-    <feature name="android.hardware.screen.landscape" />
-    <!-- devices with GPS must include android.hardware.location.gps.xml -->
-    <!-- devices with a rear-facing camera must include one of these as appropriate:
-         android.hardware.camera.xml or 
-         android.hardware.camera.autofocus.xml or 
-         android.hardware.camera.autofocus-flash.xml -->
-    <!-- devices with a front facing camera must include
-         android.hardware.camera.front.xml -->
-    <!-- devices with WiFi must also include android.hardware.wifi.xml -->
-    <!-- devices with an ambient light sensor must also include
-         android.hardware.sensor.light.xml -->
-    <!-- devices with a proximity sensor must also include
-         android.hardware.sensor.proximity.xml -->
-    <!-- devices with a barometer must also include
-         android.hardware.sensor.barometer.xml -->
-    <!-- devices with a gyroscope must also include
-         android.hardware.sensor.gyroscope.xml -->
-    <!-- GSM phones must also include android.hardware.telephony.gsm.xml -->
-    <!-- CDMA phones must also include android.hardware.telephony.cdma.xml -->
-</permissions>
diff --git a/data/fonts/vendor_fonts.xml b/data/fonts/vendor_fonts.xml
index c1116d0..5850f94 100644
--- a/data/fonts/vendor_fonts.xml
+++ b/data/fonts/vendor_fonts.xml
@@ -3,7 +3,12 @@
     Vendor-provided fallback fonts
 
     This file can be edited to add references to fonts that are not installed or referenced in the
-    default system. The file should then be placed in /vendor/etc/fallback_fonts.xml.
+    default system. The file should then be placed in /vendor/etc/fallback_fonts.xml. Note
+    that in your makefile, this directory should be referenced as $(TARGET_COPY_OUT_VENDOR)/etc/:
+
+        PRODUCT_COPY_FILES += \
+            frameworks/base/data/fonts/vendor_fonts.xml:$(TARGET_COPY_OUT_VENDOR)/etc/fallback_fonts.xml \
+            frameworks/base/data/fonts/vendor_fonts-ja.xml:$(TARGET_COPY_OUT_VENDOR)/etc/fallback_fonts-ja.xml
 
     For example, vendors might want to build configurations for locales that are
     better served by fonts which either handle glyphs not supported in the default fonts or which
@@ -28,11 +33,31 @@
     Han languages (Chinese, Japanese, and Korean) share a common range of unicode characters;
     their ordering in the fallback or vendor files gives priority to the first in the list.
     Locale-specific ordering can be configured by adding language and region codes to the end
-    of the filename (e.g. /system/etc/fallback_fonts-ja.xml). When no region code is used,
+    of the filename (e.g. /vendor/etc/fallback_fonts-ja.xml). When no region code is used,
     as with this example, all regions are matched. Use separate files for each supported locale.
     The standard fallback file (fallback_fonts.xml) is used when a locale does not have its own
     file. All fallback files must contain the same complete set of fonts; only their ordering
-    can differ.
+    can differ. For example, on a device supporting Japanese, but with English as the default,
+    /vendor/etc/fallback_fonts.xml might contain:
+
+        <familyset>
+            <family>
+                <fileset>
+                    <file>DroidSansJapanese.ttf</file>
+                </fileset>
+            </family>
+        </familyset>
+
+    placing the Japanese font at the end of the fallback sequence for English, with a corresponding
+    /system/vendor/etc/fallback_fonts-ja.xml, placing it at the front of the list.
+
+        <familyset>
+            <family order="0">
+                <fileset>
+                    <file>DroidSansJapanese.ttf</file>
+                </fileset>
+            </family>
+        </familyset>
 
     The sample configuration below is an example of how one might provide two families of fonts
     that get inserted at the first and second (0  and 1) position in the overall fallback fonts.
diff --git a/docs/html/guide/appendix/api-levels.jd b/docs/html/guide/appendix/api-levels.jd
index cc98f8f..bc7d83b 100644
--- a/docs/html/guide/appendix/api-levels.jd
+++ b/docs/html/guide/appendix/api-levels.jd
@@ -350,11 +350,11 @@
 download other platform versions as necessary. </p>
 
 <p>To access the updater, use the <code>android</code> command-line tool,
-located in the &lt;sdk&gt;/tools directory. You can launch the Updater by using
-the <code>android</code> command without specifying any options. You can
+located in the &lt;sdk&gt;/tools directory. You can launch the SDK updater by
+executing <code>android sdk</code>. You can
 also simply double-click the android.bat (Windows) or android (OS X/Linux) file.
 In ADT, you can also access the updater by selecting 
-<strong>Window</strong>&nbsp;>&nbsp;<strong>Android SDK and AVD
+<strong>Window</strong>&nbsp;>&nbsp;<strong>Android SDK
 Manager</strong>.</p>
 
 <p>To run your application against different platform versions in the emulator,
diff --git a/docs/html/guide/developing/building/building-cmdline.jd b/docs/html/guide/developing/building/building-cmdline.jd
index c43962a..fd90b1a 100644
--- a/docs/html/guide/developing/building/building-cmdline.jd
+++ b/docs/html/guide/developing/building/building-cmdline.jd
@@ -202,12 +202,12 @@
 
   <ol>
     <li>
-      <strong>Open the SDK and AVD Manager and launch a virtual device</strong>
+      <strong>Open the AVD Manager and launch a virtual device</strong>
 
-      <p>From your SDK's <code>platform-tools/</code> directory, execute the {@code android} tool with no
-      arguments:</p>
+      <p>From your SDK's <code>platform-tools/</code> directory, execute the {@code android} tool
+with the <code>avd</code> options:</p>
       <pre>
-android
+android avd
 </pre>
 
       <p>In the <em>Virtual Devices</em> view, select an AVD and click <strong>Start</strong>.</p>
@@ -237,7 +237,7 @@
   </ol>
 
   <p>If you don't see your application on the emulator, try closing the emulator and launching the
-  virtual device again from the SDK and AVD Manager. Sometimes when you install an application for the
+  virtual device again from the AVD Manager. Sometimes when you install an application for the
   first time, it won't show up in the application launcher or be accessible by other applications.
   This is because the package manager usually examines manifests completely only on emulator
   startup.</p>
diff --git a/docs/html/guide/developing/debugging/ddms.jd b/docs/html/guide/developing/debugging/ddms.jd
index 4398ec9..80b1e47 100644
--- a/docs/html/guide/developing/debugging/ddms.jd
+++ b/docs/html/guide/developing/debugging/ddms.jd
@@ -11,7 +11,19 @@
       <li><a href="#running">Running DDMS</a></li>
         <li><a href="#how-ddms-works">How DDMS Interacts with a Debugger</a></li>
 
-        <li><a href="#using-ddms">Using DDMS</a></li>
+        <li><a href="#using-ddms">Using DDMS</a>
+        <ol>
+                <li><a href="#heap">Viewing heap usage for a process</a></li>
+                <li><a href="#alloc">Tracking memory allocation of objects</a></li>
+                <li><a href="#emulator">Working with an emulator or device's file system</a></li>
+                <li><a href="#thread">Examining thread information</a></li>
+                <li><a href="#profiling">Starting method profiling</a></li>
+                <li><a href="#network">Using the Network Traffic tool</a></li>
+                <li><a href="#logcat">Using LogCat</a></li>
+                <li><a href="#ops-location">Emulating phone operations and location</a></li>
+            </ol>
+        
+        </li>
       </ol>
     </div>
   </div>
@@ -90,7 +102,7 @@
   <a href="#running">Running DDMS</a>.
   
   
-  <h3>Viewing heap usage for a process</h3>
+  <h3 id="heap">Viewing heap usage for a process</h3>
 
   <p>DDMS allows you to view how much heap memory a process is using. This information is useful in
   tracking heap usage at a certain point of time during the execution of your application.</p>
@@ -110,7 +122,7 @@
     allocated for a particular memory size in bytes.</li>
   </ol>
 
-  <h3>Tracking memory allocation of objects</h3>
+  <h3 id="alloc">Tracking memory allocation of objects</h3>
 
   <p>DDMS provides a feature to track objects that are being allocated to memory and to see which
   classes and threads are allocating the objects. This allows you to track, in real time, where
@@ -140,7 +152,7 @@
     line number of the code that allocated the object.</li>
   </ol>
 
-  <h3>Working with an emulator or device's file system</h3>
+  <h3 id="emulator">Working with an emulator or device's file system</h3>
 
   <p>DDMS provides a File Explorer tab that allows you to view, copy, and delete files on the
   device. This feature is useful in examining files that are created by your application or if you
@@ -160,7 +172,7 @@
   <!-- Need to elaborate more on where things are stored in the file system,
    databases, apks, user info, files that are important to look at -->
 
-  <h3>Examining thread information</h3>
+  <h3 id="thread">Examining thread information</h3>
 
   <p>The Threads tab in DDMS shows you the currently running threads for a selected process.</p>
 
@@ -204,6 +216,67 @@
     Profiling</strong>.</li>
   </ol>
 
+   <h3 id="network">Using the Network Traffic tool</h3>
+   
+   <p>In Android 4.0, the DDMS (Dalvik Debug Monitor Server) includes a Detailed
+Network Usage tab that makes it possible to track when your application is
+making network requests. Using this tool, you can monitor how and when your app
+transfers data and optimize the underlying code appropriately. You can also
+distinguish between different traffic types by applying a “tag” to network
+sockets before use.</p>
+
+<p>These tags are shown in a stack area chart in DDMS, as shown in figure 2:</p>
+
+<img src="{@docRoot}images/developing/ddms-network.png" />
+<p class="img-caption"><strong>Figure 2.</strong> Network Usage tab.</p>
+
+<p>By monitoring the frequency of your data transfers, and the amount of data
+transferred during each connection, you can identify areas of your application
+that can be made more battery-efficient. Generally, you should look for
+short spikes that can be delayed, or that should cause a later transfer to be
+pre-empted. </p>
+
+<p>To better identify the cause of transfer spikes, the
+{@link android.net.TrafficStats} API allows you
+to tag the data transfers occurring within a thread using {@link
+android.net.TrafficStats#setThreadStatsTag setThreadStatsTag()}, followed
+by manually tagging (and untagging) individual sockets using {@link
+android.net.TrafficStats#tagSocket tagSocket()} and {@link
+android.net.TrafficStats#untagSocket untagSocket()}. For example:</p>
+
+<pre>TrafficStats.setThreadStatsTag(0xF00D);
+TrafficStats.tagSocket(outputSocket);
+// Transfer data using socket
+TrafficStats.untagSocket(outputSocket);</pre>
+
+<p>Alternatively, the Apache {@link org.apache.http.client.HttpClient} and 
+{@link java.net.URLConnection} APIs included in the platform
+automatically tag sockets internally based on the active tag (as 
+identified by 
+{@link android.net.TrafficStats#getThreadStatsTag getThreadStatsTag()}).
+These APIs correctly tag/untag sockets when recycled through
+keep-alive pools. In the following example,  
+{@link android.net.TrafficStats#setThreadStatsTag setThreadStatsTag()} 
+sets the active tag to be {@code 0xF00D}. 
+There can only be one active tag per thread. 
+That is the value that will 
+be returned by {@link android.net.TrafficStats#getThreadStatsTag getThreadStatsTag()}
+and thus used by {@link org.apache.http.client.HttpClient}  
+ to tag sockets. The {@code finally} statement 
+invokes 
+{@link android.net.TrafficStats#clearThreadStatsTag clearThreadStatsTag()} 
+to clear the tag.</p>
+
+<pre>TrafficStats.setThreadStatsTag(0xF00D);
+    try {
+        // Make network request using HttpClient.execute()
+    } finally {
+        TrafficStats.clearThreadStatsTag();
+}</pre>
+
+<p>Socket tagging is supported in Android 4.0, but real-time stats will only be
+displayed on devices running Android 4.0.3 or higher.</p>
+   
   <h3 id="logcat">Using LogCat</h3>
 
   <p>LogCat is integrated into DDMS, and outputs the messages that you print out using the {@link android.util.Log}
@@ -230,7 +303,7 @@
   with the log tags or with the process id that generated the log message. The add filter,
   edit filter, and delete filter buttons let you manage your custom filters.</p>
 
-  <h3>Emulating phone operations and location</h3>
+  <h3 id="ops-location">Emulating phone operations and location</h3>
   <p>The Emulator control tab lets you simulate a
   phone's voice and data network status. This is useful when you want to test your application's
   robustness in differing network environments.</p>
diff --git a/docs/html/guide/developing/devices/index.jd b/docs/html/guide/developing/devices/index.jd
index a7d00f3..64651a1 100644
--- a/docs/html/guide/developing/devices/index.jd
+++ b/docs/html/guide/developing/devices/index.jd
@@ -7,9 +7,9 @@
 
   <p>The easiest way to create an AVD is to use the graphical <a href= 
   "{@docRoot}guide/developing/devices/managing-avds.html">AVD Manager</a>, which you launch
-  from Eclipse by clicking <strong>Window &gt; Android SDK and AVD Manager</strong>. You can also start
-  the AVD Manager from the command line by calling the <code>android</code> tool in the <strong>tools</strong>
-  directory of the Android SDK.</p>
+  from Eclipse by clicking <strong>Window &gt; AVD Manager</strong>. You can also start the AVD
+Manager from the command line by calling the <code>android</code> tool with the <code>avd</code>
+options, from the <strong>&lt;sdk>/tools/</strong> directory.</p>
 
   <p>You can also create AVDs on the command line by passing the <code>android</code> tool options.
   For more information on how to create AVDs in this manner, see <a href= 
diff --git a/docs/html/guide/developing/devices/managing-avds.jd b/docs/html/guide/developing/devices/managing-avds.jd
index e70a0bb..412bd91 100644
--- a/docs/html/guide/developing/devices/managing-avds.jd
+++ b/docs/html/guide/developing/devices/managing-avds.jd
@@ -42,8 +42,8 @@
     <li>Start the AVD Manager:
 
       <ul>
-        <li>In Eclipse: select <strong>Window &gt; Android SDK and AVD Manager</strong>, or click
-        the Android SDK and AVD Manager icon in the Eclipse toolbar.</li>
+        <li>In Eclipse: select <strong>Window &gt; AVD Manager</strong>, or click
+        the AVD Manager icon in the Eclipse toolbar.</li>
 
         <li>In other IDEs: Navigate to your SDK's <code>tools/</code> directory and execute the
         <code>android</code> tool with no arguments.</li>
@@ -72,7 +72,7 @@
     <li>Click <strong>Create AVD</strong>.</li>
   </ol>
 
-  <p>Your AVD is now ready and you can either close the SDK and AVD Manager, create more AVDs, or
+  <p>Your AVD is now ready and you can either close the AVD Manager, create more AVDs, or
   launch an emulator with the AVD by selecting a device and clicking <strong>Start</strong>.</p>
 
 <h3 id="hardwareopts">Hardware options</h3>
diff --git a/docs/html/guide/developing/projects/index.jd b/docs/html/guide/developing/projects/index.jd
index 63e67cd..b16e466 100644
--- a/docs/html/guide/developing/projects/index.jd
+++ b/docs/html/guide/developing/projects/index.jd
@@ -209,8 +209,8 @@
       application uses code and resources from an example library project called TicTacToeLib.</p>
 
       <p>To download the sample applications and run them as projects in
-      your environment, use the <em>Android SDK and AVD Manager</em> to download the "Samples for
-      SDK API 8" (or later) component into your SDK.</p>
+      your environment, use the <em>Android SDK Manager</em> to download the "Samples for
+      SDK API 8" (or later) package into your SDK.</p>
 
       <p>For more information and to browse the code of the samples, see
       the <a href="{@docRoot}resources/samples/TicTacToeMain/index.html">TicTacToeMain
@@ -227,8 +227,8 @@
     <p class="note"><strong>Note:</strong> You need SDK Tools r14 or newer to use the new library
     project feature that generates each library project into its own JAR file.
     You can download the tools and platforms using the
-    <em>Android SDK and AVD Manager</em>, as described in
-    <a href="{@docRoot}sdk/adding-components.html">Adding SDK Components</a>.</p>
+    <em>Android SDK Manager</em>, as described in
+    <a href="{@docRoot}sdk/adding-components.html">Adding SDK Packages</a>.</p>
 
     <p>If you have source code and resources that are common to multiple Android projects, you
     can move them to a library project so that it is easier to maintain across applications and
diff --git a/docs/html/guide/developing/tools/adt.jd b/docs/html/guide/developing/tools/adt.jd
index e48a5ae..d473e85 100644
--- a/docs/html/guide/developing/tools/adt.jd
+++ b/docs/html/guide/developing/tools/adt.jd
@@ -102,9 +102,8 @@
     (<strong>Window &gt; Open Perspective &gt; Traceview</strong>). </li>
 
     <li><a href="{@docRoot}guide/developing/tools/android.html">android</a>: Provides access to
-    the Android SDK and AVD Manager. Other <code>android</code> features such as creating or
-    updating projects (application and library) are integrated throughout the Eclipse IDE
-    (<strong>Window &gt; Android SDK and AVD Manager</strong>). </li>
+    the Android SDK Manager and AVD Manager. Other <code>android</code> features such as creating or
+    updating projects (application and library) are integrated throughout the Eclipse IDE. </li>
 
     <li><a href="{@docRoot}guide/developing/debugging/debugging-ui.html#HierarchyViewer">Hierarchy
     Viewer</a>: Allows you to visualize your application's view hierarchy to find inefficiencies
diff --git a/docs/html/guide/developing/tools/android.jd b/docs/html/guide/developing/tools/android.jd
index a67012f..295a720 100644
--- a/docs/html/guide/developing/tools/android.jd
+++ b/docs/html/guide/developing/tools/android.jd
@@ -15,9 +15,16 @@
     the Command Line</a>.</li>
 
     <li>Update your Android SDK with new platforms, add-ons, and documentation. See <a href= 
-    "{@docRoot}sdk/adding-components.html">Adding SDK Components</a>.</li>
+    "{@docRoot}sdk/adding-components.html">Adding SDK Packages</a>.</li>
   </ul>If you are using Eclipse, the <code>android</code> tool's features are integrated
   into ADT, so you should not need to use this tool directly.
+  
+  <p class="note"><strong>Note:</strong> The documentation of options below is not exhaustive
+and may be out of date. For the most current list of options, execute <code>android
+--help</code>.</p>
+  
+  
+  
 
   <h2>Syntax</h2>
   <pre>android [global options] action [action options]</pre>
@@ -52,6 +59,26 @@
     </tr>
 
     <tr>
+      <td rowspan="6"><code>avd</code></td>
+
+      <td>None</td>
+
+      <td>Launch the AVD Manager</td>
+
+      <td></td>
+    </tr>
+
+    <tr>
+      <td rowspan="6"><code>sdk</code></td>
+
+      <td>None</td>
+
+      <td>Launch the Android SDK Manager</td>
+
+      <td></td>
+    </tr>
+
+    <tr>
       <td rowspan="6"><code>create avd</code></td>
 
       <td><code>-n &lt;name&gt;</code></td>
diff --git a/docs/html/guide/developing/tools/index.jd b/docs/html/guide/developing/tools/index.jd
index 3d831f3..5e9f686 100644
--- a/docs/html/guide/developing/tools/index.jd
+++ b/docs/html/guide/developing/tools/index.jd
@@ -12,8 +12,8 @@
 <h2 id="tools-sdk">SDK Tools</h2>
 <p>The SDK tools are installed with the SDK starter package and are periodically updated.
 The SDK tools are required if you are developing Android applications. The most important SDK tools
-include the Android SDK and AVD Manager (<code>android</code>), the emulator
-(<code>emulator</code>), and the Dalvik Debug Monitor Server
+include the Android SDK Manager (<code>android sdk</code>), the AVD Manager (<code>android
+avd</code>) the emulator (<code>emulator</code>), and the Dalvik Debug Monitor Server
 (<code>ddms</code>). A short summary of some frequently-used SDK tools is provided below.</p>
 
 <dl>
diff --git a/docs/html/guide/market/billing/billing_integrate.jd b/docs/html/guide/market/billing/billing_integrate.jd
index b49dc25..4b3650f 100755
--- a/docs/html/guide/market/billing/billing_integrate.jd
+++ b/docs/html/guide/market/billing/billing_integrate.jd
@@ -149,8 +149,8 @@
 </table>
 
 <p>The in-app billing sample application is available as a downloadable component of the Android
-SDK. To download the sample application component, launch the Android SDK and AVD Manager and then
-select the "Google Market Billing package" component (see figure 1), and click <strong>Install
+SDK. To download the sample application component, launch the Android SDK Manager and then
+select the <strong>Google Market Billing package</strong> component (see figure 1), and click <strong>Install
 Selected</strong> to begin the download.</p>
 
 
@@ -160,7 +160,7 @@
   the AIDL file.
 </p>
 
-<p>When the download is complete, the Android SDK and AVD Manager saves the component into the
+<p>When the download is complete, the Android SDK Manager saves the component into the
 following directory:</p>
 
 <p><code>&lt;sdk&gt;/extras/google/market_billing/</code></p>
diff --git a/docs/html/guide/market/licensing/overview.jd b/docs/html/guide/market/licensing/overview.jd
index 05a3a40..e7e23f8 100644
--- a/docs/html/guide/market/licensing/overview.jd
+++ b/docs/html/guide/market/licensing/overview.jd
@@ -58,8 +58,9 @@
 together to assemble the information and the Google Play client passes it to the server. </p>
 
 <p>To help you add licensing to your application, the Android SDK provides a downloadable set of
-library sources that you can include in your application project: the "Google Market Licensing
-package." The License Verification Library (LVL) is a library you can add to your application that
+library sources that you can include in your application project: the Google Market
+Licensing package. The License Verification Library (LVL) is a library you can add to your
+application that
 handles all of the licensing-related communication with the Google Play licensing service. With
 the LVL added to your application, your application can determine its licensing status for the
 current user by simply calling a method and implementing a callback that receives the status
@@ -116,7 +117,7 @@
 
 <h2 id="LVL">Licensing Verification Library</h2>
 
-<p>The Android SDK provides a downloadable component called the "Google Market Licensing package,"
+<p>The Android SDK provides a downloadable package called the Google Market Licensing package,
 which includes the License Verification Library (LVL). The LVL greatly simplifies the process of
 adding licensing to your application and helps ensure a more secure, robust implementation for your
 application. The LVL provides internal classes that handle most of the standard operations of a
@@ -162,8 +163,8 @@
 when the server returns a licensed response.</dd>
 </dl>
 
-<p>The LVL is available as a downloadable component of the Android SDK. The
-component includes both the LVL itself and an example application that shows how
+<p>The LVL is available as a downloadable package of the Android SDK. The
+package includes both the LVL itself and an example application that shows how
 the library should be integrated with your application and how your application
 should manage response data, UI interaction, and error conditions. </p>
 
diff --git a/docs/html/guide/market/licensing/setting-up.jd b/docs/html/guide/market/licensing/setting-up.jd
index 41e3bc4..0de7819 100644
--- a/docs/html/guide/market/licensing/setting-up.jd
+++ b/docs/html/guide/market/licensing/setting-up.jd
@@ -173,25 +173,25 @@
 href="#acct-signin">Signing in to an authorized account</a>, below.</p></li>
 </ul>
 
-<p>Several versions of the add-on are available through the SDK Manager, but only
-<strong>Google APIs Add-On, API 8 (release 2) or higher</strong> includes the necessary Google
+<p>Several versions of the Google APIs add-on are available through the SDK Manager, but only
+the version for Android 2.2 and higher includes the necessary Google
 Play services.</p>
 
 <p>To set up an emulator for adding licensing to an application, follow
 these steps: </p>
 
 <ol>
-  <li>Launch the Android SDK Manager. </li>
-  <li>In the <strong>Available Packages</strong> panel, select and download the
-SDK component "Google APIs (Google Inc.) - API Level 8" (or higher) from the SDK
-repository.
-  <p>When the download is complete, use the Android SDK Manager to
-create a new AVD based on that component, described next.</p></li>
-  <li>In the <strong>Virtual
-Devices</strong> panel of the Android SDK Manager, click
+  <li>Launch the Android SDK Manager (available under the Eclipse <strong>Window</strong>
+menu or by executing {@code &lt;sdk>/tools/android sdk}).</li>
+  <li>Select and download <strong>Google APIs</strong> for the Android version you'd like to target
+(must be Android 2.2 or higher).</li>
+  <li>When the download is complete, open the AVD Manager (available under the Eclipse
+<strong>Window</strong>
+menu or by executing {@code &lt;sdk>/tools/android avd}).</li>
+  <li>Click
 <strong>New</strong> and set the configuration details for the new AVD. </li>
   <li>In the dialog that appears, assign a descriptive name to the AVD and then
-use the "Target" menu to choose the "Google APIs (Google Inc.) - API Level 8" as
+use the Target menu to choose the <strong>Google APIs</strong> as
 the system image to run on the new AVD. Set the other configuration details as
 needed and then click <strong>Create AVD</strong> to finish. The SDK tools
 create the new AVD configuration, which then appears in the list of available
@@ -238,8 +238,8 @@
 application. In all cases, we recommend that you download the LVL and use it as
 the basis for the licensing implementation in your application.</p>
 
-<p>The LVL is available as a downloadable component of the Android SDK. The
-component includes: </p>
+<p>The LVL is available as a downloadable package of the Android SDK. The
+package includes: </p>
 
 <ul>
 <li>The LVL sources, stored inside an Android library project. </li>
@@ -248,9 +248,9 @@
 classes to check and enforce licensing.</li>
 </ul>
 
-<p>To download the LVL component into your development environment, use the
+<p>To download the LVL package into your development environment, use the
 Android SDK Manager. Launch the Android SDK Manager and then
-select the "Google Market Licensing" component, as shown in figure 2.
+select the <strong>Google Market Licensing</strong> package, as shown in figure 2.
 Accept the terms and click <strong>Install Selected</strong> to begin the download. </p>
 
 <img src="{@docRoot}images/licensing_package.png" alt=""/>
@@ -265,8 +265,8 @@
 <code>&lt;<em>sdk</em>&gt;/extras/google/market_licensing/sample/</code>&nbsp;&nbsp;(the example
 application)</p>
 
-<p>If you aren't familiar with how to download components into your SDK, see the
-<a href="{@docRoot}sdk/adding-components.html">Adding SDK Components</a>
+<p>If you aren't familiar with how to download packess into your SDK, see the
+<a href="{@docRoot}sdk/adding-components.html">Adding SDK Packages</a>
 document. </p>
 
 
@@ -676,7 +676,7 @@
 
 <ol>
 <li>Open Settings &gt; Accounts &amp; sync</li>
-<li>Select <strong>Add Account</strong> and choose to add a "Google" account.
+<li>Select <strong>Add Account</strong> and choose to add a Google account.
 </li>
 <li>Select <strong>Next</strong> and then <strong>Sign in</strong>.</li>
 <li>Enter the username and password of either the publisher account or a test
diff --git a/docs/html/guide/practices/optimizing-for-3.0.jd b/docs/html/guide/practices/optimizing-for-3.0.jd
index 140c50c..d6c621e 100644
--- a/docs/html/guide/practices/optimizing-for-3.0.jd
+++ b/docs/html/guide/practices/optimizing-for-3.0.jd
@@ -108,7 +108,7 @@
 SDK starter package now</a>.)</p>
 
 <ol>
-  <li><a href="{@docRoot}sdk/adding-components.html#launching">Launch the Android SDK and AVD
+  <li><a href="{@docRoot}sdk/adding-components.html#launching">Launch the Android SDK
 Manager</a> and install the following:
     <ul>
       <li>SDK Platform Android 3.0</li>
@@ -147,7 +147,7 @@
 functionality on Android 3.0.</p>
 
 <p class="note"><strong>Tip:</strong> To improve the startup time for the emulator, enable snapshots
-for the AVD when you create it with the SDK and AVD Manager (there's a checkbox in the AVD creator
+for the AVD when you create it with the AVD Manager (there's a checkbox in the AVD creator
 to <strong>Enable</strong> snapshots). Then, start the AVD from the AVD manager and check <b>Launch
 from snapshot</b> and <b>Save to snapshot</b>. This way, when you close the emulator, a snapshot of
 the AVD state is saved and used to quickly relaunch the AVD next time. However, when you choose to
@@ -281,7 +281,7 @@
 href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code android:minSdkVersion}</a>
 or build target, we're providing a static library called the <a
 href="{@docRoot}sdk/compatibility-library.html">Compatibility Library</a>
-(downloadable from the AVD and SDK Manager).</p>
+(downloadable from the Android SDK Manager).</p>
 <p>This library includes APIs for <a
 href="{@docRoot}guide/topics/fundamentals/fragments.html">fragments</a>, <a
 href="{@docRoot}guide/topics/fundamentals/loaders.html">loaders</a>, and some updated classes. By
@@ -421,7 +421,7 @@
 href="{@docRoot}sdk/android-3.0.html#api">Android 3.0 Platform</a> document also have accompanying
 samples that allow you to preview the effects and can help you understand how to use them. To get
 the samples, download them from the SDK repository <a href="{@docRoot}sdk/adding-components.html"
->using the Android SDK and AVD Manager</a>. After downloading the samples ("Samples for SDK API
+>using the Android SDK Manager</a>. After downloading the samples ("Samples for SDK API
 11"), you can find them in <code>&lt;sdk_root&gt;/samples/android-11/</code>. The following list
 provides links to the browsable source code for some of the samples:</p>
 
diff --git a/docs/html/guide/practices/screens_support.jd b/docs/html/guide/practices/screens_support.jd
index 29dff26..a870b22 100644
--- a/docs/html/guide/practices/screens_support.jd
+++ b/docs/html/guide/practices/screens_support.jd
@@ -1242,12 +1242,12 @@
 <p>To set up an environment for testing your application's screen support, you should create a
 series of AVDs (Android Virtual Devices), using emulator skins and screen configurations that
 emulate the screen sizes and densities you want your application to support. To do so, you can use
-the Android SDK and AVD Manager to create the AVDs and launch them with a graphical interface.</p>
+the AVD Manager to create the AVDs and launch them with a graphical interface.</p>
 
-<p>To launch the Android SDK and AVD Manager, execute the {@code
+<p>To launch the Android SDK Manager, execute the {@code
 SDK Manager.exe} from your Android SDK directory (on Windows only) or execute {@code android} from
-the {@code &lt;sdk&gt;/tools/} directory (on all platforms). Figure 6 shows the Android SDK and
-AVD Manager with a selection of AVDs, for testing various screen configurations.</p>
+the {@code &lt;sdk&gt;/tools/} directory (on all platforms). Figure 6 shows the AVD
+Manager with a selection of AVDs, for testing various screen configurations.</p>
 
 <p>Table 3 shows the various emulator skins that are available in the Android SDK, which you can use
 to emulate some of the most common screen configurations.</p>
@@ -1340,7 +1340,7 @@
 <div class="figure" style="width:204px">
   <img src="{@docRoot}images/screens_support/avd-start.png" alt="" />
   <p class="img-caption"><strong>Figure 7.</strong>
-  Size and density options you can set, when starting an AVD from the Android SDK and AVD
+  Size and density options you can set, when starting an AVD from the AVD
 Manager.</p>
 </div>
 
@@ -1349,12 +1349,12 @@
 it a lot easier to compare the results at various sizes and densities. To
 do so you need to know the approximate density, in dpi, of your computer
 monitor (for instance, a 30" Dell monitor has a density of about 96 dpi). When you launch an AVD
-from the Android SDK and AVD Manager, you can specify the screen size for the emulator and your
+from the AVD Manager, you can specify the screen size for the emulator and your
 monitor dpi in the Launch Options, as shown in figure 7.</p>
 
 <p>If you would like to test your application on a screen that uses a resolution
 or density not supported by the built-in skins, you can create an AVD that uses a custom resolution
-or density. When creating the AVD from the Android SDK and AVD Manager, specify the Resolution,
+or density. When creating the AVD from the AVD Manager, specify the Resolution,
 instead of selecting a Built-in Skin.</p>
 
 <p>If you are launching your AVD from the command line, you can specify the scale for
diff --git a/docs/html/guide/topics/location/index.jd b/docs/html/guide/topics/location/index.jd
index 5f98902..8a2e9cd 100644
--- a/docs/html/guide/topics/location/index.jd
+++ b/docs/html/guide/topics/location/index.jd
@@ -98,7 +98,7 @@
 href="http://code.google.com/android/add-ons/google-apis">http://code.google.com/android/add-ons/google-apis</a></p>
 
 <p>For your convenience, the Google APIs add-on is also available as a downloadable component from
-the Android SDK and AVD Manager (see <a href="{@docRoot}sdk/adding-components.html">Adding SDK
+the Android SDK Manager (see <a href="{@docRoot}sdk/adding-components.html">Adding SDK
 Components</a>).</p>
 
 <p class="note"><strong>Note:</strong> In order to display Google Maps data in a
diff --git a/docs/html/guide/topics/manifest/uses-feature-element.jd b/docs/html/guide/topics/manifest/uses-feature-element.jd
index 9175566..5f0a501 100644
--- a/docs/html/guide/topics/manifest/uses-feature-element.jd
+++ b/docs/html/guide/topics/manifest/uses-feature-element.jd
@@ -501,7 +501,7 @@
 <p class="note"><strong>Note:</strong> You must use the version of
 <code>aapt</code> that is provided for the latest Platform-Tools component available. If
 you do not have the latest Platform-Tools component, download it using the <a
-href="{@docRoot}sdk/adding-components.html">Android SDK and AVD Manager</a>.
+href="{@docRoot}sdk/adding-components.html">Android SDK Manager</a>.
 </p></li>
 <li>Run <code>aapt</code> using this syntax: </li>
 </ol>
diff --git a/docs/html/guide/tutorials/views/hello-mapview.jd b/docs/html/guide/tutorials/views/hello-mapview.jd
index 458db4f..5217b6b 100644
--- a/docs/html/guide/tutorials/views/hello-mapview.jd
+++ b/docs/html/guide/tutorials/views/hello-mapview.jd
@@ -14,8 +14,8 @@
 href="http://code.google.com/android/add-ons/google-apis">http://code.google.com/android/add-ons/google-apis</a></p>
 
 <p>The Google APIs add-on requires Android 1.5 SDK or later release. After
-installing the add-on in your SDK, set your project properties to use the build
-target called "Google APIs Add-on". See the instructions for setting a build
+installing the add-on in your SDK, set your project properties to use a <strong>Google
+APIs</strong> build target. See the instructions for setting a build
 target in <a href="{@docRoot}guide/developing/eclipse-adt.html">Developing in
 Eclipse with ADT</a> or <a
 href="{@docRoot}guide/developing/other-ide.html">Developing in Other IDEs</a>,
diff --git a/docs/html/images/developing/ddms-network.png b/docs/html/images/developing/ddms-network.png
new file mode 100644
index 0000000..5aa1290
--- /dev/null
+++ b/docs/html/images/developing/ddms-network.png
Binary files differ
diff --git a/docs/html/images/efficient-downloads/DDMS.png b/docs/html/images/efficient-downloads/DDMS.png
new file mode 100644
index 0000000..e7b0b94
--- /dev/null
+++ b/docs/html/images/efficient-downloads/DDMS.png
Binary files differ
diff --git a/docs/html/images/efficient-downloads/graphs.png b/docs/html/images/efficient-downloads/graphs.png
new file mode 100644
index 0000000..65faeaa
--- /dev/null
+++ b/docs/html/images/efficient-downloads/graphs.png
Binary files differ
diff --git a/docs/html/images/efficient-downloads/mobile_radio_state_machine.png b/docs/html/images/efficient-downloads/mobile_radio_state_machine.png
new file mode 100644
index 0000000..e06608b
--- /dev/null
+++ b/docs/html/images/efficient-downloads/mobile_radio_state_machine.png
Binary files differ
diff --git a/docs/html/images/screens_support/avds-config.png b/docs/html/images/screens_support/avds-config.png
index 3af1c39..c7d534c 100644
--- a/docs/html/images/screens_support/avds-config.png
+++ b/docs/html/images/screens_support/avds-config.png
Binary files differ
diff --git a/docs/html/index.jd b/docs/html/index.jd
index 787a655..d3203bb 100644
--- a/docs/html/index.jd
+++ b/docs/html/index.jd
@@ -53,6 +53,20 @@
                                               <p><a href="{@docRoot}sdk/index.html">Learn more &raquo;</a></p>
                                       </td>
                               </tr>
+                              
+                              
+                              <tr>
+                                      <td colspan="2"><div class="seperator">&nbsp;</div></td>
+                              </tr>
+                              <tr>
+                                      <td class="imageCell"><a href="{@docRoot}design/index.html"><img src="{@docRoot}assets/images/icon_design.png" style="padding:5px" /></a></td>
+                                      <td>
+                                              <h2 class="green">Design</h2>
+                                              <p>Learn about principles, building blocks, and patterns for creating world-class Android user interfaces.</p>
+                                              <p><a href="{@docRoot}design/index.html">Learn more &raquo;</a></p>
+                                      </td>
+                              </tr>
+                              
                               <tr>
                                       <td colspan="2"><div class="seperator">&nbsp;</div></td>
                               </tr>
@@ -64,17 +78,7 @@
                                               <p><a href="http://play.google.com/apps/publish">Learn more &raquo;</a></p>
                                       </td>
                               </tr>
-                              <tr>
-                                      <td colspan="2"><div class="seperator">&nbsp;</div></td>
-                              </tr>
-                              <tr>
-                                      <td class="imageCell"><a href="http://source.android.com"><img src="{@docRoot}assets/images/icon_contribute.jpg" style="padding:0" /></a></td>
-                                      <td>
-                                              <h2 class="green">Contribute</h2>
-                                              <p>Android Open Source Project gives you access to the entire platform source.</p>
-                                              <p><a href="http://source.android.com">Learn more &raquo;</a></p>
-                                      </td>
-                              </tr>
+                              
                               <tr>
                                       <td colspan="2"><div class="seperator">&nbsp;</div></td>
                               </tr>
diff --git a/docs/html/offline.jd b/docs/html/offline.jd
index 5f8e37ce..edd8eb0 100644
--- a/docs/html/offline.jd
+++ b/docs/html/offline.jd
@@ -40,7 +40,7 @@
 <p>Follow the guide to <a href="{@docRoot}sdk/installing.html">Installing the Android SDK</a>, which
 will help you setup your development environment.</p>
 
-<h3>If you've installed new SDK components using the SDK and AVD Manager</h3>
+<h3>If you've installed new SDK components using the Android SDK Manager</h3>
 
 <p>There's no additional setup.</p>
 
diff --git a/docs/html/resources/articles/creating-input-method.jd b/docs/html/resources/articles/creating-input-method.jd
index e4b77f4..84c2704 100644
--- a/docs/html/resources/articles/creating-input-method.jd
+++ b/docs/html/resources/articles/creating-input-method.jd
@@ -3,248 +3,526 @@
 parent.link=../browser.html?tag=article
 @jd:body
 
-
 <div id="qv-wrapper">
 <div id="qv">
-
-  <h2>See also</h2>
-  <ol>
-    <li><a href="{@docRoot}resources/articles/on-screen-inputs.html">Onscreen Input Methods</a></li>
-    <li><a href="{@docRoot}resources/samples/SoftKeyboard/index.html">Soft Keyboard sample</a></li>
-  </ol>
-
+<h2>See also</h2>
+<ol>
+    <li>
+        <a href="{@docRoot}resources/articles/on-screen-inputs.html">Onscreen Input Methods</a>
+    </li>
+    <li>
+        <a href="{@docRoot}resources/samples/SoftKeyboard/index.html">Soft Keyboard sample</a>
+    </li>
+</ol>
 </div>
 </div>
-
-
-<p>To create an input method (IME) for entering text into text fields 
-and other Views, you need to extend the {@link android.inputmethodservice.InputMethodService}.
-class. This class provides much of the basic implementation for an input 
-method, in terms of managing the state and visibility of the input method and
-communicating with the currently visible activity.</p>
-
-<p>A good starting point would be the SoftKeyboard sample code provided as part
-of the SDK. You can modify the sample code to start building your own input
-method.</p>
-
-<p>An input method is packaged like any other application or service. In the 
-<code>AndroidManifest.xml</code> file, you declare the input method as a
-service, with the appropriate intent filter and any associated meta data:</p>
-
-<pre>&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.example.fastinput"&gt;
-
-    &lt;application android:label="@string/app_label"&gt;<br>
-        &lt;!-- Declares the input method service --&gt;
-        &lt;service android:name="FastInputIME"
-            android:label="@string/fast_input_label"
-            android:permission="android.permission.BIND_INPUT_METHOD"&gt;
-            &lt;intent-filter&gt;
-                &lt;action android:name="android.view.InputMethod" /&gt;
-            &lt;/intent-filter&gt;
-            &lt;meta-data android:name="android.view.im" android:resource="@xml/method" /&gt;
-        &lt;/service&gt;
-
-        &lt;!-- Optional activities. A good idea to have some user settings. --&gt;
-        &lt;activity android:name="FastInputIMESettings" android:label="@string/fast_input_settings"&gt;
-            &lt;intent-filter&gt;
-                &lt;action android:name="android.intent.action.MAIN"/&gt;
-            &lt;/intent-filter&gt;
-        &lt;/activity&gt; 
-    &lt;/application&gt;
-&lt;/manifest&gt;</pre>
-
-<p>If your input method allows the user to tweak some settings, you should
-provide a settings activity that can be launched from the Settings application.
-This is optional and you may choose to provide all user settings directly in
-your IME's UI.</p>
-
-<p>The typical life-cycle of an <code>InputMethodService</code> looks like 
-this:</p>
-
-<p><img src="images/ime_003.png" style="border: medium none ; width: 374px; height: 871px;"></p>
-
-<h3>Visual Elements</h3>
-
-<p>There are two main visual elements for an input method—the input view and the
-candidates view. You don't have to follow this style though, if one of them is
-not relevant to your input method experience.</p>
-
-<h4>Input View</h4>
-
-<p>This is where the user can input text either in the form of keypresses,
-handwriting or other gestures. When the input method is displayed for the first
-time, <code>InputMethodService.onCreateInputView()</code> will be called. Create
-and return the view hierarchy that you would like to display in the input method
-window.</p>
-
-<h4>Candidates View</h4>
-
-<p>This is where potential word corrections or completions are presented to the
-user for selection. Again, this may or may not be relevant to your input method
-and you can return <code>null</code> from calls to
-<code>InputMethodService.onCreateCandidatesView()</code>, which is the default
-behavior.</p>
-
-<h3>Designing for the different Input Types</h3>
-
-<p>An application's text fields can have different input types specified on
-them, such as free form text, numeric, URL, email address and search. When you
-implement a new input method, you need to be aware of the different input types.
-Input methods are not automatically switched for different input types and so
-you need to support all types in your IME. However, the IME is not responsible
-for validating the input sent to the application. That's the responsibility of
-the application.</p>
-
-<p>For example, the LatinIME provided with the Android platform provides 
-different layouts for text and phone number entry:</p>
-
-<p><img style="margin: 0pt 10px 0pt 0pt; width: 319px; height: 198px;" src="images/ime_002.png"><img style="width: 320px; height: 199px;" src="images/ime.png"></p>
-
-<p><code>InputMethodService.onStartInputView()</code> is called with an<code> 
-EditorInfo</code> object that contains details about the input type and other 
-attributes of the application's text field.</p><p>(<code>EditorInfo.inputType 
-&amp; EditorInfo.TYPE_CLASS_MASK</code>) can be one of many different values, 
-including:</p>
-
+<p>
+    An input method editor (IME) is a user control that enables users to enter text. Android 
+    provides an extensible input method framework that allows applications to provide users 
+    alternative input methods, such as on-screen keyboards or even speech input. Once installed, 
+    users can select which IME they want to use from the system settings and use it across the 
+    entire system; only one IME may be enabled at a time.
+</p>
+<p>
+    To add an IME to the Android system, you create an Android application
+    containing a class that extends {@link android.inputmethodservice.InputMethodService}. In 
+    addition, you usually create a "settings" activity that passes options to the IME
+    service. You can also define a settings UI that's displayed as part of the system settings.
+</p>
+<p>This article covers the following:</p>
 <ul>
-<li><code>TYPE_CLASS_NUMBER</code></li>
-<li><code>TYPE_CLASS_DATETIME</code></li>
-<li><code>TYPE_CLASS_PHONE</code></li>
-<li><code>TYPE_CLASS_TEXT</code></li>
+    <li>The IME lifecycle.</li>
+    <li>Declaring IME components in the application manifest.</li>
+    <li>The IME API.</li>
+    <li>Designing an IME UI.</li>
+    <li>Sending text from an IME to an application.</li>
+    <li>Working with IME subtypes.</li>
 </ul>
+<p>
+    If you haven't worked with IMEs before, you should read the introductory article 
+    <a href="{@docRoot}resources/articles/on-screen-inputs.html">Onscreen Input Methods</a> first.
+    Also, the Soft Keyboard sample app included in the SDK contains sample code that you can modify
+    to start building your own IME.
+</p>
+<h2 id="InputMethodLifecycle">The IME Lifecycle</h2>
+<p>
+    The following diagram describes the life cycle of an IME:
+</p>
+<img src="{@docRoot}resources/articles/images/inputmethod_lifecycle_image.png" alt="" height="845"
+    id="figure1" />
+<p class="img-caption">
+    <strong>Figure 1.</strong> The life cycle of an IME.
+</p>
+<p>
+    The following sections describe how to implement the UI and code associated with an IME that
+    follows this lifecycle.
+</p>
+<h2 id="DefiningIME">Declaring IME Components in the Manifest</h2>
+<p>
+    In the Android system, an IME is an Android application that contains a special IME service.
+    The application's manifest file must declare the service, request the necessary permissions, 
+    provide an intent filter that matches the action <code>action.view.InputMethod</code>, and 
+    provide metadata that defines characteristics of the IME. In addition, to provide a settings
+    interface that allows the user to modify the behavior of the IME, you can define a "settings"
+    activity that can be launched from System Settings.
+</p>
+<p>
+    The following snippet declares IME service. It requests the permission {@link
+    android.Manifest.permission#BIND_INPUT_METHOD} to allow the service to connect the IME to 
+    the system, sets up an intent filter that matches the action 
+    <code>android.view.InputMethod</code>, and defines metadata for the IME:
+</p>
+<pre>
+&lt;!-- Declares the input method service --&gt;
+    &lt;service android:name="FastInputIME"
+        android:label="&#64;string/fast_input_label"
+        android:permission="android.permission.BIND_INPUT_METHOD"&gt;
+        &lt;intent-filter&gt;
+            &lt;action android:name="android.view.InputMethod" /&gt;
+        &lt;/intent-filter&gt;
+        &lt;meta-data android:name="android.view.im" android:resource="&#64;xml/method" /&gt;
+    &lt;/service&gt;
+</pre>
+<p>
+    This next snippet declares the settings activity for the IME. It has an intent filter for
+    {@link android.content.Intent#ACTION_MAIN} that indicates this activity is the main entry point
+    for the IME application:</p>
+<pre>
+    &lt;!-- Optional: an activity for controlling the IME settings --&gt;
+    &lt;activity android:name="FastInputIMESettings" 
+        android:label="&#64;string/fast_input_settings"&gt;
+        &lt;intent-filter&gt;
+            &lt;action android:name="android.intent.action.MAIN"/&gt;
+        &lt;/intent-filter&gt;
+    &lt;/activity&gt;
+</pre>
+<p>
+    You can also provide access to the IME's settings directly from its UI.
+</p>
+<h2 id="IMEAPI">The Input Method API</h2>
+<p>
+    Classes specific to IMEs are found in the {@link android.inputmethodservice} and {@link
+    android.view.inputmethod} packages. The {@link android.view.KeyEvent} class is important for
+    handling keyboard characters.
+</p>
+<p>
+    The central part of an IME is a service component, a class that extends 
+    {@link android.inputmethodservice.InputMethodService}. In addition to implementing the 
+    normal service lifecycle, this class has callbacks for providing your IME's UI, handling user 
+    input, and delivering text to the field that currently has focus. By default, the
+    {@link android.inputmethodservice.InputMethodService} class provides most of the implementation 
+    for managing the state and visibility of the IME and communicating with the current 
+    input field.
+</p>
+<p>
+    The following classes are also important:
+</p>
+<dl>
+    <dt>{@link android.view.inputmethod.BaseInputConnection}</dt>
+    <dd>
+        Defines the communication channel from an {@link android.view.inputmethod.InputMethod}
+        back to the application that is receiving its input. You use it to read text around the
+        cursor, commit text to the text box, and send raw key events to the application.
+        Applications should extend this class rather than implementing the base interface 
+        {@link android.view.inputmethod.InputConnection}.
+    </dd>
+    <dt>{@link android.inputmethodservice.KeyboardView}</dt>
+    <dd>
+        An extension of {@link android.view.View} that renders a keyboard and responds to user
+        input events. The keyboard layout is specified by an instance of 
+        {@link android.inputmethodservice.Keyboard}, which you can define in an XML file.
+    </dd>
+</dl>
+<h2 id="IMEUI">Designing the Input Method UI</h2>
+<p>
+    There are two main visual elements for an IME: the <strong>input</strong> view and the
+    <strong>candidates</strong> view. You only have to implement the elements that are relevant to
+    the input method you're designing.
+</p>
+<h3 id="InputView">Input view</h3>
+<p>
+    The input view is the UI where the user inputs text, in the form of keyclicks, handwriting or
+    gestures. When the iIME is displayed for the first time, the system calls the 
+    {@link android.inputmethodservice.InputMethodService#onCreateInputView()} callback. In your
+    implementation of this method, you create the layout you want to display in the IME
+    window and return the layout to the system. This snippet is an example of implementing the
+    {@link android.inputmethodservice.InputMethodService#onCreateInputView()} method:
+<pre>
+    &#64;Override 
+    public View onCreateInputView() { 
+        MyKeyboardView inputView = 
+            (MyKeyboardView) getLayoutInflater().inflate( R.layout.input, null);
+    
+        inputView.setOnKeyboardActionListener(this); inputView.setKeyboard(mLatinKeyboard); 
+        
+        return mInputView; 
+    } 
+</pre>
+<p>
+    In this example, {@code MyKeyboardView} is an instance of a custom implementation of 
+    {@link android.inputmethodservice.KeyboardView} that renders a 
+    {@link android.inputmethodservice.Keyboard}. If you’re building a traditional QWERTY keyboard, 
+    see the <a href=”{@docRoot}resources/samples/SoftKeyboard/index.html”>Soft Keyboard</a> sample 
+    app for an example of how to extend the {@link android.inputmethodservice.KeyboardView} class.
+</p>
+<h3 id="CandidateView">Candidates view</h3>
+<p>
+    The candidates view is the UI where the IME displays potential word corrections or
+    suggestions for the user to select. In the IME lifecycle, the system calls 
+    {@link android.inputmethodservice.InputMethodService#onCreateCandidatesView()} when it's ready 
+    to display the candidate view. In your implementation of this method, return a layout that shows
+    word suggestions, or return null if you don’t want to show anything (a null response is the
+    default behavior, so you don’t have to implement this if you don’t provide suggestions).</p>
+<p>
+    For an example implementation that provides user suggestions, see the 
+    <a href=”{@docRoot}resources/samples/SoftKeyboard/index.html”>Soft Keyboard</a> sample app.
+</p>
+<h3 id="DesignConsiderations">UI design considerations</h3>
+<p>
+    This section describes some specific UI design considerations for IMEs.
+</p>
+<h4>Handling multiple screen sizes</h4>
+<p>
+    The UI for your IME must be able to scale for different screen sizes, and it also
+    must handle both landscape and portrait orientations. In non-fullscreen IME mode, leave
+    sufficient space for the application to show the text field and any associated context, so that
+    no more than half the screen is occupied by the IME. In fullscreen IME mode this is not an
+    issue.
+</p>
+<h4>Handling different input types</h4>
+<p>
+    Android text fields allow you to set a specific input type, such as free form text, numbers,
+    URLs, email addresses, and search strings. When you implement a new IME, you need to
+    detect the input type of each field and provide the appropriate interface for it. However, you
+    don't have to set up your IME to check that the user entered text that's valid for the
+    input type; that's the responsibility of the application that owns the text field.
+</p>
+<p>
+    For example, here are screenshots of the interfaces that the Latin IME provided with the
+    Android platform provides for text and phone number inputs:
+</p>
+<img src="{@docRoot}resources/articles/images/inputmethod_text_type_screenshot.png" alt=""
+    height="142" id="figure2" />
+<img src="{@docRoot}resources/articles/images/inputmethod_numeric_type_screenshot.png" alt=""
+    height="120" id="figure2a" />
+<p class="img-caption">
+    <strong>Figure 2.</strong> Latin IME input types.
+</p>
+<p>
+    When an input field receives focus and your IME starts, the system calls 
+    {@link android.inputmethodservice.InputMethodService#onStartInputView(EditorInfo, boolean)
+    onStartInputView()}, passing in an {@link android.view.inputmethod.EditorInfo} object that 
+    contains details about the input type and other attributes of the text field. In this object, 
+    the {@link android.view.inputmethod.EditorInfo#inputType} field contains the text field's input
+    type.
+</p>
+<p>
+    The {@link android.view.inputmethod.EditorInfo#inputType} field is an <code>int</code>
+    that contains bit patterns for various input type settings. To test it for the text field's
+    input type, mask it with the constant {@link android.text.InputType#TYPE_MASK_CLASS}, like
+    this:
+</p>
+<pre>
+inputType &amp; InputType.TYPE_MASK_CLASS 
+</pre>
+<p>
+The input type bit pattern can have one of several values, including:
+</p>
+<dl>
+    <dt>{@link android.text.InputType#TYPE_CLASS_NUMBER}</dt>
+    <dd>
+        A text field for entering numbers. As illustrated in the previous screen shot, the
+        Latin IME displays a number pad for fields of this type.
+    </dd>
+    <dt>{@link android.text.InputType#TYPE_CLASS_DATETIME}</dt>
+    <dd>
+        A text field for entering a date and time.
+    </dd>
+    <dt>{@link android.text.InputType#TYPE_CLASS_PHONE}</dt>
+    <dd>
+        A text field for entering telephone numbers.
+    </dd>
+    <dt>{@link android.text.InputType#TYPE_CLASS_TEXT}</dt>
+    <dd>
+        A text field for entering all supported characters.
+    </dd>
+</dl>
+<p>
+    These constants are described in more detail in the reference documentation for 
+    {@link android.text.InputType}.
+</p>
+<p>
+    The {@link android.view.inputmethod.EditorInfo#inputType} field can contain other bits that
+    indicate a variant of the text field type, such as:
+</p>
+<dl>
+    <dt>{@link android.text.InputType#TYPE_TEXT_VARIATION_PASSWORD}</dt>
+    <dd>
+        A variant of {@link android.text.InputType#TYPE_CLASS_TEXT} for entering passwords. The
+        input method will display dingbats instead of the actual text.
+    </dd>
+    <dt>{@link android.text.InputType#TYPE_TEXT_VARIATION_URI}</dt>
+    <dd>
+        A variant of {@link android.text.InputType#TYPE_CLASS_TEXT} for entering web URLs and
+        other Uniform Resource Identifiers (URIs).
+    </dd>
+    <dt>{@link android.text.InputType#TYPE_TEXT_FLAG_AUTO_COMPLETE}</dt>
+    <dd>
+        A variant of {@link android.text.InputType#TYPE_CLASS_TEXT} for entering text that the
+        application "auto-completes" from a dictionary, search, or other facility.
+    </dd>
+</dl>
+<p>
+    Remember to mask {@link android.view.inputmethod.EditorInfo#inputType} with the appropriate
+    constant when you test for these variants. The available mask constants are listed in the
+    reference documentation for {@link android.text.InputType}.
+</p>
+<p class="caution">
+    <strong>Caution:</strong> In your own IME, make sure you handle text correctly when you send it
+    to a password field. Hide the password in your UI both in the input view and in the candidates
+    view. Also remember that you shouldn't store passwords on a device. To learn more, see the <a
+        href="{@docRoot}guide/practices/security.html">Designing for Security</a> guide.
+</p>
+<h2 id="SendText">Sending Text to the Application</h2>
+<p>
+    As the user inputs text with your IME, you can send text to the application by
+    sending individual key events or by editing the text around the cursor in the application's text
+    field. In either case, you use an instance of {@link android.view.inputmethod.InputConnection} 
+    to deliver the text. To get this instance, call 
+    {@link android.inputmethodservice.InputMethodService#getCurrentInputConnection
+    InputMethodService.getCurrentInputConnection()}.
+</p>
+<h3 id="EditingCursor">Editing the text around the cursor</h3>
+<p>
+    When you're handling the editing of existing text in a text field, some of the more useful
+    methods in {@link android.view.inputmethod.BaseInputConnection} are:
+</p>
+<dl>
+    <dt>
+        {@link android.view.inputmethod.BaseInputConnection#getTextBeforeCursor(int, int)
+        getTextBeforeCursor()}</dt>
+    <dd>
+        Returns a {@link java.lang.CharSequence} containing the number of requested characters
+        before the current cursor position.
+    </dd>
+    <dt>
+        {@link android.view.inputmethod.BaseInputConnection#getTextAfterCursor(int, int)
+        getTextAfterCursor()}
+    </dt>
+    <dd>
+        Returns a {@link java.lang.CharSequence} containing the number of requested characters
+        following the current cursor position.
+    </dd>
+    <dt>
+        {@link android.view.inputmethod.BaseInputConnection#deleteSurroundingText(int, int)
+        deleteSurroundingText()}
+    </dt>
+    <dd>
+        Deletes the specified number of characters before and following the current cursor
+        position.
+    </dd>
+    <dt>
+        {@link android.view.inputmethod.BaseInputConnection#commitText(CharSequence, int)
+        commitText()}
+    </dt>
+    <dd>
+        Commit a {@link java.lang.CharSequence} to the text field and set a new cursor
+        position.
+    </dd>
+</dl>
+<p>
+    For example, the following snippet shows how to replace the text "Fell" to the left of the
+    with the text "Hello!":
+</p>
+<pre>
+    InputConnection ic = getCurrentInputConnection();
+    
+    ic.deleteSurroundingText(4, 0);
+    
+    ic.commitText("Hello", 1);
+    
+    ic.commitText("!", 1);
+</pre>
+<h3 id="ComposeThenCommit">Composing text before committing</h3>
+<p>
+    If your IME does text prediction or requires multiple steps to compose a glyph or
+    word, you can show the progress in the text field until the user commits the word, and then you
+    can replace the partial composition with the completed text. You may give special treatment to 
+    the text by adding a "span" to it when you pass it to InputConnection#setComposingText().
+</p>
+<p>
+    The following snippet shows how to show progress in a text field:
+</p>
+<pre>
+    InputConnection ic = getCurrentInputConnection();
 
-<p>See <code>android.text.InputType</code> for more details.</p>
-
-<p><code>EditorInfo.inputType</code> can contain other masked bits that 
-indicate the class variation and other flags. For example, 
-<code>TYPE_TEXT_VARIATION_PASSWORD</code> or <code>TYPE_TEXT_VARIATION_URI</code> 
-or <code>TYPE_TEXT_FLAG_AUTO_COMPLETE</code>.</p>
-
-<h4>Password fields</h4>
-
-<p>Pay
-specific attention when sending text to password fields. Make sure that
-the password is not visible within your UI &mdash; neither in the input
-view or the candidates view. Also, do not save the password anywhere without
-explicitly informing the user.</p>
-
-<h3>Landscape vs. portrait</h3>
-
-<p>The UI needs to be able to scale between landscape and portrait orientations.
-In non-fullscreen IME mode, leave sufficient space for the application to show
-the text field and any associated context. Preferably, no more than half the
-screen should be occupied by the IME. In fullscreen IME mode this is not an
-issue.</p>
-
-<h3>Sending text to the application</h3>
-
-<p>There are two ways to send text to the application. You can either send
-individual key events or you can edit the text around the cursor in the
-application's text field.</p>
-
-<p>To send a key event, you can simply construct KeyEvent objects and call 
-<code>InputConnection.sendKeyEvent()</code>. Here are some examples:</p>
-
-<pre>InputConnection ic = getCurrentInputConnection();
-long eventTime = SystemClock.uptimeMillis();
-ic.sendKeyEvent(new KeyEvent(eventTime, eventTime,
-    KeyEvent.ACTION_DOWN, keyEventCode, 0, 0, 0, 0,
-    KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
-ic.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
-    KeyEvent.ACTION_UP, keyEventCode, 0, 0, 0, 0,
-    KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));</pre>
-
-<p>Or use the convenience method:</p>
-
-<pre>InputMethodService.sendDownUpKeyEvents(keyEventCode);</pre>
-
-<p class="note"><strong>Note</strong>:
-It is recommended to use the above method for certain fields such as
-phone number fields because of filters that may be applied to the text
-after each key press. Return key and delete key should also be sent as
-raw key events for certain input types, as applications may be watching
-for specific key events in order to perform an action.</p>
-
-<p>When editing text in a text field, some of the more useful methods on 
-<code>android.view.inputmethod.InputConnection</code> are:</p>
-
-<ul>
-<li><code>getTextBeforeCursor()</code></li>
-<li><code>getTextAfterCursor()</code></li>
-<li><code>deleteSurroundingText()</code></li>
-<li><code>commitText()</code></li>
-</ul>
-
-<p>For example, let's say the text "Fell" is to the left of the cursor
-and you want to replace it with "Hello!":</p>
-
-<pre>InputConnection ic = getCurrentInputConnection();
-ic.deleteSurroundingText(4, 0);
-ic.commitText("Hello", 1);
-ic.commitText("!", 1);</pre>
-
-<h4>Composing text before committing</h4>
-
-<p>If your input method does some kind of text prediction or requires multiple
-steps to compose a word or glyph, you can show the progress in the text field
-until the user commits the word and then you can replace the partial composition
-with the completed text. The text that is being composed will be highlighted in
-the text field in some fashion, such as an underline.</p>
-
-<pre>InputConnection ic = getCurrentInputConnection();
-ic.setComposingText("Composi", 1);
+    ic.setComposingText("Composi", 1);
 ...
-ic.setComposingText("Composin", 1);
+
+    ic.setComposingText("Composin", 1);
+
 ...
-ic.commitText("Composing ", 1);</pre>
 
-<p><img style="width: 320px; height: 98px; margin-bottom: 10px;" src="images/ime_006.png">
-<img style="width: 320px; height: 97px; margin-bottom: 10px;" src="images/ime_005.png">
-<img style="width: 320px; height: 97px;" src="images/ime_004.png"></p>
-
-<h3>Intercepting hard key events</h3>
-
-<p>Even though the input method window doesn't have explicit focus, it receives
-hard key events first and can choose to consume them or forward them along to
-the application. For instance, you may want to consume the directional keys to
-navigate within your UI for candidate selection during composition. Or you may
-want to trap the back key to dismiss any popups originating from the input
-method window. To intercept hard keys, override
-<code>InputMethodService.onKeyDown()</code> and
-<code>InputMethodService.onKeyUp().</code> Remember to call
-<code>super.onKey</code>* if you don't want to consume a certain key
-yourself.</p>
-
-<h3>Other considerations</h3>
-
+    ic.commitText("Composing ", 1);
+</pre>
+<p>
+    The following screenshots show how this appears to the user:
+</p>
+<img src="{@docRoot}resources/articles/images/inputmethod_composing_text_1.png" alt="" height="54"
+    id="figure3a" />
+<img src="{@docRoot}resources/articles/images/inputmethod_composing_text_2.png" alt="" height="53"
+    id="figure3b" />
+<img src="{@docRoot}resources/articles/images/inputmethod_composing_text_3.png" alt="" height="31"
+    id="figure3c" />
+<p class="img-caption">
+    <strong>Figure 3.</strong> Composing text before committing.
+</p>
+<h3 id="HardwareKeyEvents">Intercepting hardware key events</h3>
+<p>
+    Even though the input method window doesn't have explicit focus, it receives hardware key
+    events first and can choose to consume them or forward them along to the application. For
+    example, you may want to consume the directional keys to navigate within your UI for candidate
+    selection during composition. You may also want to trap the back key to dismiss any popups
+    originating from the input method window.</p>
+<p>
+    To intercept hardware keys, override 
+    {@link android.inputmethodservice.InputMethodService#onKeyDown(int, KeyEvent) onKeyDown()}
+    and {@link android.inputmethodservice.InputMethodService#onKeyUp(int, KeyEvent) onKeyUp()}. 
+    See the <a href=”{@docRoot}resources/samples/SoftKeyboard/index.html”>Soft Keyboard</a> sample 
+    app for an example.
+</p>
+<p>
+    Remember to call the <code>super()</code> method for keys you don't want to handle yourself.
+</p>
+<h2 id="IMESubTypes">Creating an IME Subtype</h2>
+<p>
+    Subtypes allow the IME to expose multiple input modes and languages supported by an IME. A 
+    subtype can represent:
+</p>
 <ul>
-<li>Provide a way for the user to easily bring up any associated settings 
-directly from the input method UI</li>
-<li>Provide
-a way for the user to switch to a different input method (multiple
-input methods may be installed) directly from the input method UI.</li>
-<li>Bring
-up the UI quickly - preload or lazy-load any large resources so that
-the user sees the input method quickly on tapping on a text field. And
-cache any resources and views for subsequent invocations of the input
-method.</li>
-<li>On the flip side, any large memory allocations should
-be released soon after the input method window is hidden so that
-applications can have sufficient memory to run. Consider using a
-delayed message to release resources if the input method is in a hidden
-state for a few seconds.</li>
-<li>Make sure that most common characters
-can be entered using the input method, as users may use punctuation in
-passwords or user names and they shouldn't be stuck in a situation
-where they can't enter a certain character in order to gain access into
-a password-locked device.</li>
+    <li>A locale such as en_US or fr_FR</li>
+    <li>An input mode such as voice, keyboard, or handwriting</li>
+    <li>
+        Other input styles, forms, or properties specific to the IME, such as 10-key or qwerty
+        keyboard layouts.
+    </li>
 </ul>
-
-<h3>Samples</h3>
-
-<p>For a real world example, with support for multiple input types and text
-prediction, see the <a id="ccpb"
-href="http://android.git.kernel.org/?p=platform/packages/inputmethods/LatinIME.
-git;a=tree" title="LatinIME source code online">LatinIME source code</a>. The
-Android SDK also includes a SoftKeyboard sample as well.</p>
+<p>
+    Basically, the mode can be any text such as "keyboard", "voice", and so forth.
+</p>
+<p>A subtype can also expose a combination of these.</p>
+<p>
+    Subtype information is used for an IME switcher dialog that's available from the notification
+    bar and also for IME settings. The information also allows the framework to bring up a
+    specific subtype of an IME directly. When you build an IME, use the subtype facility, because 
+    it helps the user identify and switch between different IME languages and modes.
+</p>
+<p>
+    You define subtypes in one of the input method's XML resource files, using the
+    <code>&lt;subtype&gt;</code> element. The following snippet defines an IME with two 
+    subtypes: a keyboard subtype for the US English locale, and another keyboard subtype for the 
+    French language locale for France:
+</p>
+<pre>
+&lt;input-method xmlns:android="http://schemas.android.com/apk/res/android"
+        android:settingsActivity="com.example.softkeyboard.Settings"
+        android:icon="&#64;drawable/ime_icon"
+    &lt;subtype android:name="&#64;string/display_name_english_keyboard_ime"
+            android:icon="&#64;drawable/subtype_icon_english_keyboard_ime"
+            android:imeSubtypeLanguage="en_US"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="somePrivateOption=true"
+    /&gt;
+    &lt;subtype android:name="&#64;string/display_name_french_keyboard_ime"
+            android:icon="&#64;drawable/subtype_icon_french_keyboard_ime"
+            android:imeSubtypeLanguage="fr_FR"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="foobar=30,someInternalOption=false"
+    /&gt;
+    &lt;subtype android:name="&#64;string/display_name_german_keyboard_ime"
+            ...
+    /&gt;
+/&gt;
+</pre>
+<p>
+    To ensure that your subtypes are labeled correctly in the UI, use %s to get a subtype label
+    that is the same as the subtype’s locale label. This is demonstrated in the next two snippets.
+    The first snippet shows part of the input method's XML file:
+</p>
+<pre>
+    &lt;subtype
+        android:label="&#64;string/label_subtype_generic"
+        android:imeSubtypeLocale="en_US"
+        android:icon="&#64;drawable/icon_en_us"
+        android:imeSubtypeMode="keyboard" /&gt;
+</pre>
+<p>
+    The next snippet is part of the IME's <code>strings.xml</code> file. The string 
+    resource <code>label_subtype_generic</code>, which is used by the input method UI definition to 
+    set the subtype's label, is defined as:
+</p>
+<pre>
+&lt;string name="label_subtype_generic"&gt;%s&lt;/string&gt;
+</pre>
+<p>
+    This sets the subtype’s display name to “English (United States)” in any English language
+    locale, or to the appropriate localization in other locales.
+</p>
+<h3 id="SubtypeProcessing">Choosing IME subtypes from the notification bar</h3>
+<p>
+    The Android system manages all subtypes exposed by all IMEs. IME subtypes are
+    treated as modes of the IME they belong to. In the notification bar, a user can select an
+    available subtype for the currently-set IME, as shown in the following screenshot:
+</p>
+<img src="{@docRoot}resources/articles/images/inputmethod_subtype_notification.png" alt=""
+    height="85" id="figure4" />
+<p class="img-caption">
+    <strong>Figure 4.</strong> Choosing an IME subtype from the notification bar.
+</p>
+<img src="{@docRoot}resources/articles/images/inputmethod_subtype_preferences.png" alt=""
+    height="165" id="figure5" />
+<p class="img-caption">
+    <strong>Figure 5.</strong> Setting subtype preferences in System Settings.
+</p>
+<h3 id="SubtypeSettings">Choosing IME subtypes from System Settings</h3>
+<p>
+    A user can control how subtypes are used in the “Language &amp; input” settings panel in the
+    System Settings area. In the Soft Keyboard sample, the file 
+    <code>InputMethodSettingsFragment.java</code> contains an implementation that 
+    facilitates a subtype enabler in the IME settings. Please refer to the SoftKeyboard sample in 
+    the Android SDK for more information about how to support Input Method Subtypes in your IME.
+</p>
+<img src="{@docRoot}resources/articles/images/inputmethod_subtype_settings.png" alt=""
+    height="210" id="figure6" />
+<p class="img-caption">
+    <strong>Figure 6.</strong> Choosing a language for the IME.
+</p>
+<h2 id="GeneralDesign">General IME Considerations</h2>
+<p>
+    Here are some other things to consider as you're implementing your IME:
+</p>
+<ul>
+<li>
+    Provide a way for users to set options directly from the IME's UI.
+</li>
+<li>
+    Because multiple IMEs may be installed on the device, provide a way for the user to switch to a 
+    different IME directly from the input method UI.
+</li>
+<li>
+    Bring up the IME's UI quickly. Preload or load on demand any large resources so that users 
+    see the IME as soon as they tap on a text field. Cache resources and views for subsequent 
+    invocations of the input method.
+</li>
+<li>
+    Conversely, you should release large memory allocations soon after the input method window is 
+    hidden, so that applications can have sufficient memory to run. Consider using a delayed message
+    to release resources if the IME is in a hidden state for a few seconds.
+</li>    
+<li>
+    Make sure that users can enter as many characters as possible for the language or locale 
+    associated with the IME. Remember that users may use punctuation in passwords or user 
+    names, so your IME has to provide many different characters to allow users to enter a
+    password and get access to the device.
+</li>
+</ul>
diff --git a/docs/html/resources/articles/images/inputmethod_composing_text_1.png b/docs/html/resources/articles/images/inputmethod_composing_text_1.png
new file mode 100644
index 0000000..3403489
--- /dev/null
+++ b/docs/html/resources/articles/images/inputmethod_composing_text_1.png
Binary files differ
diff --git a/docs/html/resources/articles/images/inputmethod_composing_text_2.png b/docs/html/resources/articles/images/inputmethod_composing_text_2.png
new file mode 100644
index 0000000..67bd1d9
--- /dev/null
+++ b/docs/html/resources/articles/images/inputmethod_composing_text_2.png
Binary files differ
diff --git a/docs/html/resources/articles/images/inputmethod_composing_text_3.png b/docs/html/resources/articles/images/inputmethod_composing_text_3.png
new file mode 100644
index 0000000..4fd1a30
--- /dev/null
+++ b/docs/html/resources/articles/images/inputmethod_composing_text_3.png
Binary files differ
diff --git a/docs/html/resources/articles/images/inputmethod_lifecycle_image.png b/docs/html/resources/articles/images/inputmethod_lifecycle_image.png
new file mode 100644
index 0000000..5f7cf95
--- /dev/null
+++ b/docs/html/resources/articles/images/inputmethod_lifecycle_image.png
Binary files differ
diff --git a/docs/html/resources/articles/images/inputmethod_numeric_type_screenshot.png b/docs/html/resources/articles/images/inputmethod_numeric_type_screenshot.png
new file mode 100644
index 0000000..61b7483
--- /dev/null
+++ b/docs/html/resources/articles/images/inputmethod_numeric_type_screenshot.png
Binary files differ
diff --git a/docs/html/resources/articles/images/inputmethod_subtype_notification.png b/docs/html/resources/articles/images/inputmethod_subtype_notification.png
new file mode 100644
index 0000000..3f13927
--- /dev/null
+++ b/docs/html/resources/articles/images/inputmethod_subtype_notification.png
Binary files differ
diff --git a/docs/html/resources/articles/images/inputmethod_subtype_preferences.png b/docs/html/resources/articles/images/inputmethod_subtype_preferences.png
new file mode 100644
index 0000000..d8aa0cf
--- /dev/null
+++ b/docs/html/resources/articles/images/inputmethod_subtype_preferences.png
Binary files differ
diff --git a/docs/html/resources/articles/images/inputmethod_subtype_settings.png b/docs/html/resources/articles/images/inputmethod_subtype_settings.png
new file mode 100644
index 0000000..b8942c6
--- /dev/null
+++ b/docs/html/resources/articles/images/inputmethod_subtype_settings.png
Binary files differ
diff --git a/docs/html/resources/articles/images/inputmethod_text_type_screenshot.png b/docs/html/resources/articles/images/inputmethod_text_type_screenshot.png
new file mode 100644
index 0000000..8008b27
--- /dev/null
+++ b/docs/html/resources/articles/images/inputmethod_text_type_screenshot.png
Binary files differ
diff --git a/docs/html/resources/resources_toc.cs b/docs/html/resources/resources_toc.cs
index e1a5e0f..303a6d4 100644
--- a/docs/html/resources/resources_toc.cs
+++ b/docs/html/resources/resources_toc.cs
@@ -97,6 +97,30 @@
           </li>
         </ul>
       </li>
+      
+      <li class="toggle-list">
+        <div><a href="<?cs var:toroot ?>training/efficient-downloads/index.html">
+            <span class="en">Transferring Data Without Draining the Battery</span>
+          </a> <span class="new">new!</span></div>
+        <ul>
+          <li><a href="<?cs var:toroot ?>training/efficient-downloads/efficient-network-access.html">
+            <span class="en">Optimizing Downloads for Efficient Network Access</span>
+          </a>
+          </li>
+          <li><a href="<?cs var:toroot ?>training/efficient-downloads/regular_updates.html">
+            <span class="en">Minimizing the Effect of Regular Updates</span>
+          </a>
+          </li>
+          <li><a href="<?cs var:toroot ?>training/efficient-downloads/redundant_redundant.html">
+            <span class="en">Redundant Downloads are Redundant</span>
+          </a>
+          </li>
+          <li><a href="<?cs var:toroot ?>training/efficient-downloads/connectivity_patterns.html">
+            <span class="en">Modifying Patterns Based on the Connectivity Type</span>
+          </a>
+          </li>
+        </ul>
+      </li>
 
       <li class="toggle-list">
         <div><a href="<?cs var:toroot ?>training/search/index.html">
diff --git a/docs/html/resources/samples/get.jd b/docs/html/resources/samples/get.jd
index 86ec836..751965f 100644
--- a/docs/html/resources/samples/get.jd
+++ b/docs/html/resources/samples/get.jd
@@ -26,15 +26,15 @@
 <p>The SDK sample code is available to you as a set of downloadable SDK
 components, each of which contains the samples for a specific Android platform
 version. Once you have installed the SDK, you can download one or more samples
-component(s) into your SDK environment using the Android SDK and AVD Manager
+component(s) into your SDK environment using the Android SDK Manager
 tool, which is pre-installed in the SDK. </p>
 
-<p>To download the samples, launch the Android SDK and AVD Manager tool and
+<p>To download the samples, launch the Android SDK Manager tool and
 select one of the samples components from the <strong>Available
 Packages</strong> panel, for example "Samples for SDK API 7". Select
 <strong>Install Selected</strong>, verify and accept the download, then select
 <strong>Install Accepted</strong> to download the component into your SDK. If
-you aren't familiar with the Android SDK and AVD Manager and how to launch or
+you aren't familiar with the Android SDK Manager and how to launch or
 use it, please read the <a href="{@docRoot}sdk/adding-components.html">Adding
 SDK Components</a> document. </p>
 
@@ -74,7 +74,7 @@
 types of concepts, features, or APIs it includes. Then, use the links provided
 to move through the directories and files of each sample. The browseable source
 is generated from the same source code that is downloadable through the Android
-SDK and AVD Manager, as described above. </p>
+SDK Manager, as described above. </p>
 
 <p>The browseable samples files are available online, at the Android Developers
 site only and are not included in the downloadable offline documentation.
diff --git a/docs/html/resources/tutorials/hello-world.jd b/docs/html/resources/tutorials/hello-world.jd
index cc8cb3e..70ba06c 100644
--- a/docs/html/resources/tutorials/hello-world.jd
+++ b/docs/html/resources/tutorials/hello-world.jd
@@ -42,7 +42,7 @@
 
 <ol>
 
-  <li>In the Android SDK and AVD Manager, choose <strong>Available
+  <li>In the Android SDK Manager, choose <strong>Available
 Packages</strong> in the left panel.</li>
 
 <li>In the right panel, expand the Android Repository list to display
@@ -72,7 +72,7 @@
 
 <p>To create an AVD:</p>
 <ol>
-  <li>In Eclipse, select <strong>Window &gt; Android SDK and AVD Manager</strong>.</li>
+  <li>In Eclipse, select <strong>Window &gt; AVD Manager</strong>.</li>
   <li>Select <strong>Virtual Devices</strong> in the left panel.</li>
 
   <li>Click <strong>New...</strong>.
diff --git a/docs/html/resources/tutorials/testing/activity_test.jd b/docs/html/resources/tutorials/testing/activity_test.jd
index 4b861e2..f88b768 100644
--- a/docs/html/resources/tutorials/testing/activity_test.jd
+++ b/docs/html/resources/tutorials/testing/activity_test.jd
@@ -163,7 +163,7 @@
             open the Android SDK and AVD Manager and check in the
             <strong>Installed Packages</strong> panel.
             If aren't sure how to download a platform into your SDK,
-            read <a href="{@docRoot}sdk/adding-components.html">Adding SDK Components</a>.
+            read <a href="{@docRoot}sdk/adding-components.html">Adding SDK Packages</a>.
         </p>
     </li>
   </ul>
diff --git a/docs/html/sdk/adding-components.jd b/docs/html/sdk/adding-components.jd
index ca27bba..599b2a8 100644
--- a/docs/html/sdk/adding-components.jd
+++ b/docs/html/sdk/adding-components.jd
@@ -1,4 +1,4 @@
-page.title=Adding SDK Components
+page.title=Adding SDK Packages
 @jd:body
 
 
@@ -6,74 +6,74 @@
 <div id="qv">
 <h2>Quickview</h2>
 <ul>
-  <li>Use the Android SDK and AVD Manager to
+  <li>Use the Android SDK Manager to
    set up your SDK and keep it up-to-date.</li>
 </ul>
 
 <h2>In this document</h2>
 <ol>
-  <li><a href="#launching">Launching the Android SDK and AVD Manager</a>
-  <li><a href="#InstallingComponents">Installing SDK Components</a>
-  <li><a href="#UpdatingComponents">Updating SDK Components</a>
-  <li><a href="#dependencies">Component Dependencies</a></li>
+  <li><a href="#launching">Launching the Android SDK Manager</a>
+  <li><a href="#InstallingComponents">Installing SDK Packages</a>
+  <li><a href="#UpdatingComponents">Updating SDK Packages</a>
+  <li><a href="#dependencies">Package Dependencies</a></li>
   <li><a href="#AddingSites">Adding New Sites</a></li>
   <li><a href="#troubleshooting">Troubleshooting</a></li>
 </ol>
 </div>
 </div>
 
-<p>Adding and updating components in your Android SDK is fast and easy. To add or
-update the individual SDK components that you need, use the <em>Android SDK and AVD
+<p>Adding and updating packages in your Android SDK is fast and easy. To add or
+update the individual SDK packages that you need, use the <em>Android SDK
 Manager</em> (included in the SDK Tools).</p>
 
 <p>It only takes a couple of clicks to install individual versions of the
 Android platform, new development tools, new documentation, and SDK add-ons. The
-new SDK components are automatically installed into your existing SDK directory,
+new SDK packages are automatically installed into your existing SDK directory,
 so you don't need to update your development environment to specify a new SDK
 location.</p>
 
 <p>If you're setting up your Android SDK for the first time,
 see <a href="{@docRoot}sdk/installing.html#components">Installing the SDK</a> for information about
-what components to install.</p>
+what packages to install.</p>
 
 <p class="note"><strong>Note:</strong> If you develop in Eclipse, you might also need
 to update your ADT plugin when you update your development tools. See the revisions listed in the
 <a href="{@docRoot}sdk/eclipse-adt.html">ADT Plugin for Eclipse</a> document.</p>
 
 <img src="{@docRoot}images/sdk_manager_packages.png" alt="" />
-<p class="img-caption"><strong>Figure 1.</strong> The Android SDK and AVD Manager's
-<strong>Available Packages</strong> panel, which shows the SDK components that are
+<p class="img-caption"><strong>Figure 1.</strong> The Android SDK Manager's
+<strong>Available Packages</strong> panel, which shows the SDK packages that are
 available for you to download into your environment. </p>
 </div>
 
-<h2 id="launching">Launching the Android SDK and AVD Manager</h2>
+<h2 id="launching">Launching the Android SDK Manager</h2>
 
-<p>The Android SDK and AVD Manager is the tool that you use to install and
-upgrade SDK components in your development environment. </p>
+<p>The Android SDK Manager is the tool that you use to install and
+upgrade SDK packages in your development environment. </p>
 
-<p>You can launch the Android SDK and AVD Manager in one of the following ways.</p>
+<p>You can launch the Android SDK Manager in one of the following ways.</p>
 
 <h4>Launching from Eclipse/ADT</h4>
 
 <p>If you are developing in Eclipse and have already installed the ADT Plugin,
-follow these steps to access the Android SDK and AVD Manager tool:</p>
+follow these steps to access the Android SDK Manager tool:</p>
 
 <ol>
 <li>Open Eclipse</li>
-<li>Select <strong>Window</strong> &gt; <strong>Android SDK and AVD
+<li>Select <strong>Window</strong> &gt; <strong>Android SDK
 Manager</strong>.</li>
 </ol>
 
 <h4>Launching from the SDK Manager script (Windows only)</h4>
 
-<p>For Windows only, the SDK includes a script that invokes the Android SDK and
-AVD Manager. To launch the tool using the script, double-click {@code SDK
+<p>For Windows only, the SDK includes a script that invokes the Android SDK Manager. To launch the
+tool using the script, double-click {@code SDK
 Manager.exe} at the root of the the SDK directory.</p>
 
 <h4>Launching from a command line</h4>
 
-<p>In all development environments, follow these steps to access the Android SDK
-and AVD Manager tool from the command line: </p>
+<p>In all development environments, follow these steps to access the Android SDK Manager tool from
+the command line: </p>
 
 <ol>
 <li>Navigate to the <code>&lt;<em>sdk</em>&gt;/tools/</code> directory.</li>
@@ -82,26 +82,26 @@
 </ol>
 
 
-<h2 id="InstallingComponents">Installing SDK Components</h2>
+<h2 id="InstallingComponents">Installing SDK Packages</h2>
 
-<p class="caution"><strong>Caution:</strong> Before you install SDK components,
+<p class="caution"><strong>Caution:</strong> Before you install SDK packages,
 we recommend that you disable any antivirus software that may be running on
 your computer. There are cases in which antivirus software on Windows is known to interfere with the
 installation process, so we suggest you disable your antivirus until installation is
 complete.</p>
 
-<p>Follow these steps to install new SDK components in your environment:</p>
+<p>Follow these steps to install new SDK packages in your environment:</p>
 
 <ol>
-  <li>Launch the Android SDK and AVD Manager as described in the section above.</li>
+  <li>Launch the Android SDK Manager as described in the section above.</li>
   <li>Select <strong>Available Packages</strong> in the left panel.
-  This will reveal all of the components that are currently available for download
+  This will reveal all of the packages that are currently available for download
   from the SDK repository.</li>
-  <li>Select the component(s) you'd like to install and click <strong>Install
+  <li>Select the package(s) you'd like to install and click <strong>Install
   Selected</strong>. (If you aren't sure which packages to select, read <a
-  href="installing.html#which">Recommended Components</a>.)</li>
-  <li>Verify and accept the components you want (ensure each one is selected with a green
-checkmark) and click <strong>Install</strong>. The components will now be installed into
+  href="installing.html#which">Recommended Packages</a>.)</li>
+  <li>Verify and accept the packages you want (ensure each one is selected with a green
+checkmark) and click <strong>Install</strong>. The packages will now be installed into
 your existing Android SDK directories.</li>
 </ol>
 
@@ -114,60 +114,60 @@
 <code>&lt;sdk&gt;/docs/</code> directory (old docs are replaced).</p>
 
 
-<h2 id="UpdatingComponents">Updating SDK Components</h2>
+<h2 id="UpdatingComponents">Updating SDK Packages</h2>
 
-<p>From time to time, new revisions of existing SDK components are released and
+<p>From time to time, new revisions of existing SDK packages are released and
 made available to you through the SDK repository. In most cases, if you have those
-components installed in your environment, you will want
+packages installed in your environment, you will want
 to download the new revisions as soon as possible. </p>
 
 <p>You can learn about the release of new revisions in two ways: </p>
 
 <ul>
 <li>You can watch for updates listed in the "SDK" tab of the Android Developers
-site, in the "Downloadable SDK Components" section. </li>
+site, in the "Downloadable SDK Packages" section. </li>
 <li>You can watch for updates listed in the <strong>Available Packages</strong>
-panel of the Android SDK and AVD Manager. </li>
+panel of the Android SDK Manager. </li>
 </ul>
 
-<p>When you see that a new revision is available, you can use the Android SDK
-and AVD Manager to quickly download it to your environment. Follow the same
-procedure as given in <a href="#InstallingComponents">Installing SDK Components</a>, above. The new
-component is installed in place of the old, but without impacting your
+<p>When you see that a new revision is available, you can use the Android SDK Manager to quickly
+download it to your environment. Follow the same
+procedure as given in <a href="#InstallingComponents">Installing SDK Packages</a>, above. The new
+package is installed in place of the old, but without impacting your
 applications. </p>
 
 <p class="note"><strong>Tip:</strong>
-Use the "Display updates only" checkbox to show only the components
+Use the "Display updates only" checkbox to show only the packages
 you do not have.</p>
 
 
-<h2 id="dependencies">SDK Component Dependencies</h2>
+<h2 id="dependencies">SDK Package Dependencies</h2>
 
-<p>In some cases, an SDK component may require a specific minimum revision of
-another component or SDK tool. Where such dependencies exist, they are
-documented in the revision notes for each component, available from the links in
-the "Downloadable SDK Components" section at left.</p>
+<p>In some cases, an SDK package may require a specific minimum revision of
+another package or SDK tool. Where such dependencies exist, they are
+documented in the revision notes for each package, available from the links in
+the "Downloadable SDK packages" section at left.</p>
 
 <p>For example, there may be a dependency between the ADT Plugin for Eclipse and
-the SDK Tools component. When you install the SDK Tools
-component, you should also upgrade to the required version of ADT (if you
+the SDK Tools package. When you install the SDK Tools
+package, you should also upgrade to the required version of ADT (if you
 are developing in Eclipse). In this case,  the major version number for your ADT plugin should
 always match the revision number of your SDK Tools (for example, ADT 8.x requires SDK Tools r8).
 </p>
 
 <p>Also make sure that, each time you install a new version of the Android platform, you have
-the latest version of the SDK Platform-tools component. The SDK Platform-tools contain
+the latest version of the SDK Platform-tools package. The SDK Platform-tools contain
 tools that are backward compatible with all versions of the Android platform and are
 often updated to support new features in the latest version of the Android platform.</p>
 
 <p>The development tools will notify you with debug warnings if there is dependency that you need to
-address. The SDK and AVD Manager also enforces dependencies by requiring that you download any
-components that are needed by those you have selected.</p>
+address. The Android SDK Manager also enforces dependencies by requiring that you download any
+packages that are needed by those you have selected.</p>
 
 
 <h2 id="AddingSites">Adding New Sites</h2>
 
-<p>By default, <strong>Available Packages</strong> displays components available from the
+<p>By default, <strong>Available Packages</strong> displays packages available from the
 <em>Android Repository</em> and <em>Third party Add-ons</em>. You can add other sites that host
 their own Android SDK add-ons, then download the SDK add-ons
 from those sites.</p>
@@ -178,7 +178,7 @@
 available under <em>Third party Add-ons</em>. </p>
 
 <p>If a carrier or device manufacturer has hosted an SDK add-on repository file
-on their web site, follow these steps to add their site to the SDK and AVD
+on their web site, follow these steps to add their site to the Android SDK
 Manager:</p>
 
 <ol>
@@ -186,7 +186,7 @@
   <li>Click <strong>Add Add-on Site</strong> and enter the URL of the
 {@code repository.xml} file. Click <strong>OK</strong>.</li>
 </ol>
-<p>Any SDK components available from the site will now be listed under a new item named
+<p>Any SDK packages available from the site will now be listed under a new item named
 <strong>User Add-ons</strong>.</p>
 
 
@@ -194,12 +194,12 @@
 
 <p><strong>Problems connecting to the SDK repository</strong></p>
 
-<p>If you are using the SDK and AVD Manager to download components and are encountering
+<p>If you are using the Android SDK Manager to download packages and are encountering
 connection problems, try connecting over http, rather than https. To switch the
-protocol used by the SDK and AVD Manager, follow these steps: </p>
+protocol used by the Android SDK Manager, follow these steps: </p>
 
 <ol>
-  <li>With the Android SDK and AVD Manager window open, select "Settings" in the
+  <li>With the Android SDK Manager window open, select "Settings" in the
   left pane. </li>
   <li>On the right, in the "Misc" section, check the checkbox labeled "Force
   https://... sources to be fetched using http://..." </li>
diff --git a/docs/html/sdk/android-3.0.jd b/docs/html/sdk/android-3.0.jd
index 39a234f..3acb358 100644
--- a/docs/html/sdk/android-3.0.jd
+++ b/docs/html/sdk/android-3.0.jd
@@ -25,7 +25,8 @@
 
 <h2>See Also</h2>
 <ol>
-  <li><a href="{@docRoot}guide/practices/optimizing-for-3.0.html">Optimizing Apps for Android 3.0</a></li>
+  <li><a href="{@docRoot}guide/practices/tablets-and-handsets.html">Supporting Tablets and
+Handsets</a></li>
 </ol>
 
 </div>
@@ -41,7 +42,7 @@
 
 <p>To get started developing or testing against Android {@sdkPlatformVersion}, use the Android SDK
 Manager to download the platform into your SDK. For more information, see <a
-href="{@docRoot}sdk/adding-components.html">Adding SDK Components</a>. If you are new to Android, <a
+href="{@docRoot}sdk/adding-components.html">Adding SDK Packages</a>. If you are new to Android, <a
 href="{@docRoot}sdk/index.html">download the SDK Starter Package</a> first.</p>
 
 <p>For a high-level introduction to Android {@sdkPlatformVersion}, see the <a
@@ -52,8 +53,8 @@
 If you've already published an Android application, please test and optimize your application on
 Android 3.0 as soon as possible. You should do so to be sure your application provides the best
 experience possible on the latest Android-powered devices. For information about what you can do,
-read <a href="{@docRoot}guide/practices/optimizing-for-3.0.html">Optimizing Apps for Android
-3.0</a>.</p>
+read <a href="{@docRoot}guide/practices/tablets-and-handsets.html">Supporting Tablets and
+Handsets</a>.</p>
 
 
 <h2 id="relnotes">Revisions</h2>
diff --git a/docs/html/sdk/android-4.0.3.jd b/docs/html/sdk/android-4.0.3.jd
index 809c83c..c8563ac 100644
--- a/docs/html/sdk/android-4.0.3.jd
+++ b/docs/html/sdk/android-4.0.3.jd
@@ -68,15 +68,16 @@
   <p><a href="#" onclick="return toggleContent(this)">
     <img src="{@docRoot}assets/images/triangle-opened.png"
 class="toggle-content-img" alt="" />
-    Android {@sdkPlatformVersion}, Revision 2</a> <em>(January 2012)</em>
+    Revision 2</a> <em>(January 2012)</em>
   </a></p>
 
   <div class="toggle-content-toggleme" style="padding-left:2em;">
 
-<dl>
-<dt>Maintenance release. SDK Tools r14 or higher is required.
-</dt>
-</dl>
+    <p>Maintenance update. The system version is 4.0.3.</p>
+    <dl>
+      <dt>Dependencies:</dt>
+      <dd>SDK Tools r14 or higher is required.</dd>
+    </dl>
 
   </div>
 </div>
@@ -86,15 +87,16 @@
   <p><a href="#" onclick="return toggleContent(this)">
     <img src="{@docRoot}assets/images/triangle-closed.png"
 class="toggle-content-img" alt="" />
-    Android {@sdkPlatformVersion}, Revision 1</a> <em>(December 2011)</em>
+    Revision 1</a> <em>(December 2011)</em>
   </a></p>
 
   <div class="toggle-content-toggleme" style="padding-left:2em;">
 
-<dl>
-<dt>Initial release. SDK Tools r14 or higher is required.
-</dt>
-</dl>
+    <p>Initial release. The system version is 4.0.3.</p>
+    <dl>
+      <dt>Dependencies:</dt>
+      <dd>SDK Tools r14 or higher is required.</dd>
+    </dl>
 
   </div>
 </div>
diff --git a/docs/html/sdk/compatibility-library.jd b/docs/html/sdk/compatibility-library.jd
index df71552..f81e8ae 100644
--- a/docs/html/sdk/compatibility-library.jd
+++ b/docs/html/sdk/compatibility-library.jd
@@ -46,11 +46,40 @@
 <p>The sections below provide notes about successive releases of
 the Support Package, as denoted by revision number.</p>
 
-
 <div class="toggle-content open">
 
   <p><a href="#" onclick="return toggleContent(this)">
     <img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-content-img" />
+    Support Package, revision 7 (March 2012)
+  </a></p>
+
+  <div class="toggle-content-toggleme" style="padding-left:2em">
+    <dl>
+      <dt>Changes for v4 support library:</dt>
+      <dd>
+        <ul>
+          <li>Added {@link android.support.v4.app.ShareCompat}, which provides helper classes
+for sending and receiving content for social sharing applications, including new metadata for
+attributing shared data to the source app. This class also provides compatible integration with the
+new {@link android.widget.ShareActionProvider} in Android 4.0.</li>
+          <li>Added {@link android.support.v4.app.NavUtils} and {@link
+android.support.v4.app.TaskStackBuilder} to provide support for implementing the
+<a href="{@docRoot}design/index.html">Android Design</a> guidelines for navigation. These 
+additions include a way to implement the action bar's <em>Up</em> button across versions.
+For an example implementation of this pattern, see the AppNavigation sample in
+({@code <em>&lt;sdk&gt;</em>/samples/<em>&lt;platform&gt;</em>/AppNavigation}).</li>
+          <li>Added {@link android.support.v4.app.NotificationCompat.Builder} to provide a
+compatibility implementation of Android 3.0's {@link android.app.Notification.Builder} helper class
+for creating standardized system notifications.</li>
+        </ul>
+      </dd>
+    </dl>
+  </div>
+
+<div class="toggle-content closed">
+
+  <p><a href="#" onclick="return toggleContent(this)">
+    <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img" />
     Support Package, revision 6 (December 2011)
   </a></p>
 
@@ -301,13 +330,13 @@
 
 <h2 id="Downloading">Downloading the Support Package</h2>
 
-<p>The Support Package is provided as a downloadable package from the Android SDK and AVD
+<p>The Support Package is provided as a downloadable package from the Android SDK
 Manager. To install:</p>
 
 <ol>
-  <li>Launch the SDK and AVD Manager.
+  <li>Launch the Android SDK Manager.
     <p>From Eclipse, you can select <strong>Window</strong>
-&gt; <strong>Android SDK and AVD Manager</strong>. Or, launch {@code SDK Manager.exe} from
+&gt; <strong>Android SDK Manager</strong>. Or, launch {@code SDK Manager.exe} from
 the {@code &lt;sdk&gt;/} directory (on Windows only) or {@code android} from the {@code
 &lt;sdk&gt;/tools/} directory.</p></li>
   <li>Expand the Android Repository, check <strong>Android Support package</strong>
diff --git a/docs/html/sdk/eclipse-adt.jd b/docs/html/sdk/eclipse-adt.jd
index 30825ee..485f01e 100644
--- a/docs/html/sdk/eclipse-adt.jd
+++ b/docs/html/sdk/eclipse-adt.jd
@@ -1,8 +1,8 @@
 page.title=ADT Plugin for Eclipse
-adt.zip.version=16.0.1
-adt.zip.download=ADT-16.0.1.zip
-adt.zip.bytes=7000078
-adt.zip.checksum=03a2a23650ddac128c8b9e8aaf0aa433
+adt.zip.version=17.0.0
+adt.zip.download=ADT-17.0.0.zip
+adt.zip.bytes=12836115
+adt.zip.checksum=ecb12c07e534997cd32c66d57f21b770
 
 @jd:body
 
@@ -36,7 +36,7 @@
 to build Android applications.</p>
 
 <p>ADT extends the capabilities of Eclipse to let you quickly set up new Android
-projects, create an application UI, add components based on the Android
+projects, create an application UI, add packages based on the Android
 Framework API, debug your applications using the Android SDK tools, and even
 export signed (or unsigned) {@code .apk} files in order to distribute your application.</p>
 
@@ -108,11 +108,111 @@
 }
 </style>
 
-
 <div class="toggleable opened">
   <a href="#" onclick="return toggleDiv(this)">
         <img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-img" height="9px"
 width="9px" />
+ADT 17.0.0</a> <em>(March 2012)</em>
+  <div class="toggleme">
+<dl>
+  <dt>Dependencies:</dt>
+
+  <dd>
+    <ul>
+      <li>Java 1.6 or higher is required for ADT 17.0.0.</li>
+      <li>Eclipse Helios (Version 3.6.2) or higher is required for ADT 17.0.0.</li>
+      <li>ADT 17.0.0 is designed for use with <a href="{@docRoot}sdk/tools-notes.html">SDK Tools
+      r17</a>. If you haven't already installed SDK Tools r17 into your SDK, use the Android SDK
+      Manager to do so.</li>
+    </ul>
+  </dd>
+
+  <dt>General improvements:</dt>
+  <dd>
+    <ul>
+      <li>New build features
+        <ul>
+          <li>Added feature to automatically setup JAR dependencies. Any {@code .jar} files in the
+          {@code /libs} folder are added to the build configuration (similar to how the Ant build
+          system works). Also, {@code .jar} files needed by library projects are also automatically
+          added to projects that depend on those library projects.
+          (<a href="http://tools.android.com/recent/dealingwithdependenciesinandroidprojects">more
+          info</a>)</li>
+          <li>Added a feature that allows you to run some code only in debug mode. Builds now
+generate a class called {@code BuildConfig} containing a {@code DEBUG} constant that is
+automatically set according to your build type. You can check the ({@code BuildConfig.DEBUG})
+constant in your code to run debug-only functions.</li>
+          <li>Added support for custom views with custom attributes in libraries. Layouts using
+custom attributes must use the namespace URI {@code http://schemas.android.com/apk/res-auto} instead
+of the URI that includes the app package name. This URI is replaced with the app specific one at
+build time.</li>
+        </ul>
+      </li>
+      <li>Improved Lint features. See the <a href="{@docRoot}sdk/tools-notes.html">SDK Tools r17</a>
+release notes.</li>
+      <li>Improved the Lint user interface
+        <ul>
+          <li>Added <strong>Run Lint</strong> toolbar action with a dropdown menu for selecting
+specific (or all) projects, clearing results and other actions.</li>
+          <li>Updated the results window to be organized as a tree rather than a flat list. Each
+issue type has a single top level item, which makes it easier to quickly scan through the reported
+issues and narrow down to the issues you are most interested in.</li>
+          <li>Added many new toolbar actions to the results window, including expand/collapse,
+ignore in file, ignore in project, ignore everywhere, show options, and configure columns.</li>
+          <li>Added new column options for the <strong>Lint Warnings</strong> tab, such as
+category, priority, project, file and line. The column selection (as well as the column sizes) are
+persisted. You can also click on columns to sort by those values.</li>
+          <li>Added Enable All and Disable All buttons to the Lint Options dialog, and a search
+filter textbox to filter by issue id, summary and severity.</li>
+        </ul>
+      </li>
+      <li>Added Quick Outline for XML editors (Ctrl-O, Command-O). This feature shows the structure
+of the current file including icons and ids, lets you filter and quickly jump to specific ids.</li>
+      <li>Updated the resource chooser to shows the resolved value for resources. For example,
+when selecting {@code @string/hello} the chooser displays a resolved value such as "Hello World").
+The resource chooser also now allows you to edit the chosen value directly.</li>
+      <li>Updated Layout Editor so that it does not assign default ids to layouts, includes and
+merge tags. This behavior tended to pollute the namespace with a lot of unused resources since
+layouts are not usually manipulated via code, or referenced from XML. (The RelativeLayout editor
+automatically assigns ids to views without ids when pointing to them.)</li>
+      <li>Added ability to export screenshots from the Layout Editor</li>
+    </ul>
+  </dd>
+
+  <dt>Bug fixes:</dt>
+  <dd>
+    <ul>
+      <li>Fixed problem using Layout Editor with {@link android.widget.SlidingDrawer} which could
+        not be dragged into the layout on some platforms.</li>
+      <li>Fixed preview rendering for {@link android.widget.SlidingDrawer} and 
+        {@link android.widget.TabHost}.
+        (<a href="http://code.google.com/p/android/issues/detail?id=23022">Issue 23022</a>).</li>
+      <li>Fixed issues that could prevent layout rendering due to unresolvable resources.
+        (<a href="http://code.google.com/p/android/issues/detail?id=21046">Issue 21046</a>,
+        <a href="http://code.google.com/p/android/issues/detail?id=21051">Issue 21051</a>)</li>
+      <li>Fixed a bug in resource chooser which made some types of framework resources impossible to
+select. (<a href="http://code.google.com/p/android/issues/detail?id=20589">Issue 20589</a>)</li>
+      <li>Fixed a bug in the formatter where a certain whitespace pattern could result in a
+        non-space character getting deleted.
+        (<a href="http://code.google.com/p/android/issues/detail?id=23940">Issue 23940</a>)</li>
+      <li>Fixed a locale bug affecting Turkish locales in particular.
+        (<a href="http://code.google.com/p/android/issues/detail?id=23747">Issue 23747</a>)</li>
+      <li>Fixed issue where dex complains about duplicate classes in cases where a Library
+        Project depends on the same jar files or Java-only projects.</li>
+      <li>Fixed issue where test projects had to independently reference the library projects used
+        by an app project. Now referencing only the app project is enough.</li>
+    </ul>
+  </dd>
+
+</dl>
+
+</div>
+</div>
+
+<div class="toggleable closed">
+  <a href="#" onclick="return toggleDiv(this)">
+        <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-img" height="9px"
+width="9px" />
 ADT 16.0.1</a> <em>(December 2011)</em>
   <div class="toggleme">
 <dl>
@@ -182,11 +282,11 @@
   <div class="toggleme">
 <dl>
   <dt>Dependencies:</dt>
-  
+
   <dd>ADT 15.0.1 is designed for use with <a href="{@docRoot}sdk/tools-notes.html">SDK Tools r15</a>.
   If you haven't already installed SDK Tools r15 into your SDK, use the Android SDK Manager to
   do so.</dd>
-  
+
   <dt>Bug fixes:</dt>
   <dd>
     <ul>
@@ -368,7 +468,7 @@
 
 <dd>ADT 12.0.0 is designed for use with <a href="{@docRoot}sdk/tools-notes.html">SDK Tools r12</a>. If you haven't
 already installed SDK Tools r12 into your SDK, use
-the Android SDK and AVD Manager to do so.</dd>
+the Android SDK Manager to do so.</dd>
 
 <dt>Visual Layout Editor:</dt>
 <dd>
@@ -420,7 +520,7 @@
 <dt>Dependencies:</dt>
 
 <dd>ADT 11.0.0 is designed for use with SDK Tools r11. If you haven't
-already installed SDK Tools r11 into your SDK, use the Android SDK and AVD Manager to do
+already installed SDK Tools r11 into your SDK, use the Android SDK Manager to do
 so.</dd>
 
 <dt>Visual Refactoring:</dt>
@@ -556,7 +656,7 @@
 <dt>Dependencies:</dt>
 
 <dd>ADT 10.0.1 is designed for use with SDK Tools r10. If you haven't
-already installed SDK Tools r10 into your SDK, use the Android SDK and AVD Manager to do
+already installed SDK Tools r10 into your SDK, use the Android SDK Manager to do
 so.</dd>
 
 <dt>General notes:</dt>
@@ -586,7 +686,7 @@
 <dt>Dependencies:</dt>
 
 <dd>ADT 10.0.0 is designed for use with SDK Tools r10. If you haven't
-already installed SDK Tools r10 into your SDK, use the Android SDK and AVD Manager to do
+already installed SDK Tools r10 into your SDK, use the Android SDK Manager to do
 so.</dd>
 
 <dt>General notes:</dt>
@@ -636,7 +736,7 @@
 <dt>Dependencies:</dt>
 
 <dd>ADT 9.0.0 is designed for use with SDK Tools r9. If you haven't
-already installed SDK Tools r9 into your SDK, use the Android SDK and AVD Manager to do
+already installed SDK Tools r9 into your SDK, use the Android SDK Manager to do
 so.</dd>
 
 <dt>General notes:</dt>
@@ -745,7 +845,7 @@
 <dt>Dependencies:</dt>
 
 <p><p>ADT 8.0.1 is designed for use with SDK Tools r8. If you haven't
-already installed SDK Tools r8 into your SDK, use the Android SDK and AVD Manager to do
+already installed SDK Tools r8 into your SDK, use the Android SDK Manager to do
 so.</p></dd>
 
 <dt>General notes:</dt>
@@ -774,7 +874,7 @@
 <dt>Dependencies:</dt>
 
 <p><p>ADT 8.0.0 is designed for use with SDK Tools r8. If you haven't
-already installed SDK Tools r8 into your SDK, use the Android SDK and AVD Manager to do
+already installed SDK Tools r8 into your SDK, use the Android SDK Manager to do
 so.</p></dd>
 
 <dt>General notes:</dt>
@@ -920,8 +1020,8 @@
 <dt>Dependencies:</dt>
 
 <dd><p>ADT 0.9.6 is designed for use with SDK Tools r5 and later. Before
-updating to ADT 0.9.6, we highly recommend that you use the Android SDK and
-AVD Manager to install SDK Tools r5 into your SDK.</p></dd>
+updating to ADT 0.9.6, we highly recommend that you use the Android SDK Manager to install SDK
+Tools r5 into your SDK.</p></dd>
 
 <dt>General Notes:</dt>
 <dd>
@@ -938,8 +1038,8 @@
 <dt>AVD/SDK Manager:</dt>
 <dd>
 <ul>
-<li>Adds support for platform samples components.</li>
-<li>Improves support for dependency between components.</li>
+<li>Adds support for platform samples packages.</li>
+<li>Improves support for dependency between packages.</li>
 <li>AVDs now sorted by API level.</li>
 <li>The AVD creation dialog now enforces a minimum SD card size of 9MB.</li>
 <li>Prevents deletion of running AVDs.</li>
@@ -987,9 +1087,9 @@
 <dt>Dependencies:</dt>
 
 <dd><p>ADT 0.9.5 requires features provided in SDK Tools r4 or higher. If you install
-ADT 0.9.5, which is highly recommended, you should use the Android SDK and AVD
+ADT 0.9.5, which is highly recommended, you should use the Android SDK
 Manager to download the latest SDK Tools into your SDK. For more information,
-see <a href="{@docRoot}sdk/adding-components.html">Adding SDK Components</a>.</p>
+see <a href="{@docRoot}sdk/adding-components.html">Adding SDK Packages</a>.</p>
 </dd>
 
 <dt>General notes:</dt>
@@ -1014,9 +1114,9 @@
 <dt>Dependencies:</dt>
 
 <dd><p>ADT 0.9.4 requires features provided in SDK Tools r3 or higher. If you install
-ADT 0.9.4, which is highly recommended, you should use the Android SDK and AVD
+ADT 0.9.4, which is highly recommended, you should use the Android SDK
 Manager to download the latest SDK Tools into your SDK. For more information,
-see <a href="{@docRoot}sdk/adding-components.html">Adding SDK Components</a>.</p>
+see <a href="{@docRoot}sdk/adding-components.html">Adding SDK Packages</a>.</p>
 </dd>
 
 <dt>Project Creation Wizard:</dt>
@@ -1218,9 +1318,9 @@
 <h4>Other install errors</h4>
 
 <p>Note that there are features of ADT that require some optional
-Eclipse components (for example, WST). If you encounter an error when
-installing ADT, your Eclipse installion might not include these components.
-For information about how to quickly add the necessary components to your
+Eclipse packages (for example, WST). If you encounter an error when
+installing ADT, your Eclipse installion might not include these packages.
+For information about how to quickly add the necessary packages to your
 Eclipse installation, see the troubleshooting topic
 <a href="{@docRoot}resources/faq/troubleshooting.html#installeclipsecomponents">ADT
 Installation Error: "requires plug-in org.eclipse.wst.sse.ui"</a>.</p>
@@ -1245,9 +1345,9 @@
 
 <p>In some cases, a new revision of ADT will have a dependency on a specific
 revision of the Android SDK Tools. If such dependencies exist, you will need to
-update the SDK Tools component of the SDK after installing the new revision of
-ADT. To update the SDK Tools component, use the Android SDK and AVD Manager, as
-described in <a href="adding-components.html">Adding SDK Components</a>.</p>
+update the SDK Tools package of the SDK after installing the new revision of
+ADT. To update the SDK Tools package, use the Android SDK Manager, as
+described in <a href="adding-components.html">Adding SDK Packages</a>.</p>
 
 <p>To learn about new features of each ADT revision and also any dependencies on
 the SDK Tools, see the listings in the <a href="#notes">Revisions</a>
diff --git a/docs/html/sdk/index.jd b/docs/html/sdk/index.jd
index 5cf05e0..175ab50 100644
--- a/docs/html/sdk/index.jd
+++ b/docs/html/sdk/index.jd
@@ -2,21 +2,21 @@
 page.metaDescription=Download the official Android SDK to develop apps for Android-powered devices.
 sdk.redirect=0
 
-sdk.win_installer=installer_r16-windows.exe
-sdk.win_installer_bytes=29561554
-sdk.win_installer_checksum=3521dda4904886b05980590f83cf3469
+sdk.win_installer=installer_r17-windows.exe
+sdk.win_installer_bytes=37410775
+sdk.win_installer_checksum=5afaf6511ebaa52bd6d1dba4afc61e41
 
-sdk.win_download=android-sdk_r16-windows.zip
-sdk.win_bytes=29562413
-sdk.win_checksum=6b926d0c0a871f1a946e65259984701a
+sdk.win_download=android-sdk_r17-windows.zip
+sdk.win_bytes=37417953
+sdk.win_checksum=3af1baeb39707e54df068e939aea5a79
 
-sdk.mac_download=android-sdk_r16-macosx.zip
-sdk.mac_bytes=26158334
-sdk.mac_checksum=d1dc2b6f13eed5e3ce5cf26c4e4c47aa
+sdk.mac_download=android-sdk_r17-macosx.zip
+sdk.mac_bytes=33867836
+sdk.mac_checksum=52639aae036b7c2e47cf291696b23236
 
-sdk.linux_download=android-sdk_r16-linux.tgz
-sdk.linux_bytes=22048174
-sdk.linux_checksum=3ba457f731d51da3741c29c8830a4583
+sdk.linux_download=android-sdk_r17-linux.tgz
+sdk.linux_bytes=29706368
+sdk.linux_checksum=14e99dfa8eb1a8fadd2f3557322245c4
 
 @jd:body
 
@@ -28,7 +28,7 @@
   <li>Install the SDK starter package from the table above. (If you're on Windows, download the
 installer for help with the initial setup.)</li>
   <li>Install the ADT Plugin for Eclipse (if you'll be developing in Eclipse).</li>
-  <li>Add Android platforms and other components to your SDK.</li>
+  <li>Add Android platforms and other packages to your SDK.</li>
   <li>Explore the contents of the Android SDK (optional).</li>
 </ol>
 
diff --git a/docs/html/sdk/installing.jd b/docs/html/sdk/installing.jd
index 1dce483..7461eb0 100644
--- a/docs/html/sdk/installing.jd
+++ b/docs/html/sdk/installing.jd
@@ -53,10 +53,10 @@
     <li><a href="#Preparing">1. Preparing Your Development Computer</a></li>
     <li><a href="#Installing">2. Downloading the SDK Starter Package</a></li>
     <li><a href="#InstallingADT">3. Installing the ADT Plugin for Eclipse</a></li>
-    <li><a href="#AddingComponents">4. Adding Platforms and Other Components</a>
+    <li><a href="#AddingComponents">4. Adding Platforms and Other Packages</a>
       <ol>
-        <li><a href="#components">Available Components</a></li>
-        <li><a href="#which">Recommended Components</a></li>
+        <li><a href="#components">Available Packages</a></li>
+        <li><a href="#which">Recommended Packages</a></li>
       </ol></li>
     <li><a href="#sdkContents">5. Exploring the SDK (Optional)</a></li>
     <li><a href="#NextSteps">Next Steps</a></li>
@@ -66,7 +66,7 @@
 <h2>See also</h2>
   <ol>
     <li><a href="{@docRoot}sdk/eclipse-adt.html">ADT Plugin for Eclipse</a></li>
-    <li><a href="{@docRoot}sdk/adding-components.html">Adding SDK Components</a></li>
+    <li><a href="{@docRoot}sdk/adding-components.html">Adding SDK Packages</a></li>
   </ol>
 
 </div>
@@ -81,9 +81,9 @@
 
 <h4>Updating?</h4>
 
-<p>If you already have an Android SDK, use the Android SDK and AVD Manager tool to install
+<p>If you already have an Android SDK, use the Android SDK Manager tool to install
 updated tools and new Android platforms into your existing environment. For information about how to
-do that, see <a href="{@docRoot}sdk/adding-components.html">Adding SDK Components</a>.</p>
+do that, see <a href="{@docRoot}sdk/adding-components.html">Adding SDK Packages</a>.</p>
 
 
 <h2 id="Preparing">Step 1. Preparing Your Development Computer</h2>
@@ -111,7 +111,7 @@
 
 <p>The SDK starter package is not a full
 development environment&mdash;it includes only the core SDK Tools, which you can
-use to download the rest of the SDK components (such as the latest Android platform).</p>
+use to download the rest of the SDK packages (such as the latest Android platform).</p>
 
 <p>If you haven't already, get the latest version of the SDK starter package from the <a
 href="{@docRoot}sdk/index.html">SDK download page</a>.</p>
@@ -154,53 +154,53 @@
 
 
 
-<h2 id="AddingComponents">Step 4. Adding Platforms and Other Components</h2>
+<h2 id="AddingComponents">Step 4. Adding Platforms and Other Packages</h2>
 
-<p>The last step in setting up your SDK is using the Android SDK and AVD Manager (a
-tool included in the SDK starter package) to download essential SDK components into your development
+<p>The last step in setting up your SDK is using the Android SDK Manager (a
+tool included in the SDK starter package) to download essential SDK packages into your development
 environment.</p>
 
 <p>The SDK uses a modular structure that separates the major parts of the SDK&mdash;Android platform
 versions, add-ons, tools, samples, and documentation&mdash;into a set of separately installable
-components. The SDK starter package, which you've already downloaded, includes only a single
-component: the latest version of the SDK Tools. To develop an Android application, you also need to
+packages. The SDK starter package, which you've already downloaded, includes only a single
+package: the latest version of the SDK Tools. To develop an Android application, you also need to
 download at least one Android platform and the associated platform tools. You can add other
-components and platforms as well, which is highly recommended.</p>
+packages and platforms as well, which is highly recommended.</p>
 
 <p>If you used the Windows installer, when you complete the installation wizard, it will launch the
-Android SDK and AVD Manager with a default set of platforms and other components selected
+Android SDK Manager with a default set of platforms and other packages selected
 for you to install. Simply click <strong>Install</strong> to accept the recommended set of
-components and install them. You can then skip to <a href="#sdkContents">Step 5</a>, but we
-recommend you first read the section about the <a href="#components">Available Components</a> to
-better understand the components available from the Android SDK and AVD Manager.</p>
+packages and install them. You can then skip to <a href="#sdkContents">Step 5</a>, but we
+recommend you first read the section about the <a href="#components">Available Packages</a> to
+better understand the packages available from the Android SDK Manager.</p>
 
-<p>You can launch the Android SDK and AVD Manager in one of the following ways:</p>
+<p>You can launch the Android SDK Manager in one of the following ways:</p>
 <ul>
-  <li>From within Eclipse, select <strong>Window &gt; Android SDK and AVD Manager</strong>.</li>
+  <li>From within Eclipse, select <strong>Window &gt; Android SDK Manager</strong>.</li>
   <li>On Windows, double-click the <code>SDK Manager.exe</code> file at the root of the Android
 SDK directory.</li>
   <li>On Mac or Linux, open a terminal and navigate to the <code>tools/</code> directory in the
 Android SDK, then execute: <pre>android</pre> </li>
 </ul>
 
-<p>To download components, use the graphical UI of the Android SDK and AVD
+<p>To download packages, use the graphical UI of the Android SDK
 Manager to browse the SDK repository and select new or updated
-components (see figure 1). The Android SDK and AVD Manager installs the selected components in
-your SDK environment. For information about which components you should download, see <a
-href="#which">Recommended Components</a>.</p>
+packages (see figure 1). The Android SDK Manager installs the selected packages in
+your SDK environment. For information about which packages you should download, see <a
+href="#which">Recommended Packages</a>.</p>
 
 <img src="/images/sdk_manager_packages.png" />
-<p class="img-caption"><strong>Figure 1.</strong> The Android SDK and AVD Manager's
-<strong>Available Packages</strong> panel, which shows the SDK components that are
+<p class="img-caption"><strong>Figure 1.</strong> The Android SDK Manager's
+<strong>Available Packages</strong> panel, which shows the SDK packages that are
 available for you to download into your environment.</p>
 
 
-<h3 id="components">Available Components</h3>
+<h3 id="components">Available Packages</h3>
 
-<p>By default, there are two repositories of components for your SDK: <em>Android
+<p>By default, there are two repositories of packages for your SDK: <em>Android
 Repository</em> and <em>Third party Add-ons</em>.</p>
 
-<p>The <em>Android Repository</em> offers these types of components:</p>
+<p>The <em>Android Repository</em> offers these types of packages:</p>
 
 <ul>
 <li><strong>SDK Tools</strong> &mdash; Contains tools for debugging and testing your application
@@ -219,9 +219,9 @@
 
 <li><strong>Android platforms</strong> &mdash; An SDK platform is
 available for every production Android platform deployable to Android-powered devices. Each
-SDK platform component includes a fully compliant Android library, system image, sample code,
+SDK platform package includes a fully compliant Android library, system image, sample code,
 and emulator skins. To learn more about a specific platform, see the list of platforms that appears
-under the section "Downloadable SDK Components" on the left part of this page.</li>
+under the section "Downloadable SDK Packages" on the left part of this page.</li>
 
 <li><strong>USB Driver for Windows</strong> (Windows only) &mdash; Contains driver files
 that you can install on your Windows computer, so that you can run and debug
@@ -243,16 +243,16 @@
 multiversion documentation for the Android framework API. </li>
 </ul>
 
-<p>The <em>Third party Add-ons</em> provide components that allow you to create a development
+<p>The <em>Third party Add-ons</em> provide packages that allow you to create a development
 environment using a specific Android external library (such as the Google Maps library) or a
 customized (but fully compliant) Android system image. You can add additional Add-on repositories by
 clicking <strong>Add Add-on Site</strong>.</p>
 
 
-<h3 id="which">Recommended Components</h3>
+<h3 id="which">Recommended Packages</h3>
 
-<p>The SDK repository contains a range of components that you can download.
-Use the table below to determine which components you need, based on whether you
+<p>The SDK repository contains a range of packages that you can download.
+Use the table below to determine which packages you need, based on whether you
 want to set up a basic, recommended, or full development environment:
 </p>
 
@@ -260,7 +260,7 @@
 
 <tr>
 <th>Environment</th>
-<th>SDK&nbsp;Component</th>
+<th>SDK&nbsp;Package</th>
 <th>Comments</th>
 </tr>
 
@@ -268,8 +268,8 @@
 <td rowspan="3" style="font-size:.9em;background-color:#FFE;">Basic</td>
 <td style="font-size:.9em;background-color:#FFE;">SDK Tools</td>
 <td style="font-size:.9em;background-color:#FFE;">If you've just installed
-the SDK starter package, then you already have the latest version of this component. The
-SDK Tools component is required to develop an Android application. Make sure you keep this up to
+the SDK starter package, then you already have the latest version of this package. The
+SDK Tools package is required to develop an Android application. Make sure you keep this up to
 date.</td>
 </tr>
 
@@ -300,21 +300,21 @@
 <tr>
 <td rowspan="3">Recommended<br/>(plus Basic)</td>
 <td>Documentation</td>
-<td>The Documentation component is useful because it lets you work offline and
+<td>The Documentation package is useful because it lets you work offline and
 also look up API reference information from inside Eclipse.</td>
 </tr>
 
 <tr>
 <td>Samples</td>
-<td>The Samples components give you source code that you can use to learn about
+<td>The Samples packages give you source code that you can use to learn about
 Android, load as a project and run, or reuse in your own app. Note that multiple
-samples components are available &mdash; one for each Android platform version. When
-you are choosing a samples component to download, select the one whose API Level
+samples packages are available &mdash; one for each Android platform version. When
+you are choosing a samples package to download, select the one whose API Level
 matches the API Level of the Android platform that you plan to use.</td>
 </tr>
 <tr>
 <td>Usb Driver</td>
-<td>The Usb Driver component is needed only if you are developing on Windows and
+<td>The Usb Driver package is needed only if you are developing on Windows and
 have an Android-powered device on which you want to install your application for
 debugging and testing. For Mac OS X and Linux platforms, no
 special driver is needed.</td>
@@ -344,12 +344,12 @@
 
 </table>
 
-<p>Once you've installed at least the basic configuration of SDK components, you're ready to start
+<p>Once you've installed at least the basic configuration of SDK packages, you're ready to start
 developing Android apps. The next section describes the contents of the Android SDK to familiarize
-you with the components you've just installed.</p>
+you with the packages you've just installed.</p>
 
-<p>For more information about using the Android SDK and AVD Manager, see the <a
-href="{@docRoot}sdk/adding-components.html">Adding SDK Components</a> document. </p>
+<p>For more information about using the Android SDK Manager, see the <a
+href="{@docRoot}sdk/adding-components.html">Adding SDK Packages</a> document. </p>
 
 
 <h2 id="sdkContents">Step 5. Exploring the SDK (Optional)</h2>
@@ -358,7 +358,7 @@
 and add-ons that you need, we suggest that you open the SDK directory and take a look at what's
 inside.</p>
 
-<p>The table below describes the full SDK directory contents, with components
+<p>The table below describes the full SDK directory contents, with packages
 installed. </p>
 
 <table>
@@ -405,20 +405,21 @@
 <tr>
 <td colspan="3"><code>tools/</code></td>
 <td>Contains the set of development and profiling tools that are platform-independent, such
-as the emulator, the Android SDK and AVD Manager, <code>ddms</code>, <code>hierarchyviewer</code>
-and more. The tools in this directory may be updated at any time using the Android SDK and AVD
+as the emulator, the Android SDK Manager, the AVD Manager, <code>ddms</code>,
+<code>hierarchyviewer</code>
+and more. The tools in this directory may be updated at any time using the Android SDK
 Manager and are independent of platform releases.</td>
 </tr>
 <tr>
 <td colspan="3"><code>SDK Readme.txt</code></td>
 <td>A file that explains how to perform the initial setup of your SDK,
-including how to launch the Android SDK and AVD Manager tool on all
+including how to launch the Android SDK Manager tool on all
 platforms.</td>
 </tr>
 <tr>
 <td colspan="3"><code>SDK Manager.exe</code></td>
-<td>Windows SDK only. A shortcut that launches the Android SDK and AVD
-Manager tool, which you use to add components to your SDK.</td>
+<td>Windows SDK only. A shortcut that launches the Android SDK
+Manager tool, which you use to add packages to your SDK.</td>
 </tr>
 <!--<tr>
 <td colspan="3"><code>documentation.html</code></td>
@@ -531,7 +532,7 @@
   <li>The Android SDK includes sample code and applications for each platform
 version. You can browse the samples in the <a
 href="{@docRoot}resources/index.html">Resources</a> tab or download them
-into your SDK using the Android SDK and AVD Manager. Once you've downloaded the
+into your SDK using the Android SDK Manager. Once you've downloaded the
 samples, you'll find them in
 <code><em>&lt;sdk&gt;</em>/samples/<em>&lt;platform&gt;/</em></code>. </li>
 </ul>
diff --git a/docs/html/sdk/oem-usb.jd b/docs/html/sdk/oem-usb.jd
index 818a0af..88d66dd 100644
--- a/docs/html/sdk/oem-usb.jd
+++ b/docs/html/sdk/oem-usb.jd
@@ -314,6 +314,10 @@
 href="http://www.teleepoch.com/android.html">http://www.teleepoch.com/android.html</a></td>
 </tr>
 
+<tr><td>Yulong Coolpad</td>	<td><a
+href="http://www.yulong.com/product/product/product/downloadList.html#downListUL">http://www.yulong.com/product/product/product/downloadList.html#downListUL</a></td>
+</tr>
+
 <tr>
 <td>ZTE</td>	<td><a
 href="http://support.zte.com.cn/support/news/NewsDetail.aspx?newsId=1000442">http://support.zte.com.cn/support/news/NewsDetail.aspx?newsId=1000442</a></td></tr>
diff --git a/docs/html/sdk/requirements.jd b/docs/html/sdk/requirements.jd
index c970f6c..c76e8c8 100644
--- a/docs/html/sdk/requirements.jd
+++ b/docs/html/sdk/requirements.jd
@@ -24,7 +24,7 @@
 
 <h4 style="margin-top:.25em"><em>Eclipse IDE</em></h4>
     <ul>
-      <li>Eclipse 3.6 (Helios) or greater
+      <li>Eclipse 3.6.2 (Helios) or greater
 <p class="note"><strong>Note:</strong> Eclipse 3.5 (Galileo) is no longer
 supported with the latest version of ADT.</p></li>
       <li>Eclipse <a href="http://www.eclipse.org/jdt">JDT</a> plugin (included
@@ -41,8 +41,8 @@
            <li>Eclipse IDE for Java EE Developers</li>
          </ul>
       </li>
-      <li><a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html">JDK 5 or JDK
-6</a> (JRE alone is not sufficient)</li>
+      <li><a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html">JDK 6</a>
+        (JRE alone is not sufficient)</li>
       <li><a href="eclipse-adt.html">Android Development Tools plugin</a>
 (recommended)</li>
       <li><strong>Not</strong> compatible with Gnu Compiler for Java (gcj)</li>
@@ -51,8 +51,8 @@
 
 <h4><em>Other development environments or IDEs</em></h4>
     <ul>
-      <li><a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html">JDK 5 or JDK
-6</a> (JRE alone is not sufficient)</li>
+      <li><a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html">JDK 6</a>
+        (JRE alone is not sufficient)</li>
       <li><a href="http://ant.apache.org/">Apache Ant</a> 1.8 or later</li>
       <li><strong>Not</strong> compatible with Gnu Compiler for Java (gcj)</li>
     </ul>
diff --git a/docs/html/sdk/sdk_toc.cs b/docs/html/sdk/sdk_toc.cs
index f7541f7..5413784 100644
--- a/docs/html/sdk/sdk_toc.cs
+++ b/docs/html/sdk/sdk_toc.cs
@@ -55,7 +55,7 @@
   /if ?>
   <li>
     <span class="heading">
-      <span class="en">Downloadable SDK Components</span>
+      <span class="en">Downloadable SDK Packages</span>
       <span style="display:none" class="de"></span>
       <span style="display:none" class="es"></span>
       <span style="display:none" class="fr"></span>
@@ -66,7 +66,7 @@
     </span>
     <ul>
       <li><a href="<?cs var:toroot ?>sdk/adding-components.html">
-      <span class="en">Adding SDK Components</span>
+      <span class="en">Adding SDK Packages</span>
       <span style="display:none" class="de"></span>
       <span style="display:none" class="es"></span>
       <span style="display:none" class="fr"></span>
@@ -81,7 +81,8 @@
         <div><a href="<?cs var:toroot ?>sdk/android-4.0-highlights.html">
         <span class="en">Android 4.0.x Platform</span></a> <span class="new">new!</span></div>
         <ul>
-          <li><a href="<?cs var:toroot ?>sdk/android-4.0.3.html">Android 4.0.3 Platform</a> <span class="new">new!</span></li>
+          <li><a href="<?cs var:toroot ?>sdk/android-4.0.3.html">Android 4.0.3 Platform</a>
+            <span class="new">new!</span></li>
           <li><a href="<?cs var:toroot ?>sdk/android-4.0.html">Android 4.0 Platform</a> </li>
         </ul>
       </li>
@@ -152,10 +153,10 @@
       </li>
     </ul>
     <ul>
-      <li><a href="<?cs var:toroot ?>sdk/tools-notes.html">SDK Tools, r16</a> <span
+      <li><a href="<?cs var:toroot ?>sdk/tools-notes.html">SDK Tools, r17</a> <span
 class="new">new!</span></li>
       <li><a href="<?cs var:toroot ?>sdk/win-usb.html">Google USB Driver, r4</a></li>
-      <li><a href="<?cs var:toroot ?>sdk/compatibility-library.html">Support Package, r6</a>
+      <li><a href="<?cs var:toroot ?>sdk/compatibility-library.html">Support Package, r7</a>
       <span class="new">new!</span></li>
     </ul>
   </li>
@@ -171,7 +172,7 @@
       <span style="display:none" class="zh-TW"></span>
     </span>
     <ul>
-      <li><a href="<?cs var:toroot ?>sdk/eclipse-adt.html">ADT 16.0.1
+      <li><a href="<?cs var:toroot ?>sdk/eclipse-adt.html">ADT 17.0.0
       <span style="display:none" class="de"></span>
       <span style="display:none" class="es"></span>
       <span style="display:none" class="fr"></span>
diff --git a/docs/html/sdk/tools-notes.jd b/docs/html/sdk/tools-notes.jd
index 91bcb7d..dea0c38 100644
--- a/docs/html/sdk/tools-notes.jd
+++ b/docs/html/sdk/tools-notes.jd
@@ -64,11 +64,113 @@
 }
 </style>
 
-
 <div class="toggleable opened">
   <a href="#" onclick="return toggleDiv(this)">
     <img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-img" height="9px"
     width="9px" />
+    SDK Tools, Revision 17</a> <em>(March 2012)</em>
+
+  <div class="toggleme">
+    <p class="caution"><strong>Important:</strong> To download the new Android
+    4.0 system components from the Android SDK Manager, you must first update the
+    SDK tools to revision 14 or later and restart the Android SDK Manager. If you do not,
+    the Android 4.0 system components will not be available for download.</p>
+
+    <dl>
+    <dt>Dependencies:</dt>
+    <dd>
+      <ul>
+        <li>Android SDK Platform-tools revision 9 or later.</li>
+        <li>If you are developing in Eclipse with ADT, note that the SDK Tools r17 is designed for
+        use with ADT 17.0.0 and later. If you haven't already, we highly recommend updating your
+        <a href="{@docRoot}sdk/eclipse-adt.html">ADT Plugin</a> to 17.0.0.</li>
+        <li>If you are developing outside Eclipse, you must have
+          <a href="http://ant.apache.org/">Apache Ant</a> 1.8 or later.</li>
+    </ul>
+    </dd>
+    <dt>General notes:</dt>
+    <dd>
+      <ul>
+        <li>Emulator
+          <ul>
+            <li>Added support for hardware accelerated graphics rendering. This feature requires an
+API Level 15, Revision 3 or later system image.
+(<a href="{@docRoot}guide/developing/devices/emulator.html#accel-graphics">more info</a>)
+              <p class="note"><strong>Note:</strong> As of the SDK Tools Revision 17 release, the
+API Level 15, Revision 3 system image is not yet available.</p>
+            </li>
+            <li>Added support for running Android x86 system images in virtualization mode on
+Windows and Mac OS X.
+(<a href="{@docRoot}guide/developing/devices/emulator.html#accel-vm">more info</a>)</li>
+            <li>Added experimental support for multi-touch input by enabing the emulator to receive
+              touch input from a USB-tethered physical Android device.
+              (<a href="http://tools.android.com/tips/hardware-emulation">more info</a>)</li>
+          </ul>
+        </li>
+        <li>Added viewing of live detailed network usage of an app in DDMS. (<a
+    href="http://tools.android.com/recent/detailednetworkusageinddms">more info</a>)</li>
+        <li>ProGuard
+          <ul>
+            <li>Updated the bundled ProGuard tool to version 4.7. In addition to many new features,
+this update fixes the {@code Conversion to Dalvik format failed with error 1} error some users have
+experienced.</li>
+            <li>Updated the default {@code proguard.cfg} file with better default flags for
+              Android.</li>
+            <li>Split the ProGuard configuration file has been in half, with project specific flags
+kept in project and the generic Android flags distributed (and updated) with the tools
+themselves.</li>
+          </ul>
+        </li>
+        <li>Build
+          <ul>
+            <li>Added a feature that allows you to run some code only in debug mode. Builds now
+generate a class called {@code BuildConfig} containing a {@code DEBUG} constant that is
+automatically set according to your build type. You can check the ({@code BuildConfig.DEBUG})
+constant in your code to run debug-only functions.</li>
+            <li>Fixed issue when a project and its libraries include the same jar file in their libs
+              folder. (<a href="http://tools.android.com/recent/dealingwithdependenciesinandroidprojects">more
+              info</a>)</li>
+            <li>Added support for custom views with custom attributes in libraries. Layouts using
+custom attributes must use the namespace URI {@code http://schemas.android.com/apk/res-auto} instead
+of the URI that includes the app package name. This URI is replaced with the app specific one at
+build time.</li>
+          </ul>
+        </li>
+        <li>Lint
+          <ul>
+            <li>Updated Lint to check Android application code. Lint rules which previously
+performed pattern based searches in the application code (such as the unused resource check) have
+been rewritten to use the more accurate Java-style parse trees.</li>
+            <li>Added support for checking library projects. This change means that rules such as
+the unused resource check properly handle resources declared in a library project and referenced in
+a downstream project.</li>
+            <li>Added ability to suppress Lint warnings in Java code with the new
+{@code @SuppressLint} annotation, and in XML files with the new tools: namespace and
+ignore attribute. (<a
+    href="http://tools.android.com/recent/ignoringlintwarnings">more info</a>)</li>
+            <li>New Lint checks:
+              <ul>
+                <li>Added check for Android API calls that require a version of Android higher than
+                  the minimum supported version. You can use the new {@code @TargetApi} annotation
+                  to suppress warnings when the code is wrapped in a system version condition.
+                  (<a href="http://tools.android.com/recent/lintapicheck">more info</a>)</li>
+                <li>Added over 20 new Lint rules, including checks for
+                  <a href="http://tools.android.com/recent/lintperformancechecks">performance</a>,
+                  XML layouts, manifest and file handling.</li>
+              </ul>
+            </li>
+          </ul>
+        </li>
+      </ul>
+    </dd>
+    </dl>
+  </div>
+</div>
+
+<div class="toggleable closed">
+  <a href="#" onclick="return toggleDiv(this)">
+    <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-img" height="9px"
+    width="9px" />
     SDK Tools, Revision 16</a> <em>(December 2011)</em>
 
   <div class="toggleme">
@@ -92,10 +194,10 @@
 <dt>General notes:</dt>
 <dd>
   <ul>
-    <li>Added Lint tools to detect common errors in Android projects. 
+    <li>Added Lint tools to detect common errors in Android projects.
       (<a href="http://tools.android.com/recent/lint">more info</a>)</li>
     <li>Added sensor emulation support, which allows the emulator to read sensor data from a
-      physical Android device. 
+      physical Android device.
       (<a href="http://tools.android.com/recent/sensoremulation">more info</a>)</li>
     <li>Added support for using a webcam to emulate a camera on Mac OS X.</li>
   </ul>
diff --git a/docs/html/sdk/win-usb.jd b/docs/html/sdk/win-usb.jd
index 6869d74..3be0faf 100644
--- a/docs/html/sdk/win-usb.jd
+++ b/docs/html/sdk/win-usb.jd
@@ -12,7 +12,7 @@
   <ol>
     <li><a href="{@docRoot}sdk/oem-usb.html#InstallingDriver">Installing a USB Driver</a></li>
     <li><a href="{@docRoot}guide/developing/device.html">Using Hardware Devices</a></li>
-    <li><a href="{@docRoot}sdk/adding-components.html">Adding SDK Components</a></li>
+    <li><a href="{@docRoot}sdk/adding-components.html">Adding SDK Packages</a></li>
   </ol>
 </div>
 </div>
@@ -51,7 +51,7 @@
 <p>The sections below provide notes about successive revisions of the USB Driver
 for Windows, as denoted by revision number. To determine what revision of the
 USB Driver for Windows you are using, refer to the "Installed Packages" listing
-in the Android SDK and AVD Manager.</p>
+in the Android SDK Manager.</p>
 
 <script type="text/javascript">
 function toggleDiv(link) {
@@ -149,7 +149,7 @@
 
 <div class="figure" style="width:536px;margin:0">
   <img src="{@docRoot}images/developing/sdk-usb-driver.png" alt="" />
-  <p class="img-caption"><strong>Figure 1.</strong> The SDK and AVD Manager
+  <p class="img-caption"><strong>Figure 1.</strong> The SDK Manager
     with the Google USB Driver selected.</p>
 </div>
 
@@ -158,10 +158,10 @@
 want to connect an Android-powered device (ADP, Nexus One, or Nexus S) to your
 development environment over USB. </p>
 
-<p>To download the driver, use the Android SDK and AVD Manager tool that is
+<p>To download the driver, use the Android SDK Manager tool that is
 included with the <a href="{@docRoot}sdk/index.html">Android SDK</a>:</p>
 <ol>
-  <li>Launch the SDK and AVD Manager by double-clicking <code>SDK Manager.exe</code>,
+  <li>Launch the Android SDK Manager by double-clicking <code>SDK Manager.exe</code>,
   at the root of your SDK directory.</li>
   <li>Expand <em>Extras</em>.</li>
   <li>Check <strong>Google USB Driver package</strong> and click <strong>Install</strong>.</li>
diff --git a/docs/html/training/efficient-downloads/connectivity_patterns.jd b/docs/html/training/efficient-downloads/connectivity_patterns.jd
new file mode 100644
index 0000000..81f1540
--- /dev/null
+++ b/docs/html/training/efficient-downloads/connectivity_patterns.jd
@@ -0,0 +1,76 @@
+page.title=Modifying your Download Patterns Based on the Connectivity Type
+parent.title=Transferring Data Without Draining the Battery
+parent.link=index.html
+
+trainingnavtop=true
+previous.title=Redundant Downloads are Redundant
+previous.link=redundant_redundant.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#WiFi">Use Wi-Fi</a></li>
+  <li><a href="#Bandwidth">Use greater bandwidth to download more data less often</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+  <li><a href="{@docRoot}training/monitoring-device-state/index.html">Optimizing Battery Life</a></li>
+</ul>
+
+</div>
+</div>
+
+<p>When it comes to impact on battery life, not all connection types are created equal. Not only does the Wi-Fi radio use significantly less battery than its wireless radio counterparts, but the radios used in different wireless radio technologies have different battery implications.</p>
+ 
+<h2 id="WiFi">Use Wi-Fi</h2>
+
+<p>In most cases a Wi-Fi radio will offer greater bandwidth at a significantly lower battery cost. As a result, you should endeavor to perform data transfers when connected over Wi-Fi whenever possible.</p>
+
+<p>You can use a broadcast receiver to listen for connectivity changes that indicate when a Wi-Fi connection has been established to execute significant downloads, preempt scheduled updates, and potentially even temporarily increase the frequency of regular updates as described in <a href="{@docRoot}training/monitoring-device-state/index.html">Optimizing Battery Life</a> lesson <a href="{@docRoot}training/monitoring-device-state/connectivity-monitoring.html">Determining and Monitoring the Connectivity Status</a>.</p>
+
+<h2 id="Bandwidth">Use Greater Bandwidth to Download More Data Less Often</h2>
+
+<p>When connected over a wireless radio, higher bandwidth generally comes at the price of higher battery cost. Meaning that LTE typically consumes more energy than 3G, which is in turn more expensive than 2G.</p>
+
+<p>This means that while the underlying radio state machine varies based on the radio technology, generally speaking the relative battery impact of the state change tail-time is greater for higher bandwidth radios.</p>
+
+<p>At the same time, the higher bandwidth means you can prefetch more aggressively, downloading more data over the same time. Perhaps less intuitively, because the tail-time battery cost is relatively higher, it's also more efficient to keep the radio active for longer periods during each transfer session to reduce the frequency of updates.</p>
+
+<p>For example, if an LTE radio is has double the bandwidth and double the energy cost of 3G, you should download 4 times as much data during each session&mdash;or potentially as much as 10mb. When downloading this much data, it's important to consider the effect of your prefetching on the available local storage and flush your prefetch cache regularly.</p>
+
+<p>You can use the connectivity manager to determine the active wireless radio, and modify your prefetching routines accordingly:</p>
+
+<pre>ConnectivityManager cm =
+ (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
+
+TelephonyManager tm =
+  (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
+  
+NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
+ 
+int PrefetchCacheSize = DEFAULT_PREFETCH_CACHE;
+ 
+switch (activeNetwork.getType()) {
+  case (ConnectivityManager.TYPE_WIFI): 
+    PrefetchCacheSize = MAX_PREFETCH_CACHE; break;
+  case (ConnectivityManager.TYPE_MOBILE): {
+    switch (tm.getNetworkType()) {
+      case (TelephonyManager.NETWORK_TYPE_LTE | 
+            TelephonyManager.NETWORK_TYPE_HSPAP): 
+        PrefetchCacheSize *= 4;
+        break;
+      case (TelephonyManager.NETWORK_TYPE_EDGE | 
+            TelephonyManager.NETWORK_TYPE_GPRS): 
+        PrefetchCacheSize /= 2;
+        break;
+      default: break;
+    }
+    break;
+  }
+  default: break;
+}</pre>
\ No newline at end of file
diff --git a/docs/html/training/efficient-downloads/efficient-network-access.jd b/docs/html/training/efficient-downloads/efficient-network-access.jd
new file mode 100644
index 0000000..0efad7d
--- /dev/null
+++ b/docs/html/training/efficient-downloads/efficient-network-access.jd
@@ -0,0 +1,170 @@
+page.title=Optimizing Downloads for Efficient Network Access
+parent.title=Transferring Data Without Draining the Battery
+parent.link=index.html
+
+trainingnavtop=true
+next.title=Minimizing the Effect of Regular Updates
+next.link=regular_updates.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#RadioStateMachine">Understand the radio state machine</a></li>
+  <li><a href="#AppsStateMachine">Understand how apps can impact the radio state machine</a></li>
+  <li><a href="#PrefetchData">Efficiently prefetch data</a></li>
+  <li><a href="#BatchTransfers">Batch transfers and connections</a></li>
+  <li><a href="#ReduceConnections">Reduce the number of connections you use</a></li>
+  <li><a href="#DDMSNetworkTraffic">Use the DDMS Network Traffic Tool to identify areas of concern</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+  <li><a href="{@docRoot}training/monitoring-device-state/index.html">Optimizing Battery Life</a></li>
+</ul>
+
+</div>
+</div>
+
+<p>Using the wireless radio to transfer data is potentially one of your app's most significant sources of battery drain. To minimize the battery drain associated with network activity, it's critical that you understand how your connectivity model will affect the underlying radio hardware.</p>
+
+<p>This lesson introduces the wireless radio state machine and explains how your app's connectivity model interacts with it. It goes on to propose ways to minimize your data connections, use prefetching, and bundle your transfers in order to minimize the battery drain associated with your data transfers.</p>
+ 
+<h2 id="RadioStateMachine">The Radio State Machine</h2> 
+ 
+<p>A fully active wireless radio consumes significant power, so it transitions between different energy states in order to conserve power when not in use, while attempting to minimize latency associated with "powering up" the radio when it's required.</p>
+
+<p>The state machine for a typical 3G network radio consists of three energy states:
+<ol><li><b>Full power</b>: Used when a connection is active, allowing the device to transfer data at its highest possible rate.</li>
+<li><b>Low power</b>: An intermediate state that uses around 50% of the battery power at the full state.</li>
+<li><b>Standby</b>: The minimal energy state during which no network connection is active or required.</li>
+</ol></p>
+
+<p>While the low and idle states drain significantly less battery, they also introduce significant latency to network requests. Returning to full power from the low state takes around 1.5 seconds, while moving from idle to full can take over 2 seconds.</p>
+
+<p>To minimize latency, the state machine uses a delay to postpone the transition to lower energy states. Figure 1 uses AT&T's timings for a typical 3G radio.</p>
+
+<img src="{@docRoot}images/efficient-downloads/mobile_radio_state_machine.png" />
+<p class="img-caption"><strong>Figure 1.</strong> Typical 3G wireless radio state machine.</p>
+
+<p>The radio state machine on each device, particularly the associated transition delay ("tail time") and startup latency, will vary based on the wireless radio technology employed (2G, 3G, LTE, etc.) and is defined and configured by the carrier network over which the device is operating.</p>
+
+<p>This lesson describes a representative state machine for a typical 3G wireless radio, based on <a href="http://www.research.att.com/articles/featured_stories/2011_03/201102_Energy_efficient?fbid=1zObBOMOZSB">data provided by AT&T</a>. However, the general principles and resulting best practices are applicable for all wireless radio implementations.</p>
+
+<p>This approach is particularly effective for typical web browsing as it prevents unwelcome latency while users browse the web. The relatively low tail-time also ensures that once a browsing session has finished, the radio can move to a lower energy state.</p>
+
+<p>Unfortunately, this approach can lead to inefficient apps on modern smartphone OSs like Android, where apps run both in the foreground (where latency is important) and in the background (where battery life should be prioritized).</p>
+ 
+<h2 id="AppsStateMachine">How Apps Impact the Radio State Machine</h2> 
+ 
+<p>Every time you create a new network connection, the radio transitions to the full power state. In the case of the typical 3G radio state machine described above, it will remain at full power for the duration of your transfer&mdash;plus an additional 5 seconds of tail time&mdash;followed by 12 seconds at the low energy state.  So for a typical 3G device, every data transfer session will cause the radio to draw energy for almost 20 seconds.</p>
+
+<p>In practice, this means an app that transfers unbundled data for 1 second every 18 seconds will keep the wireless radio perpetually active, moving it back to high power just as it was about to become idle. As a result, every minute it will consume battery at the high power state for 18 seconds, and at the low power state for the remaining 42 seconds.</p>
+
+<p>By comparison, the same app that bundles transfers of 3 seconds of every minute will keep the radio in the high power state for only 8 seconds, and will keep it in the low power state for only an additional 12 seconds.</p>
+
+<p>The second example allows the radio to be idle for an additional 40 second every minute, resulting in a massive reduction in battery consumption.</p>
+
+<img src="{@docRoot}images/efficient-downloads/graphs.png" />
+<p class="img-caption"><strong>Figure 2.</strong> Relative wireless radio power use for bundled versus unbundled transfers.</p>
+
+<h2 id="PrefetchData">Prefetch Data</h2> 
+
+<p>Prefetching data is an effective way to reduce the number of independent data transfer sessions. Prefetching allows you to download all the data you are likely to need for a given time period in a single burst, over a single connection, at full capacity.</p>
+
+<p>By front loading your transfers, you reduce the number of radio activations required to download the data. As a result you not only conserve battery life, but also improve the latency, lower the required bandwidth, and reduce download times.</p>
+
+<p>Prefetching also provides an improved user experience by minimizing in-app latency caused by waiting for downloads to complete before performing  an action or viewing data.</p>
+
+<p>However, used too aggressively, prefetching introduces the risk of increasing battery drain and bandwidth use&mdash;as well as download quota&mdash;by downloading data that isn't used. It's also important to ensure that prefetching doesn't delay application startup while the app waits for the prefetch to complete. In practical terms that might mean processing data progressively, or initiating consecutive transfers prioritized such that the data required for application startup is downloaded and processed first.</p>
+
+<p>How aggressively you prefetch depends on the size of the data being downloaded and the likelihood of it being used. As a rough guide, based on the state machine described above, for data that has a 50% chance of being used within the current user session, you can typically prefetch for around 6 seconds (approximately 1-2 Mb) before the potential cost of downloading unused data matches the potential savings of not downloading that data to begin with.</p>
+
+<p>Generally speaking, it's good practice to prefetch data such that you will only need to initiate another download every 2 to 5 minutes, and in the order of 1 to 5 megabytes.</p>
+
+<p>Following this principle, large downloads&mdash;such as video files&mdash;should be downloaded in chunks at regular intervals (every 2 to 5 minutes), effectively prefetching only the video data likely to be viewed in the next few minutes.</p>
+
+<p>Note that further downloads should be bundled, as described in the next section, <a href="#BatchTransfers">Batch Transfers and Connections</a>, and that these approximations will vary based on the connection type and speed, as discussed in <a href="connectivity_patterns.html">Modify your Download Patterns Based on the Connectivity Type</a>.</p>
+
+<p>Let's look at some practical examples:</p>
+
+<p><b>A music player</b></p>
+
+<p>You could choose to prefetch an entire album, however should the user stop listening after the first song, you've wasted a significant amount of bandwidth and battery life.</p>
+
+<p>A better approach would be to maintain a buffer of one song in addition to the one being played. For streaming music, rather than maintaining a continuous stream that keeps the radio active at all times, consider using HTTP live streaming to transmit the audio stream in bursts, simulating the prefetching approach described above.</p>
+
+<p><b>A news reader</b></p>
+
+<p>Many news apps attempt to reduce bandwidth by downloading headlines only after a category has been selected, full articles only when the user wants to read them, and thumbnails just as they scroll into view.</p>
+
+<p>Using this approach, the radio will be forced to remain active for the majority of users' news-reading session as they scroll headlines, change categories, and read articles. Not only that, but the constant switching between energy states will result in significant latency when switching categories or reading articles.</p>
+
+<p>A better approach would be to prefetch a reasonable  amount of data at startup, beginning with the first set of news headlines and thumbnails&mdash;ensuring a low latency startup time&mdash;and continuing with the remaining headlines and thumbnails, as well as the article text for each article available from at least the primary headline list.</p>
+
+<p>Another alternative is to prefetch every headline, thumbnail, article text, and possibly even full article pictures&mdash;typically in the background on a predetermined schedule. This approach risks spending significant bandwidth and battery life downloading content that's never used, so it should be implemented with caution.</p>
+
+<p>One solution is to schedule the full download to occur only when connected to Wi-Fi, and possibly only when the device is charging. This is investigated in more detail in <a href="connectivity_patterns.html">Modify your Download Patterns Based on the Connectivity Type</a>.</p>
+
+<h2 id="BatchTransfers">Batch Transfers and Connections</h2>
+
+Every time you initiate a connection&mdash;irrespective of the size of the associated data transfer&mdash;you potentially cause the radio to draw power for nearly 20 seconds when using a typical 3G wireless radio.</p>
+
+<p>An app that pings the server every 20 seconds, just to acknowledge that the app is running and visible to the user, will keep the radio powered on indefinitely, resulting in a significant battery cost for almost no actual data transfer.</p>
+
+<p>With that in mind it's important to bundle your data transfers and create a pending transfer queue. Done correctly, you can effectively phase-shift transfers that are due to occur within a similar time window, to make them all happen simultaneously&mdash;ensuring that the radio draws power for as short a duration as possible.</p>
+
+<p>The underlying philosophy of this approach is to transfer as much data as possible during each transfer session in an effort to limit the number of sessions you require.</p>
+
+<p>That means you should batch your transfers by queuing delay tolerant transfers, and preempting scheduled updates and prefetches, so that they are all executed when time-sensitive transfers are required. Similarly, your scheduled updates and regular prefetching should initiate the execution of your pending transfer queue.</p>
+
+<p>For a practical example, let's return to the earlier examples from <a href="#PrefetchData">Prefetch Data</a>.</p>
+
+<p>Take a news application that uses the prefetching routine described above. The news reader collects analytics information to understand the reading patterns of its users and to rank the most popular stories. To keep the news fresh, it checks for updates every hour. To conserve bandwidth, rather than download full photos for each article, it prefetches only thumbnails and downloads the full photos when they are selected.</p>
+
+<p>In this example, all the analytics information collected within the app should be bundled together and queued for download, rather than being transmitted as it's collected. The resulting bundle should be transferred when either a full-sized photo is being downloaded, or when an hourly update is being performed.</p>
+
+<p>Any time-sensitive or on-demand transfer&mdash;such as downloading a full-sized image&mdash;should preempt regularly scheduled updates. The planned update should be executed at the same time as the on-demand transfer, with the next update scheduled to occur after the set interval. This approach mitigates the cost of performing a regular update by piggy-backing on the necessary time-sensitive photo download.</p>
+
+<h2 id="ReduceConnections">Reduce Connections</h2>
+
+<p>It's generally more efficient to reuse existing network connections than to initiate new ones. Reusing connections also allows the network to more intelligently react to congestion and related network data issues.</p>
+
+<p>Rather than creating multiple simultaneous connections to download data, or chaining multiple consecutive GET requests, where possible you should bundle those requests into a single GET.</p>
+
+<p>For example, it would be more efficient to make a single request for every news article to be returned in a single request / response than to make multiple queries for several news categories. 
+The wireless radio needs to become active in order to transmit the termination / termination acknowledgement packets associated with server and client  timeout, so it's also good practice to close your connections when they aren't in use, rather than waiting for these timeouts.</p>
+
+<p>That said, closing a connection too early can prevent it from being reused, which then requires additional overhead for establishing a new connection. A useful compromise is not to close the connection immediately, but to still close it before the inherent timeout expires.</p>
+
+<h2 id="DDMSNetworkTraffic">Use the DDMS Network Traffic Tool to Identify Areas of Concern</h2>
+
+<p>The Android <a href="{@docRoot}guide/developing/debugging/ddms.html">DDMS (Dalvik Debug Monitor Server)</a> includes a Detailed Network Usage tab that makes it possible to track when your application is making network requests. Using this tool, you can monitor how and when your app transfers data and optimize the underlying code appropriately.</p>
+
+<p>Figure 3 shows a pattern of transferring small amounts of data roughly 15 seconds apart, suggesting that efficiency could be dramatically improved by prefetching each request or bundling the uploads.</p>
+
+<img src="{@docRoot}images/efficient-downloads/DDMS.png" />
+<p class="img-caption"><strong>Figure 3.</strong> Tracking network usage with DDMS.</p>
+
+<p>By monitoring the frequency of your data transfers, and the amount of data transferred during each connection, you can identify areas of your application that can be made more battery-efficient. Generally, you will be looking for short spikes that can be delayed, or that should cause a later transfer to be preempted.</p>
+
+<p>To better identify the cause of transfer spikes, the Traffic Stats API allows you to tag the data transfers occurring within a thread using the {@code TrafficStats.setThreadStatsTag()} method, followed by manually tagging (and untagging) individual sockets using {@code tagSocket()} and {@code untagSocket()}. For example:</p>
+
+<pre>TrafficStats.setThreadStatsTag(0xF00D);
+TrafficStats.tagSocket(outputSocket);
+// Transfer data using socket
+TrafficStats.untagSocket(outputSocket);</pre>
+
+<p>The Apache {@code HttpClient} and {@code URLConnection} libraries automatically tag sockets based on the current {@code getThreadStatsTag()} value. These libraries also tag and untag sockets when recycled through keep-alive pools.</p>
+
+<pre>TrafficStats.setThreadStatsTag(0xF00D);
+try {
+  // Make network request using HttpClient.execute()
+} finally {
+  TrafficStats.clearThreadStatsTag();
+}</pre>
+
+<p>Socket tagging is supported in Android 4.0, but real-time stats will only be displayed on devices running Android 4.0.3 or higher.</p>
\ No newline at end of file
diff --git a/docs/html/training/efficient-downloads/index.jd b/docs/html/training/efficient-downloads/index.jd
new file mode 100644
index 0000000..a29be91
--- /dev/null
+++ b/docs/html/training/efficient-downloads/index.jd
@@ -0,0 +1,51 @@
+page.title=Transferring Data Without Draining the Battery
+
+trainingnavtop=true
+startpage=true
+next.title=Optimizing Downloads for Efficient Network Access
+next.link=efficient-network-access.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<!-- Required platform, tools, add-ons, devices, knowledge, etc. -->
+<h2>Dependencies and prerequisites</h2>
+<ul>
+  <li>Android 2.0 (API Level 5) or higher</li>
+</ul>
+
+<!-- related docs (NOT javadocs) -->
+<h2>You should also read</h2>
+<ul>
+  <li><a href="{@docRoot}training/monitoring-device-state/index.html">Optimizing Battery Life</a></li>
+</ul>
+
+</div>
+</div>
+
+<p>In this class you will learn to minimize the battery life impact of downloads and network connections, particularly in relation to the wireless radio.</P
+
+<p>This class demonstrates the best practices for scheduling and executing downloads using techniques such as caching, polling, and prefetching. You will learn how the power-use profile of the wireless radio can affect your choices on when, what, and how to transfer data in order to minimize impact on battery life.</p> 
+
+<h2>Lessons</h2> 
+ 
+<!-- Create a list of the lessons in this class along with a short description of each lesson.
+These should be short and to the point. It should be clear from reading the summary whether someone
+will want to jump to a lesson or not.--> 
+ 
+<dl> 
+  <dt><b><a href="efficient-network-access.html">Optimizing Downloads for Efficient Network Access</a></b></dt>
+    <dd>This lesson introduces the wireless radio state machine, explains how your app’s connectivity model interacts with it, and how you can minimize your data connection and use prefetching and bundling to minimize the battery drain associated with your data transfers.</dd>
+ 
+  <dt><b><a href="regular_updates.html">Minimizing the Effect of Regular Updates</a></b></dt>
+    <dd>This lesson will examine how your refresh frequency can be varied to best mitigate the effect of background updates on the underlying wireless radio state machine.</dd>
+
+  <dt><b><a href="redundant_redundant.html">Redundant Downloads are Redundant</a></b></dt>
+    <dd>The most fundamental way to reduce your downloads is to download only what you need. This lesson introduces some best practices to eliminate redundant downloads.</dd>
+
+  <dt><b><a href="connectivity_patterns.html">Modifying your Download Patterns Based on the Connectivity Type</a></b></dt>
+    <dd>When it comes to impact on battery life, not all connection types are created equal. Not only does the Wi-Fi radio use significantly less battery than its wireless radio counterparts, but the radios used in different wireless radio technologies have different battery implications.</dd>
+
+</dl> 
diff --git a/docs/html/training/efficient-downloads/redundant_redundant.jd b/docs/html/training/efficient-downloads/redundant_redundant.jd
new file mode 100644
index 0000000..4bf9af9
--- /dev/null
+++ b/docs/html/training/efficient-downloads/redundant_redundant.jd
@@ -0,0 +1,87 @@
+page.title=Redundant Downloads are Redundant
+parent.title=Transferring Data Without Draining the Battery
+parent.link=index.html
+
+trainingnavtop=true
+previous.title=Minimizing the Effect of Regular Updates
+previous.link=regular_updates.html
+next.title=Connectivity Based Download Patterns
+next.link=connectivity_patterns.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#LocalCache">Cache files locally</a></li>
+  <li><a href="#ResponseCache">Use the HttpURLConnection response cache</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+  <li><a href="{@docRoot}training/monitoring-device-state/index.html">Optimizing Battery Life</a></li>
+</ul>
+
+</div>
+</div>
+
+<p>The most fundamental way to reduce your downloads is to download only what you need. In terms of data, that means implementing REST APIs that allow you to specify query criteria that limit the returned data by using parameters such as the time of your last update.</p>
+
+<p>Similarly, when downloading images, it's good practice to reduce the size of the images server-side, rather than downloading full-sized images that are reduced on the client.</p>
+ 
+<h2 id="LocalCache">Cache Files Locally</h2> 
+
+<p>Another important technique is to avoid downloading duplicate data. You can do this by aggressive caching. Always cache static resources, including on-demand downloads such as full size images, for as long as reasonably possible. On-demand resources should be stored separately to enable you to regularly flush your on-demand cache to manage its size.</p>
+
+<p>To ensure that your caching doesn't result in your app displaying stale data, be sure to extract the time at which the requested content was last updated, and when it expires, from within the HTTP response headers. This will allow you to determine when the associated content should be refreshed.</p>
+
+<pre>long currentTime = System.currentTimeMillis());
+
+HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+
+long expires = conn.getHeaderFieldDate("Expires", currentTime);
+long lastModified = conn.getHeaderFieldDate("Last-Modified", currentTime);
+
+setDataExpirationDate(expires);
+
+if (lastModified < lastUpdateTime) {
+  // Skip update
+} else {
+  // Parse update
+}</pre>
+
+<p>Using this approach, you can also effectively cache dynamic content while ensuring it doesn't result in your application displaying stale information.</p>
+
+<p>You can cache non-sensitive data can in the unmanaged external cache directory:</p>
+
+<pre>Context.getExternalCacheDir();</pre>
+
+<p>Alternatively, you can use the managed / secure application cache. Note that this internal cache may be flushed when the system is running low on available storage.</p>
+
+<pre>Context.getCache();</pre>
+
+<p>Files stored in either cache location will be erased when the application is uninstalled.</p>
+
+<h2 id="ResponseCache">Use the HttpURLConnection Response Cache</h2> 
+
+<p>Android 4.0 added a response cache to {@code HttpURLConnection}. You can enable HTTP response caching on supported devices using reflection as follows:</p>
+
+<pre>private void enableHttpResponseCache() {
+  try {
+    long httpCacheSize = 10 * 1024 * 1024; // 10 MiB
+    File httpCacheDir = new File(getCacheDir(), "http");
+    Class.forName("android.net.http.HttpResponseCache")
+         .getMethod("install", File.class, long.class)
+         .invoke(null, httpCacheDir, httpCacheSize);
+  } catch (Exception httpResponseCacheNotAvailable) {
+    Log.d(TAG, "HTTP response cache is unavailable.");
+  }
+}</pre>
+
+<p>This sample code will turn on the response cache on Android 4.0+ devices without affecting earlier releases.</p>
+
+<p>With the cache installed, fully cached HTTP requests can be served directly from local storage, eliminating the need to open a network connection. Conditionally cached responses can validate their freshness from the server, eliminating the bandwidth cost associated with the download.</p>
+
+<p>Uncached responses get stored in the response cache for for future requests.</p>
\ No newline at end of file
diff --git a/docs/html/training/efficient-downloads/regular_updates.jd b/docs/html/training/efficient-downloads/regular_updates.jd
new file mode 100644
index 0000000..feb7a8e
--- /dev/null
+++ b/docs/html/training/efficient-downloads/regular_updates.jd
@@ -0,0 +1,102 @@
+page.title=Minimizing the Effect of Regular Updates
+parent.title=Transferring Data Without Draining the Battery
+parent.link=index.html
+
+trainingnavtop=true
+previous.title=Optimizing Downloads for Efficient Network Access
+previous.link=efficient-network-access.html
+next.title=Redundant Downloads are Redundant
+next.link=redundant_redundant.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#C2DM">Use Cloud to Device Messaging as an alternative to polling</a></li>
+  <li><a href="#OptimizedPolling">Optimize polling with inexact repeating alarms and exponential back-offs</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+  <li><a href="{@docRoot}training/monitoring-device-state/index.html">Optimizing Battery Life</a></li>
+  <li><a href="http://code.google.com/android/c2dm/">Android Cloud to Device Messaging</a></li>
+</ul>
+
+</div>
+</div>
+
+<p>The optimal frequency of regular updates will vary based on device state, network connectivity, user behavior, and explicit user preferences.</p>
+
+<p><a href="{@docRoot}training/monitoring-device-state/index.html">Optimizing Battery Life</a> discusses how to build battery-efficient apps that modify their refresh frequency based on the state of the host device. That includes disabling background service updates when you lose connectivity and reducing the rate of updates when the battery level is low.</p>
+
+<p>This lesson will examine how your refresh frequency can be varied to best mitigate the effect of background updates on the underlying wireless radio state machine.</p>
+ 
+<h2 id="C2DM">Use Cloud to Device Messaging as an Alternative to Polling</h2> 
+ 
+<p>Every time your app polls your server to check if an update is required, you activate the wireless radio, drawing power unnecessarily, for up to 20 seconds on a typical 3G connection.</p>
+
+<p><a href="http://code.google.com/android/c2dm/">Android Cloud to Device Messaging (C2DM)</a> is a lightweight mechanism used to transmit data from a server to a particular app instance. Using C2DM, your server can notify your app running on a particular device that there is new data available for it.</p>
+
+<p>Compared to polling, where your app must regularly ping the server to query for new data, this event-driven model allows your app to create a new connection only when it knows there is data to download.</p>
+
+<p>The result is a reduction in unnecessary connections, and a reduced latency for updated data within your application.</p>
+
+<p>C2DM is implemented using a persistent TCP/IP connection. While it's possible to implement your own push service, it's best practice to use C2DM. This minimizes the number of persistent connections and allows the platform to optimize bandwidth and minimize the associated impact on battery life.</p>
+
+<h2 id="OptimizedPolling">Optimize Polling with Inexact Repeating Alarms and Exponential Backoffs</h2> 
+
+<p>Where polling is required, it's good practice to set the default data refresh frequency of your app as low as possible without detracting from the user experience.</p>
+
+<p>A simple approach is to offer preferences to allow users to explicitly set their required update rate, allowing them to define their own balance between data freshness and battery life.</p>
+
+<p>When scheduling updates, use inexact repeating alarms that allow the system to "phase shift" the exact moment each alarm triggers.</p>
+
+<pre>int alarmType = AlarmManager.ELAPSED_REALTIME;
+long interval = AlarmManager.INTERVAL_HOUR;
+long start = System.currentTimeMillis() + interval;
+
+alarmManager.setInexactRepeating(alarmType, start, interval, pi);</pre>
+
+<p>If several alarms are scheduled to trigger at similar times, this phase-shifting will cause them to be triggered simultaneously, allowing each update to piggyback on top of a single active radio state change.</p>
+
+<p>Wherever possible, set your alarm type to {@code ELAPSED_REALTIME} or {@code RTC} rather than to their {@code _WAKEUP} equivalents. This further reduces battery impact by waiting until the phone is no longer in standby mode before the alarm triggers.</p>
+
+<p>You can further reduce the impact of these scheduled alarms by opportunistically reducing their frequency based on how recently your app was used.</p>
+
+<p>One approach is to implement an exponential back-off pattern to reduce the frequency of your updates (and / or the degree of prefetching you perform) if the app hasn't been used since the previous update. It's often useful to assert a minimum update frequency and to reset the frequency whenever the app is used, for example:</p>
+
+<pre>SharedPreferences sp = 
+  context.getSharedPreferences(PREFS, Context.MODE_WORLD_READABLE);
+
+boolean appUsed = sp.getBoolean(PREFS_APPUSED, false);
+long updateInterval = sp.getLong(PREFS_INTERVAL, DEFAULT_REFRESH_INTERVAL);
+
+if (!appUsed)
+  if ((updateInterval *= 2) > MAX_REFRESH_INTERVAL)  
+    updateInterval = MAX_REFRESH_INTERVAL;
+
+Editor spEdit = sp.edit();
+spEdit.putBoolean(PREFS_APPUSED, false);
+spEdit.putLong(PREFS_INTERVAL, updateInterval);
+spEdit.apply();
+
+rescheduleUpdates(updateInterval);
+executeUpdateOrPrefetch();</pre>
+
+<p>You can use a similar exponential back-off pattern to reduce the effect of failed connections and download errors.</p>
+
+<p>The cost of initiating a network connection is the same whether you are able to contact your server and download data or not. For time-sensitive transfers where successful completion is important, an exponential back-off algorithm can be used to reduce the frequency of retries in order to minimize the associated battery impact, for example:</p>
+
+<pre>private void retryIn(long interval) {
+  boolean success = attemptTransfer();
+    
+  if (!success) {
+    retryIn(interval*2 < MAX_RETRY_INTERVAL ? 
+            interval*2 : MAX_RETRY_INTERVAL);      
+  }
+}</pre>
+
+<p>Alternatively, for transfers that are failure tolerant (such as regular updates), you can simply ignore failed connection and transfer attempts.</p>
\ No newline at end of file
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 6698d31..383fe71 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -575,6 +575,11 @@
     @Override
     public Drawable mutate() {
         if (!mMutated && super.mutate() == this) {
+            if (!mLayerState.canConstantState()) {
+                throw new IllegalStateException("One or more children of this LayerDrawable does " +
+                        "not have constant state; this drawable cannot be mutated.");
+            }
+            mLayerState = new LayerState(mLayerState, this, null);
             final ChildDrawable[] array = mLayerState.mChildren;
             final int N = mLayerState.mNum;
             for (int i = 0; i < N; i++) {
@@ -694,7 +699,7 @@
             return stateful;
         }
 
-        public synchronized boolean canConstantState() {
+        public boolean canConstantState() {
             if (!mCheckedConstantState && mChildren != null) {
                 mCanConstantState = true;
                 final int N = mNum;
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index 6539ff3..a76a628 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -131,22 +131,13 @@
     public static final int USAGE_GRAPHICS_RENDER_TARGET = 0x0010;
 
     /**
-     * USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE The allocation
-     * will be used as a SurfaceTexture graphics consumer. This
-     * usage may only be used with USAGE_GRAPHICS_TEXTURE.
-     *
-     * @hide
-     */
-    public static final int USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE = 0x0020;
-
-    /**
      * USAGE_IO_INPUT The allocation will be used as SurfaceTexture
      * consumer.  This usage will cause the allocation to be created
      * read only.
      *
      * @hide
      */
-    public static final int USAGE_IO_INPUT = 0x0040;
+    public static final int USAGE_IO_INPUT = 0x0020;
 
     /**
      * USAGE_IO_OUTPUT The allocation will be used as a
@@ -155,7 +146,7 @@
      *
      * @hide
      */
-    public static final int USAGE_IO_OUTPUT = 0x0080;
+    public static final int USAGE_IO_OUTPUT = 0x0040;
 
     /**
      * Controls mipmap behavior when using the bitmap creation and
@@ -197,6 +188,40 @@
         return getID();
     }
 
+
+   /**
+     * Get the element of the type of the Allocation.
+     *
+     * @hide
+     * @return Element
+     *
+     */
+    public Element getElement() {
+        return mType.getElement();
+    }
+
+    /**
+     * Get the usage flags of the Allocation.
+     *
+     * @hide
+     * @return usage
+     *
+     */
+    public int getUsage() {
+        return mUsage;
+    }
+
+    /**
+     * Get the size of the Allocation in bytes.
+     *
+     * @hide
+     * @return sizeInBytes
+     *
+     */
+    public int getSizeBytes() {
+        return mType.getCount() * mType.getElement().getSizeBytes();
+    }
+
     private void updateCacheInfo(Type t) {
         mCurrentDimX = t.getX();
         mCurrentDimY = t.getY();
@@ -217,17 +242,15 @@
                        USAGE_GRAPHICS_VERTEX |
                        USAGE_GRAPHICS_CONSTANTS |
                        USAGE_GRAPHICS_RENDER_TARGET |
-                       USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE |
                        USAGE_IO_INPUT |
                        USAGE_IO_OUTPUT)) != 0) {
             throw new RSIllegalArgumentException("Unknown usage specified.");
         }
 
-        if ((usage & (USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE | USAGE_IO_INPUT)) != 0) {
+        if ((usage & USAGE_IO_INPUT) != 0) {
             mWriteAllowed = false;
 
-            if ((usage & ~(USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE |
-                           USAGE_IO_INPUT |
+            if ((usage & ~(USAGE_IO_INPUT |
                            USAGE_GRAPHICS_TEXTURE |
                            USAGE_SCRIPT)) != 0) {
                 throw new RSIllegalArgumentException("Invalid usage combination.");
@@ -305,10 +328,21 @@
         }
     }
 
+    /**
+     * Get the type of the Allocation.
+     *
+     * @return Type
+     *
+     */
     public Type getType() {
         return mType;
     }
 
+    /**
+     * Propagate changes from one usage of the allocation to the
+     * remaining usages of the allocation.
+     *
+     */
     public void syncAll(int srcLocation) {
         switch (srcLocation) {
         case USAGE_SCRIPT:
@@ -348,12 +382,17 @@
     public void ioGetInput() {
         if ((mUsage & USAGE_IO_INPUT) == 0) {
             throw new RSIllegalArgumentException(
-                "Can only send buffer if IO_OUTPUT usage specified.");
+                "Can only receive if IO_INPUT usage specified.");
         }
         mRS.validate();
         mRS.nAllocationIoReceive(getID());
     }
 
+    /**
+     * Copy an array of RS objects to the allocation.
+     *
+     * @param d Source array.
+     */
     public void copyFrom(BaseObj[] d) {
         mRS.validate();
         validateIsObject();
@@ -1134,13 +1173,15 @@
      *
      */
     public SurfaceTexture getSurfaceTexture() {
-        if ((mUsage & USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE) == 0) {
+        if ((mUsage & USAGE_IO_INPUT) == 0) {
             throw new RSInvalidStateException("Allocation is not a surface texture.");
         }
 
         int id = mRS.nAllocationGetSurfaceTextureID(getID());
-        return new SurfaceTexture(id);
+        SurfaceTexture st = new SurfaceTexture(id);
+        mRS.nAllocationGetSurfaceTextureID2(getID(), st);
 
+        return st;
     }
 
     /**
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index 6921f37..ab6ba54 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -16,8 +16,8 @@
 
 package android.renderscript;
 
-import java.lang.reflect.Field;
 import java.io.File;
+import java.lang.reflect.Field;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -294,6 +294,11 @@
         validate();
         return rsnAllocationGetSurfaceTextureID(mContext, alloc);
     }
+    native void rsnAllocationGetSurfaceTextureID2(int con, int alloc, SurfaceTexture st);
+    synchronized void nAllocationGetSurfaceTextureID2(int alloc, SurfaceTexture st) {
+        validate();
+        rsnAllocationGetSurfaceTextureID2(mContext, alloc, st);
+    }
     native void rsnAllocationSetSurfaceTexture(int con, int alloc, SurfaceTexture sur);
     synchronized void nAllocationSetSurfaceTexture(int alloc, SurfaceTexture sur) {
         validate();
diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp
index 9fc4fd4..9d4c64f 100644
--- a/graphics/jni/android_renderscript_RenderScript.cpp
+++ b/graphics/jni/android_renderscript_RenderScript.cpp
@@ -477,6 +477,15 @@
 }
 
 static void
+nAllocationGetSurfaceTextureID2(JNIEnv *_env, jobject _this, RsContext con, jint a, jobject jst)
+{
+    LOG_API("nAllocationGetSurfaceTextureID2, con(%p), a(%p)", con, (RsAllocation)a);
+    sp<SurfaceTexture> st = SurfaceTexture_getSurfaceTexture(_env, jst);
+
+    rsAllocationGetSurfaceTextureID2(con, (RsAllocation)a, st.get(), sizeof(SurfaceTexture *));
+}
+
+static void
 nAllocationSetSurfaceTexture(JNIEnv *_env, jobject _this, RsContext con,
                              RsAllocation alloc, jobject sur)
 {
@@ -1352,7 +1361,8 @@
 
 {"rsnAllocationSyncAll",             "(III)V",                                (void*)nAllocationSyncAll },
 {"rsnAllocationGetSurfaceTextureID", "(II)I",                                 (void*)nAllocationGetSurfaceTextureID },
-{"rsnAllocationSetSurfaceTexture",   "(IILandroid/graphics/SurfaceTexture;)V", (void*)nAllocationSetSurfaceTexture },
+{"rsnAllocationGetSurfaceTextureID2","(IILandroid/graphics/SurfaceTexture;)V",(void*)nAllocationGetSurfaceTextureID2 },
+{"rsnAllocationSetSurfaceTexture",   "(IILandroid/graphics/SurfaceTexture;)V",(void*)nAllocationSetSurfaceTexture },
 {"rsnAllocationIoSend",              "(II)V",                                 (void*)nAllocationIoSend },
 {"rsnAllocationIoReceive",           "(II)V",                                 (void*)nAllocationIoReceive },
 {"rsnAllocationData1D",              "(IIIII[II)V",                           (void*)nAllocationData1D_i },
diff --git a/include/androidfw/AssetManager.h b/include/androidfw/AssetManager.h
index d95b45e..d153c31 100644
--- a/include/androidfw/AssetManager.h
+++ b/include/androidfw/AssetManager.h
@@ -22,13 +22,13 @@
 
 #include <androidfw/Asset.h>
 #include <androidfw/AssetDir.h>
-#include <androidfw/ZipFileRO.h>
 #include <utils/KeyedVector.h>
 #include <utils/SortedVector.h>
 #include <utils/String16.h>
 #include <utils/String8.h>
 #include <utils/threads.h>
 #include <utils/Vector.h>
+#include <utils/ZipFileRO.h>
 
 /*
  * Native-app access is via the opaque typedef struct AAssetManager in the C namespace.
diff --git a/include/androidfw/ZipFileCRO.h b/include/androidfw/ZipFileCRO.h
deleted file mode 100644
index 3e42a95..0000000
--- a/include/androidfw/ZipFileCRO.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//
-// C API for ead-only access to Zip archives, with minimal heap allocation.
-//
-#ifndef __LIBS_ZIPFILECRO_H
-#define __LIBS_ZIPFILECRO_H
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include <utils/Compat.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/*
- * Trivial typedef to ensure that ZipFileCRO is not treated as a simple integer.
- */
-typedef void* ZipFileCRO;
-
-/*
- * Trivial typedef to ensure that ZipEntryCRO is not treated as a simple
- * integer.  We use NULL to indicate an invalid value.
- */
-typedef void* ZipEntryCRO;
-
-extern ZipFileCRO ZipFileXRO_open(const char* path);
-
-extern void ZipFileCRO_destroy(ZipFileCRO zip);
-
-extern ZipEntryCRO ZipFileCRO_findEntryByName(ZipFileCRO zip,
-        const char* fileName);
-
-extern bool ZipFileCRO_getEntryInfo(ZipFileCRO zip, ZipEntryCRO entry,
-        int* pMethod, size_t* pUncompLen,
-        size_t* pCompLen, off64_t* pOffset, long* pModWhen, long* pCrc32);
-
-extern bool ZipFileCRO_uncompressEntry(ZipFileCRO zip, ZipEntryCRO entry, int fd);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /*__LIBS_ZIPFILECRO_H*/
diff --git a/include/androidfw/ZipFileRO.h b/include/androidfw/ZipFileRO.h
deleted file mode 100644
index 547e36a..0000000
--- a/include/androidfw/ZipFileRO.h
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/*
- * Read-only access to Zip archives, with minimal heap allocation.
- *
- * This is similar to the more-complete ZipFile class, but no attempt
- * has been made to make them interchangeable.  This class operates under
- * a very different set of assumptions and constraints.
- *
- * One such assumption is that if you're getting file descriptors for
- * use with this class as a child of a fork() operation, you must be on
- * a pread() to guarantee correct operation. This is because pread() can
- * atomically read at a file offset without worrying about a lock around an
- * lseek() + read() pair.
- */
-#ifndef __LIBS_ZIPFILERO_H
-#define __LIBS_ZIPFILERO_H
-
-#include <utils/Compat.h>
-#include <utils/Errors.h>
-#include <utils/FileMap.h>
-#include <utils/threads.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <time.h>
-
-namespace android {
-
-/*
- * Trivial typedef to ensure that ZipEntryRO is not treated as a simple
- * integer.  We use NULL to indicate an invalid value.
- */
-typedef void* ZipEntryRO;
-
-/*
- * Open a Zip archive for reading.
- *
- * We want "open" and "find entry by name" to be fast operations, and we
- * want to use as little memory as possible.  We memory-map the file,
- * and load a hash table with pointers to the filenames (which aren't
- * null-terminated).  The other fields are at a fixed offset from the
- * filename, so we don't need to extract those (but we do need to byte-read
- * and endian-swap them every time we want them).
- *
- * To speed comparisons when doing a lookup by name, we could make the mapping
- * "private" (copy-on-write) and null-terminate the filenames after verifying
- * the record structure.  However, this requires a private mapping of
- * every page that the Central Directory touches.  Easier to tuck a copy
- * of the string length into the hash table entry.
- *
- * NOTE: If this is used on file descriptors inherited from a fork() operation,
- * you must be on a platform that implements pread() to guarantee correctness
- * on the shared file descriptors.
- */
-class ZipFileRO {
-public:
-    ZipFileRO()
-        : mFd(-1), mFileName(NULL), mFileLength(-1),
-          mDirectoryMap(NULL),
-          mNumEntries(-1), mDirectoryOffset(-1),
-          mHashTableSize(-1), mHashTable(NULL)
-        {}
-
-    ~ZipFileRO();
-
-    /*
-     * Open an archive.
-     */
-    status_t open(const char* zipFileName);
-
-    /*
-     * Find an entry, by name.  Returns the entry identifier, or NULL if
-     * not found.
-     *
-     * If two entries have the same name, one will be chosen at semi-random.
-     */
-    ZipEntryRO findEntryByName(const char* fileName) const;
-
-    /*
-     * Return the #of entries in the Zip archive.
-     */
-    int getNumEntries(void) const {
-        return mNumEntries;
-    }
-
-    /*
-     * Return the Nth entry.  Zip file entries are not stored in sorted
-     * order, and updated entries may appear at the end, so anyone walking
-     * the archive needs to avoid making ordering assumptions.  We take
-     * that further by returning the Nth non-empty entry in the hash table
-     * rather than the Nth entry in the archive.
-     *
-     * Valid values are [0..numEntries).
-     *
-     * [This is currently O(n).  If it needs to be fast we can allocate an
-     * additional data structure or provide an iterator interface.]
-     */
-    ZipEntryRO findEntryByIndex(int idx) const;
-
-    /*
-     * Copy the filename into the supplied buffer.  Returns 0 on success,
-     * -1 if "entry" is invalid, or the filename length if it didn't fit.  The
-     * length, and the returned string, include the null-termination.
-     */
-    int getEntryFileName(ZipEntryRO entry, char* buffer, int bufLen) const;
-
-    /*
-     * Get the vital stats for an entry.  Pass in NULL pointers for anything
-     * you don't need.
-     *
-     * "*pOffset" holds the Zip file offset of the entry's data.
-     *
-     * Returns "false" if "entry" is bogus or if the data in the Zip file
-     * appears to be bad.
-     */
-    bool getEntryInfo(ZipEntryRO entry, int* pMethod, size_t* pUncompLen,
-        size_t* pCompLen, off64_t* pOffset, long* pModWhen, long* pCrc32) const;
-
-    /*
-     * Create a new FileMap object that maps a subset of the archive.  For
-     * an uncompressed entry this effectively provides a pointer to the
-     * actual data, for a compressed entry this provides the input buffer
-     * for inflate().
-     */
-    FileMap* createEntryFileMap(ZipEntryRO entry) const;
-
-    /*
-     * Uncompress the data into a buffer.  Depending on the compression
-     * format, this is either an "inflate" operation or a memcpy.
-     *
-     * Use "uncompLen" from getEntryInfo() to determine the required
-     * buffer size.
-     *
-     * Returns "true" on success.
-     */
-    bool uncompressEntry(ZipEntryRO entry, void* buffer) const;
-
-    /*
-     * Uncompress the data to an open file descriptor.
-     */
-    bool uncompressEntry(ZipEntryRO entry, int fd) const;
-
-    /* Zip compression methods we support */
-    enum {
-        kCompressStored     = 0,        // no compression
-        kCompressDeflated   = 8,        // standard deflate
-    };
-
-    /*
-     * Utility function: uncompress deflated data, buffer to buffer.
-     */
-    static bool inflateBuffer(void* outBuf, const void* inBuf,
-        size_t uncompLen, size_t compLen);
-
-    /*
-     * Utility function: uncompress deflated data, buffer to fd.
-     */
-    static bool inflateBuffer(int fd, const void* inBuf,
-        size_t uncompLen, size_t compLen);
-
-    /*
-     * Utility function to convert ZIP's time format to a timespec struct.
-     */
-    static inline void zipTimeToTimespec(long when, struct tm* timespec) {
-        const long date = when >> 16;
-        timespec->tm_year = ((date >> 9) & 0x7F) + 80; // Zip is years since 1980
-        timespec->tm_mon = (date >> 5) & 0x0F;
-        timespec->tm_mday = date & 0x1F;
-
-        timespec->tm_hour = (when >> 11) & 0x1F;
-        timespec->tm_min = (when >> 5) & 0x3F;
-        timespec->tm_sec = (when & 0x1F) << 1;
-    }
-
-    /*
-     * Some basic functions for raw data manipulation.  "LE" means
-     * Little Endian.
-     */
-    static inline unsigned short get2LE(const unsigned char* buf) {
-        return buf[0] | (buf[1] << 8);
-    }
-    static inline unsigned long get4LE(const unsigned char* buf) {
-        return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
-    }
-
-private:
-    /* these are private and not defined */ 
-    ZipFileRO(const ZipFileRO& src);
-    ZipFileRO& operator=(const ZipFileRO& src);
-
-    /* locate and parse the central directory */
-    bool mapCentralDirectory(void);
-
-    /* parse the archive, prepping internal structures */
-    bool parseZipArchive(void);
-
-    /* add a new entry to the hash table */
-    void addToHash(const char* str, int strLen, unsigned int hash);
-
-    /* compute string hash code */
-    static unsigned int computeHash(const char* str, int len);
-
-    /* convert a ZipEntryRO back to a hash table index */
-    int entryToIndex(const ZipEntryRO entry) const;
-
-    /*
-     * One entry in the hash table.
-     */
-    typedef struct HashEntry {
-        const char*     name;
-        unsigned short  nameLen;
-        //unsigned int    hash;
-    } HashEntry;
-
-    /* open Zip archive */
-    int         mFd;
-
-    /* Lock for handling the file descriptor (seeks, etc) */
-    mutable Mutex mFdLock;
-
-    /* zip file name */
-    char*       mFileName;
-
-    /* length of file */
-    size_t      mFileLength;
-
-    /* mapped file */
-    FileMap*    mDirectoryMap;
-
-    /* number of entries in the Zip archive */
-    int         mNumEntries;
-
-    /* CD directory offset in the Zip archive */
-    off64_t     mDirectoryOffset;
-
-    /*
-     * We know how many entries are in the Zip archive, so we have a
-     * fixed-size hash table.  We probe for an empty slot.
-     */
-    int         mHashTableSize;
-    HashEntry*  mHashTable;
-};
-
-}; // namespace android
-
-#endif /*__LIBS_ZIPFILERO_H*/
diff --git a/include/androidfw/ZipUtils.h b/include/androidfw/ZipUtils.h
deleted file mode 100644
index 42c42b6..0000000
--- a/include/androidfw/ZipUtils.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//
-// Miscellaneous zip/gzip utility functions.
-//
-#ifndef __LIBS_ZIPUTILS_H
-#define __LIBS_ZIPUTILS_H
-
-#include <stdio.h>
-
-namespace android {
-
-/*
- * Container class for utility functions, primarily for namespace reasons.
- */
-class ZipUtils {
-public:
-    /*
-     * General utility function for uncompressing "deflate" data from a file
-     * to a buffer.
-     */
-    static bool inflateToBuffer(int fd, void* buf, long uncompressedLen,
-        long compressedLen);
-    static bool inflateToBuffer(FILE* fp, void* buf, long uncompressedLen,
-        long compressedLen);
-
-    /*
-     * Someday we might want to make this generic and handle bzip2 ".bz2"
-     * files too.
-     *
-     * We could declare gzip to be a sub-class of zip that has exactly
-     * one always-compressed entry, but we currently want to treat Zip
-     * and gzip as distinct, so there's no value.
-     *
-     * The zlib library has some gzip utilities, but it has no interface
-     * for extracting the uncompressed length of the file (you do *not*
-     * want to gzseek to the end).
-     *
-     * Pass in a seeked file pointer for the gzip file.  If this is a gzip
-     * file, we set our return values appropriately and return "true" with
-     * the file seeked to the start of the compressed data.
-     */
-    static bool examineGzip(FILE* fp, int* pCompressionMethod,
-        long* pUncompressedLen, long* pCompressedLen, unsigned long* pCRC32);
-
-private:
-    ZipUtils() {}
-    ~ZipUtils() {}
-};
-
-}; // namespace android
-
-#endif /*__LIBS_ZIPUTILS_H*/
diff --git a/include/common_time/local_clock.h b/include/common_time/local_clock.h
index 845d1c21..384c3de 100644
--- a/include/common_time/local_clock.h
+++ b/include/common_time/local_clock.h
@@ -28,7 +28,7 @@
 
 class LocalClock {
   public:
-     LocalClock();
+    LocalClock();
 
     bool initCheck();
 
diff --git a/include/cpustats/CentralTendencyStatistics.h b/include/cpustats/CentralTendencyStatistics.h
deleted file mode 100644
index 21b6981..0000000
--- a/include/cpustats/CentralTendencyStatistics.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _CENTRAL_TENDENCY_STATISTICS_H
-#define _CENTRAL_TENDENCY_STATISTICS_H
-
-#include <math.h>
-
-// Not multithread safe
-class CentralTendencyStatistics {
-
-public:
-
-    CentralTendencyStatistics() :
-            mMean(NAN), mMedian(NAN), mMinimum(INFINITY), mMaximum(-INFINITY), mN(0), mM2(0),
-            mVariance(NAN), mVarianceKnownForN(0), mStddev(NAN), mStddevKnownForN(0) { }
-
-    ~CentralTendencyStatistics() { }
-
-    // add x to the set of samples
-    void sample(double x);
-
-    // return the arithmetic mean of all samples so far
-    double mean() const { return mMean; }
-
-    // return the minimum of all samples so far
-    double minimum() const { return mMinimum; }
-
-    // return the maximum of all samples so far
-    double maximum() const { return mMaximum; }
-
-    // return the variance of all samples so far
-    double variance() const;
-
-    // return the standard deviation of all samples so far
-    double stddev() const;
-
-    // return the number of samples added so far
-    unsigned n() const { return mN; }
-
-    // reset the set of samples to be empty
-    void reset();
-
-private:
-    double mMean;
-    double mMedian;
-    double mMinimum;
-    double mMaximum;
-    unsigned mN;    // number of samples so far
-    double mM2;
-
-    // cached variance, and n at time of caching
-    mutable double mVariance;
-    mutable unsigned mVarianceKnownForN;
-
-    // cached standard deviation, and n at time of caching
-    mutable double mStddev;
-    mutable unsigned mStddevKnownForN;
-
-};
-
-#endif // _CENTRAL_TENDENCY_STATISTICS_H
diff --git a/include/cpustats/README.txt b/include/cpustats/README.txt
deleted file mode 100644
index 14439f0..0000000
--- a/include/cpustats/README.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-This is a static library of CPU usage statistics, originally written
-for audio but most are not actually specific to audio.
-
-Requirements to be here:
- * should be related to CPU usage statistics
- * should be portable to host; avoid Android OS dependencies without a conditional
diff --git a/include/cpustats/ThreadCpuUsage.h b/include/cpustats/ThreadCpuUsage.h
deleted file mode 100644
index 9cd93d8..0000000
--- a/include/cpustats/ThreadCpuUsage.h
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _THREAD_CPU_USAGE_H
-#define _THREAD_CPU_USAGE_H
-
-#include <fcntl.h>
-#include <pthread.h>
-
-namespace android {
-
-// Track CPU usage for the current thread.
-// Units are in per-thread CPU ns, as reported by
-// clock_gettime(CLOCK_THREAD_CPUTIME_ID).  Simple usage: for cyclic
-// threads where you want to measure the execution time of the whole
-// cycle, just call sampleAndEnable() at the start of each cycle.
-// For acyclic threads, or for cyclic threads where you want to measure/track
-// only part of each cycle, call enable(), disable(), and/or setEnabled()
-// to demarcate the region(s) of interest, and then call sample() periodically.
-// This class is not thread-safe for concurrent calls from multiple threads;
-// the methods of this class may only be called by the current thread
-// which constructed the object.
-
-class ThreadCpuUsage
-{
-
-public:
-    ThreadCpuUsage() :
-        mIsEnabled(false),
-        mWasEverEnabled(false),
-        mAccumulator(0),
-        // mPreviousTs
-        // mMonotonicTs
-        mMonotonicKnown(false)
-        {
-            (void) pthread_once(&sOnceControl, &init);
-            for (int i = 0; i < sKernelMax; ++i) {
-                mCurrentkHz[i] = (uint32_t) ~0;   // unknown
-            }
-        }
-
-    ~ThreadCpuUsage() { }
-
-    // Return whether currently tracking CPU usage by current thread
-    bool isEnabled() const  { return mIsEnabled; }
-
-    // Enable tracking of CPU usage by current thread;
-    // any CPU used from this point forward will be tracked.
-    // Returns the previous enabled status.
-    bool enable()       { return setEnabled(true); }
-
-    // Disable tracking of CPU usage by current thread;
-    // any CPU used from this point forward will be ignored.
-    // Returns the previous enabled status.
-    bool disable()      { return setEnabled(false); }
-
-    // Set the enabled status and return the previous enabled status.
-    // This method is intended to be used for safe nested enable/disabling.
-    bool setEnabled(bool isEnabled);
-
-    // Add a sample point, and also enable tracking if needed.
-    // If tracking has never been enabled, then this call enables tracking but
-    // does _not_ add a sample -- it is not possible to add a sample the
-    // first time because there is no previous point to subtract from.
-    // Otherwise, if tracking is enabled,
-    // then adds a sample for tracked CPU ns since the previous
-    // sample, or since the first call to sampleAndEnable(), enable(), or
-    // setEnabled(true).  If there was a previous sample but tracking is
-    // now disabled, then adds a sample for the tracked CPU ns accumulated
-    // up until the most recent disable(), resets this accumulator, and then
-    // enables tracking.  Calling this method rather than enable() followed
-    // by sample() avoids a race condition for the first sample.
-    // Returns true if the sample 'ns' is valid, or false if invalid.
-    // Note that 'ns' is an output parameter passed by reference.
-    // The caller does not need to initialize this variable.
-    // The units are CPU nanoseconds consumed by current thread.
-    bool sampleAndEnable(double& ns);
-
-    // Add a sample point, but do not
-    // change the tracking enabled status.  If tracking has either never been
-    // enabled, or has never been enabled since the last sample, then log a warning
-    // and don't add sample.  Otherwise, adds a sample for tracked CPU ns since
-    // the previous sample or since the first call to sampleAndEnable(),
-    // enable(), or setEnabled(true) if no previous sample.
-    // Returns true if the sample is valid, or false if invalid.
-    // Note that 'ns' is an output parameter passed by reference.
-    // The caller does not need to initialize this variable.
-    // The units are CPU nanoseconds consumed by current thread.
-    bool sample(double& ns);
-
-    // Return the elapsed delta wall clock ns since initial enable or reset,
-    // as reported by clock_gettime(CLOCK_MONOTONIC).
-    long long elapsed() const;
-
-    // Reset elapsed wall clock.  Has no effect on tracking or accumulator.
-    void resetElapsed();
-
-    // Return current clock frequency for specified CPU, in kHz.
-    // You can get your CPU number using sched_getcpu(2).  Note that, unless CPU affinity
-    // has been configured appropriately, the CPU number can change.
-    // Also note that, unless the CPU governor has been configured appropriately,
-    // the CPU frequency can change.  And even if the CPU frequency is locked down
-    // to a particular value, that the frequency might still be adjusted
-    // to prevent thermal overload.  Therefore you should poll for your thread's
-    // current CPU number and clock frequency periodically.
-    uint32_t getCpukHz(int cpuNum);
-
-private:
-    bool mIsEnabled;                // whether tracking is currently enabled
-    bool mWasEverEnabled;           // whether tracking was ever enabled
-    long long mAccumulator;         // accumulated thread CPU time since last sample, in ns
-    struct timespec mPreviousTs;    // most recent thread CPU time, valid only if mIsEnabled is true
-    struct timespec mMonotonicTs;   // most recent monotonic time
-    bool mMonotonicKnown;           // whether mMonotonicTs has been set
-
-    static const int MAX_CPU = 8;
-    static int sScalingFds[MAX_CPU];// file descriptor per CPU for reading scaling_cur_freq
-    uint32_t mCurrentkHz[MAX_CPU];  // current CPU frequency in kHz, not static to avoid a race
-    static pthread_once_t sOnceControl;
-    static int sKernelMax;          // like MAX_CPU, but determined at runtime == cpu/kernel_max + 1
-    static void init();
-};
-
-}   // namespace android
-
-#endif //  _THREAD_CPU_USAGE_H
diff --git a/include/media/AudioRecord.h b/include/media/AudioRecord.h
index 7df6668..5bfb65b 100644
--- a/include/media/AudioRecord.h
+++ b/include/media/AudioRecord.h
@@ -354,7 +354,6 @@
                                 audio_format_t format,
                                 uint32_t channelMask,
                                 int frameCount,
-                                uint32_t flags,
                                 audio_io_handle_t input);
             audio_io_handle_t getInput_l();
             status_t restoreRecord_l(audio_track_cblk_t*& cblk);
diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h
index 552e829..7d5d772 100644
--- a/include/media/AudioTrack.h
+++ b/include/media/AudioTrack.h
@@ -169,7 +169,7 @@
                                     callback_t cbf       = 0,
                                     void* user           = 0,
                                     int notificationFrames = 0,
-                                    int sessionId = 0);
+                                    int sessionId        = 0);
 
     /* Creates an audio track and registers it with AudioFlinger. With this constructor,
      * the PCM data to be rendered by AudioTrack is passed in a shared memory buffer
@@ -215,7 +215,7 @@
                             int notificationFrames = 0,
                             const sp<IMemory>& sharedBuffer = 0,
                             bool threadCanCallJava = false,
-                            int sessionId = 0);
+                            int sessionId       = 0);
 
 
     /* Result of constructing the AudioTrack. This must be checked
@@ -468,6 +468,7 @@
 
             // body of AudioTrackThread::threadLoop()
             bool processAudioBuffer(const sp<AudioTrackThread>& thread);
+
             status_t createTrack_l(audio_stream_type_t streamType,
                                  uint32_t sampleRate,
                                  audio_format_t format,
@@ -475,8 +476,7 @@
                                  int frameCount,
                                  audio_policy_output_flags_t flags,
                                  const sp<IMemory>& sharedBuffer,
-                                 audio_io_handle_t output,
-                                 bool enforceFrameCount);
+                                 audio_io_handle_t output);
             void flush_l();
             status_t setLoop_l(uint32_t loopStart, uint32_t loopEnd, int loopCount);
             audio_io_handle_t getOutput_l();
diff --git a/include/media/IAudioFlinger.h b/include/media/IAudioFlinger.h
index 0f39cf3..8239b0e 100644
--- a/include/media/IAudioFlinger.h
+++ b/include/media/IAudioFlinger.h
@@ -43,6 +43,14 @@
 public:
     DECLARE_META_INTERFACE(AudioFlinger);
 
+    // or-able bits shared by createTrack and openRecord, but not all combinations make sense
+    enum {
+        TRACK_DEFAULT = 0,  // client requests a default AudioTrack
+        TRACK_TIMED   = 1,  // client requests a TimedAudioTrack
+        TRACK_FAST    = 2,  // client requests a fast AudioTrack
+    };
+    typedef uint32_t track_flags_t;
+
     /* create an audio track and registers it with AudioFlinger.
      * return null if the track cannot be created.
      */
@@ -53,10 +61,9 @@
                                 audio_format_t format,
                                 uint32_t channelMask,
                                 int frameCount,
-                                uint32_t flags,
+                                track_flags_t flags,
                                 const sp<IMemory>& sharedBuffer,
                                 audio_io_handle_t output,
-                                bool isTimed,
                                 int *sessionId,
                                 status_t *status) = 0;
 
@@ -67,7 +74,7 @@
                                 audio_format_t format,
                                 uint32_t channelMask,
                                 int frameCount,
-                                uint32_t flags,
+                                track_flags_t flags,
                                 int *sessionId,
                                 status_t *status) = 0;
 
diff --git a/include/media/ICrypto.h b/include/media/ICrypto.h
new file mode 100644
index 0000000..916abe0
--- /dev/null
+++ b/include/media/ICrypto.h
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+#include <binder/IInterface.h>
+#include <media/stagefright/foundation/ABase.h>
+
+#ifndef ANDROID_ICRYPTO_H_
+
+#define ANDROID_ICRYPTO_H_
+
+namespace android {
+
+struct ICrypto : public IInterface {
+    DECLARE_META_INTERFACE(Crypto);
+
+    virtual status_t initialize() = 0;
+    virtual status_t terminate() = 0;
+
+    virtual status_t setEntitlementKey(
+            const void *key, size_t keyLength) = 0;
+
+    virtual status_t setEntitlementControlMessage(
+            const void *msg, size_t msgLength) = 0;
+
+    // "dstData" is in media_server's address space (but inaccessible).
+    virtual ssize_t decryptVideo(
+            const void *iv, size_t ivLength,
+            const void *srcData, size_t srcDataSize,
+            void *dstData, size_t dstDataOffset) = 0;
+
+    // "dstData" is in the calling process' address space.
+    virtual ssize_t decryptAudio(
+            const void *iv, size_t ivLength,
+            const void *srcData, size_t srcDataSize,
+            void *dstData, size_t dstDataSize) = 0;
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(ICrypto);
+};
+
+struct BnCrypto : public BnInterface<ICrypto> {
+    virtual status_t onTransact(
+            uint32_t code, const Parcel &data, Parcel *reply,
+            uint32_t flags = 0);
+};
+
+}  // namespace android
+
+#endif // ANDROID_ICRYPTO_H_
+
diff --git a/include/media/IMediaPlayerService.h b/include/media/IMediaPlayerService.h
index 4f46fcd..76c45a0 100644
--- a/include/media/IMediaPlayerService.h
+++ b/include/media/IMediaPlayerService.h
@@ -31,6 +31,7 @@
 
 namespace android {
 
+struct ICrypto;
 class IMediaRecorder;
 class IOMX;
 struct IStreamSource;
@@ -47,6 +48,7 @@
     virtual sp<IMemory>         decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat) = 0;
     virtual sp<IMemory>         decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat) = 0;
     virtual sp<IOMX>            getOMX() = 0;
+    virtual sp<ICrypto>         makeCrypto() = 0;
 
     // codecs and audio devices usage tracking for the battery app
     enum BatteryDataBits {
diff --git a/include/media/IOMX.h b/include/media/IOMX.h
index a295e9a..be1b2fc 100644
--- a/include/media/IOMX.h
+++ b/include/media/IOMX.h
@@ -26,8 +26,6 @@
 #include <OMX_Core.h>
 #include <OMX_Video.h>
 
-#include "jni.h"
-
 namespace android {
 
 class IMemory;
diff --git a/include/media/JetPlayer.h b/include/media/JetPlayer.h
index 38a3e44..0616bf0 100644
--- a/include/media/JetPlayer.h
+++ b/include/media/JetPlayer.h
@@ -18,7 +18,6 @@
 #define JETPLAYER_H_
 
 #include <utils/threads.h>
-#include <nativehelper/jni.h>
 
 #include <libsonivox/jet.h>
 #include <libsonivox/eas_types.h>
@@ -40,7 +39,7 @@
     static const int JET_NUMQUEUEDSEGMENT_UPDATE = 3;
     static const int JET_PAUSE_UPDATE            = 4;
 
-    JetPlayer(jobject javaJetPlayer,
+    JetPlayer(void *javaJetPlayer,
             int maxTracks = 32,
             int trackBufferSize = 1200);
     ~JetPlayer();
@@ -75,7 +74,7 @@
 
     jetevent_callback   mEventCallback;
 
-    jobject             mJavaJetPlayerRef;
+    void*               mJavaJetPlayerRef;
     Mutex               mMutex; // mutex to sync the render and playback thread with the JET calls
     pid_t               mTid;
     Condition           mCondition;
diff --git a/include/media/stagefright/ACodec.h b/include/media/stagefright/ACodec.h
index fa1a416..7d7af63 100644
--- a/include/media/stagefright/ACodec.h
+++ b/include/media/stagefright/ACodec.h
@@ -89,6 +89,10 @@
         kPortIndexOutput = 1
     };
 
+    enum {
+        kFlagIsSecure   = 1,
+    };
+
     struct BufferInfo {
         enum Status {
             OWNED_BY_US,
@@ -118,6 +122,7 @@
     sp<FlushingState> mFlushingState;
 
     AString mComponentName;
+    uint32_t mFlags;
     uint32_t mQuirks;
     sp<IOMX> mOMX;
     IOMX::node_id mNode;
@@ -176,7 +181,8 @@
 
     status_t setupAACCodec(
             bool encoder,
-            int32_t numChannels, int32_t sampleRate, int32_t bitRate);
+            int32_t numChannels, int32_t sampleRate, int32_t bitRate,
+            bool isADTS);
 
     status_t selectAudioPortFormat(
             OMX_U32 portIndex, OMX_AUDIO_CODINGTYPE desiredFormat);
diff --git a/include/media/stagefright/MediaCodec.h b/include/media/stagefright/MediaCodec.h
index 72ac56a..0fc88e1 100644
--- a/include/media/stagefright/MediaCodec.h
+++ b/include/media/stagefright/MediaCodec.h
@@ -27,18 +27,21 @@
 struct ABuffer;
 struct ACodec;
 struct AMessage;
+struct ICrypto;
 struct SoftwareRenderer;
 struct SurfaceTextureClient;
 
 struct MediaCodec : public AHandler {
     enum ConfigureFlags {
         CONFIGURE_FLAG_ENCODE   = 1,
+        CONFIGURE_FLAG_SECURE   = 2,
     };
 
     enum BufferFlags {
         BUFFER_FLAG_SYNCFRAME   = 1,
         BUFFER_FLAG_CODECCONFIG = 2,
         BUFFER_FLAG_EOS         = 4,
+        BUFFER_FLAG_ENCRYPTED   = 8,
     };
 
     static sp<MediaCodec> CreateByType(
@@ -137,11 +140,13 @@
         kFlagStickyError                = 8,
         kFlagDequeueInputPending        = 16,
         kFlagDequeueOutputPending       = 32,
+        kFlagIsSecure                   = 64,
     };
 
     struct BufferInfo {
         void *mBufferID;
         sp<ABuffer> mData;
+        sp<ABuffer> mEncryptedData;
         sp<AMessage> mNotify;
         bool mOwnedByClient;
     };
@@ -165,6 +170,8 @@
     int32_t mDequeueOutputTimeoutGeneration;
     uint32_t mDequeueOutputReplyID;
 
+    sp<ICrypto> mCrypto;
+
     MediaCodec(const sp<ALooper> &looper);
 
     static status_t PostAndAwaitResponse(
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index 8b4b8ed..639446e 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -24,6 +24,7 @@
 
 #include <utils/RefBase.h>
 #include <utils/KeyedVector.h>
+#include <utils/String8.h>
 
 namespace android {
 
@@ -70,6 +71,8 @@
     kKeyThumbnailTime     = 'thbT',  // int64_t (usecs)
     kKeyTrackID           = 'trID',
     kKeyIsDRM             = 'idrm',  // int32_t (bool)
+    kKeyEncoderDelay      = 'encd',  // int32_t (frames)
+    kKeyEncoderPadding    = 'encp',  // int32_t (frames)
 
     kKeyAlbum             = 'albu',  // cstring
     kKeyArtist            = 'arti',  // cstring
@@ -125,6 +128,12 @@
     kKeyTextFormatData    = 'text',  // raw data
 
     kKeyRequiresSecureBuffers = 'secu',  // bool (int32_t)
+
+    kKeyScrambling        = 'scrm',  // int32_t
+    kKeyEMM               = 'emm ',  // raw data
+    kKeyECM               = 'ecm ',  // raw data
+
+    kKeyIsADTS            = 'adts',  // bool (int32_t)
 };
 
 enum {
@@ -178,6 +187,8 @@
     bool findData(uint32_t key, uint32_t *type,
                   const void **data, size_t *size) const;
 
+    void dumpToLog() const;
+
 protected:
     virtual ~MetaData();
 
@@ -192,6 +203,7 @@
         void clear();
         void setData(uint32_t type, const void *data, size_t size);
         void getData(uint32_t *type, const void **data, size_t *size) const;
+        String8 asString() const;
 
     private:
         uint32_t mType;
diff --git a/include/media/stagefright/NuMediaExtractor.h b/include/media/stagefright/NuMediaExtractor.h
index 96efdff..07c7be5 100644
--- a/include/media/stagefright/NuMediaExtractor.h
+++ b/include/media/stagefright/NuMediaExtractor.h
@@ -31,6 +31,11 @@
 struct MediaSource;
 
 struct NuMediaExtractor : public RefBase {
+    enum SampleFlags {
+        SAMPLE_FLAG_SYNC        = 1,
+        SAMPLE_FLAG_ENCRYPTED   = 2,
+    };
+
     NuMediaExtractor();
 
     status_t setDataSource(const char *path);
@@ -46,6 +51,7 @@
     status_t readSampleData(const sp<ABuffer> &buffer);
     status_t getSampleTrackIndex(size_t *trackIndex);
     status_t getSampleTime(int64_t *sampleTimeUs);
+    status_t getSampleFlags(uint32_t *sampleFlags);
 
 protected:
     virtual ~NuMediaExtractor();
@@ -61,7 +67,9 @@
         status_t mFinalResult;
         MediaBuffer *mSample;
         int64_t mSampleTimeUs;
-        uint32_t mFlags;  // bitmask of "TrackFlags"
+        uint32_t mSampleFlags;
+
+        uint32_t mTrackFlags;  // bitmask of "TrackFlags"
     };
 
     sp<MediaExtractor> mImpl;
diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h
index 392ea87..7d51dee 100644
--- a/include/media/stagefright/OMXCodec.h
+++ b/include/media/stagefright/OMXCodec.h
@@ -30,6 +30,7 @@
 class MemoryDealer;
 struct OMXCodecObserver;
 struct CodecProfileLevel;
+class SkipCutBuffer;
 
 struct OMXCodec : public MediaSource,
                   public MediaBufferObserver {
@@ -201,6 +202,7 @@
     ReadOptions::SeekMode mSeekMode;
     int64_t mTargetTimeUs;
     bool mOutputPortSettingsChangedPending;
+    SkipCutBuffer *mSkipCutBuffer;
 
     MediaBuffer *mLeftOverBuffer;
 
@@ -236,7 +238,11 @@
     void setComponentRole();
 
     void setAMRFormat(bool isWAMR, int32_t bitRate);
-    status_t setAACFormat(int32_t numChannels, int32_t sampleRate, int32_t bitRate);
+
+    status_t setAACFormat(
+            int32_t numChannels, int32_t sampleRate, int32_t bitRate,
+            bool isADTS);
+
     void setG711Format(int32_t numChannels);
 
     status_t setVideoPortFormatType(
@@ -378,6 +384,7 @@
         const char *mimeType, bool queryDecoders,
         Vector<CodecCapabilities> *results);
 
+
 }  // namespace android
 
 #endif  // OMX_CODEC_H_
diff --git a/include/media/stagefright/SkipCutBuffer.h b/include/media/stagefright/SkipCutBuffer.h
new file mode 100644
index 0000000..5c7cd47
--- /dev/null
+++ b/include/media/stagefright/SkipCutBuffer.h
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+#ifndef SKIP_CUT_BUFFER_H_
+
+#define SKIP_CUT_BUFFER_H_
+
+#include <media/stagefright/MediaBuffer.h>
+
+namespace android {
+
+/**
+ * utility class to cut the start and end off a stream of data in MediaBuffers
+ *
+ */
+class SkipCutBuffer {
+ public:
+    // 'skip' is the number of bytes to skip from the beginning
+    // 'cut' is the number of bytes to cut from the end
+    // 'output_size' is the size in bytes of the MediaBuffers that will be used
+    SkipCutBuffer(int32_t skip, int32_t cut, int32_t output_size);
+    virtual ~SkipCutBuffer();
+
+    // Submit one MediaBuffer for skipping and cutting. This may consume all or
+    // some of the data in the buffer, or it may add data to it.
+    // After this, the caller should continue processing the buffer as usual.
+    void submit(MediaBuffer *buffer);
+    void clear();
+    size_t size(); // how many bytes are currently stored in the buffer
+
+ private:
+    void write(const char *src, size_t num);
+    size_t read(char *dst, size_t num);
+    int32_t mFrontPadding;
+    int32_t mBackPadding;
+    int32_t mWriteHead;
+    int32_t mReadHead;
+    int32_t mCapacity;
+    char* mCutBuffer;
+    DISALLOW_EVIL_CONSTRUCTORS(SkipCutBuffer);
+};
+
+}  // namespace android
+
+#endif  // OMX_CODEC_H_
diff --git a/include/media/stagefright/SurfaceMediaSource.h b/include/media/stagefright/SurfaceMediaSource.h
index 54baab6..936b057 100644
--- a/include/media/stagefright/SurfaceMediaSource.h
+++ b/include/media/stagefright/SurfaceMediaSource.h
@@ -111,7 +111,7 @@
     // Make sure this is called when the mutex is locked
     virtual status_t onFrameReceivedLocked();
 
-    virtual status_t setScalingMode(int mode) { } // no op for encoding
+    virtual status_t setScalingMode(int mode) { return OK; } // no op for encoding
     virtual int query(int what, int* value);
 
     // Just confirming to the ISurfaceTexture interface as of now
diff --git a/include/media/stagefright/foundation/AString.h b/include/media/stagefright/foundation/AString.h
index 55ade64..0f8f1e1 100644
--- a/include/media/stagefright/foundation/AString.h
+++ b/include/media/stagefright/foundation/AString.h
@@ -73,6 +73,7 @@
     int compare(const AString &other) const;
 
     bool startsWith(const char *prefix) const;
+    bool endsWith(const char *suffix) const;
 
     void tolower();
 
diff --git a/include/private/hwui/DrawGlInfo.h b/include/private/hwui/DrawGlInfo.h
index 1e9912b..abcf41d 100644
--- a/include/private/hwui/DrawGlInfo.h
+++ b/include/private/hwui/DrawGlInfo.h
@@ -42,6 +42,23 @@
     float dirtyTop;
     float dirtyRight;
     float dirtyBottom;
+
+    /**
+     * Values used by OpenGL functors to tell the framework
+     * what to do next.
+     */
+    enum Status {
+        // The functor is done
+        kStatusDone,
+        // The functor is requesting a redraw (the clip rect
+        // used by the redraw is specified by DrawGlInfo.)
+        // The rest of the UI might redraw too.
+        kStatusDraw,
+        // The functor needs to be invoked again but will
+        // not redraw. Only the functor is invoked again
+        // (unless another functor requests a redraw.)
+        kStatusInvoke
+    };
 }; // struct DrawGlInfo
 
 }; // namespace uirenderer
diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk
index 4e89d87..a3f92cb 100644
--- a/libs/androidfw/Android.mk
+++ b/libs/androidfw/Android.mk
@@ -24,10 +24,7 @@
     AssetManager.cpp \
     ObbFile.cpp \
     ResourceTypes.cpp \
-    StreamingZipInflater.cpp \
-    ZipFileCRO.cpp \
-    ZipFileRO.cpp \
-    ZipUtils.cpp
+    StreamingZipInflater.cpp
 
 # formerly in libui
 commonUiSources:= \
diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp
index cb7628d..fd5b3e4 100644
--- a/libs/androidfw/Asset.cpp
+++ b/libs/androidfw/Asset.cpp
@@ -23,11 +23,11 @@
 
 #include <androidfw/Asset.h>
 #include <androidfw/StreamingZipInflater.h>
-#include <androidfw/ZipFileRO.h>
-#include <androidfw/ZipUtils.h>
 #include <utils/Atomic.h>
 #include <utils/FileMap.h>
 #include <utils/Log.h>
+#include <utils/ZipFileRO.h>
+#include <utils/ZipUtils.h>
 #include <utils/threads.h>
 
 #include <assert.h>
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 4829add..8d59d8e 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -25,13 +25,13 @@
 #include <androidfw/AssetDir.h>
 #include <androidfw/AssetManager.h>
 #include <androidfw/ResourceTypes.h>
-#include <androidfw/ZipFileRO.h>
 #include <utils/Atomic.h>
 #include <utils/Log.h>
 #include <utils/String8.h>
 #include <utils/String8.h>
 #include <utils/threads.h>
 #include <utils/Timers.h>
+#include <utils/ZipFileRO.h>
 
 #include <assert.h>
 #include <dirent.h>
diff --git a/libs/androidfw/ZipFileCRO.cpp b/libs/androidfw/ZipFileCRO.cpp
deleted file mode 100644
index c8df845..0000000
--- a/libs/androidfw/ZipFileCRO.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <androidfw/ZipFileCRO.h>
-#include <androidfw/ZipFileRO.h>
-
-using namespace android;
-
-ZipFileCRO ZipFileXRO_open(const char* path) {
-    ZipFileRO* zip = new ZipFileRO();
-    if (zip->open(path) == NO_ERROR) {
-        return (ZipFileCRO)zip;
-    }
-    return NULL;
-}
-
-void ZipFileCRO_destroy(ZipFileCRO zipToken) {
-    ZipFileRO* zip = (ZipFileRO*)zipToken;
-    delete zip;
-}
-
-ZipEntryCRO ZipFileCRO_findEntryByName(ZipFileCRO zipToken,
-        const char* fileName) {
-    ZipFileRO* zip = (ZipFileRO*)zipToken;
-    return (ZipEntryCRO)zip->findEntryByName(fileName);
-}
-
-bool ZipFileCRO_getEntryInfo(ZipFileCRO zipToken, ZipEntryRO entryToken,
-        int* pMethod, size_t* pUncompLen,
-        size_t* pCompLen, off64_t* pOffset, long* pModWhen, long* pCrc32) {
-    ZipFileRO* zip = (ZipFileRO*)zipToken;
-    ZipEntryRO entry = (ZipEntryRO)entryToken;
-    return zip->getEntryInfo(entry, pMethod, pUncompLen, pCompLen, pOffset,
-            pModWhen, pCrc32);
-}
-
-bool ZipFileCRO_uncompressEntry(ZipFileCRO zipToken, ZipEntryRO entryToken, int fd) {
-    ZipFileRO* zip = (ZipFileRO*)zipToken;
-    ZipEntryRO entry = (ZipEntryRO)entryToken;
-    return zip->uncompressEntry(entry, fd);
-}
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
deleted file mode 100644
index 4b7f1e7..0000000
--- a/libs/androidfw/ZipFileRO.cpp
+++ /dev/null
@@ -1,931 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//
-// Read-only access to Zip archives, with minimal heap allocation.
-//
-#define LOG_TAG "zipro"
-//#define LOG_NDEBUG 0
-#include <androidfw/ZipFileRO.h>
-#include <utils/Log.h>
-#include <utils/misc.h>
-#include <utils/threads.h>
-
-#include <zlib.h>
-
-#include <string.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <assert.h>
-#include <unistd.h>
-
-#if HAVE_PRINTF_ZD
-#  define ZD "%zd"
-#  define ZD_TYPE ssize_t
-#else
-#  define ZD "%ld"
-#  define ZD_TYPE long
-#endif
-
-/*
- * We must open binary files using open(path, ... | O_BINARY) under Windows.
- * Otherwise strange read errors will happen.
- */
-#ifndef O_BINARY
-#  define O_BINARY  0
-#endif
-
-/*
- * TEMP_FAILURE_RETRY is defined by some, but not all, versions of
- * <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's
- * not already defined, then define it here.
- */
-#ifndef TEMP_FAILURE_RETRY
-/* Used to retry syscalls that can return EINTR. */
-#define TEMP_FAILURE_RETRY(exp) ({         \
-    typeof (exp) _rc;                      \
-    do {                                   \
-        _rc = (exp);                       \
-    } while (_rc == -1 && errno == EINTR); \
-    _rc; })
-#endif
-
-using namespace android;
-
-/*
- * Zip file constants.
- */
-#define kEOCDSignature      0x06054b50
-#define kEOCDLen            22
-#define kEOCDNumEntries     8               // offset to #of entries in file
-#define kEOCDSize           12              // size of the central directory
-#define kEOCDFileOffset     16              // offset to central directory
-
-#define kMaxCommentLen      65535           // longest possible in ushort
-#define kMaxEOCDSearch      (kMaxCommentLen + kEOCDLen)
-
-#define kLFHSignature       0x04034b50
-#define kLFHLen             30              // excluding variable-len fields
-#define kLFHNameLen         26              // offset to filename length
-#define kLFHExtraLen        28              // offset to extra length
-
-#define kCDESignature       0x02014b50
-#define kCDELen             46              // excluding variable-len fields
-#define kCDEMethod          10              // offset to compression method
-#define kCDEModWhen         12              // offset to modification timestamp
-#define kCDECRC             16              // offset to entry CRC
-#define kCDECompLen         20              // offset to compressed length
-#define kCDEUncompLen       24              // offset to uncompressed length
-#define kCDENameLen         28              // offset to filename length
-#define kCDEExtraLen        30              // offset to extra length
-#define kCDECommentLen      32              // offset to comment length
-#define kCDELocalOffset     42              // offset to local hdr
-
-/*
- * The values we return for ZipEntryRO use 0 as an invalid value, so we
- * want to adjust the hash table index by a fixed amount.  Using a large
- * value helps insure that people don't mix & match arguments, e.g. to
- * findEntryByIndex().
- */
-#define kZipEntryAdj        10000
-
-ZipFileRO::~ZipFileRO() {
-    free(mHashTable);
-    if (mDirectoryMap)
-        mDirectoryMap->release();
-    if (mFd >= 0)
-        TEMP_FAILURE_RETRY(close(mFd));
-    if (mFileName)
-        free(mFileName);
-}
-
-/*
- * Convert a ZipEntryRO to a hash table index, verifying that it's in a
- * valid range.
- */
-int ZipFileRO::entryToIndex(const ZipEntryRO entry) const
-{
-    long ent = ((long) entry) - kZipEntryAdj;
-    if (ent < 0 || ent >= mHashTableSize || mHashTable[ent].name == NULL) {
-        ALOGW("Invalid ZipEntryRO %p (%ld)\n", entry, ent);
-        return -1;
-    }
-    return ent;
-}
-
-
-/*
- * Open the specified file read-only.  We memory-map the entire thing and
- * close the file before returning.
- */
-status_t ZipFileRO::open(const char* zipFileName)
-{
-    int fd = -1;
-
-    assert(mDirectoryMap == NULL);
-
-    /*
-     * Open and map the specified file.
-     */
-    fd = ::open(zipFileName, O_RDONLY | O_BINARY);
-    if (fd < 0) {
-        ALOGW("Unable to open zip '%s': %s\n", zipFileName, strerror(errno));
-        return NAME_NOT_FOUND;
-    }
-
-    mFileLength = lseek64(fd, 0, SEEK_END);
-    if (mFileLength < kEOCDLen) {
-        TEMP_FAILURE_RETRY(close(fd));
-        return UNKNOWN_ERROR;
-    }
-
-    if (mFileName != NULL) {
-        free(mFileName);
-    }
-    mFileName = strdup(zipFileName);
-
-    mFd = fd;
-
-    /*
-     * Find the Central Directory and store its size and number of entries.
-     */
-    if (!mapCentralDirectory()) {
-        goto bail;
-    }
-
-    /*
-     * Verify Central Directory and create data structures for fast access.
-     */
-    if (!parseZipArchive()) {
-        goto bail;
-    }
-
-    return OK;
-
-bail:
-    free(mFileName);
-    mFileName = NULL;
-    TEMP_FAILURE_RETRY(close(fd));
-    return UNKNOWN_ERROR;
-}
-
-/*
- * Parse the Zip archive, verifying its contents and initializing internal
- * data structures.
- */
-bool ZipFileRO::mapCentralDirectory(void)
-{
-    ssize_t readAmount = kMaxEOCDSearch;
-    if (readAmount > (ssize_t) mFileLength)
-        readAmount = mFileLength;
-
-    unsigned char* scanBuf = (unsigned char*) malloc(readAmount);
-    if (scanBuf == NULL) {
-        ALOGW("couldn't allocate scanBuf: %s", strerror(errno));
-        free(scanBuf);
-        return false;
-    }
-
-    /*
-     * Make sure this is a Zip archive.
-     */
-    if (lseek64(mFd, 0, SEEK_SET) != 0) {
-        ALOGW("seek to start failed: %s", strerror(errno));
-        free(scanBuf);
-        return false;
-    }
-
-    ssize_t actual = TEMP_FAILURE_RETRY(read(mFd, scanBuf, sizeof(int32_t)));
-    if (actual != (ssize_t) sizeof(int32_t)) {
-        ALOGI("couldn't read first signature from zip archive: %s", strerror(errno));
-        free(scanBuf);
-        return false;
-    }
-
-    {
-        unsigned int header = get4LE(scanBuf);
-        if (header == kEOCDSignature) {
-            ALOGI("Found Zip archive, but it looks empty\n");
-            free(scanBuf);
-            return false;
-        } else if (header != kLFHSignature) {
-            ALOGV("Not a Zip archive (found 0x%08x)\n", header);
-            free(scanBuf);
-            return false;
-        }
-    }
-
-    /*
-     * Perform the traditional EOCD snipe hunt.
-     *
-     * We're searching for the End of Central Directory magic number,
-     * which appears at the start of the EOCD block.  It's followed by
-     * 18 bytes of EOCD stuff and up to 64KB of archive comment.  We
-     * need to read the last part of the file into a buffer, dig through
-     * it to find the magic number, parse some values out, and use those
-     * to determine the extent of the CD.
-     *
-     * We start by pulling in the last part of the file.
-     */
-    off64_t searchStart = mFileLength - readAmount;
-
-    if (lseek64(mFd, searchStart, SEEK_SET) != searchStart) {
-        ALOGW("seek %ld failed: %s\n",  (long) searchStart, strerror(errno));
-        free(scanBuf);
-        return false;
-    }
-    actual = TEMP_FAILURE_RETRY(read(mFd, scanBuf, readAmount));
-    if (actual != (ssize_t) readAmount) {
-        ALOGW("Zip: read " ZD ", expected " ZD ". Failed: %s\n",
-            (ZD_TYPE) actual, (ZD_TYPE) readAmount, strerror(errno));
-        free(scanBuf);
-        return false;
-    }
-
-    /*
-     * Scan backward for the EOCD magic.  In an archive without a trailing
-     * comment, we'll find it on the first try.  (We may want to consider
-     * doing an initial minimal read; if we don't find it, retry with a
-     * second read as above.)
-     */
-    int i;
-    for (i = readAmount - kEOCDLen; i >= 0; i--) {
-        if (scanBuf[i] == 0x50 && get4LE(&scanBuf[i]) == kEOCDSignature) {
-            ALOGV("+++ Found EOCD at buf+%d\n", i);
-            break;
-        }
-    }
-    if (i < 0) {
-        ALOGD("Zip: EOCD not found, %s is not zip\n", mFileName);
-        free(scanBuf);
-        return false;
-    }
-
-    off64_t eocdOffset = searchStart + i;
-    const unsigned char* eocdPtr = scanBuf + i;
-
-    assert(eocdOffset < mFileLength);
-
-    /*
-     * Grab the CD offset and size, and the number of entries in the
-     * archive. After that, we can release our EOCD hunt buffer.
-     */
-    unsigned int numEntries = get2LE(eocdPtr + kEOCDNumEntries);
-    unsigned int dirSize = get4LE(eocdPtr + kEOCDSize);
-    unsigned int dirOffset = get4LE(eocdPtr + kEOCDFileOffset);
-    free(scanBuf);
-
-    // Verify that they look reasonable.
-    if ((long long) dirOffset + (long long) dirSize > (long long) eocdOffset) {
-        ALOGW("bad offsets (dir %ld, size %u, eocd %ld)\n",
-            (long) dirOffset, dirSize, (long) eocdOffset);
-        return false;
-    }
-    if (numEntries == 0) {
-        ALOGW("empty archive?\n");
-        return false;
-    }
-
-    ALOGV("+++ numEntries=%d dirSize=%d dirOffset=%d\n",
-        numEntries, dirSize, dirOffset);
-
-    mDirectoryMap = new FileMap();
-    if (mDirectoryMap == NULL) {
-        ALOGW("Unable to create directory map: %s", strerror(errno));
-        return false;
-    }
-
-    if (!mDirectoryMap->create(mFileName, mFd, dirOffset, dirSize, true)) {
-        ALOGW("Unable to map '%s' (" ZD " to " ZD "): %s\n", mFileName,
-                (ZD_TYPE) dirOffset, (ZD_TYPE) (dirOffset + dirSize), strerror(errno));
-        return false;
-    }
-
-    mNumEntries = numEntries;
-    mDirectoryOffset = dirOffset;
-
-    return true;
-}
-
-bool ZipFileRO::parseZipArchive(void)
-{
-    bool result = false;
-    const unsigned char* cdPtr = (const unsigned char*) mDirectoryMap->getDataPtr();
-    size_t cdLength = mDirectoryMap->getDataLength();
-    int numEntries = mNumEntries;
-
-    /*
-     * Create hash table.  We have a minimum 75% load factor, possibly as
-     * low as 50% after we round off to a power of 2.
-     */
-    mHashTableSize = roundUpPower2(1 + (numEntries * 4) / 3);
-    mHashTable = (HashEntry*) calloc(mHashTableSize, sizeof(HashEntry));
-
-    /*
-     * Walk through the central directory, adding entries to the hash
-     * table.
-     */
-    const unsigned char* ptr = cdPtr;
-    for (int i = 0; i < numEntries; i++) {
-        if (get4LE(ptr) != kCDESignature) {
-            ALOGW("Missed a central dir sig (at %d)\n", i);
-            goto bail;
-        }
-        if (ptr + kCDELen > cdPtr + cdLength) {
-            ALOGW("Ran off the end (at %d)\n", i);
-            goto bail;
-        }
-
-        long localHdrOffset = (long) get4LE(ptr + kCDELocalOffset);
-        if (localHdrOffset >= mDirectoryOffset) {
-            ALOGW("bad LFH offset %ld at entry %d\n", localHdrOffset, i);
-            goto bail;
-        }
-
-        unsigned int fileNameLen, extraLen, commentLen, hash;
-
-        fileNameLen = get2LE(ptr + kCDENameLen);
-        extraLen = get2LE(ptr + kCDEExtraLen);
-        commentLen = get2LE(ptr + kCDECommentLen);
-
-        /* add the CDE filename to the hash table */
-        hash = computeHash((const char*)ptr + kCDELen, fileNameLen);
-        addToHash((const char*)ptr + kCDELen, fileNameLen, hash);
-
-        ptr += kCDELen + fileNameLen + extraLen + commentLen;
-        if ((size_t)(ptr - cdPtr) > cdLength) {
-            ALOGW("bad CD advance (%d vs " ZD ") at entry %d\n",
-                (int) (ptr - cdPtr), (ZD_TYPE) cdLength, i);
-            goto bail;
-        }
-    }
-    ALOGV("+++ zip good scan %d entries\n", numEntries);
-    result = true;
-
-bail:
-    return result;
-}
-
-/*
- * Simple string hash function for non-null-terminated strings.
- */
-/*static*/ unsigned int ZipFileRO::computeHash(const char* str, int len)
-{
-    unsigned int hash = 0;
-
-    while (len--)
-        hash = hash * 31 + *str++;
-
-    return hash;
-}
-
-/*
- * Add a new entry to the hash table.
- */
-void ZipFileRO::addToHash(const char* str, int strLen, unsigned int hash)
-{
-    int ent = hash & (mHashTableSize-1);
-
-    /*
-     * We over-allocate the table, so we're guaranteed to find an empty slot.
-     */
-    while (mHashTable[ent].name != NULL)
-        ent = (ent + 1) & (mHashTableSize-1);
-
-    mHashTable[ent].name = str;
-    mHashTable[ent].nameLen = strLen;
-}
-
-/*
- * Find a matching entry.
- *
- * Returns NULL if not found.
- */
-ZipEntryRO ZipFileRO::findEntryByName(const char* fileName) const
-{
-    /*
-     * If the ZipFileRO instance is not initialized, the entry number will
-     * end up being garbage since mHashTableSize is -1.
-     */
-    if (mHashTableSize <= 0) {
-        return NULL;
-    }
-
-    int nameLen = strlen(fileName);
-    unsigned int hash = computeHash(fileName, nameLen);
-    int ent = hash & (mHashTableSize-1);
-
-    while (mHashTable[ent].name != NULL) {
-        if (mHashTable[ent].nameLen == nameLen &&
-            memcmp(mHashTable[ent].name, fileName, nameLen) == 0)
-        {
-            /* match */
-            return (ZipEntryRO)(long)(ent + kZipEntryAdj);
-        }
-
-        ent = (ent + 1) & (mHashTableSize-1);
-    }
-
-    return NULL;
-}
-
-/*
- * Find the Nth entry.
- *
- * This currently involves walking through the sparse hash table, counting
- * non-empty entries.  If we need to speed this up we can either allocate
- * a parallel lookup table or (perhaps better) provide an iterator interface.
- */
-ZipEntryRO ZipFileRO::findEntryByIndex(int idx) const
-{
-    if (idx < 0 || idx >= mNumEntries) {
-        ALOGW("Invalid index %d\n", idx);
-        return NULL;
-    }
-
-    for (int ent = 0; ent < mHashTableSize; ent++) {
-        if (mHashTable[ent].name != NULL) {
-            if (idx-- == 0)
-                return (ZipEntryRO) (ent + kZipEntryAdj);
-        }
-    }
-
-    return NULL;
-}
-
-/*
- * Get the useful fields from the zip entry.
- *
- * Returns "false" if the offsets to the fields or the contents of the fields
- * appear to be bogus.
- */
-bool ZipFileRO::getEntryInfo(ZipEntryRO entry, int* pMethod, size_t* pUncompLen,
-    size_t* pCompLen, off64_t* pOffset, long* pModWhen, long* pCrc32) const
-{
-    bool ret = false;
-
-    const int ent = entryToIndex(entry);
-    if (ent < 0)
-        return false;
-
-    HashEntry hashEntry = mHashTable[ent];
-
-    /*
-     * Recover the start of the central directory entry from the filename
-     * pointer.  The filename is the first entry past the fixed-size data,
-     * so we can just subtract back from that.
-     */
-    const unsigned char* ptr = (const unsigned char*) hashEntry.name;
-    off64_t cdOffset = mDirectoryOffset;
-
-    ptr -= kCDELen;
-
-    int method = get2LE(ptr + kCDEMethod);
-    if (pMethod != NULL)
-        *pMethod = method;
-
-    if (pModWhen != NULL)
-        *pModWhen = get4LE(ptr + kCDEModWhen);
-    if (pCrc32 != NULL)
-        *pCrc32 = get4LE(ptr + kCDECRC);
-
-    size_t compLen = get4LE(ptr + kCDECompLen);
-    if (pCompLen != NULL)
-        *pCompLen = compLen;
-    size_t uncompLen = get4LE(ptr + kCDEUncompLen);
-    if (pUncompLen != NULL)
-        *pUncompLen = uncompLen;
-
-    /*
-     * If requested, determine the offset of the start of the data.  All we
-     * have is the offset to the Local File Header, which is variable size,
-     * so we have to read the contents of the struct to figure out where
-     * the actual data starts.
-     *
-     * We also need to make sure that the lengths are not so large that
-     * somebody trying to map the compressed or uncompressed data runs
-     * off the end of the mapped region.
-     *
-     * Note we don't verify compLen/uncompLen if they don't request the
-     * dataOffset, because dataOffset is expensive to determine.  However,
-     * if they don't have the file offset, they're not likely to be doing
-     * anything with the contents.
-     */
-    if (pOffset != NULL) {
-        long localHdrOffset = get4LE(ptr + kCDELocalOffset);
-        if (localHdrOffset + kLFHLen >= cdOffset) {
-            ALOGE("ERROR: bad local hdr offset in zip\n");
-            return false;
-        }
-
-        unsigned char lfhBuf[kLFHLen];
-
-#ifdef HAVE_PREAD
-        /*
-         * This file descriptor might be from zygote's preloaded assets,
-         * so we need to do an pread64() instead of a lseek64() + read() to
-         * guarantee atomicity across the processes with the shared file
-         * descriptors.
-         */
-        ssize_t actual =
-                TEMP_FAILURE_RETRY(pread64(mFd, lfhBuf, sizeof(lfhBuf), localHdrOffset));
-
-        if (actual != sizeof(lfhBuf)) {
-            ALOGW("failed reading lfh from offset %ld\n", localHdrOffset);
-            return false;
-        }
-
-        if (get4LE(lfhBuf) != kLFHSignature) {
-            ALOGW("didn't find signature at start of lfh; wanted: offset=%ld data=0x%08x; "
-                    "got: data=0x%08lx\n",
-                    localHdrOffset, kLFHSignature, get4LE(lfhBuf));
-            return false;
-        }
-#else /* HAVE_PREAD */
-        /*
-         * For hosts don't have pread64() we cannot guarantee atomic reads from
-         * an offset in a file. Android should never run on those platforms.
-         * File descriptors inherited from a fork() share file offsets and
-         * there would be nothing to protect from two different processes
-         * calling lseek64() concurrently.
-         */
-
-        {
-            AutoMutex _l(mFdLock);
-
-            if (lseek64(mFd, localHdrOffset, SEEK_SET) != localHdrOffset) {
-                ALOGW("failed seeking to lfh at offset %ld\n", localHdrOffset);
-                return false;
-            }
-
-            ssize_t actual =
-                    TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf)));
-            if (actual != sizeof(lfhBuf)) {
-                ALOGW("failed reading lfh from offset %ld\n", localHdrOffset);
-                return false;
-            }
-
-            if (get4LE(lfhBuf) != kLFHSignature) {
-                off64_t actualOffset = lseek64(mFd, 0, SEEK_CUR);
-                ALOGW("didn't find signature at start of lfh; wanted: offset=%ld data=0x%08x; "
-                        "got: offset=" ZD " data=0x%08lx\n",
-                        localHdrOffset, kLFHSignature, (ZD_TYPE) actualOffset, get4LE(lfhBuf));
-                return false;
-            }
-        }
-#endif /* HAVE_PREAD */
-
-        off64_t dataOffset = localHdrOffset + kLFHLen
-            + get2LE(lfhBuf + kLFHNameLen) + get2LE(lfhBuf + kLFHExtraLen);
-        if (dataOffset >= cdOffset) {
-            ALOGW("bad data offset %ld in zip\n", (long) dataOffset);
-            return false;
-        }
-
-        /* check lengths */
-        if ((off64_t)(dataOffset + compLen) > cdOffset) {
-            ALOGW("bad compressed length in zip (%ld + " ZD " > %ld)\n",
-                (long) dataOffset, (ZD_TYPE) compLen, (long) cdOffset);
-            return false;
-        }
-
-        if (method == kCompressStored &&
-            (off64_t)(dataOffset + uncompLen) > cdOffset)
-        {
-            ALOGE("ERROR: bad uncompressed length in zip (%ld + " ZD " > %ld)\n",
-                (long) dataOffset, (ZD_TYPE) uncompLen, (long) cdOffset);
-            return false;
-        }
-
-        *pOffset = dataOffset;
-    }
-
-    return true;
-}
-
-/*
- * Copy the entry's filename to the buffer.
- */
-int ZipFileRO::getEntryFileName(ZipEntryRO entry, char* buffer, int bufLen)
-    const
-{
-    int ent = entryToIndex(entry);
-    if (ent < 0)
-        return -1;
-
-    int nameLen = mHashTable[ent].nameLen;
-    if (bufLen < nameLen+1)
-        return nameLen+1;
-
-    memcpy(buffer, mHashTable[ent].name, nameLen);
-    buffer[nameLen] = '\0';
-    return 0;
-}
-
-/*
- * Create a new FileMap object that spans the data in "entry".
- */
-FileMap* ZipFileRO::createEntryFileMap(ZipEntryRO entry) const
-{
-    /*
-     * TODO: the efficient way to do this is to modify FileMap to allow
-     * sub-regions of a file to be mapped.  A reference-counting scheme
-     * can manage the base memory mapping.  For now, we just create a brand
-     * new mapping off of the Zip archive file descriptor.
-     */
-
-    FileMap* newMap;
-    size_t compLen;
-    off64_t offset;
-
-    if (!getEntryInfo(entry, NULL, NULL, &compLen, &offset, NULL, NULL))
-        return NULL;
-
-    newMap = new FileMap();
-    if (!newMap->create(mFileName, mFd, offset, compLen, true)) {
-        newMap->release();
-        return NULL;
-    }
-
-    return newMap;
-}
-
-/*
- * Uncompress an entry, in its entirety, into the provided output buffer.
- *
- * This doesn't verify the data's CRC, which might be useful for
- * uncompressed data.  The caller should be able to manage it.
- */
-bool ZipFileRO::uncompressEntry(ZipEntryRO entry, void* buffer) const
-{
-    const size_t kSequentialMin = 32768;
-    bool result = false;
-    int ent = entryToIndex(entry);
-    if (ent < 0)
-        return -1;
-
-    int method;
-    size_t uncompLen, compLen;
-    off64_t offset;
-    const unsigned char* ptr;
-
-    getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL);
-
-    FileMap* file = createEntryFileMap(entry);
-    if (file == NULL) {
-        goto bail;
-    }
-
-    ptr = (const unsigned char*) file->getDataPtr();
-
-    /*
-     * Experiment with madvise hint.  When we want to uncompress a file,
-     * we pull some stuff out of the central dir entry and then hit a
-     * bunch of compressed or uncompressed data sequentially.  The CDE
-     * visit will cause a limited amount of read-ahead because it's at
-     * the end of the file.  We could end up doing lots of extra disk
-     * access if the file we're prying open is small.  Bottom line is we
-     * probably don't want to turn MADV_SEQUENTIAL on and leave it on.
-     *
-     * So, if the compressed size of the file is above a certain minimum
-     * size, temporarily boost the read-ahead in the hope that the extra
-     * pair of system calls are negated by a reduction in page faults.
-     */
-    if (compLen > kSequentialMin)
-        file->advise(FileMap::SEQUENTIAL);
-
-    if (method == kCompressStored) {
-        memcpy(buffer, ptr, uncompLen);
-    } else {
-        if (!inflateBuffer(buffer, ptr, uncompLen, compLen))
-            goto unmap;
-    }
-
-    if (compLen > kSequentialMin)
-        file->advise(FileMap::NORMAL);
-
-    result = true;
-
-unmap:
-    file->release();
-bail:
-    return result;
-}
-
-/*
- * Uncompress an entry, in its entirety, to an open file descriptor.
- *
- * This doesn't verify the data's CRC, but probably should.
- */
-bool ZipFileRO::uncompressEntry(ZipEntryRO entry, int fd) const
-{
-    bool result = false;
-    int ent = entryToIndex(entry);
-    if (ent < 0)
-        return -1;
-
-    int method;
-    size_t uncompLen, compLen;
-    off64_t offset;
-    const unsigned char* ptr;
-
-    getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL);
-
-    FileMap* file = createEntryFileMap(entry);
-    if (file == NULL) {
-        goto bail;
-    }
-
-    ptr = (const unsigned char*) file->getDataPtr();
-
-    if (method == kCompressStored) {
-        ssize_t actual = write(fd, ptr, uncompLen);
-        if (actual < 0) {
-            ALOGE("Write failed: %s\n", strerror(errno));
-            goto unmap;
-        } else if ((size_t) actual != uncompLen) {
-            ALOGE("Partial write during uncompress (" ZD " of " ZD ")\n",
-                (ZD_TYPE) actual, (ZD_TYPE) uncompLen);
-            goto unmap;
-        } else {
-            ALOGI("+++ successful write\n");
-        }
-    } else {
-        if (!inflateBuffer(fd, ptr, uncompLen, compLen))
-            goto unmap;
-    }
-
-    result = true;
-
-unmap:
-    file->release();
-bail:
-    return result;
-}
-
-/*
- * Uncompress "deflate" data from one buffer to another.
- */
-/*static*/ bool ZipFileRO::inflateBuffer(void* outBuf, const void* inBuf,
-    size_t uncompLen, size_t compLen)
-{
-    bool result = false;
-    z_stream zstream;
-    int zerr;
-
-    /*
-     * Initialize the zlib stream struct.
-     */
-    memset(&zstream, 0, sizeof(zstream));
-    zstream.zalloc = Z_NULL;
-    zstream.zfree = Z_NULL;
-    zstream.opaque = Z_NULL;
-    zstream.next_in = (Bytef*)inBuf;
-    zstream.avail_in = compLen;
-    zstream.next_out = (Bytef*) outBuf;
-    zstream.avail_out = uncompLen;
-    zstream.data_type = Z_UNKNOWN;
-
-    /*
-     * Use the undocumented "negative window bits" feature to tell zlib
-     * that there's no zlib header waiting for it.
-     */
-    zerr = inflateInit2(&zstream, -MAX_WBITS);
-    if (zerr != Z_OK) {
-        if (zerr == Z_VERSION_ERROR) {
-            ALOGE("Installed zlib is not compatible with linked version (%s)\n",
-                ZLIB_VERSION);
-        } else {
-            ALOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
-        }
-        goto bail;
-    }
-
-    /*
-     * Expand data.
-     */
-    zerr = inflate(&zstream, Z_FINISH);
-    if (zerr != Z_STREAM_END) {
-        ALOGW("Zip inflate failed, zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)\n",
-            zerr, zstream.next_in, zstream.avail_in,
-            zstream.next_out, zstream.avail_out);
-        goto z_bail;
-    }
-
-    /* paranoia */
-    if (zstream.total_out != uncompLen) {
-        ALOGW("Size mismatch on inflated file (%ld vs " ZD ")\n",
-            zstream.total_out, (ZD_TYPE) uncompLen);
-        goto z_bail;
-    }
-
-    result = true;
-
-z_bail:
-    inflateEnd(&zstream);        /* free up any allocated structures */
-
-bail:
-    return result;
-}
-
-/*
- * Uncompress "deflate" data from one buffer to an open file descriptor.
- */
-/*static*/ bool ZipFileRO::inflateBuffer(int fd, const void* inBuf,
-    size_t uncompLen, size_t compLen)
-{
-    bool result = false;
-    const size_t kWriteBufSize = 32768;
-    unsigned char writeBuf[kWriteBufSize];
-    z_stream zstream;
-    int zerr;
-
-    /*
-     * Initialize the zlib stream struct.
-     */
-    memset(&zstream, 0, sizeof(zstream));
-    zstream.zalloc = Z_NULL;
-    zstream.zfree = Z_NULL;
-    zstream.opaque = Z_NULL;
-    zstream.next_in = (Bytef*)inBuf;
-    zstream.avail_in = compLen;
-    zstream.next_out = (Bytef*) writeBuf;
-    zstream.avail_out = sizeof(writeBuf);
-    zstream.data_type = Z_UNKNOWN;
-
-    /*
-     * Use the undocumented "negative window bits" feature to tell zlib
-     * that there's no zlib header waiting for it.
-     */
-    zerr = inflateInit2(&zstream, -MAX_WBITS);
-    if (zerr != Z_OK) {
-        if (zerr == Z_VERSION_ERROR) {
-            ALOGE("Installed zlib is not compatible with linked version (%s)\n",
-                ZLIB_VERSION);
-        } else {
-            ALOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
-        }
-        goto bail;
-    }
-
-    /*
-     * Loop while we have more to do.
-     */
-    do {
-        /*
-         * Expand data.
-         */
-        zerr = inflate(&zstream, Z_NO_FLUSH);
-        if (zerr != Z_OK && zerr != Z_STREAM_END) {
-            ALOGW("zlib inflate: zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)\n",
-                zerr, zstream.next_in, zstream.avail_in,
-                zstream.next_out, zstream.avail_out);
-            goto z_bail;
-        }
-
-        /* write when we're full or when we're done */
-        if (zstream.avail_out == 0 ||
-            (zerr == Z_STREAM_END && zstream.avail_out != sizeof(writeBuf)))
-        {
-            long writeSize = zstream.next_out - writeBuf;
-            int cc = write(fd, writeBuf, writeSize);
-            if (cc != (int) writeSize) {
-                ALOGW("write failed in inflate (%d vs %ld)\n", cc, writeSize);
-                goto z_bail;
-            }
-
-            zstream.next_out = writeBuf;
-            zstream.avail_out = sizeof(writeBuf);
-        }
-    } while (zerr == Z_OK);
-
-    assert(zerr == Z_STREAM_END);       /* other errors should've been caught */
-
-    /* paranoia */
-    if (zstream.total_out != uncompLen) {
-        ALOGW("Size mismatch on inflated file (%ld vs " ZD ")\n",
-            zstream.total_out, (ZD_TYPE) uncompLen);
-        goto z_bail;
-    }
-
-    result = true;
-
-z_bail:
-    inflateEnd(&zstream);        /* free up any allocated structures */
-
-bail:
-    return result;
-}
diff --git a/libs/androidfw/ZipUtils.cpp b/libs/androidfw/ZipUtils.cpp
deleted file mode 100644
index db3479d..0000000
--- a/libs/androidfw/ZipUtils.cpp
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//
-// Misc zip/gzip utility functions.
-//
-
-#define LOG_TAG "ziputil"
-
-#include <androidfw/ZipUtils.h>
-#include <androidfw/ZipFileRO.h>
-#include <utils/Log.h>
-
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-
-#include <zlib.h>
-
-using namespace android;
-
-/*
- * Utility function that expands zip/gzip "deflate" compressed data
- * into a buffer.
- *
- * "fd" is an open file positioned at the start of the "deflate" data
- * "buf" must hold at least "uncompressedLen" bytes.
- */
-/*static*/ bool ZipUtils::inflateToBuffer(int fd, void* buf,
-    long uncompressedLen, long compressedLen)
-{
-    bool result = false;
-	const unsigned long kReadBufSize = 32768;
-	unsigned char* readBuf = NULL;
-    z_stream zstream;
-    int zerr;
-    unsigned long compRemaining;
-
-    assert(uncompressedLen >= 0);
-    assert(compressedLen >= 0);
-
-	readBuf = new unsigned char[kReadBufSize];
-	if (readBuf == NULL)
-        goto bail;
-    compRemaining = compressedLen;
-
-    /*
-     * Initialize the zlib stream.
-     */
-	memset(&zstream, 0, sizeof(zstream));
-    zstream.zalloc = Z_NULL;
-    zstream.zfree = Z_NULL;
-    zstream.opaque = Z_NULL;
-    zstream.next_in = NULL;
-    zstream.avail_in = 0;
-    zstream.next_out = (Bytef*) buf;
-    zstream.avail_out = uncompressedLen;
-    zstream.data_type = Z_UNKNOWN;
-
-	/*
-	 * Use the undocumented "negative window bits" feature to tell zlib
-	 * that there's no zlib header waiting for it.
-	 */
-    zerr = inflateInit2(&zstream, -MAX_WBITS);
-    if (zerr != Z_OK) {
-        if (zerr == Z_VERSION_ERROR) {
-            ALOGE("Installed zlib is not compatible with linked version (%s)\n",
-                ZLIB_VERSION);
-        } else {
-            ALOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
-        }
-        goto bail;
-    }
-
-    /*
-     * Loop while we have data.
-     */
-    do {
-        unsigned long getSize;
-
-        /* read as much as we can */
-        if (zstream.avail_in == 0) {
-            getSize = (compRemaining > kReadBufSize) ?
-                        kReadBufSize : compRemaining;
-            ALOGV("+++ reading %ld bytes (%ld left)\n",
-                getSize, compRemaining);
-
-            int cc = read(fd, readBuf, getSize);
-            if (cc != (int) getSize) {
-                ALOGD("inflate read failed (%d vs %ld)\n",
-                    cc, getSize);
-                goto z_bail;
-            }
-
-            compRemaining -= getSize;
-
-            zstream.next_in = readBuf;
-            zstream.avail_in = getSize;
-        }
-
-        /* uncompress the data */
-        zerr = inflate(&zstream, Z_NO_FLUSH);
-        if (zerr != Z_OK && zerr != Z_STREAM_END) {
-            ALOGD("zlib inflate call failed (zerr=%d)\n", zerr);
-            goto z_bail;
-        }
-
-		/* output buffer holds all, so no need to write the output */
-    } while (zerr == Z_OK);
-
-    assert(zerr == Z_STREAM_END);       /* other errors should've been caught */
-
-    if ((long) zstream.total_out != uncompressedLen) {
-        ALOGW("Size mismatch on inflated file (%ld vs %ld)\n",
-            zstream.total_out, uncompressedLen);
-        goto z_bail;
-    }
-
-    // success!
-    result = true;
-
-z_bail:
-    inflateEnd(&zstream);        /* free up any allocated structures */
-
-bail:
-	delete[] readBuf;
-    return result;
-}
-
-/*
- * Utility function that expands zip/gzip "deflate" compressed data
- * into a buffer.
- *
- * (This is a clone of the previous function, but it takes a FILE* instead
- * of an fd.  We could pass fileno(fd) to the above, but we can run into
- * trouble when "fp" has a different notion of what fd's file position is.)
- *
- * "fp" is an open file positioned at the start of the "deflate" data
- * "buf" must hold at least "uncompressedLen" bytes.
- */
-/*static*/ bool ZipUtils::inflateToBuffer(FILE* fp, void* buf,
-    long uncompressedLen, long compressedLen)
-{
-    bool result = false;
-	const unsigned long kReadBufSize = 32768;
-	unsigned char* readBuf = NULL;
-    z_stream zstream;
-    int zerr;
-    unsigned long compRemaining;
-
-    assert(uncompressedLen >= 0);
-    assert(compressedLen >= 0);
-
-	readBuf = new unsigned char[kReadBufSize];
-	if (readBuf == NULL)
-        goto bail;
-    compRemaining = compressedLen;
-
-    /*
-     * Initialize the zlib stream.
-     */
-	memset(&zstream, 0, sizeof(zstream));
-    zstream.zalloc = Z_NULL;
-    zstream.zfree = Z_NULL;
-    zstream.opaque = Z_NULL;
-    zstream.next_in = NULL;
-    zstream.avail_in = 0;
-    zstream.next_out = (Bytef*) buf;
-    zstream.avail_out = uncompressedLen;
-    zstream.data_type = Z_UNKNOWN;
-
-	/*
-	 * Use the undocumented "negative window bits" feature to tell zlib
-	 * that there's no zlib header waiting for it.
-	 */
-    zerr = inflateInit2(&zstream, -MAX_WBITS);
-    if (zerr != Z_OK) {
-        if (zerr == Z_VERSION_ERROR) {
-            ALOGE("Installed zlib is not compatible with linked version (%s)\n",
-                ZLIB_VERSION);
-        } else {
-            ALOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
-        }
-        goto bail;
-    }
-
-    /*
-     * Loop while we have data.
-     */
-    do {
-        unsigned long getSize;
-
-        /* read as much as we can */
-        if (zstream.avail_in == 0) {
-            getSize = (compRemaining > kReadBufSize) ?
-                        kReadBufSize : compRemaining;
-            ALOGV("+++ reading %ld bytes (%ld left)\n",
-                getSize, compRemaining);
-
-            int cc = fread(readBuf, 1, getSize, fp);
-            if (cc != (int) getSize) {
-                ALOGD("inflate read failed (%d vs %ld)\n",
-                    cc, getSize);
-                goto z_bail;
-            }
-
-            compRemaining -= getSize;
-
-            zstream.next_in = readBuf;
-            zstream.avail_in = getSize;
-        }
-
-        /* uncompress the data */
-        zerr = inflate(&zstream, Z_NO_FLUSH);
-        if (zerr != Z_OK && zerr != Z_STREAM_END) {
-            ALOGD("zlib inflate call failed (zerr=%d)\n", zerr);
-            goto z_bail;
-        }
-
-		/* output buffer holds all, so no need to write the output */
-    } while (zerr == Z_OK);
-
-    assert(zerr == Z_STREAM_END);       /* other errors should've been caught */
-
-    if ((long) zstream.total_out != uncompressedLen) {
-        ALOGW("Size mismatch on inflated file (%ld vs %ld)\n",
-            zstream.total_out, uncompressedLen);
-        goto z_bail;
-    }
-
-    // success!
-    result = true;
-
-z_bail:
-    inflateEnd(&zstream);        /* free up any allocated structures */
-
-bail:
-	delete[] readBuf;
-    return result;
-}
-
-/*
- * Look at the contents of a gzip archive.  We want to know where the
- * data starts, and how long it will be after it is uncompressed.
- *
- * We expect to find the CRC and length as the last 8 bytes on the file.
- * This is a pretty reasonable thing to expect for locally-compressed
- * files, but there's a small chance that some extra padding got thrown
- * on (the man page talks about compressed data written to tape).  We
- * don't currently deal with that here.  If "gzip -l" whines, we're going
- * to fail too.
- *
- * On exit, "fp" is pointing at the start of the compressed data.
- */
-/*static*/ bool ZipUtils::examineGzip(FILE* fp, int* pCompressionMethod,
-    long* pUncompressedLen, long* pCompressedLen, unsigned long* pCRC32)
-{
-    enum {  // flags
-        FTEXT       = 0x01,
-        FHCRC       = 0x02,
-        FEXTRA      = 0x04,
-        FNAME       = 0x08,
-        FCOMMENT    = 0x10,
-    };
-    int ic;
-    int method, flags;
-    int i;
-
-    ic = getc(fp);
-    if (ic != 0x1f || getc(fp) != 0x8b)
-        return false;       // not gzip
-    method = getc(fp);
-    flags = getc(fp);
-
-    /* quick sanity checks */
-    if (method == EOF || flags == EOF)
-        return false;
-    if (method != ZipFileRO::kCompressDeflated)
-        return false;
-
-    /* skip over 4 bytes of mod time, 1 byte XFL, 1 byte OS */
-    for (i = 0; i < 6; i++)
-        (void) getc(fp);
-    /* consume "extra" field, if present */
-    if ((flags & FEXTRA) != 0) {
-        int len;
-
-        len = getc(fp);
-        len |= getc(fp) << 8;
-        while (len-- && getc(fp) != EOF)
-            ;
-    }
-    /* consume filename, if present */
-    if ((flags & FNAME) != 0) {
-        do {
-            ic = getc(fp);
-        } while (ic != 0 && ic != EOF);
-    }
-    /* consume comment, if present */
-    if ((flags & FCOMMENT) != 0) {
-        do {
-            ic = getc(fp);
-        } while (ic != 0 && ic != EOF);
-    }
-    /* consume 16-bit header CRC, if present */
-    if ((flags & FHCRC) != 0) {
-        (void) getc(fp);
-        (void) getc(fp);
-    }
-
-    if (feof(fp) || ferror(fp))
-        return false;
-
-    /* seek to the end; CRC and length are in the last 8 bytes */
-    long curPosn = ftell(fp);
-    unsigned char buf[8];
-    fseek(fp, -8, SEEK_END);
-    *pCompressedLen = ftell(fp) - curPosn;
-
-    if (fread(buf, 1, 8, fp) != 8)
-        return false;
-    /* seek back to start of compressed data */
-    fseek(fp, curPosn, SEEK_SET);
-
-    *pCompressionMethod = method;
-    *pCRC32 = ZipFileRO::get4LE(&buf[0]);
-    *pUncompressedLen = ZipFileRO::get4LE(&buf[4]);
-
-    return true;
-}
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
index d85009b..39009b8 100644
--- a/libs/androidfw/tests/Android.mk
+++ b/libs/androidfw/tests/Android.mk
@@ -7,8 +7,7 @@
     InputChannel_test.cpp \
     InputEvent_test.cpp \
     InputPublisherAndConsumer_test.cpp \
-    ObbFile_test.cpp \
-    ZipFileRO_test.cpp 
+    ObbFile_test.cpp
 
 shared_libraries := \
 	libandroidfw \
diff --git a/libs/androidfw/tests/ZipFileRO_test.cpp b/libs/androidfw/tests/ZipFileRO_test.cpp
deleted file mode 100644
index 344f974..0000000
--- a/libs/androidfw/tests/ZipFileRO_test.cpp
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "ZipFileRO_test"
-#include <androidfw/ZipFileRO.h>
-#include <utils/Log.h>
-
-#include <gtest/gtest.h>
-
-#include <fcntl.h>
-#include <string.h>
-
-namespace android {
-
-class ZipFileROTest : public testing::Test {
-protected:
-    virtual void SetUp() {
-    }
-
-    virtual void TearDown() {
-    }
-};
-
-TEST_F(ZipFileROTest, ZipTimeConvertSuccess) {
-    struct tm t;
-
-    // 2011-06-29 14:40:40
-    long when = 0x3EDD7514;
-
-    ZipFileRO::zipTimeToTimespec(when, &t);
-
-    EXPECT_EQ(2011, t.tm_year + 1900)
-            << "Year was improperly converted.";
-
-    EXPECT_EQ(6, t.tm_mon)
-            << "Month was improperly converted.";
-
-    EXPECT_EQ(29, t.tm_mday)
-            << "Day was improperly converted.";
-
-    EXPECT_EQ(14, t.tm_hour)
-            << "Hour was improperly converted.";
-
-    EXPECT_EQ(40, t.tm_min)
-            << "Minute was improperly converted.";
-
-    EXPECT_EQ(40, t.tm_sec)
-            << "Second was improperly converted.";
-}
-
-}
diff --git a/libs/cpustats/Android.mk b/libs/cpustats/Android.mk
deleted file mode 100644
index 21bacbb..0000000
--- a/libs/cpustats/Android.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES :=     \
-        CentralTendencyStatistics.cpp \
-        ThreadCpuUsage.cpp
-
-LOCAL_MODULE := libcpustats
-
-include $(BUILD_STATIC_LIBRARY)
-
-#include $(CLEAR_VARS)
-#
-#LOCAL_SRC_FILES :=     \
-#       CentralTendencyStatistics.cpp \
-#       ThreadCpuUsage.cpp
-#
-#LOCAL_MODULE := libcpustats
-#
-#include $(BUILD_HOST_STATIC_LIBRARY)
diff --git a/libs/cpustats/CentralTendencyStatistics.cpp b/libs/cpustats/CentralTendencyStatistics.cpp
deleted file mode 100644
index 42ab62b..0000000
--- a/libs/cpustats/CentralTendencyStatistics.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <stdlib.h>
-
-#include <cpustats/CentralTendencyStatistics.h>
-
-void CentralTendencyStatistics::sample(double x)
-{
-    // update min and max
-    if (x < mMinimum)
-        mMinimum = x;
-    if (x > mMaximum)
-        mMaximum = x;
-    // Knuth
-    if (mN == 0) {
-        mMean = 0;
-    }
-    ++mN;
-    double delta = x - mMean;
-    mMean += delta / mN;
-    mM2 += delta * (x - mMean);
-}
-
-void CentralTendencyStatistics::reset()
-{
-    mMean = NAN;
-    mMedian = NAN;
-    mMinimum = INFINITY;
-    mMaximum = -INFINITY;
-    mN = 0;
-    mM2 = 0;
-    mVariance = NAN;
-    mVarianceKnownForN = 0;
-    mStddev = NAN;
-    mStddevKnownForN = 0;
-}
-
-double CentralTendencyStatistics::variance() const
-{
-    double variance;
-    if (mVarianceKnownForN != mN) {
-        if (mN > 1) {
-            // double variance_n = M2/n;
-            variance = mM2 / (mN - 1);
-        } else {
-            variance = NAN;
-        }
-        mVariance = variance;
-        mVarianceKnownForN = mN;
-    } else {
-        variance = mVariance;
-    }
-    return variance;
-}
-
-double CentralTendencyStatistics::stddev() const
-{
-    double stddev;
-    if (mStddevKnownForN != mN) {
-        stddev = sqrt(variance());
-        mStddev = stddev;
-        mStddevKnownForN = mN;
-    } else {
-        stddev = mStddev;
-    }
-    return stddev;
-}
diff --git a/libs/cpustats/ThreadCpuUsage.cpp b/libs/cpustats/ThreadCpuUsage.cpp
deleted file mode 100644
index 99b4c8363..0000000
--- a/libs/cpustats/ThreadCpuUsage.cpp
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "ThreadCpuUsage"
-//#define LOG_NDEBUG 0
-
-#include <errno.h>
-#include <stdlib.h>
-#include <time.h>
-
-#include <utils/Debug.h>
-#include <utils/Log.h>
-
-#include <cpustats/ThreadCpuUsage.h>
-
-namespace android {
-
-bool ThreadCpuUsage::setEnabled(bool isEnabled)
-{
-    bool wasEnabled = mIsEnabled;
-    // only do something if there is a change
-    if (isEnabled != wasEnabled) {
-        ALOGV("setEnabled(%d)", isEnabled);
-        int rc;
-        // enabling
-        if (isEnabled) {
-            rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &mPreviousTs);
-            if (rc) {
-                ALOGE("clock_gettime(CLOCK_THREAD_CPUTIME_ID) errno=%d", errno);
-                isEnabled = false;
-            } else {
-                mWasEverEnabled = true;
-                // record wall clock time at first enable
-                if (!mMonotonicKnown) {
-                    rc = clock_gettime(CLOCK_MONOTONIC, &mMonotonicTs);
-                    if (rc) {
-                        ALOGE("clock_gettime(CLOCK_MONOTONIC) errno=%d", errno);
-                    } else {
-                        mMonotonicKnown = true;
-                    }
-                }
-            }
-        // disabling
-        } else {
-            struct timespec ts;
-            rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
-            if (rc) {
-                ALOGE("clock_gettime(CLOCK_THREAD_CPUTIME_ID) errno=%d", errno);
-            } else {
-                long long delta = (ts.tv_sec - mPreviousTs.tv_sec) * 1000000000LL +
-                        (ts.tv_nsec - mPreviousTs.tv_nsec);
-                mAccumulator += delta;
-#if 0
-                mPreviousTs = ts;
-#endif
-            }
-        }
-        mIsEnabled = isEnabled;
-    }
-    return wasEnabled;
-}
-
-bool ThreadCpuUsage::sampleAndEnable(double& ns)
-{
-    bool ret;
-    bool wasEverEnabled = mWasEverEnabled;
-    if (enable()) {
-        // already enabled, so add a new sample relative to previous
-        return sample(ns);
-    } else if (wasEverEnabled) {
-        // was disabled, but add sample for accumulated time while enabled
-        ns = (double) mAccumulator;
-        mAccumulator = 0;
-        ALOGV("sampleAndEnable %.0f", ns);
-        return true;
-    } else {
-        // first time called
-        ns = 0.0;
-        ALOGV("sampleAndEnable false");
-        return false;
-    }
-}
-
-bool ThreadCpuUsage::sample(double &ns)
-{
-    if (mWasEverEnabled) {
-        if (mIsEnabled) {
-            struct timespec ts;
-            int rc;
-            rc = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
-            if (rc) {
-                ALOGE("clock_gettime(CLOCK_THREAD_CPUTIME_ID) errno=%d", errno);
-                ns = 0.0;
-                return false;
-            } else {
-                long long delta = (ts.tv_sec - mPreviousTs.tv_sec) * 1000000000LL +
-                        (ts.tv_nsec - mPreviousTs.tv_nsec);
-                mAccumulator += delta;
-                mPreviousTs = ts;
-            }
-        } else {
-            mWasEverEnabled = false;
-        }
-        ns = (double) mAccumulator;
-        ALOGV("sample %.0f", ns);
-        mAccumulator = 0;
-        return true;
-    } else {
-        ALOGW("Can't add sample because measurements have never been enabled");
-        ns = 0.0;
-        return false;
-    }
-}
-
-long long ThreadCpuUsage::elapsed() const
-{
-    long long elapsed;
-    if (mMonotonicKnown) {
-        struct timespec ts;
-        int rc;
-        rc = clock_gettime(CLOCK_MONOTONIC, &ts);
-        if (rc) {
-            ALOGE("clock_gettime(CLOCK_MONOTONIC) errno=%d", errno);
-            elapsed = 0;
-        } else {
-            // mMonotonicTs is updated only at first enable and resetStatistics
-            elapsed = (ts.tv_sec - mMonotonicTs.tv_sec) * 1000000000LL +
-                    (ts.tv_nsec - mMonotonicTs.tv_nsec);
-        }
-    } else {
-        ALOGW("Can't compute elapsed time because measurements have never been enabled");
-        elapsed = 0;
-    }
-    ALOGV("elapsed %lld", elapsed);
-    return elapsed;
-}
-
-void ThreadCpuUsage::resetElapsed()
-{
-    ALOGV("resetElapsed");
-    if (mMonotonicKnown) {
-        int rc;
-        rc = clock_gettime(CLOCK_MONOTONIC, &mMonotonicTs);
-        if (rc) {
-            ALOGE("clock_gettime(CLOCK_MONOTONIC) errno=%d", errno);
-            mMonotonicKnown = false;
-        }
-    }
-}
-
-/*static*/
-int ThreadCpuUsage::sScalingFds[ThreadCpuUsage::MAX_CPU];
-pthread_once_t ThreadCpuUsage::sOnceControl = PTHREAD_ONCE_INIT;
-int ThreadCpuUsage::sKernelMax;
-
-/*static*/
-void ThreadCpuUsage::init()
-{
-    // read the number of CPUs
-    sKernelMax = 1;
-    int fd = open("/sys/devices/system/cpu/kernel_max", O_RDONLY);
-    if (fd >= 0) {
-#define KERNEL_MAX_SIZE 12
-        char kernelMax[KERNEL_MAX_SIZE];
-        ssize_t actual = read(fd, kernelMax, sizeof(kernelMax));
-        if (actual >= 2 && kernelMax[actual-1] == '\n') {
-            sKernelMax = atoi(kernelMax);
-            if (sKernelMax >= MAX_CPU - 1) {
-                ALOGW("kernel_max %d but MAX_CPU %d", sKernelMax, MAX_CPU);
-                sKernelMax = MAX_CPU;
-            } else if (sKernelMax < 0) {
-                ALOGW("kernel_max invalid %d", sKernelMax);
-                sKernelMax = 1;
-            } else {
-                ++sKernelMax;
-                ALOGV("number of CPUs %d", sKernelMax);
-            }
-        } else {
-            ALOGW("Can't read number of CPUs");
-        }
-        (void) close(fd);
-    } else {
-        ALOGW("Can't open number of CPUs");
-    }
-
-    // open fd to each frequency per CPU
-#define FREQ_SIZE 64
-    char freq_path[FREQ_SIZE];
-#define FREQ_DIGIT 27
-    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(MAX_CPU <= 10);
-    strlcpy(freq_path, "/sys/devices/system/cpu/cpu?/cpufreq/scaling_cur_freq", sizeof(freq_path));
-    int i;
-    for (i = 0; i < MAX_CPU; ++i) {
-        sScalingFds[i] = -1;
-    }
-    for (i = 0; i < sKernelMax; ++i) {
-        freq_path[FREQ_DIGIT] = i + '0';
-        fd = open(freq_path, O_RDONLY);
-        if (fd >= 0) {
-            // keep this fd until process exit
-            sScalingFds[i] = fd;
-        } else {
-            ALOGW("Can't open CPU %d", i);
-        }
-    }
-}
-
-uint32_t ThreadCpuUsage::getCpukHz(int cpuNum)
-{
-    if (cpuNum < 0 || cpuNum >= MAX_CPU) {
-        ALOGW("getCpukHz called with invalid CPU %d", cpuNum);
-        return 0;
-    }
-    int fd = sScalingFds[cpuNum];
-    if (fd < 0) {
-        ALOGW("getCpukHz called for unopened CPU %d", cpuNum);
-        return 0;
-    }
-#define KHZ_SIZE 12
-    char kHz[KHZ_SIZE];   // kHz base 10
-    ssize_t actual = pread(fd, kHz, sizeof(kHz), (off_t) 0);
-    uint32_t ret;
-    if (actual >= 2 && kHz[actual-1] == '\n') {
-        ret = atoi(kHz);
-    } else {
-        ret = 0;
-    }
-    if (ret != mCurrentkHz[cpuNum]) {
-        if (ret > 0) {
-            ALOGV("CPU %d frequency %u kHz", cpuNum, ret);
-        } else {
-            ALOGW("Can't read CPU %d frequency", cpuNum);
-        }
-        mCurrentkHz[cpuNum] = ret;
-    }
-    return ret;
-}
-
-}   // namespace android
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 118608d..3a3f8a5 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -18,6 +18,8 @@
 
 #include <SkCamera.h>
 
+#include <private/hwui/DrawGlInfo.h>
+
 #include "DisplayListLogBuffer.h"
 #include "DisplayListRenderer.h"
 #include "Caches.h"
@@ -104,7 +106,7 @@
 void DisplayList::initProperties() {
     mLeft = 0;
     mTop = 0;
-    mTop = 0;
+    mRight = 0;
     mBottom = 0;
     mApplicationScale = -1;
     mClipChildren = true;
@@ -119,6 +121,7 @@
     mScaleY = 1;
     mPivotX = 0;
     mPivotY = 0;
+    mCameraDistance = 0;
     mMatrixDirty = false;
     mMatrixFlags = 0;
     mPrevWidth = -1;
@@ -684,7 +687,7 @@
                         mTransformMatrix->get(8));
             }
         }
-        if (mAlpha < 1) {
+        if (mAlpha < 1 && !mCaching) {
             // TODO: should be able to store the size of a DL at record time and not
             // have to pass it into this call. In fact, this information might be in the
             // location/size info that we store with the new native transform data.
@@ -763,42 +766,14 @@
     }
 }
 
-void DisplayList::transformRect(float left, float top, float right, float bottom, Rect& result) {
-    result.left = left + mLeft;
-    result.top = top + mTop;
-    result.right = right + mLeft;
-    result.bottom = bottom + mTop;
-    if (mMatrixFlags != 0) {
-        if (mMatrixFlags == TRANSLATION) {
-            result.left += mTranslationX;
-            result.top += mTranslationY;
-            result.right += mTranslationX;
-            result.bottom += mTranslationY;
-        } else {
-            updateMatrix();
-            SkRect r;
-            r.fLeft = result.left;
-            r.fTop = result.top;
-            r.fRight = result.right;
-            r.fBottom = result.bottom;
-            mTransformMatrix->mapRect(&r);
-            result.left = r.fLeft;
-            result.top = r.fTop;
-            result.right = r.fRight;
-            result.bottom = r.fBottom;
-        }
-    }
-}
-
-
 /**
  * Changes to replay(), specifically those involving opcode or parameter changes, should be mimicked
  * in the output() function, since that function processes the same list of opcodes for the
  * purposes of logging display list info for a given view.
  */
-bool DisplayList::replay(OpenGLRenderer& renderer, uint32_t width,
+status_t DisplayList::replay(OpenGLRenderer& renderer, uint32_t width,
         uint32_t height, Rect& dirty, int32_t flags, uint32_t level) {
-    bool needsInvalidate = false;
+    status_t drawGlStatus = 0;
     TextContainer text;
     mReader.rewind();
 
@@ -820,6 +795,12 @@
         restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
     }
     setViewProperties(renderer, width, height, level);
+    if (USE_DISPLAY_LIST_PROPERTIES && renderer.quickReject(0, 0, width, height)) {
+        DISPLAY_LIST_LOGD("%s%s %d", (char*) indent, "RestoreToCount", restoreTo);
+        renderer.restoreToCount(restoreTo);
+        renderer.endMark();
+        return false;
+    }
 
     DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
     int saveCount = renderer.getSaveCount() - 1;
@@ -843,7 +824,7 @@
                 Functor *functor = (Functor *) getInt();
                 DISPLAY_LIST_LOGD("%s%s %p", (char*) indent, OP_NAMES[op], functor);
                 renderer.startMark("GL functor");
-                needsInvalidate |= renderer.callDrawGLFunction(functor, dirty);
+                drawGlStatus |= renderer.callDrawGLFunction(functor, dirty);
                 renderer.endMark();
             }
             break;
@@ -950,7 +931,7 @@
                 int32_t flags = getInt();
                 DISPLAY_LIST_LOGD("%s%s %p, %dx%d, 0x%x %d", (char*) indent, OP_NAMES[op],
                         displayList, width, height, flags, level + 1);
-                needsInvalidate |= renderer.drawDisplayList(displayList, width,
+                drawGlStatus |= renderer.drawDisplayList(displayList, width,
                         height, dirty, flags, level + 1);
             }
             break;
@@ -972,6 +953,9 @@
                 float x = getFloat();
                 float y = getFloat();
                 SkPaint* paint = getPaint(renderer);
+                if (mCaching && mMultipliedAlpha < 255) {
+                    paint->setAlpha(mMultipliedAlpha);
+                }
                 DISPLAY_LIST_LOGD("%s%s %p, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
                         bitmap, x, y, paint);
                 renderer.drawBitmap(bitmap, x, y, paint);
@@ -1233,8 +1217,8 @@
     renderer.endMark();
 
     DISPLAY_LIST_LOGD("%sDone (%p, %s), returning %d", (char*) indent + 2, this, mName.string(),
-            needsInvalidate);
-    return needsInvalidate;
+            drawGlStatus);
+    return drawGlStatus;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1321,11 +1305,11 @@
 void DisplayListRenderer::resume() {
 }
 
-bool DisplayListRenderer::callDrawGLFunction(Functor *functor, Rect& dirty) {
+status_t DisplayListRenderer::callDrawGLFunction(Functor *functor, Rect& dirty) {
     // Ignore dirty during recording, it matters only when we replay
     addOp(DisplayList::DrawGLFunction);
     addInt((int) functor);
-    return false; // No invalidate needed at record-time
+    return DrawGlInfo::kStatusDone; // No invalidate needed at record-time
 }
 
 int DisplayListRenderer::save(int flags) {
@@ -1415,29 +1399,16 @@
     return OpenGLRenderer::clipRect(left, top, right, bottom, op);
 }
 
-bool DisplayListRenderer::drawDisplayList(DisplayList* displayList,
+status_t DisplayListRenderer::drawDisplayList(DisplayList* displayList,
         uint32_t width, uint32_t height, Rect& dirty, int32_t flags, uint32_t level) {
     // dirty is an out parameter and should not be recorded,
     // it matters only when replaying the display list
-    float top = 0;
-    float left = 0;
-    float right = width;
-    float bottom = height;
-    if (USE_DISPLAY_LIST_PROPERTIES) {
-        Rect transformedRect;
-        displayList->transformRect(left, top, right, bottom, transformedRect);
-        left = transformedRect.left;
-        top = transformedRect.top;
-        right = transformedRect.right;
-        bottom = transformedRect.bottom;
-    }
-    const bool reject = quickReject(left, top, right, bottom);
-    uint32_t* location = addOp(DisplayList::DrawDisplayList, reject);
+
+    addOp(DisplayList::DrawDisplayList);
     addDisplayList(displayList);
     addSize(width, height);
     addInt(flags);
-    addSkip(location);
-    return false;
+    return DrawGlInfo::kStatusDone;
 }
 
 void DisplayListRenderer::drawLayer(Layer* layer, float x, float y, SkPaint* paint) {
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index 38d0374..4a9886b 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -137,7 +137,7 @@
 
     void initFromDisplayListRenderer(const DisplayListRenderer& recorder, bool reusing = false);
 
-    bool replay(OpenGLRenderer& renderer, uint32_t width, uint32_t height,
+    status_t replay(OpenGLRenderer& renderer, uint32_t width, uint32_t height,
             Rect& dirty, int32_t flags, uint32_t level = 0);
 
     void output(OpenGLRenderer& renderer, uint32_t level = 0);
@@ -379,8 +379,6 @@
         mCaching = caching;
     }
 
-    void transformRect(float left, float top, float right, float bottom, Rect& result);
-
 private:
     void init();
 
@@ -525,7 +523,7 @@
     virtual void prepareDirty(float left, float top, float right, float bottom, bool opaque);
     virtual void finish();
 
-    virtual bool callDrawGLFunction(Functor *functor, Rect& dirty);
+    virtual status_t callDrawGLFunction(Functor *functor, Rect& dirty);
 
     virtual void interrupt();
     virtual void resume();
@@ -549,7 +547,7 @@
 
     virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
 
-    virtual bool drawDisplayList(DisplayList* displayList, uint32_t width, uint32_t height,
+    virtual status_t drawDisplayList(DisplayList* displayList, uint32_t width, uint32_t height,
             Rect& dirty, int32_t flags, uint32_t level = 0);
     virtual void drawLayer(Layer* layer, float x, float y, SkPaint* paint);
     virtual void drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint);
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 92a7573..9e7fbb5 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -518,6 +518,10 @@
     mCacheLines.clear();
 
     if (mInitialized) {
+        // Unbinding the buffer shouldn't be necessary but it crashes with some drivers
+        Caches::getInstance().unbindIndicesBuffer();
+        glDeleteBuffers(1, &mIndexBufferID);
+
         delete[] mTextMeshPtr;
         delete mCacheTextureSmall;
         delete mCacheTexture128;
@@ -775,7 +779,7 @@
     // We store a string with letters in a rough frequency of occurrence
     mLatinPrecache = String16("eisarntolcdugpmhbyfvkwzxjq ");
     mLatinPrecache += String16("EISARNTOLCDUGPMHBYFVKWZXJQ");
-    mLatinPrecache += String16(",.?!()-+@;:`'");
+    mLatinPrecache += String16(",.?!()-+@;:'");
     mLatinPrecache += String16("0123456789");
 
     mInitialized = true;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 685fddc..eb4b83b 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -236,7 +236,7 @@
     glBlendEquation(GL_FUNC_ADD);
 }
 
-bool OpenGLRenderer::callDrawGLFunction(Functor *functor, Rect& dirty) {
+status_t OpenGLRenderer::callDrawGLFunction(Functor *functor, Rect& dirty) {
     interrupt();
     if (mDirtyClip) {
         setScissorFromClip();
@@ -269,7 +269,7 @@
     }
 
     resume();
-    return result != 0;
+    return result;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1321,21 +1321,10 @@
 // Drawing
 ///////////////////////////////////////////////////////////////////////////////
 
-bool OpenGLRenderer::drawDisplayList(DisplayList* displayList, uint32_t width, uint32_t height,
+status_t OpenGLRenderer::drawDisplayList(DisplayList* displayList, uint32_t width, uint32_t height,
         Rect& dirty, int32_t flags, uint32_t level) {
-    float top = 0;
-    float left = 0;
-    float right = width;
-    float bottom = height;
-    if (USE_DISPLAY_LIST_PROPERTIES) {
-        Rect transformedRect;
-        displayList->transformRect(left, top, right, bottom, transformedRect);
-        left = transformedRect.left;
-        top = transformedRect.top;
-        right = transformedRect.right;
-        bottom = transformedRect.bottom;
-    }
-    if (quickReject(left, top, right, bottom)) {
+
+    if (!USE_DISPLAY_LIST_PROPERTIES && quickReject(0, 0, width, height)) {
         return false;
     }
 
@@ -1345,7 +1334,7 @@
         return displayList->replay(*this, width, height, dirty, flags, level);
     }
 
-    return false;
+    return DrawGlInfo::kStatusDone;
 }
 
 void OpenGLRenderer::outputDisplayList(DisplayList* displayList, uint32_t level) {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 4d7a491..3ba6202 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -72,7 +72,7 @@
     virtual void interrupt();
     virtual void resume();
 
-    virtual bool callDrawGLFunction(Functor *functor, Rect& dirty);
+    virtual status_t callDrawGLFunction(Functor *functor, Rect& dirty);
 
     ANDROID_API int getSaveCount() const;
     virtual int save(int flags);
@@ -97,7 +97,7 @@
     ANDROID_API bool quickReject(float left, float top, float right, float bottom);
     virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
 
-    virtual bool drawDisplayList(DisplayList* displayList, uint32_t width, uint32_t height,
+    virtual status_t drawDisplayList(DisplayList* displayList, uint32_t width, uint32_t height,
             Rect& dirty, int32_t flags, uint32_t level = 0);
     virtual void outputDisplayList(DisplayList* displayList, uint32_t level = 0);
     virtual void drawLayer(Layer* layer, float x, float y, SkPaint* paint);
diff --git a/libs/rs/Allocation.cpp b/libs/rs/Allocation.cpp
index d69c55f..e37d5de 100644
--- a/libs/rs/Allocation.cpp
+++ b/libs/rs/Allocation.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008-2012 The Android Open Source Project
+ * 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.
@@ -51,17 +51,14 @@
                    RS_ALLOCATION_USAGE_GRAPHICS_VERTEX |
                    RS_ALLOCATION_USAGE_GRAPHICS_CONSTANTS |
                    RS_ALLOCATION_USAGE_GRAPHICS_RENDER_TARGET |
-                   RS_ALLOCATION_USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE |
                    RS_ALLOCATION_USAGE_IO_INPUT |
                    RS_ALLOCATION_USAGE_IO_OUTPUT)) != 0) {
         ALOGE("Unknown usage specified.");
     }
 
-    if ((usage & (RS_ALLOCATION_USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE |
-                  RS_ALLOCATION_USAGE_IO_INPUT)) != 0) {
+    if ((usage & RS_ALLOCATION_USAGE_IO_INPUT) != 0) {
         mWriteAllowed = false;
-        if ((usage & ~(RS_ALLOCATION_USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE |
-                       RS_ALLOCATION_USAGE_IO_INPUT |
+        if ((usage & ~(RS_ALLOCATION_USAGE_IO_INPUT |
                        RS_ALLOCATION_USAGE_GRAPHICS_TEXTURE |
                        RS_ALLOCATION_USAGE_SCRIPT)) != 0) {
             ALOGE("Invalid usage combination.");
diff --git a/libs/rs/driver/rsdAllocation.cpp b/libs/rs/driver/rsdAllocation.cpp
index fb93d82..f358f93 100644
--- a/libs/rs/driver/rsdAllocation.cpp
+++ b/libs/rs/driver/rsdAllocation.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2011-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.
@@ -27,6 +27,7 @@
 #include "hardware/gralloc.h"
 #include "ui/Rect.h"
 #include "ui/GraphicBufferMapper.h"
+#include "gui/SurfaceTexture.h"
 
 #include <GLES/gl.h>
 #include <GLES2/gl2.h>
@@ -139,7 +140,7 @@
 static void UploadToTexture(const Context *rsc, const Allocation *alloc) {
     DrvAllocation *drv = (DrvAllocation *)alloc->mHal.drv;
 
-    if (alloc->mHal.state.usageFlags & RS_ALLOCATION_USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE) {
+    if (alloc->mHal.state.usageFlags & RS_ALLOCATION_USAGE_IO_INPUT) {
         if (!drv->textureID) {
             RSD_CALL_GL(glGenTextures, 1, &drv->textureID);
         }
@@ -475,7 +476,8 @@
 }
 
 void rsdAllocationIoReceive(const Context *rsc, Allocation *alloc) {
-    ALOGE("not implemented");
+    DrvAllocation *drv = (DrvAllocation *)alloc->mHal.drv;
+    alloc->mHal.state.surfaceTexture->updateTexImage();
 }
 
 
diff --git a/libs/rs/driver/rsdCore.cpp b/libs/rs/driver/rsdCore.cpp
index 35a5c08..6a532e9 100644
--- a/libs/rs/driver/rsdCore.cpp
+++ b/libs/rs/driver/rsdCore.cpp
@@ -227,13 +227,13 @@
 
 
     int cpu = sysconf(_SC_NPROCESSORS_ONLN);
-    ALOGV("%p Launching thread(s), CPUs %i", rsc, cpu);
     if(rsc->props.mDebugMaxThreads && (cpu > (int)rsc->props.mDebugMaxThreads)) {
         cpu = rsc->props.mDebugMaxThreads;
     }
     if (cpu < 2) {
         cpu = 0;
     }
+    ALOGV("%p Launching thread(s), CPUs %i", rsc, cpu);
 
     dc->mWorkers.mCount = (uint32_t)cpu;
     dc->mWorkers.mThreadId = (pthread_t *) calloc(dc->mWorkers.mCount, sizeof(pthread_t));
diff --git a/libs/rs/driver/rsdGL.cpp b/libs/rs/driver/rsdGL.cpp
index fae602c..0860417 100644
--- a/libs/rs/driver/rsdGL.cpp
+++ b/libs/rs/driver/rsdGL.cpp
@@ -43,6 +43,7 @@
 #include "rsdFrameBufferObj.h"
 
 #include <gui/SurfaceTextureClient.h>
+#include <gui/DummyConsumer.h>
 
 using namespace android;
 using namespace android::renderscript;
@@ -326,8 +327,11 @@
     }
     gGLContextCount++;
 
-    sp<SurfaceTexture> st(new SurfaceTexture(123));
-    sp<SurfaceTextureClient> stc(new SurfaceTextureClient(st));
+    // Create a BufferQueue with a fake consumer
+    sp<BufferQueue> bq = new BufferQueue();
+    sp<DummyConsumer> dummy = new DummyConsumer(bq);
+    sp<SurfaceTextureClient> stc(new SurfaceTextureClient(static_cast<sp<ISurfaceTexture> >(bq)));
+
     dc->gl.egl.surfaceDefault = eglCreateWindowSurface(dc->gl.egl.display, dc->gl.egl.config,
                                                        static_cast<ANativeWindow*>(stc.get()),
                                                        NULL);
diff --git a/libs/rs/driver/rsdProgram.cpp b/libs/rs/driver/rsdProgram.cpp
index fa4cb0f..a96a5f9 100644
--- a/libs/rs/driver/rsdProgram.cpp
+++ b/libs/rs/driver/rsdProgram.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2011-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.
@@ -41,7 +41,7 @@
                                    textureNames, textureNamesCount, textureNamesLength);
     pv->mHal.drv = drv;
 
-    return drv->createShader();
+    return true;
 }
 
 static void SyncProgramConstants(const Context *rsc, const Program *p) {
@@ -71,10 +71,10 @@
     if(pv->mHal.drv) {
         drv = (RsdShader*)pv->mHal.drv;
         if (rsc->props.mLogShaders) {
-            ALOGV("Destroying vertex shader with ID %u", drv->getShaderID());
+            ALOGV("Destroying vertex shader with ID %u", (uint32_t)pv);
         }
-        if (drv->getShaderID()) {
-            dc->gl.shaderCache->cleanupVertex(drv->getShaderID());
+        if (drv->getStateBasedIDCount()) {
+            dc->gl.shaderCache->cleanupVertex(drv);
         }
         delete drv;
     }
@@ -88,7 +88,7 @@
                                    textureNames, textureNamesCount, textureNamesLength);
     pf->mHal.drv = drv;
 
-    return drv->createShader();
+    return true;
 }
 
 void rsdProgramFragmentSetActive(const Context *rsc, const ProgramFragment *pf) {
@@ -105,10 +105,10 @@
     if(pf->mHal.drv) {
         drv = (RsdShader*)pf->mHal.drv;
         if (rsc->props.mLogShaders) {
-            ALOGV("Destroying fragment shader with ID %u", drv->getShaderID());
+            ALOGV("Destroying fragment shader with ID %u", (uint32_t)pf);
         }
-        if (drv->getShaderID()) {
-            dc->gl.shaderCache->cleanupFragment(drv->getShaderID());
+        if (drv->getStateBasedIDCount()) {
+            dc->gl.shaderCache->cleanupFragment(drv);
         }
         delete drv;
     }
diff --git a/libs/rs/driver/rsdShader.cpp b/libs/rs/driver/rsdShader.cpp
index 1e73b95..6d9fa90 100644
--- a/libs/rs/driver/rsdShader.cpp
+++ b/libs/rs/driver/rsdShader.cpp
@@ -39,34 +39,81 @@
     initMemberVars();
     initAttribAndUniformArray();
     init(textureNames, textureNamesCount, textureNamesLength);
-    createTexturesString(textureNames, textureNamesCount, textureNamesLength);
+
+    for(size_t i=0; i < textureNamesCount; i++) {
+        mTextureNames.push(String8(textureNames[i], textureNamesLength[i]));
+    }
 }
 
 RsdShader::~RsdShader() {
-    if (mShaderID) {
-        glDeleteShader(mShaderID);
+    for (uint32_t i = 0; i < mStateBasedShaders.size(); i ++) {
+        StateBasedKey *state = mStateBasedShaders.itemAt(i);
+        if (state->mShaderID) {
+            glDeleteShader(state->mShaderID);
+        }
+        delete state;
     }
 
     delete[] mAttribNames;
     delete[] mUniformNames;
     delete[] mUniformArraySizes;
-    delete[] mTextureTargets;
 }
 
 void RsdShader::initMemberVars() {
     mDirty = true;
-    mShaderID = 0;
     mAttribCount = 0;
     mUniformCount = 0;
 
     mAttribNames = NULL;
     mUniformNames = NULL;
     mUniformArraySizes = NULL;
-    mTextureTargets = NULL;
+    mCurrentState = NULL;
 
     mIsValid = false;
 }
 
+RsdShader::StateBasedKey *RsdShader::getExistingState() {
+    RsdShader::StateBasedKey *returnKey = NULL;
+
+    for (uint32_t i = 0; i < mStateBasedShaders.size(); i ++) {
+        returnKey = mStateBasedShaders.itemAt(i);
+
+        for (uint32_t ct = 0; ct < mRSProgram->mHal.state.texturesCount; ct ++) {
+            uint32_t texType = 0;
+            if (mRSProgram->mHal.state.textureTargets[ct] == RS_TEXTURE_2D) {
+                Allocation *a = mRSProgram->mHal.state.textures[ct];
+                if (a && a->mHal.state.surfaceTextureID) {
+                    texType = GL_TEXTURE_EXTERNAL_OES;
+                } else {
+                    texType = GL_TEXTURE_2D;
+                }
+            } else {
+                texType = GL_TEXTURE_CUBE_MAP;
+            }
+            if (texType != returnKey->mTextureTargets[ct]) {
+                returnKey = NULL;
+                break;
+            }
+        }
+    }
+    return returnKey;
+}
+
+uint32_t RsdShader::getStateBasedShaderID(const Context *rsc) {
+    StateBasedKey *state = getExistingState();
+    if (state != NULL) {
+        mCurrentState = state;
+        return mCurrentState->mShaderID;
+    }
+    // We have not created a shader for this particular state yet
+    state = new StateBasedKey(mTextureCount);
+    mCurrentState = state;
+    mStateBasedShaders.add(state);
+    createShader();
+    loadShader(rsc);
+    return mCurrentState->mShaderID;
+}
+
 void RsdShader::init(const char** textureNames, size_t textureNamesCount,
                      const size_t *textureNamesLength) {
     uint32_t attribCount = 0;
@@ -138,70 +185,80 @@
     }
 }
 
-void RsdShader::createTexturesString(const char** textureNames, size_t textureNamesCount,
-                                     const size_t *textureNamesLength) {
-    mShaderTextures.setTo("");
+void RsdShader::appendTextures() {
+
+    // TODO: this does not yet handle cases where the texture changes between IO
+    // input and local
+    bool appendUsing = true;
     for (uint32_t ct = 0; ct < mRSProgram->mHal.state.texturesCount; ct ++) {
         if (mRSProgram->mHal.state.textureTargets[ct] == RS_TEXTURE_2D) {
             Allocation *a = mRSProgram->mHal.state.textures[ct];
             if (a && a->mHal.state.surfaceTextureID) {
-                mShaderTextures.append("uniform samplerExternalOES UNI_");
+                if(appendUsing) {
+                    mShader.append("#extension GL_OES_EGL_image_external : require\n");
+                    appendUsing = false;
+                }
+                mShader.append("uniform samplerExternalOES UNI_");
+                mCurrentState->mTextureTargets[ct] = GL_TEXTURE_EXTERNAL_OES;
             } else {
-                mShaderTextures.append("uniform sampler2D UNI_");
+                mShader.append("uniform sampler2D UNI_");
+                mCurrentState->mTextureTargets[ct] = GL_TEXTURE_2D;
             }
-            mTextureTargets[ct] = GL_TEXTURE_2D;
         } else {
-            mShaderTextures.append("uniform samplerCube UNI_");
-            mTextureTargets[ct] = GL_TEXTURE_CUBE_MAP;
+            mShader.append("uniform samplerCube UNI_");
+            mCurrentState->mTextureTargets[ct] = GL_TEXTURE_CUBE_MAP;
         }
 
-        mShaderTextures.append(textureNames[ct], textureNamesLength[ct]);
-        mShaderTextures.append(";\n");
+        mShader.append(mTextureNames[ct]);
+        mShader.append(";\n");
     }
 }
 
 bool RsdShader::createShader() {
-
+    mShader.clear();
     if (mType == GL_FRAGMENT_SHADER) {
         mShader.append("precision mediump float;\n");
     }
     appendUserConstants();
     appendAttributes();
-    mShader.append(mShaderTextures);
-
+    appendTextures();
     mShader.append(mUserShader);
 
     return true;
 }
 
 bool RsdShader::loadShader(const Context *rsc) {
-    mShaderID = glCreateShader(mType);
-    rsAssert(mShaderID);
+    mCurrentState->mShaderID = glCreateShader(mType);
+    rsAssert(mCurrentState->mShaderID);
+
+    if(!mShader.length()) {
+        createShader();
+    }
 
     if (rsc->props.mLogShaders) {
-        ALOGV("Loading shader type %x, ID %i", mType, mShaderID);
+        ALOGV("Loading shader type %x, ID %i", mType, mCurrentState->mShaderID);
         ALOGV("%s", mShader.string());
     }
 
-    if (mShaderID) {
+    if (mCurrentState->mShaderID) {
         const char * ss = mShader.string();
-        RSD_CALL_GL(glShaderSource, mShaderID, 1, &ss, NULL);
-        RSD_CALL_GL(glCompileShader, mShaderID);
+        RSD_CALL_GL(glShaderSource, mCurrentState->mShaderID, 1, &ss, NULL);
+        RSD_CALL_GL(glCompileShader, mCurrentState->mShaderID);
 
         GLint compiled = 0;
-        RSD_CALL_GL(glGetShaderiv, mShaderID, GL_COMPILE_STATUS, &compiled);
+        RSD_CALL_GL(glGetShaderiv, mCurrentState->mShaderID, GL_COMPILE_STATUS, &compiled);
         if (!compiled) {
             GLint infoLen = 0;
-            RSD_CALL_GL(glGetShaderiv, mShaderID, GL_INFO_LOG_LENGTH, &infoLen);
+            RSD_CALL_GL(glGetShaderiv, mCurrentState->mShaderID, GL_INFO_LOG_LENGTH, &infoLen);
             if (infoLen) {
                 char* buf = (char*) malloc(infoLen);
                 if (buf) {
-                    RSD_CALL_GL(glGetShaderInfoLog, mShaderID, infoLen, NULL, buf);
+                    RSD_CALL_GL(glGetShaderInfoLog, mCurrentState->mShaderID, infoLen, NULL, buf);
                     rsc->setError(RS_ERROR_FATAL_PROGRAM_LINK, buf);
                     free(buf);
                 }
-                RSD_CALL_GL(glDeleteShader, mShaderID);
-                mShaderID = 0;
+                RSD_CALL_GL(glDeleteShader, mCurrentState->mShaderID);
+                mCurrentState->mShaderID = 0;
                 return false;
             }
         }
@@ -418,12 +475,14 @@
 
         if (!mRSProgram->mHal.state.textures[ct]) {
             // if nothing is bound, reset to default GL texture
-            RSD_CALL_GL(glBindTexture, mTextureTargets[ct], 0);
+            RSD_CALL_GL(glBindTexture, mCurrentState->mTextureTargets[ct], 0);
             continue;
         }
 
         DrvAllocation *drvTex = (DrvAllocation *)mRSProgram->mHal.state.textures[ct]->mHal.drv;
-        if (drvTex->glTarget != GL_TEXTURE_2D && drvTex->glTarget != GL_TEXTURE_CUBE_MAP) {
+        if (drvTex->glTarget != GL_TEXTURE_2D &&
+            drvTex->glTarget != GL_TEXTURE_CUBE_MAP &&
+            drvTex->glTarget != GL_TEXTURE_EXTERNAL_OES) {
             ALOGE("Attempting to bind unknown texture to shader id %u, texture unit %u",
                   (uint)this, ct);
             rsc->setError(RS_ERROR_BAD_SHADER, "Non-texture allocation bound to a shader");
@@ -523,9 +582,6 @@
     }
 
     mTextureCount = mRSProgram->mHal.state.texturesCount;
-    if (mTextureCount) {
-        mTextureTargets = new uint32_t[mTextureCount];
-    }
 }
 
 void RsdShader::initAddUserElement(const Element *e, String8 *names, uint32_t *arrayLengths,
diff --git a/libs/rs/driver/rsdShader.h b/libs/rs/driver/rsdShader.h
index e32145f..2680b3e 100644
--- a/libs/rs/driver/rsdShader.h
+++ b/libs/rs/driver/rsdShader.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2011-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.
@@ -44,9 +44,13 @@
               const size_t *textureNamesLength);
     virtual ~RsdShader();
 
-    bool createShader();
+    uint32_t getStateBasedShaderID(const android::renderscript::Context *);
 
-    uint32_t getShaderID() const {return mShaderID;}
+    // Add ability to get all ID's to clean up the cached program objects
+    uint32_t getStateBasedIDCount() const { return mStateBasedShaders.size(); }
+    uint32_t getStateBasedID(uint32_t index) const {
+        return mStateBasedShaders.itemAt(index)->mShaderID;
+    }
 
     uint32_t getAttribCount() const {return mAttribCount;}
     uint32_t getUniformCount() const {return mUniformCount;}
@@ -64,6 +68,21 @@
 
 protected:
 
+    class StateBasedKey {
+    public:
+        StateBasedKey(uint32_t texCount) : mShaderID(0) {
+            mTextureTargets = new uint32_t[texCount];
+        }
+        ~StateBasedKey() {
+            delete[] mTextureTargets;
+        }
+        uint32_t mShaderID;
+        uint32_t *mTextureTargets;
+    };
+
+    bool createShader();
+    StateBasedKey *getExistingState();
+
     const android::renderscript::Program *mRSProgram;
     bool mIsValid;
 
@@ -81,26 +100,26 @@
 
     void appendAttributes();
     void appendTextures();
-    void createTexturesString(const char** textureNames, size_t textureNamesCount,
-                              const size_t *textureNamesLength);
 
     void initAttribAndUniformArray();
 
     mutable bool mDirty;
     android::String8 mShader;
     android::String8 mUserShader;
-    android::String8 mShaderTextures;
-    uint32_t mShaderID;
     uint32_t mType;
 
     uint32_t mTextureCount;
-    uint32_t *mTextureTargets;
+    StateBasedKey *mCurrentState;
     uint32_t mAttribCount;
     uint32_t mUniformCount;
     android::String8 *mAttribNames;
     android::String8 *mUniformNames;
     uint32_t *mUniformArraySizes;
 
+    android::Vector<android::String8> mTextureNames;
+
+    android::Vector<StateBasedKey*> mStateBasedShaders;
+
     int32_t mTextureUniformIndexStart;
 
     void logUniform(const android::renderscript::Element *field,
diff --git a/libs/rs/driver/rsdShaderCache.cpp b/libs/rs/driver/rsdShaderCache.cpp
index 89d3c45..69b43fc 100644
--- a/libs/rs/driver/rsdShaderCache.cpp
+++ b/libs/rs/driver/rsdShaderCache.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2011-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.
@@ -108,22 +108,17 @@
 
     RsdShader *vtx = mVertex;
     RsdShader *frag = mFragment;
-    if (!vtx->getShaderID()) {
-        vtx->loadShader(rsc);
-    }
-    if (!frag->getShaderID()) {
-        frag->loadShader(rsc);
-    }
+
+    uint32_t vID = vtx->getStateBasedShaderID(rsc);
+    uint32_t fID = frag->getStateBasedShaderID(rsc);
 
     // Don't try to cache if shaders failed to load
-    if (!vtx->getShaderID() || !frag->getShaderID()) {
+    if (!vID || !fID) {
         return false;
     }
-    //ALOGV("rsdShaderCache lookup  vtx %i, frag %i", vtx->getShaderID(), frag->getShaderID());
     uint32_t entryCount = mEntries.size();
     for (uint32_t ct = 0; ct < entryCount; ct ++) {
-        if ((mEntries[ct]->vtx == vtx->getShaderID()) &&
-            (mEntries[ct]->frag == frag->getShaderID())) {
+        if ((mEntries[ct]->vtx == vID) && (mEntries[ct]->frag == fID)) {
 
             //ALOGV("SC using program %i", mEntries[ct]->program);
             glUseProgram(mEntries[ct]->program);
@@ -134,21 +129,19 @@
         }
     }
 
-    //ALOGV("RsdShaderCache miss");
-    //ALOGE("e0 %x", glGetError());
     ProgramEntry *e = new ProgramEntry(vtx->getAttribCount(),
                                        vtx->getUniformCount(),
                                        frag->getUniformCount());
     mEntries.push(e);
     mCurrent = e;
-    e->vtx = vtx->getShaderID();
-    e->frag = frag->getShaderID();
+    e->vtx = vID;
+    e->frag = fID;
     e->program = glCreateProgram();
     if (e->program) {
         GLuint pgm = e->program;
-        glAttachShader(pgm, vtx->getShaderID());
+        glAttachShader(pgm, vID);
         //ALOGE("e1 %x", glGetError());
-        glAttachShader(pgm, frag->getShaderID());
+        glAttachShader(pgm, fID);
 
         glBindAttribLocation(pgm, 0, "ATTRIB_position");
         glBindAttribLocation(pgm, 1, "ATTRIB_color");
@@ -244,30 +237,38 @@
     return -1;
 }
 
-void RsdShaderCache::cleanupVertex(uint32_t id) {
+void RsdShaderCache::cleanupVertex(RsdShader *s) {
     int32_t numEntries = (int32_t)mEntries.size();
-    for (int32_t ct = 0; ct < numEntries; ct ++) {
-        if (mEntries[ct]->vtx == id) {
-            glDeleteProgram(mEntries[ct]->program);
+    uint32_t numShaderIDs = s->getStateBasedIDCount();
+    for (uint32_t sId = 0; sId < numShaderIDs; sId ++) {
+        uint32_t id = s->getStateBasedID(sId);
+        for (int32_t ct = 0; ct < numEntries; ct ++) {
+            if (mEntries[ct]->vtx == id) {
+                glDeleteProgram(mEntries[ct]->program);
 
-            delete mEntries[ct];
-            mEntries.removeAt(ct);
-            numEntries = (int32_t)mEntries.size();
-            ct --;
+                delete mEntries[ct];
+                mEntries.removeAt(ct);
+                numEntries = (int32_t)mEntries.size();
+                ct --;
+            }
         }
     }
 }
 
-void RsdShaderCache::cleanupFragment(uint32_t id) {
+void RsdShaderCache::cleanupFragment(RsdShader *s) {
     int32_t numEntries = (int32_t)mEntries.size();
-    for (int32_t ct = 0; ct < numEntries; ct ++) {
-        if (mEntries[ct]->frag == id) {
-            glDeleteProgram(mEntries[ct]->program);
+    uint32_t numShaderIDs = s->getStateBasedIDCount();
+    for (uint32_t sId = 0; sId < numShaderIDs; sId ++) {
+        uint32_t id = s->getStateBasedID(sId);
+        for (int32_t ct = 0; ct < numEntries; ct ++) {
+            if (mEntries[ct]->frag == id) {
+                glDeleteProgram(mEntries[ct]->program);
 
-            delete mEntries[ct];
-            mEntries.removeAt(ct);
-            numEntries = (int32_t)mEntries.size();
-            ct --;
+                delete mEntries[ct];
+                mEntries.removeAt(ct);
+                numEntries = (int32_t)mEntries.size();
+                ct --;
+            }
         }
     }
 }
diff --git a/libs/rs/driver/rsdShaderCache.h b/libs/rs/driver/rsdShaderCache.h
index 0beecae..88aa32d 100644
--- a/libs/rs/driver/rsdShaderCache.h
+++ b/libs/rs/driver/rsdShaderCache.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2011-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.
@@ -49,8 +49,8 @@
 
     bool setup(const android::renderscript::Context *rsc);
 
-    void cleanupVertex(uint32_t id);
-    void cleanupFragment(uint32_t id);
+    void cleanupVertex(RsdShader *s);
+    void cleanupFragment(RsdShader *s);
 
     void cleanupAll();
 
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index cf4a391..b373056 100644
--- a/libs/rs/rs.spec
+++ b/libs/rs/rs.spec
@@ -69,6 +69,12 @@
     ret int32_t
 }
 
+AllocationGetSurfaceTextureID2 {
+    param RsAllocation alloc
+    param void *st
+    sync
+}
+
 AllocationSetSurface {
     param RsAllocation alloc
     param RsNativeWindow sur
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index a404c49..cdff49c 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -20,6 +20,7 @@
 #include "rs_hal.h"
 
 #include "system/window.h"
+#include "gui/SurfaceTexture.h"
 
 using namespace android;
 using namespace android::renderscript;
@@ -64,6 +65,7 @@
 
 Allocation::~Allocation() {
     freeChildrenUnlocked();
+    setSurfaceTexture(mRSC, NULL);
     mRSC->mHal.funcs.allocation.destroy(mRSC, this);
 }
 
@@ -424,6 +426,18 @@
     return id;
 }
 
+void Allocation::setSurfaceTexture(const Context *rsc, SurfaceTexture *st) {
+    if(st != mHal.state.surfaceTexture) {
+        if(mHal.state.surfaceTexture != NULL) {
+            mHal.state.surfaceTexture->decStrong(NULL);
+        }
+        mHal.state.surfaceTexture = st;
+        if(mHal.state.surfaceTexture != NULL) {
+            mHal.state.surfaceTexture->incStrong(NULL);
+        }
+    }
+}
+
 void Allocation::setSurface(const Context *rsc, RsNativeWindow sur) {
     ANativeWindow *nw = (ANativeWindow *)sur;
     ANativeWindow *old = mHal.state.wndSurface;
@@ -696,6 +710,11 @@
     return alloc->getSurfaceTextureID(rsc);
 }
 
+void rsi_AllocationGetSurfaceTextureID2(Context *rsc, RsAllocation valloc, void *vst, size_t len) {
+    Allocation *alloc = static_cast<Allocation *>(valloc);
+    alloc->setSurfaceTexture(rsc, static_cast<SurfaceTexture *>(vst));
+}
+
 void rsi_AllocationSetSurface(Context *rsc, RsAllocation valloc, RsNativeWindow sur) {
     Allocation *alloc = static_cast<Allocation *>(valloc);
     alloc->setSurface(rsc, sur);
diff --git a/libs/rs/rsAllocation.h b/libs/rs/rsAllocation.h
index 58a6fca..e2783d2 100644
--- a/libs/rs/rsAllocation.h
+++ b/libs/rs/rsAllocation.h
@@ -23,6 +23,8 @@
 
 // ---------------------------------------------------------------------------
 namespace android {
+class SurfaceTexture;
+
 namespace renderscript {
 
 class Program;
@@ -60,6 +62,7 @@
             void * usrPtr;
             int32_t surfaceTextureID;
             ANativeWindow *wndSurface;
+            SurfaceTexture *surfaceTexture;
         };
         State state;
 
@@ -130,6 +133,7 @@
     }
 
     int32_t getSurfaceTextureID(const Context *rsc);
+    void setSurfaceTexture(const Context *rsc, SurfaceTexture *st);
     void setSurface(const Context *rsc, RsNativeWindow sur);
     void ioSend(const Context *rsc);
     void ioReceive(const Context *rsc);
diff --git a/libs/rs/rsDefines.h b/libs/rs/rsDefines.h
index 990ef26..0e0cd8d 100644
--- a/libs/rs/rsDefines.h
+++ b/libs/rs/rsDefines.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2007-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.
@@ -100,9 +100,8 @@
     RS_ALLOCATION_USAGE_GRAPHICS_VERTEX = 0x0004,
     RS_ALLOCATION_USAGE_GRAPHICS_CONSTANTS = 0x0008,
     RS_ALLOCATION_USAGE_GRAPHICS_RENDER_TARGET = 0x0010,
-    RS_ALLOCATION_USAGE_GRAPHICS_SURFACE_TEXTURE_INPUT_OPAQUE = 0x0020,
-    RS_ALLOCATION_USAGE_IO_INPUT = 0x0040,
-    RS_ALLOCATION_USAGE_IO_OUTPUT = 0x0080,
+    RS_ALLOCATION_USAGE_IO_INPUT = 0x0020,
+    RS_ALLOCATION_USAGE_IO_OUTPUT = 0x0040,
 
     RS_ALLOCATION_USAGE_ALL = 0x00FF
 };
diff --git a/libs/rs/scriptc/rs_allocation.rsh b/libs/rs/scriptc/rs_allocation.rsh
index 89696b8..b0840a0 100644
--- a/libs/rs/scriptc/rs_allocation.rsh
+++ b/libs/rs/scriptc/rs_allocation.rsh
@@ -14,31 +14,6 @@
  * limitations under the License.
  */
 
-/*! \mainpage notitle
- *
- * Renderscript is a high-performance runtime that provides graphics rendering and
- * compute operations at the native level. Renderscript code is compiled on devices
- * at runtime to allow platform-independence as well.
- * This reference documentation describes the Renderscript runtime APIs, which you
- * can utilize to write Renderscript code in C99. The Renderscript header
- * files are automatically included for you, except for the rs_graphics.rsh header. If
- * you are doing graphics rendering, include the graphics header file like this:
- *
- * <code>#include "rs_graphics.rsh"</code>
- *
- * To use Renderscript, you need to utilize the Renderscript runtime APIs documented here
- * as well as the Android framework APIs for Renderscript.
- * For documentation on the Android framework APIs, see the <a target="_parent" href=
- * "http://developer.android.com/reference/android/renderscript/package-summary.html">
- * android.renderscript</a> package reference.
- * For more information on how to develop with Renderscript and how the runtime and
- * Android framework APIs interact, see the <a target="_parent" href=
- * "http://developer.android.com/guide/topics/renderscript/index.html">Renderscript
- * developer guide</a> and the <a target="_parent" href=
- * "http://developer.android.com/resources/samples/RenderScript/index.html">
- * Renderscript samples</a>.
- */
-
 /** @file rs_allocation.rsh
  *  \brief Allocation routines
  *
@@ -168,6 +143,8 @@
 extern const void * __attribute__((overloadable))
     rsGetElementAt(rs_allocation, uint32_t x, uint32_t y, uint32_t z);
 
+#if (defined(RS_VERSION) && (RS_VERSION >= 16))
+
 /**
  * @param a allocation to get data from
  * @return element describing allocation layout
@@ -176,129 +153,6 @@
     rsAllocationGetElement(rs_allocation a);
 
 /**
- * @param m mesh to get data from
- * @return number of allocations in the mesh that contain vertex
- *         data
- */
-extern uint32_t __attribute__((overloadable))
-    rsMeshGetVertexAllocationCount(rs_mesh m);
-
-/**
- * @param m mesh to get data from
- * @return number of primitive groups in the mesh. This would
- *         include simple primitives as well as allocations
- *         containing index data
- */
-extern uint32_t __attribute__((overloadable))
-    rsMeshGetPrimitiveCount(rs_mesh m);
-
-/**
- * @param m mesh to get data from
- * @param index index of the vertex allocation
- * @return allocation containing vertex data
- */
-extern rs_allocation __attribute__((overloadable))
-    rsMeshGetVertexAllocation(rs_mesh m, uint32_t index);
-
-/**
- * @param m mesh to get data from
- * @param index index of the index allocation
- * @return allocation containing index data
- */
-extern rs_allocation __attribute__((overloadable))
-    rsMeshGetIndexAllocation(rs_mesh m, uint32_t index);
-
-/**
- * @param m mesh to get data from
- * @param index index of the primitive
- * @return primitive describing how the mesh is rendered
- */
-extern rs_primitive __attribute__((overloadable))
-    rsMeshGetPrimitive(rs_mesh m, uint32_t index);
-
-/**
- * @param e element to get data from
- * @return number of sub-elements in this element
- */
-extern uint32_t __attribute__((overloadable))
-    rsElementGetSubElementCount(rs_element e);
-
-/**
- * @param e element to get data from
- * @param index index of the sub-element to return
- * @return sub-element in this element at given index
- */
-extern rs_element __attribute__((overloadable))
-    rsElementGetSubElement(rs_element, uint32_t index);
-
-/**
- * @param e element to get data from
- * @param index index of the sub-element to return
- * @return length of the sub-element name including the null
- *         terminator (size of buffer needed to write the name)
- */
-extern uint32_t __attribute__((overloadable))
-    rsElementGetSubElementNameLength(rs_element e, uint32_t index);
-
-/**
- * @param e element to get data from
- * @param index index of the sub-element
- * @param name array to store the name into
- * @param nameLength length of the provided name array
- * @return number of characters actually written, excluding the
- *         null terminator
- */
-extern uint32_t __attribute__((overloadable))
-    rsElementGetSubElementName(rs_element e, uint32_t index, char *name, uint32_t nameLength);
-
-/**
- * @param e element to get data from
- * @param index index of the sub-element
- * @return array size of sub-element in this element at given
- *         index
- */
-extern uint32_t __attribute__((overloadable))
-    rsElementGetSubElementArraySize(rs_element e, uint32_t index);
-
-/**
- * @param e element to get data from
- * @param index index of the sub-element
- * @return offset in bytes of sub-element in this element at
- *         given index
- */
-extern uint32_t __attribute__((overloadable))
-    rsElementGetSubElementOffsetBytes(rs_element e, uint32_t index);
-
-/**
- * @param e element to get data from
- * @return total size of the element in bytes
- */
-extern uint32_t __attribute__((overloadable))
-    rsElementGetSizeBytes(rs_element e);
-
-/**
- * @param e element to get data from
- * @return element's data type
- */
-extern rs_data_type __attribute__((overloadable))
-    rsElementGetDataType(rs_element e);
-
-/**
- * @param e element to get data from
- * @return element's data size
- */
-extern rs_data_kind __attribute__((overloadable))
-    rsElementGetDataKind(rs_element e);
-
-/**
- * @param e element to get data from
- * @return length of the element vector (for float2, float3,
- *         etc.)
- */
-extern uint32_t __attribute__((overloadable))
-    rsElementGetVectorSize(rs_element e);
-
-/**
  * Fetch allocation in a way described by the sampler
  * @param a 1D allocation to sample from
  * @param s sampler state
@@ -339,26 +193,7 @@
 extern const float4 __attribute__((overloadable))
     rsSample(rs_allocation a, rs_sampler s, float2 location, float lod);
 
-/**
- * Fetch allocation in a way described by the sampler
- * @param a 3D allocation to sample from
- * @param s sampler state
- * @param location to sample from
- */
-extern const float4 __attribute__((overloadable))
-    rsSample(rs_allocation a, rs_sampler s, float3 location);
-
-/**
- * Fetch allocation in a way described by the sampler
- * @param a 3D allocation to sample from
- * @param s sampler state
- * @param location to sample from
- * @param lod mip level to sample from, for fractional values
- *            mip levels will be interpolated if
- *            RS_SAMPLER_LINEAR_MIP_LINEAR is used
- */
-extern const float4 __attribute__((overloadable))
-    rsSample(rs_allocation a, rs_sampler s, float3 location, float lod);
+#endif // (defined(RS_VERSION) && (RS_VERSION >= 16))
 
 #endif
 
diff --git a/libs/rs/scriptc/rs_core.rsh b/libs/rs/scriptc/rs_core.rsh
index be900cb..1b0f9db 100644
--- a/libs/rs/scriptc/rs_core.rsh
+++ b/libs/rs/scriptc/rs_core.rsh
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2011-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.
@@ -14,6 +14,31 @@
  * limitations under the License.
  */
 
+ /*! \mainpage notitle
+  *
+  * Renderscript is a high-performance runtime that provides graphics rendering and
+  * compute operations at the native level. Renderscript code is compiled on devices
+  * at runtime to allow platform-independence as well.
+  * This reference documentation describes the Renderscript runtime APIs, which you
+  * can utilize to write Renderscript code in C99. The Renderscript header
+  * files are automatically included for you, except for the rs_graphics.rsh header. If
+  * you are doing graphics rendering, include the graphics header file like this:
+  *
+  * <code>#include "rs_graphics.rsh"</code>
+  *
+  * To use Renderscript, you need to utilize the Renderscript runtime APIs documented here
+  * as well as the Android framework APIs for Renderscript.
+  * For documentation on the Android framework APIs, see the <a target="_parent" href=
+  * "http://developer.android.com/reference/android/renderscript/package-summary.html">
+  * android.renderscript</a> package reference.
+  * For more information on how to develop with Renderscript and how the runtime and
+  * Android framework APIs interact, see the <a target="_parent" href=
+  * "http://developer.android.com/guide/topics/renderscript/index.html">Renderscript
+  * developer guide</a> and the <a target="_parent" href=
+  * "http://developer.android.com/resources/samples/RenderScript/index.html">
+  * Renderscript samples</a>.
+  */
+
 /** @file rs_core.rsh
  *  \brief todo-jsams
  *
@@ -31,14 +56,14 @@
 #include "rs_atomic.rsh"
 #include "rs_cl.rsh"
 #include "rs_debug.rsh"
+#include "rs_element.rsh"
 #include "rs_math.rsh"
 #include "rs_matrix.rsh"
 #include "rs_object.rsh"
 #include "rs_quaternion.rsh"
+#include "rs_sampler.rsh"
 #include "rs_time.rsh"
 
-
-
 /**
  * Send a message back to the client.  Will not block and returns true
  * if the message was sendable and false if the fifo was full.
diff --git a/libs/rs/scriptc/rs_element.rsh b/libs/rs/scriptc/rs_element.rsh
new file mode 100644
index 0000000..1a4cdb75
--- /dev/null
+++ b/libs/rs/scriptc/rs_element.rsh
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+/** @file rs_element.rsh
+ *  \brief Element routines
+ *
+ *
+ */
+
+#ifndef __RS_ELEMENT_RSH__
+#define __RS_ELEMENT_RSH__
+
+#if (defined(RS_VERSION) && (RS_VERSION >= 16))
+
+/**
+ * @param e element to get data from
+ * @return number of sub-elements in this element
+ */
+extern uint32_t __attribute__((overloadable))
+    rsElementGetSubElementCount(rs_element e);
+
+/**
+ * @param e element to get data from
+ * @param index index of the sub-element to return
+ * @return sub-element in this element at given index
+ */
+extern rs_element __attribute__((overloadable))
+    rsElementGetSubElement(rs_element, uint32_t index);
+
+/**
+ * @param e element to get data from
+ * @param index index of the sub-element to return
+ * @return length of the sub-element name including the null
+ *         terminator (size of buffer needed to write the name)
+ */
+extern uint32_t __attribute__((overloadable))
+    rsElementGetSubElementNameLength(rs_element e, uint32_t index);
+
+/**
+ * @param e element to get data from
+ * @param index index of the sub-element
+ * @param name array to store the name into
+ * @param nameLength length of the provided name array
+ * @return number of characters actually written, excluding the
+ *         null terminator
+ */
+extern uint32_t __attribute__((overloadable))
+    rsElementGetSubElementName(rs_element e, uint32_t index, char *name, uint32_t nameLength);
+
+/**
+ * @param e element to get data from
+ * @param index index of the sub-element
+ * @return array size of sub-element in this element at given
+ *         index
+ */
+extern uint32_t __attribute__((overloadable))
+    rsElementGetSubElementArraySize(rs_element e, uint32_t index);
+
+/**
+ * @param e element to get data from
+ * @param index index of the sub-element
+ * @return offset in bytes of sub-element in this element at
+ *         given index
+ */
+extern uint32_t __attribute__((overloadable))
+    rsElementGetSubElementOffsetBytes(rs_element e, uint32_t index);
+
+/**
+ * @param e element to get data from
+ * @return total size of the element in bytes
+ */
+extern uint32_t __attribute__((overloadable))
+    rsElementGetSizeBytes(rs_element e);
+
+/**
+ * @param e element to get data from
+ * @return element's data type
+ */
+extern rs_data_type __attribute__((overloadable))
+    rsElementGetDataType(rs_element e);
+
+/**
+ * @param e element to get data from
+ * @return element's data size
+ */
+extern rs_data_kind __attribute__((overloadable))
+    rsElementGetDataKind(rs_element e);
+
+/**
+ * @param e element to get data from
+ * @return length of the element vector (for float2, float3,
+ *         etc.)
+ */
+extern uint32_t __attribute__((overloadable))
+    rsElementGetVectorSize(rs_element e);
+
+#endif // (defined(RS_VERSION) && (RS_VERSION >= 16))
+
+#endif // __RS_ELEMENT_RSH__
+
diff --git a/libs/rs/scriptc/rs_graphics.rsh b/libs/rs/scriptc/rs_graphics.rsh
index e3fde828..44ee99f 100644
--- a/libs/rs/scriptc/rs_graphics.rsh
+++ b/libs/rs/scriptc/rs_graphics.rsh
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2011-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.
@@ -23,6 +23,9 @@
 #ifndef __RS_GRAPHICS_RSH__
 #define __RS_GRAPHICS_RSH__
 
+#include "rs_mesh.rsh"
+#include "rs_program.rsh"
+
 #if (defined(RS_VERSION) && (RS_VERSION >= 14))
 /**
  * Set the color target used for all subsequent rendering calls
@@ -83,88 +86,6 @@
 extern void __attribute__((overloadable))
     rsgBindProgramStore(rs_program_store ps);
 
-
-/**
- * @hide
- * Get program store depth function
- *
- * @param ps
- */
-extern rs_depth_func __attribute__((overloadable))
-    rsgProgramStoreGetDepthFunc(rs_program_store ps);
-
-/**
- * @hide
- * Get program store depth mask
- *
- * @param ps
- */
-extern bool __attribute__((overloadable))
-    rsgProgramStoreGetDepthMask(rs_program_store ps);
-/**
- * @hide
- * Get program store red component color mask
- *
- * @param ps
- */
-extern bool __attribute__((overloadable))
-    rsgProgramStoreGetColorMaskR(rs_program_store ps);
-
-/**
- * @hide
- * Get program store green component color mask
- *
- * @param ps
- */
-extern bool __attribute__((overloadable))
-    rsgProgramStoreGetColorMaskG(rs_program_store ps);
-
-/**
- * @hide
- * Get program store blur component color mask
- *
- * @param ps
- */
-extern bool __attribute__((overloadable))
-    rsgProgramStoreGetColorMaskB(rs_program_store ps);
-
-/**
- * @hide
- * Get program store alpha component color mask
- *
- * @param ps
- */
-extern bool __attribute__((overloadable))
-    rsgProgramStoreGetColorMaskA(rs_program_store ps);
-
-/**
- * @hide
- * Get program store blend source function
- *
- * @param ps
- */
-extern rs_blend_src_func __attribute__((overloadable))
-        rsgProgramStoreGetBlendSrcFunc(rs_program_store ps);
-
-/**
- * @hide
- * Get program store blend destination function
- *
- * @param ps
- */
-extern rs_blend_dst_func __attribute__((overloadable))
-    rsgProgramStoreGetBlendDstFunc(rs_program_store ps);
-
-/**
- * @hide
- * Get program store dither state
- *
- * @param ps
- */
-extern bool __attribute__((overloadable))
-    rsgProgramStoreGetDitherEnabled(rs_program_store ps);
-
-
 /**
  * Bind a new ProgramVertex to the rendering context.
  *
@@ -182,24 +103,6 @@
     rsgBindProgramRaster(rs_program_raster pr);
 
 /**
- * @hide
- * Get program raster point sprite state
- *
- * @param pr
- */
-extern bool __attribute__((overloadable))
-    rsgProgramRasterGetPointSpriteEnabled(rs_program_raster pr);
-
-/**
- * @hide
- * Get program raster cull mode
- *
- * @param pr
- */
-extern rs_cull_mode __attribute__((overloadable))
-    rsgProgramRasterGetCullMode(rs_program_raster pr);
-
-/**
  * Bind a new Sampler object to a ProgramFragment.  The sampler will
  * operate on the texture bound at the matching slot.
  *
@@ -209,51 +112,6 @@
     rsgBindSampler(rs_program_fragment, uint slot, rs_sampler);
 
 /**
- * @hide
- * Get sampler minification value
- *
- * @param pr
- */
-extern rs_sampler_value __attribute__((overloadable))
-    rsgSamplerGetMinification(rs_sampler s);
-
-/**
- * @hide
- * Get sampler magnification value
- *
- * @param pr
- */
-extern rs_sampler_value __attribute__((overloadable))
-    rsgSamplerGetMagnification(rs_sampler s);
-
-/**
- * @hide
- * Get sampler wrap S value
- *
- * @param pr
- */
-extern rs_sampler_value __attribute__((overloadable))
-    rsgSamplerGetWrapS(rs_sampler s);
-
-/**
- * @hide
- * Get sampler wrap T value
- *
- * @param pr
- */
-extern rs_sampler_value __attribute__((overloadable))
-    rsgSamplerGetWrapT(rs_sampler s);
-
-/**
- * @hide
- * Get sampler anisotropy
- *
- * @param pr
- */
-extern float __attribute__((overloadable))
-    rsgSamplerGetAnisotropy(rs_sampler s);
-
-/**
  * Bind a new Allocation object to a ProgramFragment.  The
  * Allocation must be a valid texture for the Program.  The sampling
  * of the texture will be controled by the Sampler bound at the
diff --git a/libs/rs/scriptc/rs_mesh.rsh b/libs/rs/scriptc/rs_mesh.rsh
new file mode 100644
index 0000000..87ffd33
--- /dev/null
+++ b/libs/rs/scriptc/rs_mesh.rsh
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+/** @file rs_mesh.rsh
+ *  \brief Mesh routines
+ *
+ *
+ */
+
+#ifndef __RS_MESH_RSH__
+#define __RS_MESH_RSH__
+
+#if (defined(RS_VERSION) && (RS_VERSION >= 16))
+
+/**
+ * @param m mesh to get data from
+ * @return number of allocations in the mesh that contain vertex
+ *         data
+ */
+extern uint32_t __attribute__((overloadable))
+    rsgMeshGetVertexAllocationCount(rs_mesh m);
+
+/**
+ * @param m mesh to get data from
+ * @return number of primitive groups in the mesh. This would
+ *         include simple primitives as well as allocations
+ *         containing index data
+ */
+extern uint32_t __attribute__((overloadable))
+    rsgMeshGetPrimitiveCount(rs_mesh m);
+
+/**
+ * @param m mesh to get data from
+ * @param index index of the vertex allocation
+ * @return allocation containing vertex data
+ */
+extern rs_allocation __attribute__((overloadable))
+    rsgMeshGetVertexAllocation(rs_mesh m, uint32_t index);
+
+/**
+ * @param m mesh to get data from
+ * @param index index of the index allocation
+ * @return allocation containing index data
+ */
+extern rs_allocation __attribute__((overloadable))
+    rsgMeshGetIndexAllocation(rs_mesh m, uint32_t index);
+
+/**
+ * @param m mesh to get data from
+ * @param index index of the primitive
+ * @return primitive describing how the mesh is rendered
+ */
+extern rs_primitive __attribute__((overloadable))
+    rsgMeshGetPrimitive(rs_mesh m, uint32_t index);
+
+#endif // (defined(RS_VERSION) && (RS_VERSION >= 16))
+
+#endif // __RS_MESH_RSH__
+
diff --git a/libs/rs/scriptc/rs_program.rsh b/libs/rs/scriptc/rs_program.rsh
new file mode 100644
index 0000000..6a9929e
--- /dev/null
+++ b/libs/rs/scriptc/rs_program.rsh
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+/** @file rs_program.rsh
+ *  \brief Program object routines
+ *
+ *
+ */
+
+#ifndef __RS_PROGRAM_RSH__
+#define __RS_PROGRAM_RSH__
+
+#if (defined(RS_VERSION) && (RS_VERSION >= 16))
+
+/**
+ * @hide
+ * Get program store depth function
+ *
+ * @param ps
+ */
+extern rs_depth_func __attribute__((overloadable))
+    rsgProgramStoreGetDepthFunc(rs_program_store ps);
+
+/**
+ * @hide
+ * Get program store depth mask
+ *
+ * @param ps
+ */
+extern bool __attribute__((overloadable))
+    rsgProgramStoreGetDepthMask(rs_program_store ps);
+/**
+ * @hide
+ * Get program store red component color mask
+ *
+ * @param ps
+ */
+extern bool __attribute__((overloadable))
+    rsgProgramStoreGetColorMaskR(rs_program_store ps);
+
+/**
+ * @hide
+ * Get program store green component color mask
+ *
+ * @param ps
+ */
+extern bool __attribute__((overloadable))
+    rsgProgramStoreGetColorMaskG(rs_program_store ps);
+
+/**
+ * @hide
+ * Get program store blur component color mask
+ *
+ * @param ps
+ */
+extern bool __attribute__((overloadable))
+    rsgProgramStoreGetColorMaskB(rs_program_store ps);
+
+/**
+ * @hide
+ * Get program store alpha component color mask
+ *
+ * @param ps
+ */
+extern bool __attribute__((overloadable))
+    rsgProgramStoreGetColorMaskA(rs_program_store ps);
+
+/**
+ * @hide
+ * Get program store blend source function
+ *
+ * @param ps
+ */
+extern rs_blend_src_func __attribute__((overloadable))
+        rsgProgramStoreGetBlendSrcFunc(rs_program_store ps);
+
+/**
+ * @hide
+ * Get program store blend destination function
+ *
+ * @param ps
+ */
+extern rs_blend_dst_func __attribute__((overloadable))
+    rsgProgramStoreGetBlendDstFunc(rs_program_store ps);
+
+/**
+ * @hide
+ * Get program store dither state
+ *
+ * @param ps
+ */
+extern bool __attribute__((overloadable))
+    rsgProgramStoreGetDitherEnabled(rs_program_store ps);
+
+/**
+ * @hide
+ * Get program raster point sprite state
+ *
+ * @param pr
+ */
+extern bool __attribute__((overloadable))
+    rsgProgramRasterGetPointSpriteEnabled(rs_program_raster pr);
+
+/**
+ * @hide
+ * Get program raster cull mode
+ *
+ * @param pr
+ */
+extern rs_cull_mode __attribute__((overloadable))
+    rsgProgramRasterGetCullMode(rs_program_raster pr);
+
+#endif // (defined(RS_VERSION) && (RS_VERSION >= 16))
+
+#endif // __RS_PROGRAM_RSH__
+
diff --git a/libs/rs/scriptc/rs_sampler.rsh b/libs/rs/scriptc/rs_sampler.rsh
new file mode 100644
index 0000000..c8948c7
--- /dev/null
+++ b/libs/rs/scriptc/rs_sampler.rsh
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+/** @file rs_sampler.rsh
+ *  \brief Sampler routines
+ *
+ *
+ */
+
+#ifndef __RS_SAMPLER_RSH__
+#define __RS_SAMPLER_RSH__
+
+#if (defined(RS_VERSION) && (RS_VERSION >= 16))
+
+/**
+ * @hide
+ * Get sampler minification value
+ *
+ * @param pr
+ */
+extern rs_sampler_value __attribute__((overloadable))
+    rsSamplerGetMinification(rs_sampler s);
+
+/**
+ * @hide
+ * Get sampler magnification value
+ *
+ * @param pr
+ */
+extern rs_sampler_value __attribute__((overloadable))
+    rsSamplerGetMagnification(rs_sampler s);
+
+/**
+ * @hide
+ * Get sampler wrap S value
+ *
+ * @param pr
+ */
+extern rs_sampler_value __attribute__((overloadable))
+    rsSamplerGetWrapS(rs_sampler s);
+
+/**
+ * @hide
+ * Get sampler wrap T value
+ *
+ * @param pr
+ */
+extern rs_sampler_value __attribute__((overloadable))
+    rsSamplerGetWrapT(rs_sampler s);
+
+/**
+ * @hide
+ * Get sampler anisotropy
+ *
+ * @param pr
+ */
+extern float __attribute__((overloadable))
+    rsSamplerGetAnisotropy(rs_sampler s);
+
+#endif // (defined(RS_VERSION) && (RS_VERSION >= 16))
+
+#endif // __RS_SAMPLER_RSH__
+
diff --git a/libs/rs/scriptc/rs_types.rsh b/libs/rs/scriptc/rs_types.rsh
index f8c2657..10617d8 100644
--- a/libs/rs/scriptc/rs_types.rsh
+++ b/libs/rs/scriptc/rs_types.rsh
@@ -402,6 +402,8 @@
 
 #endif //defined(RS_VERSION) && (RS_VERSION >= 14)
 
+#if (defined(RS_VERSION) && (RS_VERSION >= 16))
+
 /**
  * Describes the way mesh vertex data is interpreted when rendering
  *
@@ -552,4 +554,6 @@
     RS_SAMPLER_INVALID              = 100,
 } rs_sampler_value;
 
-#endif
+#endif // (defined(RS_VERSION) && (RS_VERSION >= 16))
+
+#endif // __RS_TYPES_RSH__
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 9748d3b..82dd308 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -608,16 +608,15 @@
      * Adjusts the master volume for the device's audio amplifier.
      * <p>
      *
-     * @param direction The direction to adjust the volume. One of
-     *            {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or
-     *            {@link #ADJUST_SAME}.
+     * @param steps The number of volume steps to adjust. A positive
+     *            value will raise the volume.
      * @param flags One or more flags.
      * @hide
      */
-    public void adjustMasterVolume(int direction, int flags) {
+    public void adjustMasterVolume(int steps, int flags) {
         IAudioService service = getService();
         try {
-            service.adjustMasterVolume(direction, flags);
+            service.adjustMasterVolume(steps, flags);
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in adjustMasterVolume", e);
         }
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index aa60d0a..5f6a61d 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -159,6 +159,9 @@
     // but to support integer based AudioManager API we translate it to 0 - 100
     private static final int MAX_MASTER_VOLUME = 100;
 
+    // Maximum volume adjust steps allowed in a single batch call.
+    private static final int MAX_BATCH_VOLUME_ADJUST_STEPS = 4;
+
     /* Sound effect file names  */
     private static final String SOUND_EFFECTS_PATH = "/media/audio/ui/";
     private static final String[] SOUND_EFFECT_FILES = new String[] {
@@ -619,29 +622,19 @@
     }
 
     /** @see AudioManager#adjustMasterVolume(int) */
-    public void adjustMasterVolume(int direction, int flags) {
-        ensureValidDirection(direction);
+    public void adjustMasterVolume(int steps, int flags) {
+        ensureValidSteps(steps);
         int volume = Math.round(AudioSystem.getMasterVolume() * MAX_MASTER_VOLUME);
         int delta = 0;
-        for (int i = 0; i < mMasterVolumeRamp.length; i += 2) {
-            int testVolume = mMasterVolumeRamp[i];
-            int testDelta =  mMasterVolumeRamp[i + 1];
-            if (direction == AudioManager.ADJUST_RAISE) {
-                if (volume >= testVolume) {
-                    delta = testDelta;
-                } else {
-                    break;
-                }
-            } else if (direction == AudioManager.ADJUST_LOWER) {
-                if (volume - testDelta >= testVolume) {
-                    delta = -testDelta;
-                } else {
-                    break;
-                }
-            }
+        int numSteps = Math.abs(steps);
+        int direction = steps > 0 ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
+        for (int i = 0; i < numSteps; ++i) {
+            delta = findVolumeDelta(direction, volume);
+            volume += delta;
         }
-//        Log.d(TAG, "adjustMasterVolume volume: " + volume + " delta: " + delta + " direction: " + direction);
-        setMasterVolume(volume + delta, flags);
+
+        //Log.d(TAG, "adjustMasterVolume volume: " + volume + " steps: " + steps);
+        setMasterVolume(volume, flags);
     }
 
     /** @see AudioManager#setStreamVolume(int, int, int) */
@@ -682,6 +675,41 @@
         sendVolumeUpdate(streamType, oldIndex, index, flags);
     }
 
+    private int findVolumeDelta(int direction, int volume) {
+        int delta = 0;
+        if (direction == AudioManager.ADJUST_RAISE) {
+            if (volume == MAX_MASTER_VOLUME) {
+                return 0;
+            }
+            // This is the default value if we make it to the end
+            delta = mMasterVolumeRamp[1];
+            // If we're raising the volume move down the ramp array until we
+            // find the volume we're above and use that groups delta.
+            for (int i = mMasterVolumeRamp.length - 1; i > 1; i -= 2) {
+                if (volume >= mMasterVolumeRamp[i - 1]) {
+                    delta = mMasterVolumeRamp[i];
+                    break;
+                }
+            }
+        } else if (direction == AudioManager.ADJUST_LOWER){
+            if (volume == 0) {
+                return 0;
+            }
+            int length = mMasterVolumeRamp.length;
+            // This is the default value if we make it to the end
+            delta = -mMasterVolumeRamp[length - 1];
+            // If we're lowering the volume move up the ramp array until we
+            // find the volume we're below and use the group below it's delta
+            for (int i = 2; i < length; i += 2) {
+                if (volume <= mMasterVolumeRamp[i]) {
+                    delta = -mMasterVolumeRamp[i - 1];
+                    break;
+                }
+            }
+        }
+        return delta;
+    }
+
     // UI update and Broadcast Intent
     private void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) {
         if (!mVoiceCapable && (streamType == AudioSystem.STREAM_RING)) {
@@ -1862,6 +1890,12 @@
         }
     }
 
+    private void ensureValidSteps(int steps) {
+        if (Math.abs(steps) > MAX_BATCH_VOLUME_ADJUST_STEPS) {
+            throw new IllegalArgumentException("Bad volume adjust steps " + steps);
+        }
+    }
+
     private void ensureValidStreamType(int streamType) {
         if (streamType < 0 || streamType >= mStreamStates.length) {
             throw new IllegalArgumentException("Bad stream type " + streamType);
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index bccf1f9..d06e302 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -48,6 +48,7 @@
     public static int FLAG_SYNCFRAME   = 1;
     public static int FLAG_CODECCONFIG = 2;
     public static int FLAG_EOS         = 4;
+    public static int FLAG_ENCRYPTED   = 8;
 
     /** Instantiate a codec component by mime type. For decoder components
         this is the mime type of media that this decoder should be able to
@@ -82,6 +83,7 @@
     public native final void release();
 
     public static int CONFIGURE_FLAG_ENCODE = 1;
+    public static int CONFIGURE_FLAG_SECURE = 2;
 
     /** Configures a component.
      *  @param format A map of string/value pairs describing the input format
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 5732c72..9ea3d0e 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -65,6 +65,13 @@
     // Returns the current sample's presentation time in microseconds.
     public native long getSampleTime();
 
+    // Keep these in sync with their equivalents in NuMediaExtractor.h
+    public static final int SAMPLE_FLAG_SYNC      = 1;
+    public static final int SAMPLE_FLAG_ENCRYPTED = 2;
+
+    // Returns the current sample's flags.
+    public native int getSampleFlags();
+
     private static native final void native_init();
     private native final void native_setup(String path);
     private native final void native_finalize();
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index a08d6c3..a8144a7 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -1396,6 +1396,7 @@
         }
 
         mMtpObjectHandle = objectHandle;
+        Cursor fileList = null;
         try {
             if (MediaFile.isPlayListFileType(fileType)) {
                 // build file cache so we can look up tracks in the playlist
@@ -1403,7 +1404,9 @@
 
                 FileEntry entry = makeEntryFor(path);
                 if (entry != null) {
-                    processPlayList(entry);
+                    fileList = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
+                            null, null, null, null);
+                    processPlayList(entry, fileList);
                 }
             } else {
                 // MTP will create a file entry for us so we don't want to do it in prescan
@@ -1417,6 +1420,9 @@
             Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
         } finally {
             mMtpObjectHandle = 0;
+            if (fileList != null) {
+                fileList.close();
+            }
         }
     }
 
@@ -1479,7 +1485,7 @@
     }
 
     private boolean addPlayListEntry(String entry, String playListDirectory,
-            Uri uri, ContentValues values, int index) {
+            Uri uri, ContentValues values, int index, Cursor fileList) {
 
         // watch for trailing whitespace
         int entryLength = entry.length();
@@ -1506,19 +1512,20 @@
         // number of rightmost file/directory names for bestMatch
         int bestMatchLength = 0;
 
-        Cursor c = null;
-        try {
-            c = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
-                    null, null, null, null);
-        } catch (RemoteException e1) {
-        }
-
-        if (c != null) {
-            while (c.moveToNext()) {
-                long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
-                String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
-                int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
-                long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
+        if (fileList != null) {
+            int count = fileList.getCount();
+            // Backing up a little in the cursor helps when the files in the
+            // playlist are not in the same order as they are in the database
+            // but are still close.
+            fileList.move(-1000);
+            while(--count >= 0) {
+                if (!fileList.moveToNext()) {
+                    fileList.moveToFirst();
+                }
+                long rowId = fileList.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
+                String path = fileList.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
+                int format = fileList.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
+                long lastModified = fileList.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
 
                 if (path.equalsIgnoreCase(entry)) {
                     bestMatch = new FileEntry(rowId, path, lastModified, format);
@@ -1531,7 +1538,6 @@
                     bestMatchLength = matchLength;
                 }
             }
-            c.close();
         }
 
         if (bestMatch == null) {
@@ -1541,7 +1547,7 @@
         try {
             // check rowid is set. Rowid may be missing if it is inserted by bulkInsert().
             if (bestMatch.mRowId == 0) {
-                c = mMediaProvider.query(mAudioUri, ID_PROJECTION,
+                Cursor c = mMediaProvider.query(mAudioUri, ID_PROJECTION,
                         MediaStore.Files.FileColumns.DATA + "=?",
                         new String[] { bestMatch.mPath }, null, null);
                 if (c != null) {
@@ -1567,7 +1573,8 @@
         return true;
     }
 
-    private void processM3uPlayList(String path, String playListDirectory, Uri uri, ContentValues values) {
+    private void processM3uPlayList(String path, String playListDirectory, Uri uri,
+            ContentValues values, Cursor fileList) {
         BufferedReader reader = null;
         try {
             File f = new File(path);
@@ -1580,7 +1587,7 @@
                     // ignore comment lines, which begin with '#'
                     if (line.length() > 0 && line.charAt(0) != '#') {
                         values.clear();
-                        if (addPlayListEntry(line, playListDirectory, uri, values, index))
+                        if (addPlayListEntry(line, playListDirectory, uri, values, index, fileList))
                             index++;
                     }
                     line = reader.readLine();
@@ -1598,7 +1605,8 @@
         }
     }
 
-    private void processPlsPlayList(String path, String playListDirectory, Uri uri, ContentValues values) {
+    private void processPlsPlayList(String path, String playListDirectory, Uri uri,
+            ContentValues values, Cursor fileList) {
         BufferedReader reader = null;
         try {
             File f = new File(path);
@@ -1613,7 +1621,8 @@
                         int equals = line.indexOf('=');
                         if (equals > 0) {
                             values.clear();
-                            if (addPlayListEntry(line.substring(equals + 1), playListDirectory, uri, values, index))
+                            if (addPlayListEntry(line.substring(equals + 1), playListDirectory,
+                                    uri, values, index, fileList))
                                 index++;
                         }
                     }
@@ -1637,12 +1646,14 @@
         final ContentHandler handler;
         String playListDirectory;
         Uri uri;
+        Cursor fileList;
         ContentValues values = new ContentValues();
         int index = 0;
 
-        public WplHandler(String playListDirectory, Uri uri) {
+        public WplHandler(String playListDirectory, Uri uri, Cursor fileList) {
             this.playListDirectory = playListDirectory;
             this.uri = uri;
+            this.fileList = fileList;
 
             RootElement root = new RootElement("smil");
             Element body = root.getChild("body");
@@ -1653,11 +1664,12 @@
             this.handler = root.getContentHandler();
         }
 
+        @Override
         public void start(Attributes attributes) {
             String path = attributes.getValue("", "src");
             if (path != null) {
                 values.clear();
-                if (addPlayListEntry(path, playListDirectory, uri, values, index)) {
+                if (addPlayListEntry(path, playListDirectory, uri, values, index, fileList)) {
                     index++;
                 }
             }
@@ -1671,14 +1683,16 @@
         }
     }
 
-    private void processWplPlayList(String path, String playListDirectory, Uri uri) {
+    private void processWplPlayList(String path, String playListDirectory, Uri uri,
+            Cursor fileList) {
         FileInputStream fis = null;
         try {
             File f = new File(path);
             if (f.exists()) {
                 fis = new FileInputStream(f);
 
-                Xml.parse(fis, Xml.findEncodingByName("UTF-8"), new WplHandler(playListDirectory, uri).getContentHandler());
+                Xml.parse(fis, Xml.findEncodingByName("UTF-8"),
+                        new WplHandler(playListDirectory, uri, fileList).getContentHandler());
             }
         } catch (SAXException e) {
             e.printStackTrace();
@@ -1694,7 +1708,7 @@
         }
     }
 
-    private void processPlayList(FileEntry entry) throws RemoteException {
+    private void processPlayList(FileEntry entry, Cursor fileList) throws RemoteException {
         String path = entry.mPath;
         ContentValues values = new ContentValues();
         int lastSlash = path.lastIndexOf('/');
@@ -1736,21 +1750,31 @@
         int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);
 
         if (fileType == MediaFile.FILE_TYPE_M3U) {
-            processM3uPlayList(path, playListDirectory, membersUri, values);
+            processM3uPlayList(path, playListDirectory, membersUri, values, fileList);
         } else if (fileType == MediaFile.FILE_TYPE_PLS) {
-            processPlsPlayList(path, playListDirectory, membersUri, values);
+            processPlsPlayList(path, playListDirectory, membersUri, values, fileList);
         } else if (fileType == MediaFile.FILE_TYPE_WPL) {
-            processWplPlayList(path, playListDirectory, membersUri);
+            processWplPlayList(path, playListDirectory, membersUri, fileList);
         }
     }
 
     private void processPlayLists() throws RemoteException {
         Iterator<FileEntry> iterator = mPlayLists.iterator();
-        while (iterator.hasNext()) {
-            FileEntry entry = iterator.next();
-            // only process playlist files if they are new or have been modified since the last scan
-            if (entry.mLastModifiedChanged) {
-                processPlayList(entry);
+        Cursor fileList = null;
+        try {
+            fileList = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
+                    null, null, null, null);
+            while (iterator.hasNext()) {
+                FileEntry entry = iterator.next();
+                // only process playlist files if they are new or have been modified since the last scan
+                if (entry.mLastModifiedChanged) {
+                    processPlayList(entry, fileList);
+                }
+            }
+        } catch (RemoteException e1) {
+        } finally {
+            if (fileList != null) {
+                fileList.close();
             }
         }
     }
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index 0c86fc2..8c661b7 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -160,6 +160,10 @@
     return mImpl->getSampleTime(sampleTimeUs);
 }
 
+status_t JMediaExtractor::getSampleFlags(uint32_t *sampleFlags) {
+    return mImpl->getSampleFlags(sampleFlags);
+}
+
 }  // namespace android
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -343,6 +347,28 @@
     return sampleTimeUs;
 }
 
+static jint android_media_MediaExtractor_getSampleFlags(
+        JNIEnv *env, jobject thiz) {
+    sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+    if (extractor == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return -1ll;
+    }
+
+    uint32_t sampleFlags;
+    status_t err = extractor->getSampleFlags(&sampleFlags);
+
+    if (err == ERROR_END_OF_STREAM) {
+        return -1ll;
+    } else if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+        return false;
+    }
+
+    return sampleFlags;
+}
+
 static void android_media_MediaExtractor_native_init(JNIEnv *env) {
     jclass clazz = env->FindClass("android/media/MediaExtractor");
     CHECK(clazz != NULL);
@@ -412,6 +438,9 @@
     { "getSampleTime", "()J",
         (void *)android_media_MediaExtractor_getSampleTime },
 
+    { "getSampleFlags", "()I",
+        (void *)android_media_MediaExtractor_getSampleFlags },
+
     { "native_init", "()V", (void *)android_media_MediaExtractor_native_init },
 
     { "native_setup", "(Ljava/lang/String;)V",
diff --git a/media/jni/android_media_MediaExtractor.h b/media/jni/android_media_MediaExtractor.h
index 70e58c6..49a64d6 100644
--- a/media/jni/android_media_MediaExtractor.h
+++ b/media/jni/android_media_MediaExtractor.h
@@ -43,6 +43,7 @@
     status_t readSampleData(jobject byteBuf, size_t offset, size_t *sampleSize);
     status_t getSampleTrackIndex(size_t *trackIndex);
     status_t getSampleTime(int64_t *sampleTimeUs);
+    status_t getSampleFlags(uint32_t *sampleFlags);
 
 protected:
     virtual ~JMediaExtractor();
diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp
index 8b2321c..1190448 100644
--- a/media/jni/android_media_Utils.cpp
+++ b/media/jni/android_media_Utils.cpp
@@ -324,7 +324,7 @@
                 env->DeleteLocalRef(byteArray); byteArray = NULL;
             }
 
-            msg->setObject(key.c_str(), buffer);
+            msg->setBuffer(key.c_str(), buffer);
         }
     }
 
diff --git a/media/jni/mediaeditor/Android.mk b/media/jni/mediaeditor/Android.mk
index 688e180..d1cf8b5 100755
--- a/media/jni/mediaeditor/Android.mk
+++ b/media/jni/mediaeditor/Android.mk
@@ -38,12 +38,12 @@
     $(call include-path-for, corecg graphics) \
     $(TOP)/frameworks/native/include/media/editor \
     $(TOP)/frameworks/base/core/jni/mediaeditor \
-    $(TOP)/frameworks/media/libvideoeditor/vss/inc \
-    $(TOP)/frameworks/media/libvideoeditor/vss/common/inc \
-    $(TOP)/frameworks/media/libvideoeditor/vss/mcs/inc \
-    $(TOP)/frameworks/media/libvideoeditor/vss/stagefrightshells/inc \
-    $(TOP)/frameworks/media/libvideoeditor/lvpp \
-    $(TOP)/frameworks/media/libvideoeditor/osal/inc \
+    $(TOP)/frameworks/av/libvideoeditor/vss/inc \
+    $(TOP)/frameworks/av/libvideoeditor/vss/common/inc \
+    $(TOP)/frameworks/av/libvideoeditor/vss/mcs/inc \
+    $(TOP)/frameworks/av/libvideoeditor/vss/stagefrightshells/inc \
+    $(TOP)/frameworks/av/libvideoeditor/lvpp \
+    $(TOP)/frameworks/av/libvideoeditor/osal/inc \
     $(TOP)/frameworks/native/include/media/openmax
 
 LOCAL_SHARED_LIBRARIES := \
diff --git a/media/jni/mediaeditor/VideoEditorMain.h b/media/jni/mediaeditor/VideoEditorMain.h
deleted file mode 100755
index 4c3b517..0000000
--- a/media/jni/mediaeditor/VideoEditorMain.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __VIDEO_EDITOR_API_H__
-#define __VIDEO_EDITOR_API_H__
-
-#include "M4OSA_Types.h"
-
-typedef enum
-{
-    MSG_TYPE_PROGRESS_INDICATION,     // Playback progress indication event
-    MSG_TYPE_PLAYER_ERROR,            // Playback error
-    MSG_TYPE_PREVIEW_END,             // Preview of clips is complete
-    MSG_TYPE_OVERLAY_UPDATE,          // update overlay during preview
-    MSG_TYPE_OVERLAY_CLEAR,           // clear the overlay
-} progress_callback_msg_type;
-
-typedef struct {
-    int overlaySettingsIndex;
-    int clipIndex;
-} VideoEditorCurretEditInfo;
-
-typedef struct
-{
-    M4OSA_Void     *pFile;                   /** PCM file path */
-    M4OSA_Bool     bRemoveOriginal;          /** If true, the original audio track
-                                                 is not taken into account */
-    M4OSA_UInt32   uiNbChannels;            /** Number of channels (1=mono, 2=stereo) of BGM clip*/
-    M4OSA_UInt32   uiSamplingFrequency;     /** Sampling audio frequency (8000 for amr, 16000 or
-                                                more for aac) of BGM clip*/
-    M4OSA_UInt32   uiExtendedSamplingFrequency; /** Extended frequency for AAC+,
-                                                eAAC+ streams of BGM clip*/
-    M4OSA_UInt32   uiAddCts;                /** Time, in milliseconds, at which the added
-                                                audio track is inserted */
-    M4OSA_UInt32   uiAddVolume;             /** Volume, in percentage, of the added audio track */
-    M4OSA_UInt32   beginCutMs;
-    M4OSA_UInt32   endCutMs;
-    M4OSA_Int32    fileType;
-    M4OSA_Bool     bLoop;                   /** Looping on/off **/
-    /* Audio ducking */
-    M4OSA_UInt32   uiInDucking_threshold;   /** Threshold value at which
-                                                background music shall duck */
-    M4OSA_UInt32   uiInDucking_lowVolume;   /** lower the background track to
-                                                this factor of current level */
-    M4OSA_Bool     bInDucking_enable;       /** enable ducking */
-    M4OSA_UInt32   uiBTChannelCount;        /** channel count for BT */
-    M4OSA_Void     *pPCMFilePath;
-} M4xVSS_AudioMixingSettings;
-
-typedef struct
-{
-    M4OSA_Void      *pBuffer;            /* YUV420 buffer of frame to be rendered*/
-    M4OSA_UInt32    timeMs;            /* time stamp of the frame to be rendered*/
-    M4OSA_UInt32    uiSurfaceWidth;    /* Surface display width*/
-    M4OSA_UInt32    uiSurfaceHeight;    /* Surface display height*/
-    M4OSA_UInt32    uiFrameWidth;        /* Frame width*/
-    M4OSA_UInt32    uiFrameHeight;        /* Frame height*/
-    M4OSA_Bool      bApplyEffect;        /* Apply video effects before render*/
-    M4OSA_UInt32    clipBeginCutTime;  /* Clip begin cut time relative to storyboard */
-    M4OSA_UInt32    clipEndCutTime;    /* Clip end cut time relative to storyboard */
-    M4OSA_UInt32    videoRotationDegree; /* Video rotation degree */
-
-} VideoEditor_renderPreviewFrameStr;
-#endif /*__VIDEO_EDITOR_API_H__*/
diff --git a/media/jni/soundpool/Android.mk b/media/jni/soundpool/Android.mk
index 98d6449..70dbfb3 100644
--- a/media/jni/soundpool/Android.mk
+++ b/media/jni/soundpool/Android.mk
@@ -2,14 +2,14 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= \
-	android_media_SoundPool.cpp \
-	SoundPool.cpp \
-	SoundPoolThread.cpp
+	android_media_SoundPool.cpp
+
+LOCAL_C_INCLUDES := \
+    frameworks/base/media/libmedia
 
 LOCAL_SHARED_LIBRARIES := \
 	libcutils \
 	libutils \
-	libbinder \
 	libandroid_runtime \
 	libnativehelper \
 	libmedia \
diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp
index da3af9d..c6dee06 100644
--- a/media/jni/soundpool/android_media_SoundPool.cpp
+++ b/media/jni/soundpool/android_media_SoundPool.cpp
@@ -23,7 +23,7 @@
 #include <nativehelper/jni.h>
 #include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
-#include "SoundPool.h"
+#include <SoundPool.h>
 
 using namespace android;
 
diff --git a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
index ca93ce5..40dffd4 100644
--- a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
+++ b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
@@ -1493,7 +1493,7 @@
         pContext->pBundledContext->firstVolume = LVM_FALSE;
     }
     return 0;
-}    /* end setVolumeLevel */
+}    /* end VolumeSetVolumeLevel */
 
 //----------------------------------------------------------------------------
 // VolumeGetVolumeLevel()
diff --git a/media/libeffects/visualizer/EffectVisualizer.cpp b/media/libeffects/visualizer/EffectVisualizer.cpp
index 51c8b68..bdb1a1c 100644
--- a/media/libeffects/visualizer/EffectVisualizer.cpp
+++ b/media/libeffects/visualizer/EffectVisualizer.cpp
@@ -21,6 +21,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <new>
+#include <time.h>
 #include <audio_effects/effect_visualizer.h>
 
 
@@ -47,9 +48,9 @@
     VISUALIZER_STATE_ACTIVE,
 };
 
-// maximum number of reads from same buffer before resetting capture buffer. This means
+// maximum time since last capture buffer update before resetting capture buffer. This means
 // that the framework has stopped playing audio and we must start returning silence
-#define MAX_STALL_COUNT 10
+#define MAX_STALL_TIME_MS 1000
 
 struct VisualizerContext {
     const struct effect_interface_s *mItfe;
@@ -59,7 +60,7 @@
     uint8_t mState;
     uint8_t mCurrentBuf;
     uint8_t mLastBuf;
-    uint8_t mStallCount;
+    struct timespec mBufferUpdateTime;
     uint8_t mCaptureBuf[2][VISUALIZER_CAPTURE_SIZE_MAX];
 };
 
@@ -72,7 +73,7 @@
     pContext->mCaptureIdx = 0;
     pContext->mCurrentBuf = 0;
     pContext->mLastBuf = 1;
-    pContext->mStallCount = 0;
+    pContext->mBufferUpdateTime.tv_sec = 0;
     memset(pContext->mCaptureBuf[0], 0x80, VISUALIZER_CAPTURE_SIZE_MAX);
     memset(pContext->mCaptureBuf[1], 0x80, VISUALIZER_CAPTURE_SIZE_MAX);
 }
@@ -321,6 +322,11 @@
     if (pContext->mCaptureIdx == pContext->mCaptureSize) {
         pContext->mCurrentBuf ^= 1;
         pContext->mCaptureIdx = 0;
+
+        // update last buffer update time stamp
+        if (clock_gettime(CLOCK_MONOTONIC, &pContext->mBufferUpdateTime) < 0) {
+            pContext->mBufferUpdateTime.tv_sec = 0;
+        }
     }
 
     if (inBuffer->raw != outBuffer->raw) {
@@ -453,16 +459,25 @@
                    pContext->mCaptureSize);
             // if audio framework has stopped playing audio although the effect is still
             // active we must clear the capture buffer to return silence
-            if (pContext->mLastBuf == pContext->mCurrentBuf) {
-                if (pContext->mStallCount < MAX_STALL_COUNT) {
-                    if (++pContext->mStallCount == MAX_STALL_COUNT) {
+            if ((pContext->mLastBuf == pContext->mCurrentBuf) &&
+                    (pContext->mBufferUpdateTime.tv_sec != 0)) {
+                struct timespec ts;
+                if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
+                    time_t secs = ts.tv_sec - pContext->mBufferUpdateTime.tv_sec;
+                    long nsec = ts.tv_nsec - pContext->mBufferUpdateTime.tv_nsec;
+                    if (nsec < 0) {
+                        --secs;
+                        nsec += 1000000000;
+                    }
+                    uint32_t deltaMs = secs * 1000 + nsec / 1000000;
+                    if (deltaMs > MAX_STALL_TIME_MS) {
+                        ALOGV("capture going to idle");
+                        pContext->mBufferUpdateTime.tv_sec = 0;
                         memset(pContext->mCaptureBuf[pContext->mCurrentBuf ^ 1],
                                 0x80,
                                 pContext->mCaptureSize);
                     }
                 }
-            } else {
-                pContext->mStallCount = 0;
             }
             pContext->mLastBuf = pContext->mCurrentBuf;
         } else {
diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk
index 21e8f29..c8e1dc7 100644
--- a/media/libmedia/Android.mk
+++ b/media/libmedia/Android.mk
@@ -17,6 +17,7 @@
     IAudioFlingerClient.cpp \
     IAudioTrack.cpp \
     IAudioRecord.cpp \
+    ICrypto.cpp \
     AudioRecord.cpp \
     AudioSystem.cpp \
     mediaplayer.cpp \
@@ -43,7 +44,9 @@
     IEffectClient.cpp \
     AudioEffect.cpp \
     Visualizer.cpp \
-    MemoryLeakTrackUtil.cpp
+    MemoryLeakTrackUtil.cpp \
+    SoundPool.cpp \
+    SoundPoolThread.cpp
 
 LOCAL_SHARED_LIBRARIES := \
 	libui libcutils libutils libbinder libsonivox libicuuc libexpat \
@@ -55,7 +58,6 @@
 LOCAL_MODULE:= libmedia
 
 LOCAL_C_INCLUDES := \
-    $(JNI_H_INCLUDE) \
     $(call include-path-for, graphics corecg) \
     $(TOP)/frameworks/native/include/media/openmax \
     external/icu4c/common \
diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp
index 05ade75..70ec593 100644
--- a/media/libmedia/AudioRecord.cpp
+++ b/media/libmedia/AudioRecord.cpp
@@ -201,7 +201,7 @@
 
     // create the IAudioRecord
     status = openRecord_l(sampleRate, format, channelMask,
-                        frameCount, flags, input);
+                        frameCount, input);
     if (status != NO_ERROR) {
         return status;
     }
@@ -458,7 +458,6 @@
         audio_format_t format,
         uint32_t channelMask,
         int frameCount,
-        uint32_t flags,
         audio_io_handle_t input)
 {
     status_t status;
@@ -471,7 +470,7 @@
                                                        sampleRate, format,
                                                        channelMask,
                                                        frameCount,
-                                                       ((uint16_t)flags) << 16,
+                                                       IAudioFlinger::TRACK_DEFAULT,
                                                        &mSessionId,
                                                        &status);
 
@@ -778,7 +777,7 @@
         // following member variables: mAudioRecord, mCblkMemory and mCblk.
         // It will also delete the strong references on previous IAudioRecord and IMemory
         result = openRecord_l(cblk->sampleRate, mFormat, mChannelMask,
-                mFrameCount, mFlags, getInput_l());
+                mFrameCount, getInput_l());
         if (result == NO_ERROR) {
             result = mAudioRecord->start(0);    // callback thread hasn't changed
         }
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index 048be1d..bafde3a 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -104,9 +104,10 @@
 {
     mStatus = set(streamType, sampleRate, format, channelMask,
             frameCount, flags, cbf, user, notificationFrames,
-            0, false, sessionId);
+            0 /*sharedBuffer*/, false /*threadCanCallJava*/, sessionId);
 }
 
+// DEPRECATED
 AudioTrack::AudioTrack(
         int streamType,
         uint32_t sampleRate,
@@ -124,7 +125,7 @@
 {
     mStatus = set((audio_stream_type_t)streamType, sampleRate, (audio_format_t)format, channelMask,
             frameCount, (audio_policy_output_flags_t)flags, cbf, user, notificationFrames,
-            0, false, sessionId);
+            0 /*sharedBuffer*/, false /*threadCanCallJava*/, sessionId);
 }
 
 AudioTrack::AudioTrack(
@@ -144,8 +145,8 @@
       mPreviousSchedulingGroup(ANDROID_TGROUP_DEFAULT)
 {
     mStatus = set(streamType, sampleRate, format, channelMask,
-            0, flags, cbf, user, notificationFrames,
-            sharedBuffer, false, sessionId);
+            0 /*frameCount*/, flags, cbf, user, notificationFrames,
+            sharedBuffer, false /*threadCanCallJava*/, sessionId);
 }
 
 AudioTrack::~AudioTrack()
@@ -194,6 +195,7 @@
     if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) {
         return NO_INIT;
     }
+
     uint32_t afLatency;
     if (AudioSystem::getOutputLatency(&afLatency, streamType) != NO_ERROR) {
         return NO_INIT;
@@ -203,9 +205,11 @@
     if (streamType == AUDIO_STREAM_DEFAULT) {
         streamType = AUDIO_STREAM_MUSIC;
     }
+
     if (sampleRate == 0) {
         sampleRate = afSampleRate;
     }
+
     // these below should probably come from the audioFlinger too...
     if (format == AUDIO_FORMAT_DEFAULT) {
         format = AUDIO_FORMAT_PCM_16_BIT;
@@ -257,8 +261,7 @@
                                   frameCount,
                                   flags,
                                   sharedBuffer,
-                                  output,
-                                  true);
+                                  output);
 
     if (status != NO_ERROR) {
         return status;
@@ -737,8 +740,7 @@
         int frameCount,
         audio_policy_output_flags_t flags,
         const sp<IMemory>& sharedBuffer,
-        audio_io_handle_t output,
-        bool enforceFrameCount)
+        audio_io_handle_t output)
 {
     status_t status;
     const sp<IAudioFlinger>& audioFlinger = AudioSystem::get_audio_flinger();
@@ -785,7 +787,8 @@
                 mNotificationFramesAct = frameCount/2;
             }
             if (frameCount < minFrameCount) {
-                ALOGW_IF(enforceFrameCount, "Minimum buffer size corrected from %d to %d",
+                // not ALOGW because it happens all the time when playing key clicks over A2DP
+                ALOGV("Minimum buffer size corrected from %d to %d",
                          frameCount, minFrameCount);
                 frameCount = minFrameCount;
             }
@@ -800,16 +803,19 @@
         }
     }
 
+    IAudioFlinger::track_flags_t trackFlags = IAudioFlinger::TRACK_DEFAULT;
+    if (mIsTimed) {
+        trackFlags |= IAudioFlinger::TRACK_TIMED;
+    }
     sp<IAudioTrack> track = audioFlinger->createTrack(getpid(),
                                                       streamType,
                                                       sampleRate,
                                                       format,
                                                       channelMask,
                                                       frameCount,
-                                                      ((uint16_t)flags) << 16,
+                                                      trackFlags,
                                                       sharedBuffer,
                                                       output,
-                                                      mIsTimed,
                                                       &mSessionId,
                                                       &status);
 
@@ -1245,8 +1251,7 @@
                                mFrameCount,
                                mFlags,
                                mSharedBuffer,
-                               getOutput_l(),
-                               false);
+                               getOutput_l());
 
         if (result == NO_ERROR) {
             uint32_t user = cblk->user;
diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp
index 07b12e4..ce10c8e 100644
--- a/media/libmedia/IAudioFlinger.cpp
+++ b/media/libmedia/IAudioFlinger.cpp
@@ -87,10 +87,9 @@
                                 audio_format_t format,
                                 uint32_t channelMask,
                                 int frameCount,
-                                uint32_t flags,
+                                track_flags_t flags,
                                 const sp<IMemory>& sharedBuffer,
                                 audio_io_handle_t output,
-                                bool isTimed,
                                 int *sessionId,
                                 status_t *status)
     {
@@ -103,10 +102,9 @@
         data.writeInt32(format);
         data.writeInt32(channelMask);
         data.writeInt32(frameCount);
-        data.writeInt32(flags);
+        data.writeInt32((int32_t) flags);
         data.writeStrongBinder(sharedBuffer->asBinder());
         data.writeInt32((int32_t) output);
-        data.writeInt32(isTimed);
         int lSessionId = 0;
         if (sessionId != NULL) {
             lSessionId = *sessionId;
@@ -136,7 +134,7 @@
                                 audio_format_t format,
                                 uint32_t channelMask,
                                 int frameCount,
-                                uint32_t flags,
+                                track_flags_t flags,
                                 int *sessionId,
                                 status_t *status)
     {
@@ -688,15 +686,14 @@
             audio_format_t format = (audio_format_t) data.readInt32();
             int channelCount = data.readInt32();
             size_t bufferCount = data.readInt32();
-            uint32_t flags = data.readInt32();
+            track_flags_t flags = (track_flags_t) data.readInt32();
             sp<IMemory> buffer = interface_cast<IMemory>(data.readStrongBinder());
             audio_io_handle_t output = (audio_io_handle_t) data.readInt32();
-            bool isTimed = data.readInt32();
             int sessionId = data.readInt32();
             status_t status;
             sp<IAudioTrack> track = createTrack(pid,
                     (audio_stream_type_t) streamType, sampleRate, format,
-                    channelCount, bufferCount, flags, buffer, output, isTimed, &sessionId, &status);
+                    channelCount, bufferCount, flags, buffer, output, &sessionId, &status);
             reply->writeInt32(sessionId);
             reply->writeInt32(status);
             reply->writeStrongBinder(track->asBinder());
@@ -710,7 +707,7 @@
             audio_format_t format = (audio_format_t) data.readInt32();
             int channelCount = data.readInt32();
             size_t bufferCount = data.readInt32();
-            uint32_t flags = data.readInt32();
+            track_flags_t flags = (track_flags_t) data.readInt32();
             int sessionId = data.readInt32();
             status_t status;
             sp<IAudioRecord> record = openRecord(pid, input,
diff --git a/media/libmedia/ICrypto.cpp b/media/libmedia/ICrypto.cpp
new file mode 100644
index 0000000..827d7af
--- /dev/null
+++ b/media/libmedia/ICrypto.cpp
@@ -0,0 +1,293 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "ICrypto"
+#include <utils/Log.h>
+
+#include <binder/Parcel.h>
+#include <media/ICrypto.h>
+#include <media/stagefright/foundation/ADebug.h>
+
+namespace android {
+
+enum {
+    INITIALIZE = IBinder::FIRST_CALL_TRANSACTION,
+    TERMINATE,
+    SET_ENTITLEMENT_KEY,
+    SET_ECM,
+    DECRYPT_VIDEO,
+    DECRYPT_AUDIO,
+};
+
+struct BpCrypto : public BpInterface<ICrypto> {
+    BpCrypto(const sp<IBinder> &impl)
+        : BpInterface<ICrypto>(impl) {
+    }
+
+    virtual status_t initialize() {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICrypto::getInterfaceDescriptor());
+        remote()->transact(INITIALIZE, data, &reply);
+
+        return reply.readInt32();
+    }
+
+    virtual status_t terminate() {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICrypto::getInterfaceDescriptor());
+        remote()->transact(TERMINATE, data, &reply);
+
+        return reply.readInt32();
+    }
+
+    virtual status_t setEntitlementKey(
+            const void *key, size_t keyLength) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICrypto::getInterfaceDescriptor());
+        data.writeInt32(keyLength);
+        data.write(key, keyLength);
+        remote()->transact(SET_ENTITLEMENT_KEY, data, &reply);
+
+        return reply.readInt32();
+    }
+
+    virtual status_t setEntitlementControlMessage(
+            const void *msg, size_t msgLength) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICrypto::getInterfaceDescriptor());
+        data.writeInt32(msgLength);
+        data.write(msg, msgLength);
+        remote()->transact(SET_ECM, data, &reply);
+
+        return reply.readInt32();
+    }
+
+    virtual ssize_t decryptVideo(
+            const void *iv, size_t ivLength,
+            const void *srcData, size_t srcDataSize,
+            void *dstData, size_t dstDataOffset) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICrypto::getInterfaceDescriptor());
+        if (iv == NULL) {
+            if (ivLength > 0) {
+                return -EINVAL;
+            }
+
+            data.writeInt32(-1);
+        } else {
+            data.writeInt32(ivLength);
+            data.write(iv, ivLength);
+        }
+
+        data.writeInt32(srcDataSize);
+        data.write(srcData, srcDataSize);
+
+        data.writeIntPtr((intptr_t)dstData);
+        data.writeInt32(dstDataOffset);
+
+        remote()->transact(DECRYPT_VIDEO, data, &reply);
+
+        return reply.readInt32();
+    }
+
+    virtual ssize_t decryptAudio(
+            const void *iv, size_t ivLength,
+            const void *srcData, size_t srcDataSize,
+            void *dstData, size_t dstDataSize) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICrypto::getInterfaceDescriptor());
+        if (iv == NULL) {
+            if (ivLength > 0) {
+                return -EINVAL;
+            }
+
+            data.writeInt32(-1);
+        } else {
+            data.writeInt32(ivLength);
+            data.write(iv, ivLength);
+        }
+
+        data.writeInt32(srcDataSize);
+        data.write(srcData, srcDataSize);
+        data.writeInt32(dstDataSize);
+
+        remote()->transact(DECRYPT_AUDIO, data, &reply);
+
+        ssize_t res = reply.readInt32();
+
+        if (res <= 0) {
+            return res;
+        }
+
+        reply.read(dstData, res);
+
+        return res;
+    }
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(BpCrypto);
+};
+
+IMPLEMENT_META_INTERFACE(Crypto, "android.hardware.ICrypto");
+
+////////////////////////////////////////////////////////////////////////////////
+
+status_t BnCrypto::onTransact(
+    uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
+    switch (code) {
+        case INITIALIZE:
+        {
+            CHECK_INTERFACE(ICrypto, data, reply);
+            reply->writeInt32(initialize());
+
+            return OK;
+        }
+
+        case TERMINATE:
+        {
+            CHECK_INTERFACE(ICrypto, data, reply);
+            reply->writeInt32(terminate());
+
+            return OK;
+        }
+
+        case SET_ENTITLEMENT_KEY:
+        {
+            CHECK_INTERFACE(ICrypto, data, reply);
+
+            size_t keyLength = data.readInt32();
+            void *key = malloc(keyLength);
+            data.read(key, keyLength);
+
+            reply->writeInt32(setEntitlementKey(key, keyLength));
+
+            free(key);
+            key = NULL;
+
+            return OK;
+        }
+
+        case SET_ECM:
+        {
+            CHECK_INTERFACE(ICrypto, data, reply);
+
+            size_t msgLength = data.readInt32();
+            void *msg = malloc(msgLength);
+            data.read(msg, msgLength);
+
+            reply->writeInt32(setEntitlementControlMessage(msg, msgLength));
+
+            free(msg);
+            msg = NULL;
+
+            return OK;
+        }
+
+        case DECRYPT_VIDEO:
+        {
+            CHECK_INTERFACE(ICrypto, data, reply);
+
+            void *iv = NULL;
+
+            int32_t ivLength = data.readInt32();
+            if (ivLength >= 0) {
+                iv = malloc(ivLength);
+                data.read(iv, ivLength);
+            }
+
+            size_t srcDataSize = data.readInt32();
+            void *srcData = malloc(srcDataSize);
+            data.read(srcData, srcDataSize);
+
+            void *dstData = (void *)data.readIntPtr();
+            size_t dstDataOffset = data.readInt32();
+
+            reply->writeInt32(
+                    decryptVideo(
+                        iv,
+                        ivLength < 0 ? 0 : ivLength,
+                        srcData,
+                        srcDataSize,
+                        dstData,
+                        dstDataOffset));
+
+            free(srcData);
+            srcData = NULL;
+
+            if (iv != NULL) {
+                free(iv);
+                iv = NULL;
+            }
+
+            return OK;
+        }
+
+        case DECRYPT_AUDIO:
+        {
+            CHECK_INTERFACE(ICrypto, data, reply);
+
+            void *iv = NULL;
+
+            int32_t ivLength = data.readInt32();
+            if (ivLength >= 0) {
+                iv = malloc(ivLength);
+                data.read(iv, ivLength);
+            }
+
+            size_t srcDataSize = data.readInt32();
+            void *srcData = malloc(srcDataSize);
+            data.read(srcData, srcDataSize);
+
+            size_t dstDataSize = data.readInt32();
+            void *dstData = malloc(dstDataSize);
+
+            ssize_t res =
+                decryptAudio(
+                        iv,
+                        ivLength < 0 ? 0 : ivLength,
+                        srcData,
+                        srcDataSize,
+                        dstData,
+                        dstDataSize);
+
+            reply->writeInt32(res);
+
+            if (res > 0) {
+                reply->write(dstData, res);
+            }
+
+            free(dstData);
+            dstData = NULL;
+
+            free(srcData);
+            srcData = NULL;
+
+            if (iv != NULL) {
+                free(iv);
+                iv = NULL;
+            }
+
+            return OK;
+        }
+
+        default:
+            return BBinder::onTransact(code, data, reply, flags);
+    }
+}
+
+}  // namespace android
+
diff --git a/media/libmedia/IMediaMetadataRetriever.cpp b/media/libmedia/IMediaMetadataRetriever.cpp
index 9b8d7c3..7e6d54b 100644
--- a/media/libmedia/IMediaMetadataRetriever.cpp
+++ b/media/libmedia/IMediaMetadataRetriever.cpp
@@ -18,7 +18,6 @@
 #include <stdint.h>
 #include <sys/types.h>
 #include <binder/Parcel.h>
-#include <SkBitmap.h>
 #include <media/IMediaMetadataRetriever.h>
 #include <utils/String8.h>
 
diff --git a/media/libmedia/IMediaPlayerService.cpp b/media/libmedia/IMediaPlayerService.cpp
index f5fccef..9120617 100644
--- a/media/libmedia/IMediaPlayerService.cpp
+++ b/media/libmedia/IMediaPlayerService.cpp
@@ -20,6 +20,7 @@
 
 #include <binder/Parcel.h>
 #include <binder/IMemory.h>
+#include <media/ICrypto.h>
 #include <media/IMediaPlayerService.h>
 #include <media/IMediaRecorder.h>
 #include <media/IOMX.h>
@@ -36,6 +37,7 @@
     CREATE_MEDIA_RECORDER,
     CREATE_METADATA_RETRIEVER,
     GET_OMX,
+    MAKE_CRYPTO,
     ADD_BATTERY_DATA,
     PULL_BATTERY_DATA
 };
@@ -111,6 +113,13 @@
         return interface_cast<IOMX>(reply.readStrongBinder());
     }
 
+    virtual sp<ICrypto> makeCrypto() {
+        Parcel data, reply;
+        data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
+        remote()->transact(MAKE_CRYPTO, data, &reply);
+        return interface_cast<ICrypto>(reply.readStrongBinder());
+    }
+
     virtual void addBatteryData(uint32_t params) {
         Parcel data, reply;
         data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
@@ -191,6 +200,12 @@
             reply->writeStrongBinder(omx->asBinder());
             return NO_ERROR;
         } break;
+        case MAKE_CRYPTO: {
+            CHECK_INTERFACE(IMediaPlayerService, data, reply);
+            sp<ICrypto> crypto = makeCrypto();
+            reply->writeStrongBinder(crypto->asBinder());
+            return NO_ERROR;
+        } break;
         case ADD_BATTERY_DATA: {
             CHECK_INTERFACE(IMediaPlayerService, data, reply);
             uint32_t params = data.readInt32();
diff --git a/media/libmedia/JetPlayer.cpp b/media/libmedia/JetPlayer.cpp
index f1f62f7..52aee49 100644
--- a/media/libmedia/JetPlayer.cpp
+++ b/media/libmedia/JetPlayer.cpp
@@ -30,7 +30,7 @@
 static const S_EAS_LIB_CONFIG* pLibConfig = NULL;
 
 //-------------------------------------------------------------------------------------------------
-JetPlayer::JetPlayer(jobject javaJetPlayer, int maxTracks, int trackBufferSize) :
+JetPlayer::JetPlayer(void *javaJetPlayer, int maxTracks, int trackBufferSize) :
         mEventCallback(NULL),
         mJavaJetPlayerRef(javaJetPlayer),
         mTid(-1),
@@ -89,7 +89,7 @@
 
     // create the output AudioTrack
     mAudioTrack = new AudioTrack();
-    mAudioTrack->set(AUDIO_STREAM_MUSIC,  //TODO parametrize this
+    mAudioTrack->set(AUDIO_STREAM_MUSIC,  //TODO parameterize this
             pLibConfig->sampleRate,
             AUDIO_FORMAT_PCM_16_BIT,
             audio_channel_out_mask_from_count(pLibConfig->numChannels),
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/libmedia/SoundPool.cpp
similarity index 99%
rename from media/jni/soundpool/SoundPool.cpp
rename to media/libmedia/SoundPool.cpp
index 5aed8a1..306c57d 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/libmedia/SoundPool.cpp
@@ -685,7 +685,7 @@
 void SoundChannel::callback(int event, void* user, void *info)
 {
     SoundChannel* channel = static_cast<SoundChannel*>((void *)((unsigned long)user & ~1));
-    
+
     channel->process(event, info, (unsigned long)user & 1);
 }
 
diff --git a/media/jni/soundpool/SoundPool.h b/media/libmedia/SoundPool.h
similarity index 100%
rename from media/jni/soundpool/SoundPool.h
rename to media/libmedia/SoundPool.h
diff --git a/media/jni/soundpool/SoundPoolThread.cpp b/media/libmedia/SoundPoolThread.cpp
similarity index 100%
rename from media/jni/soundpool/SoundPoolThread.cpp
rename to media/libmedia/SoundPoolThread.cpp
diff --git a/media/jni/soundpool/SoundPoolThread.h b/media/libmedia/SoundPoolThread.h
similarity index 100%
rename from media/jni/soundpool/SoundPoolThread.h
rename to media/libmedia/SoundPoolThread.h
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index ba5c776..675c563 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -7,6 +7,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:=               \
+    Crypto.cpp                  \
     MediaRecorderClient.cpp     \
     MediaPlayerService.cpp      \
     MetadataRetrieverClient.cpp \
@@ -38,12 +39,11 @@
         libstagefright_rtsp                     \
 
 LOCAL_C_INCLUDES :=                                               \
-	$(JNI_H_INCLUDE)                                                \
 	$(call include-path-for, graphics corecg)                       \
 	$(TOP)/frameworks/base/media/libstagefright/include             \
 	$(TOP)/frameworks/base/media/libstagefright/rtsp                \
 	$(TOP)/frameworks/native/include/media/openmax                  \
-	$(TOP)/external/tremolo/Tremolo
+	$(TOP)/external/tremolo/Tremolo                                 \
 
 LOCAL_MODULE:= libmediaplayerservice
 
diff --git a/media/libmediaplayerservice/Crypto.cpp b/media/libmediaplayerservice/Crypto.cpp
new file mode 100644
index 0000000..e02035f
--- /dev/null
+++ b/media/libmediaplayerservice/Crypto.cpp
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "Crypto"
+#include <utils/Log.h>
+
+#include "Crypto.h"
+
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+Crypto::Crypto() {
+}
+
+Crypto::~Crypto() {
+}
+
+status_t Crypto::initialize() {
+    return ERROR_UNSUPPORTED;
+}
+
+status_t Crypto::terminate() {
+    return ERROR_UNSUPPORTED;
+}
+
+status_t Crypto::setEntitlementKey(
+        const void *key, size_t keyLength) {
+    return ERROR_UNSUPPORTED;
+}
+
+status_t Crypto::setEntitlementControlMessage(
+        const void *msg, size_t msgLength) {
+    return ERROR_UNSUPPORTED;
+}
+
+ssize_t Crypto::decryptVideo(
+        const void *iv, size_t ivLength,
+        const void *srcData, size_t srcDataSize,
+        void *dstData, size_t dstDataOffset) {
+    return ERROR_UNSUPPORTED;
+}
+
+ssize_t Crypto::decryptAudio(
+        const void *iv, size_t ivLength,
+        const void *srcData, size_t srcDataSize,
+        void *dstData, size_t dstDataSize) {
+    return ERROR_UNSUPPORTED;
+}
+
+}  // namespace android
diff --git a/media/libmediaplayerservice/Crypto.h b/media/libmediaplayerservice/Crypto.h
new file mode 100644
index 0000000..9855496
--- /dev/null
+++ b/media/libmediaplayerservice/Crypto.h
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#ifndef CRYPTO_H_
+
+#define CRYPTO_H_
+
+#include <media/ICrypto.h>
+#include <utils/threads.h>
+
+namespace android {
+
+struct Crypto : public BnCrypto {
+    Crypto();
+
+    virtual status_t initialize();
+    virtual status_t terminate();
+
+    virtual status_t setEntitlementKey(
+            const void *key, size_t keyLength);
+
+    virtual status_t setEntitlementControlMessage(
+            const void *msg, size_t msgLength);
+
+    virtual ssize_t decryptVideo(
+            const void *iv, size_t ivLength,
+            const void *srcData, size_t srcDataSize,
+            void *dstData, size_t dstDataOffset);
+
+    virtual ssize_t decryptAudio(
+            const void *iv, size_t ivLength,
+            const void *srcData, size_t srcDataSize,
+            void *dstData, size_t dstDataSize);
+
+protected:
+    virtual ~Crypto();
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(Crypto);
+};
+
+}  // namespace android
+
+#endif  // CRYPTO_H_
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 8f62ee4..123d07ff 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -70,6 +70,8 @@
 
 #include <OMX.h>
 
+#include "Crypto.h"
+
 namespace android {
 sp<MediaPlayerBase> createAAH_TXPlayer();
 sp<MediaPlayerBase> createAAH_RXPlayer();
@@ -292,6 +294,16 @@
     return mOMX;
 }
 
+sp<ICrypto> MediaPlayerService::makeCrypto() {
+    Mutex::Autolock autoLock(mLock);
+
+    if (mCrypto == NULL) {
+        mCrypto = new Crypto;
+    }
+
+    return mCrypto;
+}
+
 status_t MediaPlayerService::AudioCache::dump(int fd, const Vector<String16>& args) const
 {
     const size_t SIZE = 256;
@@ -541,6 +553,12 @@
 }
 
 static player_type getDefaultPlayerType() {
+    char value[PROPERTY_VALUE_MAX];
+    if (property_get("media.stagefright.use-nuplayer", value, NULL)
+            && (!strcmp("1", value) || !strcasecmp("true", value))) {
+        return NU_PLAYER;
+    }
+
     return STAGEFRIGHT_PLAYER;
 }
 
@@ -1571,7 +1589,7 @@
                 AUDIO_POLICY_OUTPUT_FLAG_NONE,
                 CallbackWrapper,
                 mCallbackData,
-                0,
+                0,  // notification frames
                 mSessionId);
     } else {
         t = new AudioTrack(
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index d4e0eb1..b08dd6c 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -240,6 +240,7 @@
     virtual sp<IMemory>         decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat);
     virtual sp<IMemory>         decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat);
     virtual sp<IOMX>            getOMX();
+    virtual sp<ICrypto>         makeCrypto();
 
     virtual status_t            dump(int fd, const Vector<String16>& args);
 
@@ -419,6 +420,7 @@
                 SortedVector< wp<MediaRecorderClient> > mMediaRecorderClients;
                 int32_t                     mNextConnId;
                 sp<IOMX>                    mOMX;
+                sp<ICrypto>                 mCrypto;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/media/libmediaplayerservice/MetadataRetrieverClient.cpp b/media/libmediaplayerservice/MetadataRetrieverClient.cpp
index 7dbb57f..776d288 100644
--- a/media/libmediaplayerservice/MetadataRetrieverClient.cpp
+++ b/media/libmediaplayerservice/MetadataRetrieverClient.cpp
@@ -87,6 +87,7 @@
     sp<MediaMetadataRetrieverBase> p;
     switch (playerType) {
         case STAGEFRIGHT_PLAYER:
+        case NU_PLAYER:
         {
             p = new StagefrightMetadataRetriever;
             break;
diff --git a/media/libmediaplayerservice/nuplayer/Android.mk b/media/libmediaplayerservice/nuplayer/Android.mk
index 9b485d7..73336ef 100644
--- a/media/libmediaplayerservice/nuplayer/Android.mk
+++ b/media/libmediaplayerservice/nuplayer/Android.mk
@@ -2,6 +2,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:=                       \
+        GenericSource.cpp               \
         HTTPLiveSource.cpp              \
         NuPlayer.cpp                    \
         NuPlayerDecoder.cpp             \
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
new file mode 100644
index 0000000..99569c9
--- /dev/null
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
@@ -0,0 +1,265 @@
+/*
+ * 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.
+ */
+
+#include "GenericSource.h"
+
+#include "AnotherPacketSource.h"
+
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/FileSource.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+NuPlayer::GenericSource::GenericSource(
+        const char *url,
+        const KeyedVector<String8, String8> *headers,
+        bool uidValid,
+        uid_t uid)
+    : mDurationUs(0ll),
+      mAudioIsVorbis(false) {
+    DataSource::RegisterDefaultSniffers();
+
+    sp<DataSource> dataSource =
+        DataSource::CreateFromURI(url, headers);
+    CHECK(dataSource != NULL);
+
+    initFromDataSource(dataSource);
+}
+
+NuPlayer::GenericSource::GenericSource(
+        int fd, int64_t offset, int64_t length)
+    : mDurationUs(0ll),
+      mAudioIsVorbis(false) {
+    DataSource::RegisterDefaultSniffers();
+
+    sp<DataSource> dataSource = new FileSource(dup(fd), offset, length);
+
+    initFromDataSource(dataSource);
+}
+
+void NuPlayer::GenericSource::initFromDataSource(
+        const sp<DataSource> &dataSource) {
+    sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
+
+    CHECK(extractor != NULL);
+
+    for (size_t i = 0; i < extractor->countTracks(); ++i) {
+        sp<MetaData> meta = extractor->getTrackMetaData(i);
+
+        const char *mime;
+        CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+        sp<MediaSource> track;
+
+        if (!strncasecmp(mime, "audio/", 6)) {
+            if (mAudioTrack.mSource == NULL) {
+                mAudioTrack.mSource = track = extractor->getTrack(i);
+
+                if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) {
+                    mAudioIsVorbis = true;
+                } else {
+                    mAudioIsVorbis = false;
+                }
+            }
+        } else if (!strncasecmp(mime, "video/", 6)) {
+            if (mVideoTrack.mSource == NULL) {
+                mVideoTrack.mSource = track = extractor->getTrack(i);
+            }
+        }
+
+        if (track != NULL) {
+            int64_t durationUs;
+            if (meta->findInt64(kKeyDuration, &durationUs)) {
+                if (durationUs > mDurationUs) {
+                    mDurationUs = durationUs;
+                }
+            }
+        }
+    }
+}
+
+NuPlayer::GenericSource::~GenericSource() {
+}
+
+void NuPlayer::GenericSource::start() {
+    ALOGI("start");
+
+    if (mAudioTrack.mSource != NULL) {
+        CHECK_EQ(mAudioTrack.mSource->start(), (status_t)OK);
+
+        mAudioTrack.mPackets =
+            new AnotherPacketSource(mAudioTrack.mSource->getFormat());
+
+        readBuffer(true /* audio */);
+    }
+
+    if (mVideoTrack.mSource != NULL) {
+        CHECK_EQ(mVideoTrack.mSource->start(), (status_t)OK);
+
+        mVideoTrack.mPackets =
+            new AnotherPacketSource(mVideoTrack.mSource->getFormat());
+
+        readBuffer(false /* audio */);
+    }
+}
+
+status_t NuPlayer::GenericSource::feedMoreTSData() {
+    return OK;
+}
+
+sp<MetaData> NuPlayer::GenericSource::getFormat(bool audio) {
+    sp<MediaSource> source = audio ? mAudioTrack.mSource : mVideoTrack.mSource;
+
+    if (source == NULL) {
+        return NULL;
+    }
+
+    return source->getFormat();
+}
+
+status_t NuPlayer::GenericSource::dequeueAccessUnit(
+        bool audio, sp<ABuffer> *accessUnit) {
+    Track *track = audio ? &mAudioTrack : &mVideoTrack;
+
+    if (track->mSource == NULL) {
+        return -EWOULDBLOCK;
+    }
+
+    status_t finalResult;
+    if (!track->mPackets->hasBufferAvailable(&finalResult)) {
+        return finalResult == OK ? -EWOULDBLOCK : finalResult;
+    }
+
+    status_t result = track->mPackets->dequeueAccessUnit(accessUnit);
+
+    readBuffer(audio, -1ll);
+
+    return result;
+}
+
+status_t NuPlayer::GenericSource::getDuration(int64_t *durationUs) {
+    *durationUs = mDurationUs;
+    return OK;
+}
+
+status_t NuPlayer::GenericSource::seekTo(int64_t seekTimeUs) {
+    if (mVideoTrack.mSource != NULL) {
+        int64_t actualTimeUs;
+        readBuffer(false /* audio */, seekTimeUs, &actualTimeUs);
+
+        seekTimeUs = actualTimeUs;
+    }
+
+    if (mAudioTrack.mSource != NULL) {
+        readBuffer(true /* audio */, seekTimeUs);
+    }
+
+    return OK;
+}
+
+void NuPlayer::GenericSource::readBuffer(
+        bool audio, int64_t seekTimeUs, int64_t *actualTimeUs) {
+    Track *track = audio ? &mAudioTrack : &mVideoTrack;
+    CHECK(track->mSource != NULL);
+
+    if (actualTimeUs) {
+        *actualTimeUs = seekTimeUs;
+    }
+
+    MediaSource::ReadOptions options;
+
+    bool seeking = false;
+
+    if (seekTimeUs >= 0) {
+        options.setSeekTo(seekTimeUs);
+        seeking = true;
+    }
+
+    for (;;) {
+        MediaBuffer *mbuf;
+        status_t err = track->mSource->read(&mbuf, &options);
+
+        options.clearSeekTo();
+
+        if (err == OK) {
+            size_t outLength = mbuf->range_length();
+
+            if (audio && mAudioIsVorbis) {
+                outLength += sizeof(int32_t);
+            }
+
+            sp<ABuffer> buffer = new ABuffer(outLength);
+
+            memcpy(buffer->data(),
+                   (const uint8_t *)mbuf->data() + mbuf->range_offset(),
+                   mbuf->range_length());
+
+            if (audio && mAudioIsVorbis) {
+                int32_t numPageSamples;
+                if (!mbuf->meta_data()->findInt32(
+                            kKeyValidSamples, &numPageSamples)) {
+                    numPageSamples = -1;
+                }
+
+                memcpy(buffer->data() + mbuf->range_length(),
+                       &numPageSamples,
+                       sizeof(numPageSamples));
+            }
+
+            int64_t timeUs;
+            CHECK(mbuf->meta_data()->findInt64(kKeyTime, &timeUs));
+
+            buffer->meta()->setInt64("timeUs", timeUs);
+
+            if (actualTimeUs) {
+                *actualTimeUs = timeUs;
+            }
+
+            mbuf->release();
+            mbuf = NULL;
+
+            if (seeking) {
+                track->mPackets->queueDiscontinuity(
+                        ATSParser::DISCONTINUITY_SEEK, NULL);
+            }
+
+            track->mPackets->queueAccessUnit(buffer);
+            break;
+        } else if (err == INFO_FORMAT_CHANGED) {
+#if 0
+            track->mPackets->queueDiscontinuity(
+                    ATSParser::DISCONTINUITY_FORMATCHANGE, NULL);
+#endif
+        } else {
+            track->mPackets->signalEOS(err);
+            break;
+        }
+    }
+}
+
+bool NuPlayer::GenericSource::isSeekable() {
+    return true;
+}
+
+}  // namespace android
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.h b/media/libmediaplayerservice/nuplayer/GenericSource.h
new file mode 100644
index 0000000..aaa5876
--- /dev/null
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.h
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#ifndef GENERIC_SOURCE_H_
+
+#define GENERIC_SOURCE_H_
+
+#include "NuPlayer.h"
+#include "NuPlayerSource.h"
+
+#include "ATSParser.h"
+
+namespace android {
+
+struct AnotherPacketSource;
+struct ARTSPController;
+struct DataSource;
+struct MediaSource;
+
+struct NuPlayer::GenericSource : public NuPlayer::Source {
+    GenericSource(
+            const char *url,
+            const KeyedVector<String8, String8> *headers,
+            bool uidValid = false,
+            uid_t uid = 0);
+
+    GenericSource(int fd, int64_t offset, int64_t length);
+
+    virtual void start();
+
+    virtual status_t feedMoreTSData();
+
+    virtual sp<MetaData> getFormat(bool audio);
+    virtual status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit);
+
+    virtual status_t getDuration(int64_t *durationUs);
+    virtual status_t seekTo(int64_t seekTimeUs);
+    virtual bool isSeekable();
+
+protected:
+    virtual ~GenericSource();
+
+private:
+    struct Track {
+        sp<MediaSource> mSource;
+        sp<AnotherPacketSource> mPackets;
+    };
+
+    Track mAudioTrack;
+    Track mVideoTrack;
+
+    int64_t mDurationUs;
+    bool mAudioIsVorbis;
+
+    void initFromDataSource(const sp<DataSource> &dataSource);
+
+    void readBuffer(
+            bool audio,
+            int64_t seekTimeUs = -1ll, int64_t *actualTimeUs = NULL);
+
+    DISALLOW_EVIL_CONSTRUCTORS(GenericSource);
+};
+
+}  // namespace android
+
+#endif  // GENERIC_SOURCE_H_
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index 526120a..544d501 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -27,6 +27,7 @@
 #include "NuPlayerSource.h"
 #include "RTSPSource.h"
 #include "StreamingSource.h"
+#include "GenericSource.h"
 
 #include "ATSParser.h"
 
@@ -84,18 +85,44 @@
     msg->post();
 }
 
+static bool IsHTTPLiveURL(const char *url) {
+    if (!strncasecmp("http://", url, 7)
+            || !strncasecmp("https://", url, 8)) {
+        size_t len = strlen(url);
+        if (len >= 5 && !strcasecmp(".m3u8", &url[len - 5])) {
+            return true;
+        }
+
+        if (strstr(url,"m3u8")) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
 void NuPlayer::setDataSource(
         const char *url, const KeyedVector<String8, String8> *headers) {
     sp<AMessage> msg = new AMessage(kWhatSetDataSource, id());
 
-    if (!strncasecmp(url, "rtsp://", 7)) {
-        msg->setObject(
-                "source", new RTSPSource(url, headers, mUIDValid, mUID));
+    sp<Source> source;
+    if (IsHTTPLiveURL(url)) {
+        source = new HTTPLiveSource(url, headers, mUIDValid, mUID);
+    } else if (!strncasecmp(url, "rtsp://", 7)) {
+        source = new RTSPSource(url, headers, mUIDValid, mUID);
     } else {
-        msg->setObject(
-                "source", new HTTPLiveSource(url, headers, mUIDValid, mUID));
+        source = new GenericSource(url, headers, mUIDValid, mUID);
     }
 
+    msg->setObject("source", source);
+    msg->post();
+}
+
+void NuPlayer::setDataSource(int fd, int64_t offset, int64_t length) {
+    sp<AMessage> msg = new AMessage(kWhatSetDataSource, id());
+
+    sp<Source> source = new GenericSource(fd, offset, length);
+    msg->setObject("source", source);
     msg->post();
 }
 
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.h b/media/libmediaplayerservice/nuplayer/NuPlayer.h
index 6be14be..25766e0 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.h
@@ -40,6 +40,8 @@
     void setDataSource(
             const char *url, const KeyedVector<String8, String8> *headers);
 
+    void setDataSource(int fd, int64_t offset, int64_t length);
+
     void setVideoSurfaceTexture(const sp<ISurfaceTexture> &surfaceTexture);
     void setAudioSink(const sp<MediaPlayerBase::AudioSink> &sink);
     void start();
@@ -60,12 +62,13 @@
 
 private:
     struct Decoder;
+    struct GenericSource;
     struct HTTPLiveSource;
     struct NuPlayerStreamListener;
     struct Renderer;
+    struct RTSPSource;
     struct Source;
     struct StreamingSource;
-    struct RTSPSource;
 
     enum {
         kWhatSetDataSource              = '=DaS',
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
index 460fc98..5733229 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDecoder.cpp
@@ -125,6 +125,11 @@
 
         msg->setInt32("channel-count", numChannels);
         msg->setInt32("sample-rate", sampleRate);
+
+        int32_t isADTS;
+        if (meta->findInt32(kKeyIsADTS, &isADTS) && isADTS != 0) {
+            msg->setInt32("is-adts", true);
+        }
     }
 
     int32_t maxInputSize;
@@ -228,6 +233,20 @@
 
         buffer->meta()->setInt32("csd", true);
         mCSD.push(buffer);
+    } else if (meta->findData(kKeyVorbisInfo, &type, &data, &size)) {
+        sp<ABuffer> buffer = new ABuffer(size);
+        memcpy(buffer->data(), data, size);
+
+        buffer->meta()->setInt32("csd", true);
+        mCSD.push(buffer);
+
+        CHECK(meta->findData(kKeyVorbisBooks, &type, &data, &size));
+
+        buffer = new ABuffer(size);
+        memcpy(buffer->data(), data, size);
+
+        buffer->meta()->setInt32("csd", true);
+        mCSD.push(buffer);
     }
 
     return msg;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index 5aa99bf..253bc2fa 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -76,7 +76,13 @@
 }
 
 status_t NuPlayerDriver::setDataSource(int fd, int64_t offset, int64_t length) {
-    return INVALID_OPERATION;
+    CHECK_EQ((int)mState, (int)UNINITIALIZED);
+
+    mPlayer->setDataSource(fd, offset, length);
+
+    mState = STOPPED;
+
+    return OK;
 }
 
 status_t NuPlayerDriver::setDataSource(const sp<IStreamSource> &source) {
@@ -97,13 +103,16 @@
 }
 
 status_t NuPlayerDriver::prepare() {
+    sendEvent(MEDIA_SET_VIDEO_SIZE, 320, 240);
     return OK;
 }
 
 status_t NuPlayerDriver::prepareAsync() {
+    status_t err = prepare();
+
     notifyListener(MEDIA_PREPARED);
 
-    return OK;
+    return err;
 }
 
 status_t NuPlayerDriver::start() {
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
index 5738ecb..ecbc428 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerRenderer.cpp
@@ -376,7 +376,8 @@
     bool tooLate = (mVideoLateByUs > 40000);
 
     if (tooLate) {
-        ALOGV("video late by %lld us (%.2f secs)", mVideoLateByUs, mVideoLateByUs / 1E6);
+        ALOGV("video late by %lld us (%.2f secs)",
+             mVideoLateByUs, mVideoLateByUs / 1E6);
     } else {
         ALOGV("rendering video at media time %.2f secs", mediaTimeUs / 1E6);
     }
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index e5ad4b7..db2beda 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -427,24 +427,34 @@
                 sp<IMemory> mem = mDealer[portIndex]->allocate(def.nBufferSize);
                 CHECK(mem.get() != NULL);
 
-                IOMX::buffer_id buffer;
+                BufferInfo info;
+                info.mStatus = BufferInfo::OWNED_BY_US;
 
                 uint32_t requiresAllocateBufferBit =
                     (portIndex == kPortIndexInput)
                         ? OMXCodec::kRequiresAllocateBufferOnInputPorts
                         : OMXCodec::kRequiresAllocateBufferOnOutputPorts;
 
-                if (mQuirks & requiresAllocateBufferBit) {
+                if (portIndex == kPortIndexInput && (mFlags & kFlagIsSecure)) {
+                    mem.clear();
+
+                    void *ptr;
+                    err = mOMX->allocateBuffer(
+                            mNode, portIndex, def.nBufferSize, &info.mBufferID,
+                            &ptr);
+
+                    info.mData = new ABuffer(ptr, def.nBufferSize);
+                } else if (mQuirks & requiresAllocateBufferBit) {
                     err = mOMX->allocateBufferWithBackup(
-                            mNode, portIndex, mem, &buffer);
+                            mNode, portIndex, mem, &info.mBufferID);
                 } else {
-                    err = mOMX->useBuffer(mNode, portIndex, mem, &buffer);
+                    err = mOMX->useBuffer(mNode, portIndex, mem, &info.mBufferID);
                 }
 
-                BufferInfo info;
-                info.mBufferID = buffer;
-                info.mStatus = BufferInfo::OWNED_BY_US;
-                info.mData = new ABuffer(mem->pointer(), def.nBufferSize);
+                if (mem != NULL) {
+                    info.mData = new ABuffer(mem->pointer(), def.nBufferSize);
+                }
+
                 mBuffers[portIndex].push(info);
             }
         }
@@ -840,7 +850,13 @@
                 || !msg->findInt32("sample-rate", &sampleRate)) {
             err = INVALID_OPERATION;
         } else {
-            err = setupAACCodec(encoder, numChannels, sampleRate, bitRate);
+            int32_t isADTS;
+            if (!msg->findInt32("is-adts", &isADTS)) {
+                isADTS = 0;
+            }
+
+            err = setupAACCodec(
+                    encoder, numChannels, sampleRate, bitRate, isADTS != 0);
         }
     } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)) {
         err = setupAMRCodec(encoder, false /* isWAMR */, bitRate);
@@ -934,7 +950,11 @@
 
 status_t ACodec::setupAACCodec(
         bool encoder,
-        int32_t numChannels, int32_t sampleRate, int32_t bitRate) {
+        int32_t numChannels, int32_t sampleRate, int32_t bitRate, bool isADTS) {
+    if (encoder && isADTS) {
+        return -EINVAL;
+    }
+
     status_t err = setupRawAudioFormat(
             encoder ? kPortIndexInput : kPortIndexOutput,
             sampleRate,
@@ -1021,7 +1041,11 @@
 
     profile.nChannels = numChannels;
     profile.nSampleRate = sampleRate;
-    profile.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4ADTS;
+
+    profile.eAACStreamFormat =
+        isADTS
+            ? OMX_AUDIO_AACStreamFormatMP4ADTS
+            : OMX_AUDIO_AACStreamFormatMP4FF;
 
     return mOMX->setParameter(
             mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile));
@@ -2653,6 +2677,12 @@
     observer->setNotificationMessage(notify);
 
     mCodec->mComponentName = componentName;
+    mCodec->mFlags = 0;
+
+    if (componentName.endsWith(".secure")) {
+        mCodec->mFlags |= kFlagIsSecure;
+    }
+
     mCodec->mQuirks = quirks;
     mCodec->mOMX = omx;
     mCodec->mNode = node;
@@ -2701,6 +2731,7 @@
         mCodec->mNode = NULL;
         mCodec->mOMX.clear();
         mCodec->mQuirks = 0;
+        mCodec->mFlags = 0;
         mCodec->mComponentName.clear();
 
         mCodec->changeState(mCodec->mUninitializedState);
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 77714f3..8948abb 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -42,6 +42,7 @@
         OggExtractor.cpp                  \
         SampleIterator.cpp                \
         SampleTable.cpp                   \
+        SkipCutBuffer.cpp                 \
         StagefrightMediaScanner.cpp       \
         StagefrightMetadataRetriever.cpp  \
         SurfaceMediaSource.cpp            \
@@ -56,7 +57,6 @@
         avc_utils.cpp                     \
 
 LOCAL_C_INCLUDES:= \
-	$(JNI_H_INCLUDE) \
         $(TOP)/frameworks/base/include/media/stagefright/timedtext \
         $(TOP)/frameworks/native/include/media/hardware \
         $(TOP)/frameworks/native/include/media/openmax \
@@ -68,7 +68,6 @@
 LOCAL_SHARED_LIBRARIES := \
         libbinder \
         libcamera_client \
-        libchromium_net \
         libcrypto \
         libcutils \
         libdl \
@@ -101,14 +100,18 @@
         libstagefright_httplive \
         libstagefright_id3 \
         libFLAC \
-        libstagefright_chromium_http \
+
+ifneq ($(TARGET_BUILD_PDK), true)
+LOCAL_STATIC_LIBRARIES += \
+	libstagefright_chromium_http
+LOCAL_SHARED_LIBRARIES += \
+        libchromium_net
+LOCAL_CPPFLAGS += -DCHROMIUM_AVAILABLE=1
+endif
 
 LOCAL_SHARED_LIBRARIES += libstlport
 include external/stlport/libstlport.mk
 
-# TODO: Chromium is always available, so this flag can be removed.
-LOCAL_CPPFLAGS += -DCHROMIUM_AVAILABLE=1
-
 LOCAL_SHARED_LIBRARIES += \
         libstagefright_enc_common \
         libstagefright_avc_common \
diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp
index 69209b5..6abaf23 100644
--- a/media/libstagefright/MP3Extractor.cpp
+++ b/media/libstagefright/MP3Extractor.cpp
@@ -311,10 +311,18 @@
     mMeta->setInt32(kKeyBitRate, bitrate * 1000);
     mMeta->setInt32(kKeyChannelCount, num_channels);
 
-    mSeeker = XINGSeeker::CreateFromSource(mDataSource, mFirstFramePos);
+    sp<XINGSeeker> seeker = XINGSeeker::CreateFromSource(mDataSource, mFirstFramePos);
 
-    if (mSeeker == NULL) {
+    if (seeker == NULL) {
         mSeeker = VBRISeeker::CreateFromSource(mDataSource, post_id3_pos);
+    } else {
+        mSeeker = seeker;
+        int encd = seeker->getEncoderDelay();
+        int encp = seeker->getEncoderPadding();
+        if (encd != 0 || encp != 0) {
+            mMeta->setInt32(kKeyEncoderDelay, encd);
+            mMeta->setInt32(kKeyEncoderPadding, encp);
+        }
     }
 
     if (mSeeker != NULL) {
@@ -340,6 +348,37 @@
     }
 
     mInitCheck = OK;
+
+    // get iTunes-style gapless info if present
+    ID3 id3(mDataSource);
+    if (id3.isValid()) {
+        ID3::Iterator *com = new ID3::Iterator(id3, "COM");
+        if (com->done()) {
+            delete com;
+            com = new ID3::Iterator(id3, "COMM");
+        }
+        while(!com->done()) {
+            String8 commentdesc;
+            String8 commentvalue;
+            com->getString(&commentdesc, &commentvalue);
+            const char * desc = commentdesc.string();
+            const char * value = commentvalue.string();
+
+            // first 3 characters are the language, which we don't care about
+            if(strlen(desc) > 3 && strcmp(desc + 3, "iTunSMPB") == 0) {
+
+                int32_t delay, padding;
+                if (sscanf(value, " %*x %x %x %*x", &delay, &padding) == 2) {
+                    mMeta->setInt32(kKeyEncoderDelay, delay);
+                    mMeta->setInt32(kKeyEncoderPadding, padding);
+                }
+                break;
+            }
+            com->next();
+        }
+        delete com;
+        com = NULL;
+    }
 }
 
 size_t MP3Extractor::countTracks() {
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 6c95d4e5..9385b8a 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -373,7 +373,8 @@
 
     if (mInitCheck == OK) {
         if (mHasVideo) {
-            mFileMetaData->setCString(kKeyMIMEType, "video/mp4");
+            mFileMetaData->setCString(
+                    kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_MPEG4);
         } else {
             mFileMetaData->setCString(kKeyMIMEType, "audio/mp4");
         }
@@ -599,6 +600,7 @@
 }
 
 status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
+    ALOGV("entering parseChunk %lld/%d", *offset, depth);
     uint32_t hdr[2];
     if (mDataSource->readAt(*offset, hdr, 8) < 8) {
         return ERROR_IO;
@@ -625,6 +627,7 @@
 
     char chunk[5];
     MakeFourCCString(chunk_type, chunk);
+    ALOGV("chunk: %s @ %lld", chunk, *offset);
 
 #if 0
     static const char kWhitespace[] = "                                        ";
@@ -1302,6 +1305,8 @@
             break;
         }
 
+        case FOURCC('m', 'e', 'a', 'n'):
+        case FOURCC('n', 'a', 'm', 'e'):
         case FOURCC('d', 'a', 't', 'a'):
         {
             if (mPath.size() == 6 && underMetaDataPath(mPath)) {
@@ -1437,6 +1442,15 @@
             break;
         }
 
+        case FOURCC('-', '-', '-', '-'):
+        {
+            mLastCommentMean.clear();
+            mLastCommentName.clear();
+            mLastCommentData.clear();
+            *offset += chunk_size;
+            break;
+        }
+
         default:
         {
             *offset += chunk_size;
@@ -1553,6 +1567,9 @@
     uint32_t flags = U32_AT(buffer);
 
     uint32_t metadataKey = 0;
+    char chunk[5];
+    MakeFourCCString(mPath[4], chunk);
+    ALOGV("meta: %s @ %lld", chunk, offset);
     switch (mPath[4]) {
         case FOURCC(0xa9, 'a', 'l', 'b'):
         {
@@ -1632,6 +1649,35 @@
             }
             break;
         }
+        case FOURCC('-', '-', '-', '-'):
+        {
+            buffer[size] = '\0';
+            switch (mPath[5]) {
+                case FOURCC('m', 'e', 'a', 'n'):
+                    mLastCommentMean.setTo((const char *)buffer + 4);
+                    break;
+                case FOURCC('n', 'a', 'm', 'e'):
+                    mLastCommentName.setTo((const char *)buffer + 4);
+                    break;
+                case FOURCC('d', 'a', 't', 'a'):
+                    mLastCommentData.setTo((const char *)buffer + 8);
+                    break;
+            }
+            if (mLastCommentMean == "com.apple.iTunes"
+                    && mLastCommentName == "iTunSMPB"
+                    && mLastCommentData.length() != 0) {
+                int32_t delay, padding;
+                if (sscanf(mLastCommentData,
+                           " %*x %x %x %*x", &delay, &padding) == 2) {
+                    mLastTrack->meta->setInt32(kKeyEncoderDelay, delay);
+                    mLastTrack->meta->setInt32(kKeyEncoderPadding, padding);
+                }
+                mLastCommentMean.clear();
+                mLastCommentName.clear();
+                mLastCommentData.clear();
+            }
+            break;
+        }
 
         default:
             break;
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index a9e7f360..42b5c7e 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -22,10 +22,14 @@
 
 #include "include/SoftwareRenderer.h"
 
+#include <binder/IServiceManager.h>
 #include <gui/SurfaceTextureClient.h>
+#include <media/ICrypto.h>
+#include <media/IMediaPlayerService.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
 #include <media/stagefright/ACodec.h>
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/MetaData.h>
@@ -528,6 +532,12 @@
                         info.mOwnedByClient = false;
                         CHECK(msg->findBuffer(name.c_str(), &info.mData));
 
+                        if (portIndex == kPortIndexInput
+                                && (mFlags & kFlagIsSecure)) {
+                            info.mEncryptedData =
+                                new ABuffer(info.mData->capacity());
+                        }
+
                         buffers->push_back(info);
                     }
 
@@ -742,6 +752,59 @@
                 format->setInt32("encoder", true);
             }
 
+            if (flags & CONFIGURE_FLAG_SECURE) {
+                mFlags |= kFlagIsSecure;
+
+                sp<IServiceManager> sm = defaultServiceManager();
+
+                sp<IBinder> binder =
+                    sm->getService(String16("media.player"));
+
+                sp<IMediaPlayerService> service =
+                    interface_cast<IMediaPlayerService>(binder);
+
+                CHECK(service != NULL);
+
+                mCrypto = service->makeCrypto();
+
+                status_t err = mCrypto->initialize();
+
+                if (err == OK) {
+                    sp<ABuffer> emm;
+                    if (format->findBuffer("emm", &emm)) {
+                        err = mCrypto->setEntitlementKey(
+                                emm->data(), emm->size());
+                    }
+                }
+
+                if (err == OK) {
+                    sp<ABuffer> ecm;
+                    if (format->findBuffer("ecm", &ecm)) {
+                        CHECK_EQ(ecm->size(), 80u);
+
+                        // bytes 16..47 of the original ecm stream data.
+                        err = mCrypto->setEntitlementControlMessage(
+                                ecm->data() + 16, 32);
+                    }
+                }
+
+                if (err != OK) {
+                    ALOGE("failed to instantiate crypto service.");
+
+                    mCrypto.clear();
+
+                    setState(INITIALIZED);
+
+                    sp<AMessage> response = new AMessage;
+                    response->setInt32("err", UNKNOWN_ERROR);
+
+                    response->postReply(mReplyID);
+                    break;
+                }
+            } else {
+                mFlags &= ~kFlagIsSecure;
+            }
+
             mCodec->initiateConfigureComponent(format);
             break;
         }
@@ -983,7 +1046,10 @@
             for (size_t i = 0; i < srcBuffers.size(); ++i) {
                 const BufferInfo &info = srcBuffers.itemAt(i);
 
-                dstBuffers->push_back(info.mData);
+                dstBuffers->push_back(
+                        (portIndex == kPortIndexInput
+                            && (mFlags & kFlagIsSecure))
+                                ? info.mEncryptedData : info.mData);
             }
 
             (new AMessage)->postReply(replyID);
@@ -1037,10 +1103,15 @@
 }
 
 void MediaCodec::setState(State newState) {
-    if (newState == UNINITIALIZED) {
+    if (newState == INITIALIZED) {
         delete mSoftRenderer;
         mSoftRenderer = NULL;
 
+        if (mCrypto != NULL) {
+            mCrypto->terminate();
+            mCrypto.clear();
+        }
+
         mNativeWindow.clear();
 
         mOutputFormat.clear();
@@ -1150,6 +1221,43 @@
         info->mData->meta()->setInt32("csd", true);
     }
 
+    if (mFlags & kFlagIsSecure) {
+        uint8_t iv[16];
+        memset(iv, 0, sizeof(iv));
+
+        ssize_t outLength;
+
+        if (mFlags & kFlagIsSoftwareCodec) {
+            outLength = mCrypto->decryptAudio(
+                    (flags & BUFFER_FLAG_ENCRYPTED) ? iv : NULL,
+                    (flags & BUFFER_FLAG_ENCRYPTED) ? sizeof(iv) : 0,
+                        info->mEncryptedData->base() + offset,
+                        size,
+                        info->mData->base(),
+                        info->mData->capacity());
+        } else {
+            outLength = mCrypto->decryptVideo(
+                    (flags & BUFFER_FLAG_ENCRYPTED) ? iv : NULL,
+                    (flags & BUFFER_FLAG_ENCRYPTED) ? sizeof(iv) : 0,
+                        info->mEncryptedData->base() + offset,
+                        size,
+                        info->mData->base(),
+                        0  /* offset */);
+        }
+
+        if (outLength < 0) {
+            return outLength;
+        }
+
+        if ((size_t)outLength > info->mEncryptedData->capacity()) {
+            return -ERANGE;
+        }
+
+        info->mData->setRange(0, outLength);
+    } else if (flags & BUFFER_FLAG_ENCRYPTED) {
+        return -EINVAL;
+    }
+
     reply->setBuffer("buffer", info->mData);
     reply->post();
 
diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp
index 2549de6..2740d6b 100644
--- a/media/libstagefright/MediaDefs.cpp
+++ b/media/libstagefright/MediaDefs.cpp
@@ -41,7 +41,7 @@
 const char *MEDIA_MIMETYPE_AUDIO_FLAC = "audio/flac";
 const char *MEDIA_MIMETYPE_AUDIO_AAC_ADTS = "audio/aac-adts";
 
-const char *MEDIA_MIMETYPE_CONTAINER_MPEG4 = "video/mpeg4";
+const char *MEDIA_MIMETYPE_CONTAINER_MPEG4 = "video/mp4";
 const char *MEDIA_MIMETYPE_CONTAINER_WAV = "audio/wav";
 const char *MEDIA_MIMETYPE_CONTAINER_OGG = "application/ogg";
 const char *MEDIA_MIMETYPE_CONTAINER_MATROSKA = "video/x-matroska";
diff --git a/media/libstagefright/MetaData.cpp b/media/libstagefright/MetaData.cpp
index 66dec90..755594a 100644
--- a/media/libstagefright/MetaData.cpp
+++ b/media/libstagefright/MetaData.cpp
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MetaData"
+#include <utils/Log.h>
+
 #include <stdlib.h>
 #include <string.h>
 
@@ -282,5 +286,60 @@
     mSize = 0;
 }
 
+String8 MetaData::typed_data::asString() const {
+    String8 out;
+    const void *data = storage();
+    switch(mType) {
+        case TYPE_NONE:
+            out = String8::format("no type, size %d)", mSize);
+            break;
+        case TYPE_C_STRING:
+            out = String8::format("(char*) %s", (const char *)data);
+            break;
+        case TYPE_INT32:
+            out = String8::format("(int32_t) %d", *(int32_t *)data);
+            break;
+        case TYPE_INT64:
+            out = String8::format("(int64_t) %lld", *(int64_t *)data);
+            break;
+        case TYPE_FLOAT:
+            out = String8::format("(float) %f", *(float *)data);
+            break;
+        case TYPE_POINTER:
+            out = String8::format("(void*) %p", *(void **)data);
+            break;
+        case TYPE_RECT:
+        {
+            const Rect *r = (const Rect *)data;
+            out = String8::format("Rect(%d, %d, %d, %d)",
+                                  r->mLeft, r->mTop, r->mRight, r->mBottom);
+            break;
+        }
+
+        default:
+            out = String8::format("(unknown type %d, size %d)", mType, mSize);
+            break;
+    }
+    return out;
+}
+
+static void MakeFourCCString(uint32_t x, char *s) {
+    s[0] = x >> 24;
+    s[1] = (x >> 16) & 0xff;
+    s[2] = (x >> 8) & 0xff;
+    s[3] = x & 0xff;
+    s[4] = '\0';
+}
+
+void MetaData::dumpToLog() const {
+    for (int i = mItems.size(); --i >= 0;) {
+        int32_t key = mItems.keyAt(i);
+        char cc[5];
+        MakeFourCCString(key, cc);
+        const typed_data &item = mItems.valueAt(i);
+        ALOGI("%s: %s", cc, item.asString().string());
+    }
+}
+
 }  // namespace android
 
diff --git a/media/libstagefright/NuMediaExtractor.cpp b/media/libstagefright/NuMediaExtractor.cpp
index afd4763..224ec33 100644
--- a/media/libstagefright/NuMediaExtractor.cpp
+++ b/media/libstagefright/NuMediaExtractor.cpp
@@ -107,6 +107,11 @@
 
         msg->setInt32("channel-count", numChannels);
         msg->setInt32("sample-rate", sampleRate);
+
+        int32_t isADTS;
+        if (meta->findInt32(kKeyIsADTS, &isADTS)) {
+            msg->setInt32("is-adts", true);
+        }
     }
 
     int32_t maxInputSize;
@@ -232,6 +237,20 @@
         msg->setBuffer("csd-1", buffer);
     }
 
+    if (meta->findData(kKeyEMM, &type, &data, &size)) {
+        sp<ABuffer> emm = new ABuffer(size);
+        memcpy(emm->data(), data, size);
+
+        msg->setBuffer("emm", emm);
+    }
+
+    if (meta->findData(kKeyECM, &type, &data, &size)) {
+        sp<ABuffer> ecm = new ABuffer(size);
+        memcpy(ecm->data(), data, size);
+
+        msg->setBuffer("ecm", ecm);
+    }
+
     *format = msg;
 
     return OK;
@@ -267,13 +286,14 @@
     info->mFinalResult = OK;
     info->mSample = NULL;
     info->mSampleTimeUs = -1ll;
-    info->mFlags = 0;
+    info->mSampleFlags = 0;
+    info->mTrackFlags = 0;
 
     const char *mime;
     CHECK(source->getFormat()->findCString(kKeyMIMEType, &mime));
 
     if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) {
-        info->mFlags |= kIsVorbis;
+        info->mTrackFlags |= kIsVorbis;
     }
 
     return OK;
@@ -288,6 +308,7 @@
             info->mSample = NULL;
 
             info->mSampleTimeUs = -1ll;
+            info->mSampleFlags = 0;
         }
     }
 }
@@ -306,6 +327,7 @@
                 info->mSample->release();
                 info->mSample = NULL;
                 info->mSampleTimeUs = -1ll;
+                info->mSampleFlags = 0;
             }
         } else if (info->mFinalResult != OK) {
             continue;
@@ -323,11 +345,25 @@
 
                 info->mFinalResult = err;
                 info->mSampleTimeUs = -1ll;
+                info->mSampleFlags = 0;
                 continue;
             } else {
                 CHECK(info->mSample != NULL);
                 CHECK(info->mSample->meta_data()->findInt64(
                             kKeyTime, &info->mSampleTimeUs));
+
+                info->mSampleFlags = 0;
+
+                int32_t val;
+                if (info->mSample->meta_data()->findInt32(
+                            kKeyIsSyncFrame, &val) && val != 0) {
+                    info->mSampleFlags |= SAMPLE_FLAG_SYNC;
+                }
+
+                if (info->mSample->meta_data()->findInt32(
+                            kKeyScrambling, &val) && val != 0) {
+                    info->mSampleFlags |= SAMPLE_FLAG_ENCRYPTED;
+                }
             }
         }
 
@@ -371,7 +407,7 @@
 
     size_t sampleSize = info->mSample->range_length();
 
-    if (info->mFlags & kIsVorbis) {
+    if (info->mTrackFlags & kIsVorbis) {
         // Each sample's data is suffixed by the number of page samples
         // or -1 if not available.
         sampleSize += sizeof(int32_t);
@@ -387,7 +423,7 @@
 
     memcpy((uint8_t *)buffer->data(), src, info->mSample->range_length());
 
-    if (info->mFlags & kIsVorbis) {
+    if (info->mTrackFlags & kIsVorbis) {
         int32_t numPageSamples;
         if (!info->mSample->meta_data()->findInt32(
                     kKeyValidSamples, &numPageSamples)) {
@@ -430,4 +466,17 @@
     return OK;
 }
 
+status_t NuMediaExtractor::getSampleFlags(uint32_t *sampleFlags) {
+    ssize_t minIndex = fetchTrackSamples();
+
+    if (minIndex < 0) {
+        return ERROR_END_OF_STREAM;
+    }
+
+    TrackInfo *info = &mSelectedTracks.editItemAt(minIndex);
+    *sampleFlags = info->mSampleFlags;
+
+    return OK;
+}
+
 }  // namespace android
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index d5e6bec..9769f21 100755
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -38,6 +38,7 @@
 #include <media/stagefright/MetaData.h>
 #include <media/stagefright/OMXCodec.h>
 #include <media/stagefright/Utils.h>
+#include <media/stagefright/SkipCutBuffer.h>
 #include <utils/Vector.h>
 
 #include <OMX_Audio.h>
@@ -514,7 +515,12 @@
         CHECK(meta->findInt32(kKeyChannelCount, &numChannels));
         CHECK(meta->findInt32(kKeySampleRate, &sampleRate));
 
-        status_t err = setAACFormat(numChannels, sampleRate, bitRate);
+        int32_t isADTS;
+        if (!meta->findInt32(kKeyIsADTS, &isADTS)) {
+            isADTS = false;
+        }
+
+        status_t err = setAACFormat(numChannels, sampleRate, bitRate, isADTS);
         if (err != OK) {
             CODEC_LOGE("setAACFormat() failed (err = %d)", err);
             return err;
@@ -1303,6 +1309,7 @@
       mSeekMode(ReadOptions::SEEK_CLOSEST_SYNC),
       mTargetTimeUs(-1),
       mOutputPortSettingsChangedPending(false),
+      mSkipCutBuffer(NULL),
       mLeftOverBuffer(NULL),
       mPaused(false),
       mNativeWindow(
@@ -1413,6 +1420,9 @@
 
     free(mMIME);
     mMIME = NULL;
+
+    delete mSkipCutBuffer;
+    mSkipCutBuffer = NULL;
 }
 
 status_t OMXCodec::init() {
@@ -1573,6 +1583,34 @@
              portIndex == kPortIndexInput ? "input" : "output");
     }
 
+    if (portIndex == kPortIndexOutput) {
+
+        sp<MetaData> meta = mSource->getFormat();
+        int32_t delay = 0;
+        if (!meta->findInt32(kKeyEncoderDelay, &delay)) {
+            delay = 0;
+        }
+        int32_t padding = 0;
+        if (!meta->findInt32(kKeyEncoderPadding, &padding)) {
+            padding = 0;
+        }
+        int32_t numchannels = 0;
+        if (delay + padding) {
+            if (meta->findInt32(kKeyChannelCount, &numchannels)) {
+                size_t frameSize = numchannels * sizeof(int16_t);
+                if (mSkipCutBuffer) {
+                    size_t prevbuffersize = mSkipCutBuffer->size();
+                    if (prevbuffersize != 0) {
+                        ALOGW("Replacing SkipCutBuffer holding %d bytes", prevbuffersize);
+                    }
+                    delete mSkipCutBuffer;
+                }
+                mSkipCutBuffer = new SkipCutBuffer(delay * frameSize, padding * frameSize,
+                                                   def.nBufferSize);
+            }
+        }
+    }
+
     // dumpPortStatus(portIndex);
 
     if (portIndex == kPortIndexInput && (mFlags & kUseSecureInputBuffers)) {
@@ -2490,6 +2528,10 @@
             CHECK_EQ(countBuffersWeOwn(mPortBuffers[portIndex]),
                      mPortBuffers[portIndex].size());
 
+            if (mSkipCutBuffer && mPortStatus[kPortIndexOutput] == ENABLED) {
+                mSkipCutBuffer->clear();
+            }
+
             if (mState == RECONFIGURING) {
                 CHECK_EQ(portIndex, (OMX_U32)kPortIndexOutput);
 
@@ -3349,11 +3391,17 @@
     }
 }
 
-status_t OMXCodec::setAACFormat(int32_t numChannels, int32_t sampleRate, int32_t bitRate) {
-    if (numChannels > 2)
+status_t OMXCodec::setAACFormat(
+        int32_t numChannels, int32_t sampleRate, int32_t bitRate, bool isADTS) {
+    if (numChannels > 2) {
         ALOGW("Number of channels: (%d) \n", numChannels);
+    }
 
     if (mIsEncoder) {
+        if (isADTS) {
+            return -EINVAL;
+        }
+
         //////////////// input port ////////////////////
         setRawAudioFormat(kPortIndexInput, sampleRate, numChannels);
 
@@ -3408,7 +3456,9 @@
                 &profile, sizeof(profile));
 
         if (err != OK) {
-            CODEC_LOGE("setParameter('OMX_IndexParamAudioAac') failed (err = %d)", err);
+            CODEC_LOGE("setParameter('OMX_IndexParamAudioAac') failed "
+                       "(err = %d)",
+                       err);
             return err;
         }
     } else {
@@ -3422,13 +3472,19 @@
 
         profile.nChannels = numChannels;
         profile.nSampleRate = sampleRate;
-        profile.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4ADTS;
+
+        profile.eAACStreamFormat =
+            isADTS
+                ? OMX_AUDIO_AACStreamFormatMP4ADTS
+                : OMX_AUDIO_AACStreamFormatMP4FF;
 
         err = mOMX->setParameter(
                 mNode, OMX_IndexParamAudioAac, &profile, sizeof(profile));
 
         if (err != OK) {
-            CODEC_LOGE("setParameter('OMX_IndexParamAudioAac') failed (err = %d)", err);
+            CODEC_LOGE("setParameter('OMX_IndexParamAudioAac') failed "
+                       "(err = %d)",
+                       err);
             return err;
         }
     }
@@ -3800,6 +3856,9 @@
     info->mStatus = OWNED_BY_CLIENT;
 
     info->mMediaBuffer->add_ref();
+    if (mSkipCutBuffer) {
+        mSkipCutBuffer->submit(info->mMediaBuffer);
+    }
     *buffer = info->mMediaBuffer;
 
     return OK;
diff --git a/media/libstagefright/SkipCutBuffer.cpp b/media/libstagefright/SkipCutBuffer.cpp
new file mode 100755
index 0000000..6d331b0
--- /dev/null
+++ b/media/libstagefright/SkipCutBuffer.cpp
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SkipCutBuffer"
+#include <utils/Log.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/SkipCutBuffer.h>
+
+namespace android {
+
+SkipCutBuffer::SkipCutBuffer(int32_t skip, int32_t cut, int32_t output_size) {
+    mFrontPadding = skip;
+    mBackPadding = cut;
+    mWriteHead = 0;
+    mReadHead = 0;
+    mCapacity = cut + output_size;
+    mCutBuffer = new char[mCapacity];
+    ALOGV("skipcutbuffer %d %d %d", skip, cut, mCapacity);
+}
+
+SkipCutBuffer::~SkipCutBuffer() {
+    delete[] mCutBuffer;
+}
+
+void SkipCutBuffer::submit(MediaBuffer *buffer) {
+    int32_t offset = buffer->range_offset();
+    int32_t buflen = buffer->range_length();
+
+    // drop the initial data from the buffer if needed
+    if (mFrontPadding > 0) {
+        // still data left to drop
+        int32_t to_drop = (buflen < mFrontPadding) ? buflen : mFrontPadding;
+        offset += to_drop;
+        buflen -= to_drop;
+        buffer->set_range(offset, buflen);
+        mFrontPadding -= to_drop;
+    }
+
+
+    // append data to cutbuffer
+    char *src = ((char*) buffer->data()) + offset;
+    write(src, buflen);
+
+
+    // the mediabuffer is now empty. Fill it from cutbuffer, always leaving
+    // at least mBackPadding bytes in the cutbuffer
+    char *dst = (char*) buffer->data();
+    size_t copied = read(dst, buffer->size());
+    buffer->set_range(0, copied);
+}
+
+void SkipCutBuffer::clear() {
+    mWriteHead = mReadHead = 0;
+}
+
+void SkipCutBuffer::write(const char *src, size_t num) {
+    int32_t sizeused = (mWriteHead - mReadHead);
+    if (sizeused < 0) sizeused += mCapacity;
+
+    // everything must fit
+    CHECK_GE((mCapacity - size_t(sizeused)), num);
+
+    size_t copyfirst = (mCapacity - mWriteHead);
+    if (copyfirst > num) copyfirst = num;
+    if (copyfirst) {
+        memcpy(mCutBuffer + mWriteHead, src, copyfirst);
+        num -= copyfirst;
+        src += copyfirst;
+        mWriteHead += copyfirst;
+        CHECK_LE(mWriteHead, mCapacity);
+        if (mWriteHead == mCapacity) mWriteHead = 0;
+        if (num) {
+            memcpy(mCutBuffer, src, num);
+            mWriteHead += num;
+        }
+    }
+}
+
+size_t SkipCutBuffer::read(char *dst, size_t num) {
+    int32_t available = (mWriteHead - mReadHead);
+    if (available < 0) available += mCapacity;
+
+    available -= mBackPadding;
+    if (available <=0) {
+        return 0;
+    }
+    if (available < num) {
+        num = available;
+    }
+
+    size_t copyfirst = (mCapacity - mReadHead);
+    if (copyfirst > num) copyfirst = num;
+    if (copyfirst) {
+        memcpy(dst, mCutBuffer + mReadHead, copyfirst);
+        num -= copyfirst;
+        dst += copyfirst;
+        mReadHead += copyfirst;
+        CHECK_LE(mReadHead, mCapacity);
+        if (mReadHead == mCapacity) mReadHead = 0;
+        if (num) {
+            memcpy(dst, mCutBuffer, num);
+            mReadHead += num;
+        }
+    }
+    return available;
+}
+
+size_t SkipCutBuffer::size() {
+    int32_t available = (mWriteHead - mReadHead);
+    if (available < 0) available += mCapacity;
+    return available;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/TimedEventQueue.cpp b/media/libstagefright/TimedEventQueue.cpp
index 6d345bb..9df15eb 100644
--- a/media/libstagefright/TimedEventQueue.cpp
+++ b/media/libstagefright/TimedEventQueue.cpp
@@ -31,10 +31,6 @@
 
 #include <media/stagefright/foundation/ADebug.h>
 
-#ifdef ANDROID_SIMULATOR
-#include <jni.h>
-#endif
-
 namespace android {
 
 TimedEventQueue::TimedEventQueue()
@@ -193,27 +189,10 @@
 // static
 void *TimedEventQueue::ThreadWrapper(void *me) {
 
-#ifdef ANDROID_SIMULATOR
-    // The simulator runs everything as one process, so any
-    // Binder calls happen on this thread instead of a thread
-    // in another process. We therefore need to make sure that
-    // this thread can do calls into interpreted code.
-    // On the device this is not an issue because the remote
-    // thread will already be set up correctly for this.
-    JavaVM *vm;
-    int numvms;
-    JNI_GetCreatedJavaVMs(&vm, 1, &numvms);
-    JNIEnv *env;
-    vm->AttachCurrentThread(&env, NULL);
-#endif
-
     androidSetThreadPriority(0, ANDROID_PRIORITY_FOREGROUND);
 
     static_cast<TimedEventQueue *>(me)->threadEntry();
 
-#ifdef ANDROID_SIMULATOR
-    vm->DetachCurrentThread();
-#endif
     return NULL;
 }
 
diff --git a/media/libstagefright/XINGSeeker.cpp b/media/libstagefright/XINGSeeker.cpp
index 8c99c76..9c91134 100644
--- a/media/libstagefright/XINGSeeker.cpp
+++ b/media/libstagefright/XINGSeeker.cpp
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "XINGSEEKER"
+#include <utils/Log.h>
+
 #include "include/XINGSeeker.h"
 #include "include/avc_utils.h"
 
@@ -24,7 +27,9 @@
 
 XINGSeeker::XINGSeeker()
     : mDurationUs(-1),
-      mSizeBytes(0) {
+      mSizeBytes(0),
+      mEncoderDelay(0),
+      mEncoderPadding(0) {
 }
 
 bool XINGSeeker::getDuration(int64_t *durationUs) {
@@ -76,8 +81,6 @@
 
     seeker->mFirstFramePos = first_frame_pos;
 
-    ALOGI("xingseeker first frame pos: %lld", first_frame_pos);
-
     seeker->mSizeBytes = 0;
     seeker->mTOCValid = false;
     seeker->mDurationUs = 0;
@@ -111,6 +114,8 @@
         else offset += 9;
     }
 
+    int xingbase = offset;
+
     if (source->readAt(offset, &buffer, 4) < 4) { // XING header ID
         return NULL;
     }
@@ -161,10 +166,31 @@
         // do something with the quality indicator
         offset += 4;
     }
+
+    if (source->readAt(xingbase + 0xaf - 0x24, &buffer, 1) < 1) { // encoding flags
+        return false;
+    }
+
+    ALOGV("nogap preceding: %s, nogap continued in next: %s",
+              (buffer[0] & 0x80) ? "true" : "false",
+              (buffer[0] & 0x40) ? "true" : "false");
 #endif
 
+    if (source->readAt(xingbase + 0xb1 - 0x24, &buffer, 3) == 3) {
+        seeker->mEncoderDelay = (buffer[0] << 4) + (buffer[1] >> 4);
+        seeker->mEncoderPadding = ((buffer[1] & 0xf) << 8) + buffer[2];
+    }
+
     return seeker;
 }
 
+int32_t XINGSeeker::getEncoderDelay() {
+    return mEncoderDelay;
+}
+
+int32_t XINGSeeker::getEncoderPadding() {
+    return mEncoderPadding;
+}
+
 }  // namespace android
 
diff --git a/media/libstagefright/chromium_http/Android.mk b/media/libstagefright/chromium_http/Android.mk
index e37b4a8..d595686 100644
--- a/media/libstagefright/chromium_http/Android.mk
+++ b/media/libstagefright/chromium_http/Android.mk
@@ -1,5 +1,6 @@
 LOCAL_PATH:= $(call my-dir)
 
+ifneq ($(TARGET_BUILD_PDK), true)
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:=       \
@@ -8,7 +9,6 @@
         support.cpp
 
 LOCAL_C_INCLUDES:= \
-        $(JNI_H_INCLUDE) \
         frameworks/base/media/libstagefright \
         $(TOP)/frameworks/native/include/media/openmax \
         external/chromium \
@@ -22,3 +22,4 @@
 LOCAL_MODULE:= libstagefright_chromium_http
 
 include $(BUILD_STATIC_LIBRARY)
+endif
diff --git a/media/libstagefright/codecs/aacdec/SoftAAC.cpp b/media/libstagefright/codecs/aacdec/SoftAAC.cpp
index ea6c360..90f96c6 100644
--- a/media/libstagefright/codecs/aacdec/SoftAAC.cpp
+++ b/media/libstagefright/codecs/aacdec/SoftAAC.cpp
@@ -23,6 +23,7 @@
 #include "pvmp4audiodecoder_api.h"
 
 #include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/hexdump.h>
 
 namespace android {
 
@@ -42,6 +43,7 @@
         OMX_COMPONENTTYPE **component)
     : SimpleSoftOMXComponent(name, callbacks, appData, component),
       mConfig(new tPVMP4AudioDecoderExternal),
+      mIsADTS(false),
       mDecoderBuf(NULL),
       mInputBufferCount(0),
       mUpsamplingFactor(2),
@@ -140,7 +142,12 @@
             aacParams->nAACtools = 0;
             aacParams->nAACERtools = 0;
             aacParams->eAACProfile = OMX_AUDIO_AACObjectMain;
-            aacParams->eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4FF;
+
+            aacParams->eAACStreamFormat =
+                mIsADTS
+                    ? OMX_AUDIO_AACStreamFormatMP4ADTS
+                    : OMX_AUDIO_AACStreamFormatMP4FF;
+
             aacParams->eChannelMode = OMX_AUDIO_ChannelModeStereo;
 
             if (!isConfigured()) {
@@ -215,6 +222,15 @@
                 return OMX_ErrorUndefined;
             }
 
+            if (aacParams->eAACStreamFormat == OMX_AUDIO_AACStreamFormatMP4FF) {
+                mIsADTS = false;
+            } else if (aacParams->eAACStreamFormat
+                        == OMX_AUDIO_AACStreamFormatMP4ADTS) {
+                mIsADTS = true;
+            } else {
+                return OMX_ErrorUndefined;
+            }
+
             return OMX_ErrorNone;
         }
 
@@ -299,8 +315,35 @@
             mNumSamplesOutput = 0;
         }
 
-        mConfig->pInputBuffer = inHeader->pBuffer + inHeader->nOffset;
-        mConfig->inputBufferCurrentLength = inHeader->nFilledLen;
+        if (mIsADTS) {
+            // skip 30 bits, aac_frame_length follows.
+            // ssssssss ssssiiip ppffffPc ccohCCll llllllll lll?????
+
+            const uint8_t *adtsHeader = inHeader->pBuffer + inHeader->nOffset;
+
+            CHECK_GE(inHeader->nFilledLen, 7);
+
+            bool protectionAbsent = (adtsHeader[1] & 1);
+
+            unsigned aac_frame_length =
+                ((adtsHeader[3] & 3) << 11)
+                | (adtsHeader[4] << 3)
+                | (adtsHeader[5] >> 5);
+
+            CHECK_GE(inHeader->nFilledLen, aac_frame_length);
+
+            size_t headerSize = (protectionAbsent ? 7 : 9);
+
+            mConfig->pInputBuffer = (UChar *)adtsHeader + headerSize;
+            mConfig->inputBufferCurrentLength = aac_frame_length - headerSize;
+
+            inHeader->nOffset += headerSize;
+            inHeader->nFilledLen -= headerSize;
+        } else {
+            mConfig->pInputBuffer = inHeader->pBuffer + inHeader->nOffset;
+            mConfig->inputBufferCurrentLength = inHeader->nFilledLen;
+        }
+
         mConfig->inputBufferMaxLength = 0;
         mConfig->inputBufferUsedLength = 0;
         mConfig->remainderBits = 0;
diff --git a/media/libstagefright/codecs/aacdec/SoftAAC.h b/media/libstagefright/codecs/aacdec/SoftAAC.h
index 963fd27..da0b8ed 100644
--- a/media/libstagefright/codecs/aacdec/SoftAAC.h
+++ b/media/libstagefright/codecs/aacdec/SoftAAC.h
@@ -49,6 +49,7 @@
     };
 
     tPVMP4AudioDecoderExternal *mConfig;
+    bool mIsADTS;
     void *mDecoderBuf;
 
     size_t mInputBufferCount;
diff --git a/media/libstagefright/codecs/mp3dec/SoftMP3.cpp b/media/libstagefright/codecs/mp3dec/SoftMP3.cpp
index ad55295..92009ee 100644
--- a/media/libstagefright/codecs/mp3dec/SoftMP3.cpp
+++ b/media/libstagefright/codecs/mp3dec/SoftMP3.cpp
@@ -115,6 +115,7 @@
     mDecoderBuf = malloc(memRequirements);
 
     pvmp3_InitDecoder(mConfig, mDecoderBuf);
+    mIsFirst = true;
 }
 
 OMX_ERRORTYPE SoftMP3::internalGetParameter(
@@ -190,7 +191,10 @@
             inInfo->mOwnedByUs = false;
             notifyEmptyBufferDone(inHeader);
 
-            outHeader->nFilledLen = 0;
+            // pad the end of the stream with 529 samples, since that many samples
+            // were trimmed off the beginning when decoding started
+            outHeader->nFilledLen = kPVMP3DecoderDelay * mNumChannels * sizeof(int16_t);
+            memset(outHeader->pBuffer, 0, outHeader->nFilledLen);
             outHeader->nFlags = OMX_BUFFERFLAG_EOS;
 
             outQueue.erase(outQueue.begin());
@@ -251,8 +255,17 @@
             return;
         }
 
-        outHeader->nOffset = 0;
-        outHeader->nFilledLen = mConfig->outputFrameSize * sizeof(int16_t);
+        if (mIsFirst) {
+            mIsFirst = false;
+            // The decoder delay is 529 samples, so trim that many samples off
+            // the start of the first output buffer. This essentially makes this
+            // decoder have zero delay, which the rest of the pipeline assumes.
+            outHeader->nOffset = kPVMP3DecoderDelay * mNumChannels * sizeof(int16_t);
+            outHeader->nFilledLen = mConfig->outputFrameSize * sizeof(int16_t) - outHeader->nOffset;
+        } else {
+            outHeader->nOffset = 0;
+            outHeader->nFilledLen = mConfig->outputFrameSize * sizeof(int16_t);
+        }
 
         outHeader->nTimeStamp =
             mAnchorTimeUs
@@ -288,6 +301,7 @@
         // Make sure that the next buffer output does not still
         // depend on fragments from the last one decoded.
         pvmp3_InitDecoder(mConfig, mDecoderBuf);
+        mIsFirst = true;
     }
 }
 
diff --git a/media/libstagefright/codecs/mp3dec/SoftMP3.h b/media/libstagefright/codecs/mp3dec/SoftMP3.h
index 70d0682..3a05466 100644
--- a/media/libstagefright/codecs/mp3dec/SoftMP3.h
+++ b/media/libstagefright/codecs/mp3dec/SoftMP3.h
@@ -46,7 +46,8 @@
 private:
     enum {
         kNumBuffers = 4,
-        kOutputBufferSize = 4608 * 2
+        kOutputBufferSize = 4608 * 2,
+        kPVMP3DecoderDelay = 529 // frames
     };
 
     tPVMP3DecoderExternal *mConfig;
@@ -57,8 +58,7 @@
     int32_t mNumChannels;
     int32_t mSamplingRate;
 
-    bool mConfigured;
-
+    bool mIsFirst;
     bool mSignalledError;
 
     enum {
diff --git a/media/libstagefright/foundation/AString.cpp b/media/libstagefright/foundation/AString.cpp
index 61b76cf..dee786d 100644
--- a/media/libstagefright/foundation/AString.cpp
+++ b/media/libstagefright/foundation/AString.cpp
@@ -310,6 +310,16 @@
     return !strncmp(mData, prefix, strlen(prefix));
 }
 
+bool AString::endsWith(const char *suffix) const {
+    size_t suffixLen = strlen(suffix);
+
+    if (mSize < suffixLen) {
+        return false;
+    }
+
+    return !strcmp(mData + mSize - suffixLen, suffix);
+}
+
 AString StringPrintf(const char *format, ...) {
     va_list ap;
     va_start(ap, format);
diff --git a/media/libstagefright/httplive/Android.mk b/media/libstagefright/httplive/Android.mk
index a5990c3..90cb448 100644
--- a/media/libstagefright/httplive/Android.mk
+++ b/media/libstagefright/httplive/Android.mk
@@ -8,7 +8,6 @@
         M3UParser.cpp           \
 
 LOCAL_C_INCLUDES:= \
-	$(JNI_H_INCLUDE) \
 	$(TOP)/frameworks/base/media/libstagefright \
 	$(TOP)/frameworks/native/include/media/openmax \
 	$(TOP)/external/openssl/include
diff --git a/media/libstagefright/id3/ID3.cpp b/media/libstagefright/id3/ID3.cpp
index 2e92926..ca14054 100644
--- a/media/libstagefright/id3/ID3.cpp
+++ b/media/libstagefright/id3/ID3.cpp
@@ -463,40 +463,65 @@
     tmp = NULL;
 }
 
-void ID3::Iterator::getString(String8 *id) const {
+// the 2nd argument is used to get the data following the \0 in a comment field
+void ID3::Iterator::getString(String8 *id, String8 *comment) const {
+    getstring(id, false);
+    if (comment != NULL) {
+        getstring(comment, true);
+    }
+}
+
+// comment fields (COM/COMM) contain an initial short descriptor, followed by \0,
+// followed by more data. The data following the \0 can be retrieved by setting
+// "otherdata" to true.
+void ID3::Iterator::getstring(String8 *id, bool otherdata) const {
     id->setTo("");
 
-    if (mFrameData == NULL) {
+    const uint8_t *frameData = mFrameData;
+    if (frameData == NULL) {
         return;
     }
 
+    uint8_t encoding = *frameData;
+
     if (mParent.mVersion == ID3_V1 || mParent.mVersion == ID3_V1_1) {
         if (mOffset == 126 || mOffset == 127) {
             // Special treatment for the track number and genre.
             char tmp[16];
-            sprintf(tmp, "%d", (int)*mFrameData);
+            sprintf(tmp, "%d", (int)*frameData);
 
             id->setTo(tmp);
             return;
         }
 
-        convertISO8859ToString8(mFrameData, mFrameSize, id);
+        convertISO8859ToString8(frameData, mFrameSize, id);
         return;
     }
 
     size_t n = mFrameSize - getHeaderLength() - 1;
+    if (otherdata) {
+        // skip past the encoding, language, and the 0 separator
+        frameData += 4;
+        int32_t i = n - 4;
+        while(--i >= 0 && *++frameData != 0) ;
+        int skipped = (frameData - mFrameData);
+        if (skipped >= n) {
+            return;
+        }
+        n -= skipped;
+    }
 
-    if (*mFrameData == 0x00) {
+    if (encoding == 0x00) {
         // ISO 8859-1
-        convertISO8859ToString8(mFrameData + 1, n, id);
-    } else if (*mFrameData == 0x03) {
+        convertISO8859ToString8(frameData + 1, n, id);
+    } else if (encoding == 0x03) {
         // UTF-8
-        id->setTo((const char *)(mFrameData + 1), n);
-    } else if (*mFrameData == 0x02) {
+        id->setTo((const char *)(frameData + 1), n);
+    } else if (encoding == 0x02) {
         // UTF-16 BE, no byte order mark.
         // API wants number of characters, not number of bytes...
         int len = n / 2;
-        const char16_t *framedata = (const char16_t *) (mFrameData + 1);
+        const char16_t *framedata = (const char16_t *) (frameData + 1);
         char16_t *framedatacopy = NULL;
 #if BYTE_ORDER == LITTLE_ENDIAN
         framedatacopy = new char16_t[len];
@@ -513,7 +538,7 @@
         // UCS-2
         // API wants number of characters, not number of bytes...
         int len = n / 2;
-        const char16_t *framedata = (const char16_t *) (mFrameData + 1);
+        const char16_t *framedata = (const char16_t *) (frameData + 1);
         char16_t *framedatacopy = NULL;
         if (*framedata == 0xfffe) {
             // endianness marker doesn't match host endianness, convert
diff --git a/media/libstagefright/include/ID3.h b/media/libstagefright/include/ID3.h
index 98c82a4..8714008 100644
--- a/media/libstagefright/include/ID3.h
+++ b/media/libstagefright/include/ID3.h
@@ -50,7 +50,7 @@
 
         bool done() const;
         void getID(String8 *id) const;
-        void getString(String8 *s) const;
+        void getString(String8 *s, String8 *ss = NULL) const;
         const uint8_t *getData(size_t *length) const;
         void next();
 
@@ -65,6 +65,7 @@
         void findFrame();
 
         size_t getHeaderLength() const;
+        void getstring(String8 *s, bool secondhalf) const;
 
         Iterator(const Iterator &);
         Iterator &operator=(const Iterator &);
diff --git a/media/libstagefright/include/MPEG4Extractor.h b/media/libstagefright/include/MPEG4Extractor.h
index eae62c6..5c549e0 100644
--- a/media/libstagefright/include/MPEG4Extractor.h
+++ b/media/libstagefright/include/MPEG4Extractor.h
@@ -20,6 +20,7 @@
 
 #include <media/stagefright/MediaExtractor.h>
 #include <utils/Vector.h>
+#include <utils/String8.h>
 
 namespace android {
 
@@ -64,6 +65,9 @@
     sp<MetaData> mFileMetaData;
 
     Vector<uint32_t> mPath;
+    String8 mLastCommentMean;
+    String8 mLastCommentName;
+    String8 mLastCommentData;
 
     status_t readMetaData();
     status_t parseChunk(off64_t *offset, int depth);
diff --git a/media/libstagefright/include/XINGSeeker.h b/media/libstagefright/include/XINGSeeker.h
index 8510979..c408576 100644
--- a/media/libstagefright/include/XINGSeeker.h
+++ b/media/libstagefright/include/XINGSeeker.h
@@ -31,10 +31,15 @@
     virtual bool getDuration(int64_t *durationUs);
     virtual bool getOffsetForTime(int64_t *timeUs, off64_t *pos);
 
+    virtual int32_t getEncoderDelay();
+    virtual int32_t getEncoderPadding();
+
 private:
     int64_t mFirstFramePos;
     int64_t mDurationUs;
     int32_t mSizeBytes;
+    int32_t mEncoderDelay;
+    int32_t mEncoderPadding;
 
     // TOC entries in XING header. Skip the first one since it's always 0.
     unsigned char mTOC[99];
diff --git a/media/libstagefright/matroska/Android.mk b/media/libstagefright/matroska/Android.mk
index e67da4c..2cccb4f 100644
--- a/media/libstagefright/matroska/Android.mk
+++ b/media/libstagefright/matroska/Android.mk
@@ -5,7 +5,6 @@
         MatroskaExtractor.cpp
 
 LOCAL_C_INCLUDES:= \
-        $(JNI_H_INCLUDE) \
         $(TOP)/external/libvpx/mkvparser \
         $(TOP)/frameworks/native/include/media/openmax \
 
diff --git a/media/libstagefright/mpeg2ts/Android.mk b/media/libstagefright/mpeg2ts/Android.mk
index ac4c2a1..eaa139d 100644
--- a/media/libstagefright/mpeg2ts/Android.mk
+++ b/media/libstagefright/mpeg2ts/Android.mk
@@ -10,7 +10,6 @@
         MPEG2TSExtractor.cpp      \
 
 LOCAL_C_INCLUDES:= \
-	$(JNI_H_INCLUDE) \
 	$(TOP)/frameworks/base/media/libstagefright \
 	$(TOP)/frameworks/native/include/media/openmax
 
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
index d708ba6..e1ac53c 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
@@ -117,6 +117,12 @@
 
             mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);
 
+            int32_t scrambling;
+            if (buffer->meta()->findInt32("scrambling", &scrambling)
+                    && scrambling != 0) {
+                mediaBuffer->meta_data()->setInt32(kKeyScrambling, scrambling);
+            }
+
             *out = mediaBuffer;
             return OK;
         }
diff --git a/media/libstagefright/omx/Android.mk b/media/libstagefright/omx/Android.mk
index 083c7ef..d20ecb6 100644
--- a/media/libstagefright/omx/Android.mk
+++ b/media/libstagefright/omx/Android.mk
@@ -1,8 +1,6 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_C_INCLUDES += $(JNI_H_INCLUDE)
-
 LOCAL_SRC_FILES:=                     \
         OMX.cpp                       \
         OMXMaster.cpp                 \
diff --git a/media/libstagefright/omx/tests/Android.mk b/media/libstagefright/omx/tests/Android.mk
index 07d47a8..6aa7470 100644
--- a/media/libstagefright/omx/tests/Android.mk
+++ b/media/libstagefright/omx/tests/Android.mk
@@ -8,7 +8,6 @@
 	libstagefright libbinder libmedia libutils libstagefright_foundation
 
 LOCAL_C_INCLUDES := \
-	$(JNI_H_INCLUDE) \
 	frameworks/base/media/libstagefright \
 	$(TOP)/frameworks/native/include/media/openmax
 
diff --git a/media/libstagefright/rtsp/Android.mk b/media/libstagefright/rtsp/Android.mk
index b3bc37c..e0fe59b 100644
--- a/media/libstagefright/rtsp/Android.mk
+++ b/media/libstagefright/rtsp/Android.mk
@@ -18,7 +18,6 @@
         ASessionDescription.cpp     \
 
 LOCAL_C_INCLUDES:= \
-	$(JNI_H_INCLUDE) \
 	$(TOP)/frameworks/base/media/libstagefright/include \
 	$(TOP)/frameworks/native/include/media/openmax \
 	$(TOP)/external/openssl/include
@@ -45,7 +44,6 @@
         libstagefright_rtsp
 
 LOCAL_C_INCLUDES:= \
-	$(JNI_H_INCLUDE) \
 	frameworks/base/media/libstagefright \
 	$(TOP)/frameworks/native/include/media/openmax
 
diff --git a/media/libstagefright/timedtext/Android.mk b/media/libstagefright/timedtext/Android.mk
index d2d5f7b..58ef9e3 100644
--- a/media/libstagefright/timedtext/Android.mk
+++ b/media/libstagefright/timedtext/Android.mk
@@ -11,7 +11,6 @@
 
 LOCAL_CFLAGS += -Wno-multichar
 LOCAL_C_INCLUDES:= \
-        $(JNI_H_INCLUDE) \
         $(TOP)/frameworks/base/include/media/stagefright/timedtext \
         $(TOP)/frameworks/base/media/libstagefright
 
diff --git a/media/libstagefright/timedtext/TimedText3GPPSource.cpp b/media/libstagefright/timedtext/TimedText3GPPSource.cpp
index c423ef0..4854121 100644
--- a/media/libstagefright/timedtext/TimedText3GPPSource.cpp
+++ b/media/libstagefright/timedtext/TimedText3GPPSource.cpp
@@ -39,19 +39,21 @@
 }
 
 status_t TimedText3GPPSource::read(
-        int64_t *timeUs, Parcel *parcel, const MediaSource::ReadOptions *options) {
+        int64_t *startTimeUs, int64_t *endTimeUs, Parcel *parcel,
+        const MediaSource::ReadOptions *options) {
     MediaBuffer *textBuffer = NULL;
     status_t err = mSource->read(&textBuffer, options);
     if (err != OK) {
         return err;
     }
     CHECK(textBuffer != NULL);
-    textBuffer->meta_data()->findInt64(kKeyTime, timeUs);
-    // TODO: this is legacy code. when 'timeUs' can be <= 0?
-    if (*timeUs > 0) {
-        extractAndAppendLocalDescriptions(*timeUs, textBuffer, parcel);
-    }
+    textBuffer->meta_data()->findInt64(kKeyTime, startTimeUs);
+    CHECK_GE(*startTimeUs, 0);
+    extractAndAppendLocalDescriptions(*startTimeUs, textBuffer, parcel);
     textBuffer->release();
+    // endTimeUs is a dummy parameter for 3gpp timed text format.
+    // Set a negative value to it to mark it is unavailable.
+    *endTimeUs = -1;
     return OK;
 }
 
diff --git a/media/libstagefright/timedtext/TimedText3GPPSource.h b/media/libstagefright/timedtext/TimedText3GPPSource.h
index 4ec3d8a..4170940 100644
--- a/media/libstagefright/timedtext/TimedText3GPPSource.h
+++ b/media/libstagefright/timedtext/TimedText3GPPSource.h
@@ -33,7 +33,8 @@
     virtual status_t start() { return mSource->start(); }
     virtual status_t stop() { return mSource->stop(); }
     virtual status_t read(
-            int64_t *timeUs,
+            int64_t *startTimeUs,
+            int64_t *endTimeUs,
             Parcel *parcel,
             const MediaSource::ReadOptions *options = NULL);
     virtual status_t extractGlobalDescriptions(Parcel *parcel);
diff --git a/media/libstagefright/timedtext/TimedTextPlayer.cpp b/media/libstagefright/timedtext/TimedTextPlayer.cpp
index 8717914..917c62a9 100644
--- a/media/libstagefright/timedtext/TimedTextPlayer.cpp
+++ b/media/libstagefright/timedtext/TimedTextPlayer.cpp
@@ -31,6 +31,7 @@
 namespace android {
 
 static const int64_t kAdjustmentProcessingTimeUs = 100000ll;
+static const int64_t kWaitTimeUsToRetryRead = 100000ll;
 
 TimedTextPlayer::TimedTextPlayer(const wp<MediaPlayerBase> &listener)
     : mListener(listener),
@@ -139,13 +140,25 @@
 }
 
 void TimedTextPlayer::doRead(MediaSource::ReadOptions* options) {
-    int64_t timeUs = 0;
+    int64_t startTimeUs = 0;
+    int64_t endTimeUs = 0;
     sp<ParcelEvent> parcelEvent = new ParcelEvent();
-    status_t err = mSource->read(&timeUs, &(parcelEvent->parcel), options);
-    if (err != OK) {
+    status_t err = mSource->read(&startTimeUs, &endTimeUs,
+                                 &(parcelEvent->parcel), options);
+    if (err == WOULD_BLOCK) {
+        postTextEventDelayUs(NULL, kWaitTimeUsToRetryRead);
+        return;
+    } else if (err != OK) {
         notifyError(err);
-    } else {
-        postTextEvent(parcelEvent, timeUs);
+        return;
+    }
+
+    postTextEvent(parcelEvent, startTimeUs);
+    if (endTimeUs > 0) {
+        CHECK_GE(endTimeUs, startTimeUs);
+        // send an empty timed text to clear the subtitle when it reaches to the
+        // end time.
+        postTextEvent(NULL, endTimeUs);
     }
 }
 
@@ -162,6 +175,13 @@
         } else {
             delayUs = timeUs - positionUs - kAdjustmentProcessingTimeUs;
         }
+        postTextEventDelayUs(parcel, delayUs);
+    }
+}
+
+void TimedTextPlayer::postTextEventDelayUs(const sp<ParcelEvent>& parcel, int64_t delayUs) {
+    sp<MediaPlayerBase> listener = mListener.promote();
+    if (listener != NULL) {
         sp<AMessage> msg = new AMessage(kWhatSendSubtitle, id());
         msg->setInt32("generation", mSendSubtitleGeneration);
         if (parcel != NULL) {
diff --git a/media/libstagefright/timedtext/TimedTextPlayer.h b/media/libstagefright/timedtext/TimedTextPlayer.h
index b869f18..47aff03 100644
--- a/media/libstagefright/timedtext/TimedTextPlayer.h
+++ b/media/libstagefright/timedtext/TimedTextPlayer.h
@@ -67,6 +67,7 @@
     void doRead(MediaSource::ReadOptions* options = NULL);
     void onTextEvent();
     void postTextEvent(const sp<ParcelEvent>& parcel = NULL, int64_t timeUs = -1);
+    void postTextEventDelayUs(const sp<ParcelEvent>& parcel = NULL, int64_t delayUs = -1);
     void notifyError(int error = 0);
     void notifyListener(const Parcel *parcel = NULL);
 
diff --git a/media/libstagefright/timedtext/TimedTextSRTSource.cpp b/media/libstagefright/timedtext/TimedTextSRTSource.cpp
index c44a99b..7b1f7f6 100644
--- a/media/libstagefright/timedtext/TimedTextSRTSource.cpp
+++ b/media/libstagefright/timedtext/TimedTextSRTSource.cpp
@@ -19,6 +19,7 @@
 #include <utils/Log.h>
 
 #include <binder/Parcel.h>
+#include <media/stagefright/foundation/ADebug.h>  // for CHECK_xx
 #include <media/stagefright/foundation/AString.h>
 #include <media/stagefright/DataSource.h>
 #include <media/stagefright/MediaDefs.h>  // for MEDIA_MIMETYPE_xxx
@@ -63,19 +64,18 @@
 }
 
 status_t TimedTextSRTSource::read(
-        int64_t *timeUs,
+        int64_t *startTimeUs,
+        int64_t *endTimeUs,
         Parcel *parcel,
         const MediaSource::ReadOptions *options) {
-    int64_t endTimeUs;
     AString text;
-    status_t err = getText(options, &text, timeUs, &endTimeUs);
+    status_t err = getText(options, &text, startTimeUs, endTimeUs);
     if (err != OK) {
         return err;
     }
 
-    if (*timeUs > 0) {
-        extractAndAppendLocalDescriptions(*timeUs, text, parcel);
-    }
+    CHECK_GE(*startTimeUs, 0);
+    extractAndAppendLocalDescriptions(*startTimeUs, text, parcel);
     return OK;
 }
 
diff --git a/media/libstagefright/timedtext/TimedTextSRTSource.h b/media/libstagefright/timedtext/TimedTextSRTSource.h
index 62710a0..e1371b8 100644
--- a/media/libstagefright/timedtext/TimedTextSRTSource.h
+++ b/media/libstagefright/timedtext/TimedTextSRTSource.h
@@ -36,7 +36,8 @@
     virtual status_t start();
     virtual status_t stop();
     virtual status_t read(
-            int64_t *timeUs,
+            int64_t *startTimeUs,
+            int64_t *endTimeUs,
             Parcel *parcel,
             const MediaSource::ReadOptions *options = NULL);
     virtual sp<MetaData> getFormat();
diff --git a/media/libstagefright/timedtext/TimedTextSource.h b/media/libstagefright/timedtext/TimedTextSource.h
index 9349342..756cc31 100644
--- a/media/libstagefright/timedtext/TimedTextSource.h
+++ b/media/libstagefright/timedtext/TimedTextSource.h
@@ -43,7 +43,8 @@
   virtual status_t stop() = 0;
   // Returns subtitle parcel and its start time.
   virtual status_t read(
-          int64_t *timeUs,
+          int64_t *startTimeUs,
+          int64_t *endTimeUs,
           Parcel *parcel,
           const MediaSource::ReadOptions *options = NULL) = 0;
   virtual status_t extractGlobalDescriptions(Parcel *parcel) {
diff --git a/build/phone-xhdpi-1024-dalvik-heap.mk b/media/mca/Android.mk
similarity index 73%
rename from build/phone-xhdpi-1024-dalvik-heap.mk
rename to media/mca/Android.mk
index f76535a..b1ce91e 100644
--- a/build/phone-xhdpi-1024-dalvik-heap.mk
+++ b/media/mca/Android.mk
@@ -1,4 +1,3 @@
-#
 # Copyright (C) 2011 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,9 +13,9 @@
 # limitations under the License.
 #
 
-# Provides overrides to configure the Dalvik heap for a standard tablet device.
+#
+# Build all native libraries
+#
+include $(call all-subdir-makefiles)
 
-PRODUCT_PROPERTY_OVERRIDES += \
-    dalvik.vm.heapstartsize=8m \
-    dalvik.vm.heapgrowthlimit=64m \
-    dalvik.vm.heapsize=256m
+
diff --git a/media/mca/effect/java/android/media/effect/Effect.java b/media/mca/effect/java/android/media/effect/Effect.java
new file mode 100644
index 0000000..b2b4427
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/Effect.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect;
+
+
+/**
+ * <p>Effects are high-performance transformations that can be applied to image frames. These are
+ * passed in the form of OpenGL ES 2.0 texture names. Typical frames could be images loaded from
+ * disk, or frames from the camera or other video streams.</p>
+ *
+ * <p>To create an Effect you must first create an EffectContext. You can obtain an instance of the
+ * context's EffectFactory by calling
+ * {@link android.media.effect.EffectContext#getFactory() getFactory()}. The EffectFactory allows
+ * you to instantiate specific Effects.</p>
+ *
+ * <p>The application is responsible for creating an EGL context, and making it current before
+ * applying an effect. An effect is bound to a single EffectContext, which in turn is bound to a
+ * single EGL context. If your EGL context is destroyed, the EffectContext becomes invalid and any
+ * effects bound to this context can no longer be used.</p>
+ *
+ */
+public abstract class Effect {
+
+    /**
+     * Get the effect name.
+     *
+     * Returns the unique name of the effect, which matches the name used for instantiating this
+     * effect by the EffectFactory.
+     *
+     * @return The name of the effect.
+     */
+    public abstract String getName();
+
+    /**
+     * Apply an effect to GL textures.
+     *
+     * <p>Apply the Effect on the specified input GL texture, and write the result into the
+     * output GL texture. The texture names passed must be valid in the current GL context.</p>
+     *
+     * <p>The input texture must be a valid texture name with the given width and height and must be
+     * bound to a GL_TEXTURE_2D texture image (usually done by calling the glTexImage2D() function).
+     * Multiple mipmap levels may be provided.</p>
+     *
+     * <p>If the output texture has not been bound to a texture image, it will be automatically
+     * bound by the effect as a GL_TEXTURE_2D. It will contain one mipmap level (0), which will have
+     * the same size as the input. No other mipmap levels are defined. If the output texture was
+     * bound already, and its size does not match the input texture size, the result may be clipped
+     * or only partially fill the texture.</p>
+     *
+     * <p>Note, that regardless of whether a texture image was originally provided or not, both the
+     * input and output textures are owned by the caller. That is, the caller is responsible for
+     * calling glDeleteTextures() to deallocate the input and output textures.</p>
+     *
+     * @param inputTexId The GL texture name of a valid and bound input texture.
+     * @param width The width of the input texture in pixels.
+     * @param height The height of the input texture in pixels.
+     * @param outputTexId The GL texture name of the output texture.
+     */
+    public abstract void apply(int inputTexId, int width, int height, int outputTexId);
+
+    /**
+     * Set a filter parameter.
+     *
+     * Consult the effect documentation for a list of supported parameter keys for each effect.
+     *
+     * @param parameterKey The name of the parameter to adjust.
+     * @param value The new value to set the parameter to.
+     * @throws InvalidArgumentException if parameterName is not a recognized name, or the value is
+     *         not a valid value for this parameter.
+     */
+    public abstract void setParameter(String parameterKey, Object value);
+
+    /**
+     * Set an effect listener.
+     *
+     * Some effects may report state changes back to the host, if a listener is set. Consult the
+     * individual effect documentation for more details.
+     *
+     * @param listener The listener to receive update callbacks on.
+     */
+    public void setUpdateListener(EffectUpdateListener listener) {
+    }
+
+    /**
+     * Release an effect.
+     *
+     * <p>Releases the effect and any resources associated with it. You may call this if you need to
+     * make sure acquired resources are no longer held by the effect. Releasing an effect makes it
+     * invalid for reuse.</p>
+     *
+     * <p>Note that this method must be called with the EffectContext and EGL context current, as
+     * the effect may release internal GL resources.</p>
+     */
+    public abstract void release();
+}
+
diff --git a/media/mca/effect/java/android/media/effect/EffectContext.java b/media/mca/effect/java/android/media/effect/EffectContext.java
new file mode 100644
index 0000000..ef03229
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/EffectContext.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect;
+
+import android.filterfw.core.CachedFrameManager;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.FilterFactory;
+import android.filterfw.core.GLEnvironment;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.FrameManager;
+import android.opengl.GLES20;
+
+/**
+ * <p>An EffectContext keeps all necessary state information to run Effects within a Open GL ES 2.0
+ * context.</p>
+ *
+ * <p>Every EffectContext is bound to one GL context. The application is responsible for creating
+ * this EGL context, and making it current before applying any effect. If your EGL context is
+ * destroyed, the EffectContext becomes invalid and any effects bound to this context can no longer
+ * be used. If you switch to another EGL context, you must create a new EffectContext. Each Effect
+ * is bound to a single EffectContext, and can only be executed in that context.</p>
+ */
+public class EffectContext {
+
+    private final int GL_STATE_FBO          = 0;
+    private final int GL_STATE_PROGRAM      = 1;
+    private final int GL_STATE_ARRAYBUFFER  = 2;
+    private final int GL_STATE_COUNT        = 3;
+
+    FilterContext mFilterContext;
+
+    private EffectFactory mFactory;
+
+    private int[] mOldState = new int[GL_STATE_COUNT];
+
+    /**
+     * Creates a context within the current GL context.
+     *
+     * <p>Binds the EffectContext to the current OpenGL context. All subsequent calls to the
+     * EffectContext must be made in the GL context that was active during creation.
+     * When you have finished using a context, you must call {@link #release()}. to dispose of all
+     * resources associated with this context.</p>
+     */
+    public static EffectContext createWithCurrentGlContext() {
+        EffectContext result = new EffectContext();
+        result.initInCurrentGlContext();
+        return result;
+    }
+
+    /**
+     * Returns the EffectFactory for this context.
+     *
+     * <p>The EffectFactory returned from this method allows instantiating new effects within this
+     * context.</p>
+     *
+     * @return The EffectFactory instance for this context.
+     */
+    public EffectFactory getFactory() {
+        return mFactory;
+    }
+
+    /**
+     * Releases the context.
+     *
+     * <p>Releases all the resources and effects associated with the EffectContext. This renders the
+     * context and all the effects bound to this context invalid. You must no longer use the context
+     * or any of its bound effects after calling release().</p>
+     *
+     * <p>Note that this method must be called with the proper EGL context made current, as the
+     * EffectContext and its effects may release internal GL resources.</p>
+     */
+    public void release() {
+        mFilterContext.tearDown();
+        mFilterContext = null;
+    }
+
+    private EffectContext() {
+        mFilterContext = new FilterContext();
+        mFilterContext.setFrameManager(new CachedFrameManager());
+        mFactory = new EffectFactory(this);
+    }
+
+    private void initInCurrentGlContext() {
+        if (!GLEnvironment.isAnyContextActive()) {
+            throw new RuntimeException("Attempting to initialize EffectContext with no active "
+                + "GL context!");
+        }
+        GLEnvironment glEnvironment = new GLEnvironment();
+        glEnvironment.initWithCurrentContext();
+        mFilterContext.initGLEnvironment(glEnvironment);
+    }
+
+    final void assertValidGLState() {
+        GLEnvironment glEnv = mFilterContext.getGLEnvironment();
+        if (glEnv == null || !glEnv.isContextActive()) {
+            if (GLEnvironment.isAnyContextActive()) {
+                throw new RuntimeException("Applying effect in wrong GL context!");
+            } else {
+                throw new RuntimeException("Attempting to apply effect without valid GL context!");
+            }
+        }
+    }
+
+    final void saveGLState() {
+        GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, mOldState, GL_STATE_FBO);
+        GLES20.glGetIntegerv(GLES20.GL_CURRENT_PROGRAM, mOldState, GL_STATE_PROGRAM);
+        GLES20.glGetIntegerv(GLES20.GL_ARRAY_BUFFER_BINDING, mOldState, GL_STATE_ARRAYBUFFER);
+    }
+
+    final void restoreGLState() {
+        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mOldState[GL_STATE_FBO]);
+        GLES20.glUseProgram(mOldState[GL_STATE_PROGRAM]);
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mOldState[GL_STATE_ARRAYBUFFER]);
+    }
+}
+
diff --git a/media/mca/effect/java/android/media/effect/EffectFactory.java b/media/mca/effect/java/android/media/effect/EffectFactory.java
new file mode 100644
index 0000000..4330279
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/EffectFactory.java
@@ -0,0 +1,517 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect;
+
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+
+/**
+ * <p>The EffectFactory class defines the list of available Effects, and provides functionality to
+ * inspect and instantiate them. Some effects may not be available on all platforms, so before
+ * creating a certain effect, the application should confirm that the effect is supported on this
+ * platform by calling {@link #isEffectSupported(String)}.</p>
+ */
+public class EffectFactory {
+
+    private EffectContext mEffectContext;
+
+    private final static String[] EFFECT_PACKAGES = {
+        "android.media.effect.effects.",  // Default effect package
+        ""                                // Allows specifying full class path
+    };
+
+    /** List of Effects */
+    /**
+     * <p>Copies the input texture to the output.</p>
+     * <p>Available parameters: None</p>
+     * @hide
+     */
+    public final static String EFFECT_IDENTITY = "IdentityEffect";
+
+    /**
+     * <p>Adjusts the brightness of the image.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>brightness</code></td>
+     *     <td>The brightness multiplier.</td>
+     *     <td>Positive float. 1.0 means no change;
+               larger values will increase brightness.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_BRIGHTNESS =
+            "android.media.effect.effects.BrightnessEffect";
+
+    /**
+     * <p>Adjusts the contrast of the image.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>contrast</code></td>
+     *     <td>The contrast multiplier.</td>
+     *     <td>Float. 1.0 means no change;
+               larger values will increase contrast.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_CONTRAST =
+            "android.media.effect.effects.ContrastEffect";
+
+    /**
+     * <p>Applies a fisheye lens distortion to the image.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>scale</code></td>
+     *     <td>The scale of the distortion.</td>
+     *     <td>Float, between 0 and 1. Zero means no distortion.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_FISHEYE =
+            "android.media.effect.effects.FisheyeEffect";
+
+    /**
+     * <p>Replaces the background of the input frames with frames from a
+     * selected video.  Requires an initial learning period with only the
+     * background visible before the effect becomes active. The effect will wait
+     * until it does not see any motion in the scene before learning the
+     * background and starting the effect.</p>
+     *
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>source</code></td>
+     *     <td>A URI for the background video to use. This parameter must be
+     *         supplied before calling apply() for the first time.</td>
+     *     <td>String, such as from
+     *         {@link android.net.Uri#toString Uri.toString()}</td>
+     * </tr>
+     * </table>
+     *
+     * <p>If the update listener is set for this effect using
+     * {@link Effect#setUpdateListener}, it will be called when the effect has
+     * finished learning the background, with a null value for the info
+     * parameter.</p>
+     */
+    public final static String EFFECT_BACKDROPPER =
+            "android.media.effect.effects.BackDropperEffect";
+
+    /**
+     * <p>Attempts to auto-fix the image based on histogram equalization.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>scale</code></td>
+     *     <td>The scale of the adjustment.</td>
+     *     <td>Float, between 0 and 1. Zero means no adjustment, while 1 indicates the maximum
+     *     amount of adjustment.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_AUTOFIX =
+            "android.media.effect.effects.AutoFixEffect";
+
+    /**
+     * <p>Adjusts the range of minimal and maximal color pixel intensities.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>black</code></td>
+     *     <td>The value of the minimal pixel.</td>
+     *     <td>Float, between 0 and 1.</td>
+     * </tr>
+     * <tr><td><code>white</code></td>
+     *     <td>The value of the maximal pixel.</td>
+     *     <td>Float, between 0 and 1.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_BLACKWHITE =
+            "android.media.effect.effects.BlackWhiteEffect";
+
+    /**
+     * <p>Crops an upright rectangular area from the image. If the crop region falls outside of
+     * the image bounds, the results are undefined.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>xorigin</code></td>
+     *     <td>The origin's x-value.</td>
+     *     <td>Integer, between 0 and width of the image.</td>
+     * </tr>
+     * <tr><td><code>yorigin</code></td>
+     *     <td>The origin's y-value.</td>
+     *     <td>Integer, between 0 and height of the image.</td>
+     * </tr>
+     * <tr><td><code>width</code></td>
+     *     <td>The width of the cropped image.</td>
+     *     <td>Integer, between 1 and the width of the image minus xorigin.</td>
+     * </tr>
+     * <tr><td><code>height</code></td>
+     *     <td>The height of the cropped image.</td>
+     *     <td>Integer, between 1 and the height of the image minus yorigin.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_CROP =
+            "android.media.effect.effects.CropEffect";
+
+    /**
+     * <p>Applies a cross process effect on image, in which the red and green channels are
+     * enhanced while the blue channel is restricted.</p>
+     * <p>Available parameters: None</p>
+     */
+    public final static String EFFECT_CROSSPROCESS =
+            "android.media.effect.effects.CrossProcessEffect";
+
+    /**
+     * <p>Applies black and white documentary style effect on image..</p>
+     * <p>Available parameters: None</p>
+     */
+    public final static String EFFECT_DOCUMENTARY =
+            "android.media.effect.effects.DocumentaryEffect";
+
+
+    /**
+     * <p>Overlays a bitmap (with premultiplied alpha channel) onto the input image. The bitmap
+     * is stretched to fit the input image.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>bitmap</code></td>
+     *     <td>The overlay bitmap.</td>
+     *     <td>A non-null Bitmap instance.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_BITMAPOVERLAY =
+            "android.media.effect.effects.BitmapOverlayEffect";
+
+    /**
+     * <p>Representation of photo using only two color tones.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>first_color</code></td>
+     *     <td>The first color tone.</td>
+     *     <td>Integer, representing an ARGB color with 8 bits per channel. May be created using
+     *     {@link android.graphics.Color Color} class.</td>
+     * </tr>
+     * <tr><td><code>second_color</code></td>
+     *     <td>The second color tone.</td>
+     *     <td>Integer, representing an ARGB color with 8 bits per channel. May be created using
+     *     {@link android.graphics.Color Color} class.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_DUOTONE =
+            "android.media.effect.effects.DuotoneEffect";
+
+    /**
+     * <p>Applies back-light filling to the image.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>strength</code></td>
+     *     <td>The strength of the backlight.</td>
+     *     <td>Float, between 0 and 1. Zero means no change.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_FILLLIGHT =
+            "android.media.effect.effects.FillLightEffect";
+
+    /**
+     * <p>Flips image vertically and/or horizontally.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>vertical</code></td>
+     *     <td>Whether to flip image vertically.</td>
+     *     <td>Boolean</td>
+     * </tr>
+     * <tr><td><code>horizontal</code></td>
+     *     <td>Whether to flip image horizontally.</td>
+     *     <td>Boolean</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_FLIP =
+            "android.media.effect.effects.FlipEffect";
+
+    /**
+     * <p>Applies film grain effect to image.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>strength</code></td>
+     *     <td>The strength of the grain effect.</td>
+     *     <td>Float, between 0 and 1. Zero means no change.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_GRAIN =
+            "android.media.effect.effects.GrainEffect";
+
+    /**
+     * <p>Converts image to grayscale.</p>
+     * <p>Available parameters: None</p>
+     */
+    public final static String EFFECT_GRAYSCALE =
+            "android.media.effect.effects.GrayscaleEffect";
+
+    /**
+     * <p>Applies lomo-camera style effect to image.</p>
+     * <p>Available parameters: None</p>
+     */
+    public final static String EFFECT_LOMOISH =
+            "android.media.effect.effects.LomoishEffect";
+
+    /**
+     * <p>Inverts the image colors.</p>
+     * <p>Available parameters: None</p>
+     */
+    public final static String EFFECT_NEGATIVE =
+            "android.media.effect.effects.NegativeEffect";
+
+    /**
+     * <p>Applies posterization effect to image.</p>
+     * <p>Available parameters: None</p>
+     */
+    public final static String EFFECT_POSTERIZE =
+            "android.media.effect.effects.PosterizeEffect";
+
+    /**
+     * <p>Removes red eyes on specified region.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>centers</code></td>
+     *     <td>Multiple center points (x, y) of the red eye regions.</td>
+     *     <td>An array of floats, where (f[2*i], f[2*i+1]) specifies the center of the i'th eye.
+     *     Coordinate values are expected to be normalized between 0 and 1.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_REDEYE =
+            "android.media.effect.effects.RedEyeEffect";
+
+    /**
+     * <p>Rotates the image. The output frame size must be able to fit the rotated version of
+     * the input image. Note that the rotation snaps to a the closest multiple of 90 degrees.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>angle</code></td>
+     *     <td>The angle of rotation in degrees.</td>
+     *     <td>Integer value. This will be rounded to the nearest multiple of 90.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_ROTATE =
+            "android.media.effect.effects.RotateEffect";
+
+    /**
+     * <p>Adjusts color saturation of image.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>scale</code></td>
+     *     <td>The scale of color saturation.</td>
+     *     <td>Float, between -1 and 1. 0 means no change, while -1 indicates full desaturation,
+     *     i.e. grayscale.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_SATURATE =
+            "android.media.effect.effects.SaturateEffect";
+
+    /**
+     * <p>Converts image to sepia tone.</p>
+     * <p>Available parameters: None</p>
+     */
+    public final static String EFFECT_SEPIA =
+            "android.media.effect.effects.SepiaEffect";
+
+    /**
+     * <p>Sharpens the image.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>scale</code></td>
+     *     <td>The degree of sharpening.</td>
+     *     <td>Float, between 0 and 1. 0 means no change.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_SHARPEN =
+            "android.media.effect.effects.SharpenEffect";
+
+    /**
+     * <p>Rotates the image according to the specified angle, and crops the image so that no
+     * non-image portions are visible.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>angle</code></td>
+     *     <td>The angle of rotation.</td>
+     *     <td>Float, between -45 and +45.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_STRAIGHTEN =
+            "android.media.effect.effects.StraightenEffect";
+
+    /**
+     * <p>Adjusts color temperature of the image.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>scale</code></td>
+     *     <td>The value of color temperature.</td>
+     *     <td>Float, between 0 and 1, with 0 indicating cool, and 1 indicating warm. A value of
+     *     of 0.5 indicates no change.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_TEMPERATURE =
+            "android.media.effect.effects.ColorTemperatureEffect";
+
+    /**
+     * <p>Tints the photo with specified color.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>tint</code></td>
+     *     <td>The color of the tint.</td>
+     *     <td>Integer, representing an ARGB color with 8 bits per channel. May be created using
+     *     {@link android.graphics.Color Color} class.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_TINT =
+            "android.media.effect.effects.TintEffect";
+
+    /**
+     * <p>Adds a vignette effect to image, i.e. fades away the outer image edges.</p>
+     * <p>Available parameters:</p>
+     * <table>
+     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
+     * <tr><td><code>scale</code></td>
+     *     <td>The scale of vignetting.</td>
+     *     <td>Float, between 0 and 1. 0 means no change.</td>
+     * </tr>
+     * </table>
+     */
+    public final static String EFFECT_VIGNETTE =
+            "android.media.effect.effects.VignetteEffect";
+
+    EffectFactory(EffectContext effectContext) {
+        mEffectContext = effectContext;
+    }
+
+    /**
+     * Instantiate a new effect with the given effect name.
+     *
+     * <p>The effect's parameters will be set to their default values.</p>
+     *
+     * <p>Note that the EGL context associated with the current EffectContext need not be made
+     * current when creating an effect. This allows the host application to instantiate effects
+     * before any EGL context has become current.</p>
+     *
+     * @param effectName The name of the effect to create.
+     * @return A new Effect instance.
+     * @throws IllegalArgumentException if the effect with the specified name is not supported or
+     *         not known.
+     */
+    public Effect createEffect(String effectName) {
+        Class effectClass = getEffectClassByName(effectName);
+        if (effectClass == null) {
+            throw new IllegalArgumentException("Cannot instantiate unknown effect '" +
+                effectName + "'!");
+        }
+        return instantiateEffect(effectClass, effectName);
+    }
+
+    /**
+     * Check if an effect is supported on this platform.
+     *
+     * <p>Some effects may only be available on certain platforms. Use this method before
+     * instantiating an effect to make sure it is supported.</p>
+     *
+     * @param effectName The name of the effect.
+     * @return true, if the effect is supported on this platform.
+     * @throws IllegalArgumentException if the effect name is not known.
+     */
+    public static boolean isEffectSupported(String effectName) {
+        return getEffectClassByName(effectName) != null;
+    }
+
+    private static Class getEffectClassByName(String className) {
+        Class effectClass = null;
+
+        // Get context's classloader; otherwise cannot load non-framework effects
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+
+        // Look for the class in the imported packages
+        for (String packageName : EFFECT_PACKAGES) {
+            try {
+                effectClass = contextClassLoader.loadClass(packageName + className);
+            } catch (ClassNotFoundException e) {
+                continue;
+            }
+            // Exit loop if class was found.
+            if (effectClass != null) {
+                break;
+            }
+        }
+        return effectClass;
+    }
+
+    private Effect instantiateEffect(Class effectClass, String name) {
+        // Make sure this is an Effect subclass
+        try {
+            effectClass.asSubclass(Effect.class);
+        } catch (ClassCastException e) {
+            throw new IllegalArgumentException("Attempting to allocate effect '" + effectClass
+                + "' which is not a subclass of Effect!", e);
+        }
+
+        // Look for the correct constructor
+        Constructor effectConstructor = null;
+        try {
+            effectConstructor = effectClass.getConstructor(EffectContext.class, String.class);
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException("The effect class '" + effectClass + "' does not have "
+                + "the required constructor.", e);
+        }
+
+        // Construct the effect
+        Effect effect = null;
+        try {
+            effect = (Effect)effectConstructor.newInstance(mEffectContext, name);
+        } catch (Throwable t) {
+            throw new RuntimeException("There was an error constructing the effect '" + effectClass
+                + "'!", t);
+        }
+
+        return effect;
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/EffectUpdateListener.java b/media/mca/effect/java/android/media/effect/EffectUpdateListener.java
new file mode 100644
index 0000000..155fe49
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/EffectUpdateListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect;
+
+/**
+ * Some effects may issue callbacks to inform the host of changes to the effect state. This is the
+ * listener interface for receiving those callbacks.
+ */
+public interface EffectUpdateListener {
+
+    /**
+     * Called when the effect state is updated.
+     *
+     * @param effect The effect that has been updated.
+     * @param info A value that gives more information about the update. See the effect's
+     *             documentation for more details on what this object is.
+     */
+    public void onEffectUpdated(Effect effect, Object info);
+
+}
+
diff --git a/media/mca/effect/java/android/media/effect/FilterEffect.java b/media/mca/effect/java/android/media/effect/FilterEffect.java
new file mode 100644
index 0000000..d7c319e
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/FilterEffect.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect;
+
+import android.filterfw.core.CachedFrameManager;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.FilterFactory;
+import android.filterfw.core.GLEnvironment;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.FrameManager;
+import android.filterfw.format.ImageFormat;
+
+/**
+ * The FilterEffect class is the base class for all Effects based on Filters from the Mobile
+ * Filter Framework (MFF).
+ * @hide
+ */
+public abstract class FilterEffect extends Effect {
+
+    protected EffectContext mEffectContext;
+    private String mName;
+
+    /**
+     * Protected constructor as FilterEffects should be created by Factory.
+     */
+    protected FilterEffect(EffectContext context, String name) {
+        mEffectContext = context;
+        mName = name;
+    }
+
+    /**
+     * Get the effect name.
+     *
+     * Returns the unique name of the effect, which matches the name used for instantiating this
+     * effect by the EffectFactory.
+     *
+     * @return The name of the effect.
+     */
+    @Override
+    public String getName() {
+        return mName;
+    }
+
+    // Helper Methods for subclasses ///////////////////////////////////////////////////////////////
+    /**
+     * Call this before manipulating the GL context. Will assert that the GL environment is in a
+     * valid state, and save it.
+     */
+    protected void beginGLEffect() {
+        mEffectContext.assertValidGLState();
+        mEffectContext.saveGLState();
+    }
+
+    /**
+     * Call this after manipulating the GL context. Restores the previous GL state.
+     */
+    protected void endGLEffect() {
+        mEffectContext.restoreGLState();
+    }
+
+    /**
+     * Returns the active filter context for this effect.
+     */
+    protected FilterContext getFilterContext() {
+        return mEffectContext.mFilterContext;
+    }
+
+    /**
+     * Converts a texture into a Frame.
+     */
+    protected Frame frameFromTexture(int texId, int width, int height) {
+        FrameManager manager = getFilterContext().getFrameManager();
+        FrameFormat format = ImageFormat.create(width, height,
+                                                ImageFormat.COLORSPACE_RGBA,
+                                                FrameFormat.TARGET_GPU);
+        Frame frame = manager.newBoundFrame(format,
+                                            GLFrame.EXISTING_TEXTURE_BINDING,
+                                            texId);
+        frame.setTimestamp(Frame.TIMESTAMP_UNKNOWN);
+        return frame;
+    }
+
+}
+
diff --git a/media/mca/effect/java/android/media/effect/FilterGraphEffect.java b/media/mca/effect/java/android/media/effect/FilterGraphEffect.java
new file mode 100644
index 0000000..b18bea8
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/FilterGraphEffect.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.effect;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterGraph;
+import android.filterfw.core.GraphRunner;
+import android.filterfw.core.SimpleScheduler;
+import android.filterfw.core.SyncRunner;
+import android.media.effect.Effect;
+import android.media.effect.FilterEffect;
+import android.media.effect.EffectContext;
+import android.filterfw.io.GraphIOException;
+import android.filterfw.io.GraphReader;
+import android.filterfw.io.TextGraphReader;
+
+import android.util.Log;
+
+/**
+ * Effect subclass for effects based on a single Filter. Subclasses need only invoke the
+ * constructor with the correct arguments to obtain an Effect implementation.
+ *
+ * @hide
+ */
+public class FilterGraphEffect extends FilterEffect {
+
+    private static final String TAG = "FilterGraphEffect";
+
+    protected String mInputName;
+    protected String mOutputName;
+    protected GraphRunner mRunner;
+    protected FilterGraph mGraph;
+    protected Class mSchedulerClass;
+
+    /**
+     * Constructs a new FilterGraphEffect.
+     *
+     * @param name The name of this effect (used to create it in the EffectFactory).
+     * @param graphString The graph string to create the graph.
+     * @param inputName The name of the input GLTextureSource filter.
+     * @param outputName The name of the output GLTextureSource filter.
+     */
+    public FilterGraphEffect(EffectContext context,
+                              String name,
+                              String graphString,
+                              String inputName,
+                              String outputName,
+                              Class scheduler) {
+        super(context, name);
+
+        mInputName = inputName;
+        mOutputName = outputName;
+        mSchedulerClass = scheduler;
+        createGraph(graphString);
+
+    }
+
+    private void createGraph(String graphString) {
+        GraphReader reader = new TextGraphReader();
+        try {
+            mGraph = reader.readGraphString(graphString);
+        } catch (GraphIOException e) {
+            throw new RuntimeException("Could not setup effect", e);
+        }
+
+        if (mGraph == null) {
+            throw new RuntimeException("Could not setup effect");
+        }
+        mRunner = new SyncRunner(getFilterContext(), mGraph, mSchedulerClass);
+    }
+
+    @Override
+    public void apply(int inputTexId, int width, int height, int outputTexId) {
+        beginGLEffect();
+        Filter src = mGraph.getFilter(mInputName);
+        if (src != null) {
+            src.setInputValue("texId", inputTexId);
+            src.setInputValue("width", width);
+            src.setInputValue("height", height);
+        } else {
+            throw new RuntimeException("Internal error applying effect");
+        }
+        Filter dest  = mGraph.getFilter(mOutputName);
+        if (dest != null) {
+            dest.setInputValue("texId", outputTexId);
+        } else {
+            throw new RuntimeException("Internal error applying effect");
+        }
+        try {
+            mRunner.run();
+        } catch (RuntimeException e) {
+            throw new RuntimeException("Internal error applying effect: ", e);
+        }
+        endGLEffect();
+    }
+
+    @Override
+    public void setParameter(String parameterKey, Object value) {
+    }
+
+    @Override
+    public void release() {
+         mGraph.tearDown(getFilterContext());
+         mGraph = null;
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/SingleFilterEffect.java b/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
new file mode 100644
index 0000000..6f85861
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterFactory;
+import android.filterfw.core.FilterFunction;
+import android.filterfw.core.Frame;
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+
+import android.util.Log;
+
+/**
+ * Effect subclass for effects based on a single Filter. Subclasses need only invoke the
+ * constructor with the correct arguments to obtain an Effect implementation.
+ *
+ * @hide
+ */
+public class SingleFilterEffect extends FilterEffect {
+
+    protected FilterFunction mFunction;
+    protected String mInputName;
+    protected String mOutputName;
+
+    /**
+     * Constructs a new FilterFunctionEffect.
+     *
+     * @param name The name of this effect (used to create it in the EffectFactory).
+     * @param filterClass The class of the filter to wrap.
+     * @param inputName The name of the input image port.
+     * @param outputName The name of the output image port.
+     * @param finalParameters Key-value pairs of final input port assignments.
+     */
+    public SingleFilterEffect(EffectContext context,
+                              String name,
+                              Class filterClass,
+                              String inputName,
+                              String outputName,
+                              Object... finalParameters) {
+        super(context, name);
+
+        mInputName = inputName;
+        mOutputName = outputName;
+
+        String filterName = filterClass.getSimpleName();
+        FilterFactory factory = FilterFactory.sharedFactory();
+        Filter filter = factory.createFilterByClass(filterClass, filterName);
+        filter.initWithAssignmentList(finalParameters);
+
+        mFunction = new FilterFunction(getFilterContext(), filter);
+    }
+
+    @Override
+    public void apply(int inputTexId, int width, int height, int outputTexId) {
+        beginGLEffect();
+
+        Frame inputFrame = frameFromTexture(inputTexId, width, height);
+        Frame outputFrame = frameFromTexture(outputTexId, width, height);
+
+        Frame resultFrame = mFunction.executeWithArgList(mInputName, inputFrame);
+
+        outputFrame.setDataFromFrame(resultFrame);
+
+        inputFrame.release();
+        outputFrame.release();
+        resultFrame.release();
+
+        endGLEffect();
+    }
+
+    @Override
+    public void setParameter(String parameterKey, Object value) {
+        mFunction.setInputValue(parameterKey, value);
+    }
+
+    @Override
+    public void release() {
+        mFunction.tearDown();
+        mFunction = null;
+    }
+}
+
diff --git a/media/mca/effect/java/android/media/effect/SizeChangeEffect.java b/media/mca/effect/java/android/media/effect/SizeChangeEffect.java
new file mode 100644
index 0000000..4d27bae
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/SizeChangeEffect.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.effect;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterFactory;
+import android.filterfw.core.FilterFunction;
+import android.filterfw.core.Frame;
+import android.media.effect.Effect;
+import android.media.effect.EffectContext;
+
+import android.util.Log;
+
+/**
+ * Effect subclass for effects based on a single Filter with output size differnet
+ * from input.  Subclasses need only invoke the constructor with the correct arguments
+ * to obtain an Effect implementation.
+ *
+ * @hide
+ */
+public class SizeChangeEffect extends SingleFilterEffect {
+
+    public SizeChangeEffect(EffectContext context,
+                            String name,
+                            Class filterClass,
+                            String inputName,
+                            String outputName,
+                            Object... finalParameters) {
+        super(context, name, filterClass, inputName, outputName, finalParameters);
+    }
+
+    @Override
+    public void apply(int inputTexId, int width, int height, int outputTexId) {
+        beginGLEffect();
+
+        Frame inputFrame = frameFromTexture(inputTexId, width, height);
+        Frame resultFrame = mFunction.executeWithArgList(mInputName, inputFrame);
+
+        int outputWidth = resultFrame.getFormat().getWidth();
+        int outputHeight = resultFrame.getFormat().getHeight();
+
+        Frame outputFrame = frameFromTexture(outputTexId, outputWidth, outputHeight);
+        outputFrame.setDataFromFrame(resultFrame);
+
+        inputFrame.release();
+        outputFrame.release();
+        resultFrame.release();
+
+        endGLEffect();
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/AutoFixEffect.java b/media/mca/effect/java/android/media/effect/effects/AutoFixEffect.java
new file mode 100644
index 0000000..44a141b
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/AutoFixEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.AutoFixFilter;
+
+/**
+ * @hide
+ */
+public class AutoFixEffect extends SingleFilterEffect {
+    public AutoFixEffect(EffectContext context, String name) {
+        super(context, name, AutoFixFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/BackDropperEffect.java b/media/mca/effect/java/android/media/effect/effects/BackDropperEffect.java
new file mode 100644
index 0000000..d5c7aaa
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/BackDropperEffect.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.effect.effects;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.OneShotScheduler;
+import android.media.effect.EffectContext;
+import android.media.effect.FilterGraphEffect;
+import android.media.effect.EffectUpdateListener;
+
+import android.filterpacks.videoproc.BackDropperFilter;
+import android.filterpacks.videoproc.BackDropperFilter.LearningDoneListener;
+
+/**
+ * Background replacement Effect.
+ *
+ * Replaces the background of the input video stream with a selected video
+ * Learns the background when it first starts up;
+ * needs unobstructed view of background when this happens.
+ *
+ * Effect parameters:
+ *   source: A URI for the background video
+ * Listener: Called when learning period is complete
+ *
+ * @hide
+ */
+public class BackDropperEffect extends FilterGraphEffect {
+    private static final String mGraphDefinition =
+            "@import android.filterpacks.base;\n" +
+            "@import android.filterpacks.videoproc;\n" +
+            "@import android.filterpacks.videosrc;\n" +
+            "\n" +
+            "@filter GLTextureSource foreground {\n" +
+            "  texId = 0;\n" + // Will be set by base class
+            "  width = 0;\n" +
+            "  height = 0;\n" +
+            "  repeatFrame = true;\n" +
+            "}\n" +
+            "\n" +
+            "@filter MediaSource background {\n" +
+            "  sourceUrl = \"no_file_specified\";\n" +
+            "  waitForNewFrame = false;\n" +
+            "  sourceIsUrl = true;\n" +
+            "}\n" +
+            "\n" +
+            "@filter BackDropperFilter replacer {\n" +
+            "  autowbToggle = 1;\n" +
+            "}\n" +
+            "\n" +
+            "@filter GLTextureTarget output {\n" +
+            "  texId = 0;\n" +
+            "}\n" +
+            "\n" +
+            "@connect foreground[frame]  => replacer[video];\n" +
+            "@connect background[video]  => replacer[background];\n" +
+            "@connect replacer[video]    => output[frame];\n";
+
+    private EffectUpdateListener mEffectListener = null;
+
+    private LearningDoneListener mLearningListener = new LearningDoneListener() {
+        public void onLearningDone(BackDropperFilter filter) {
+            if (mEffectListener != null) {
+                mEffectListener.onEffectUpdated(BackDropperEffect.this, null);
+            }
+        }
+    };
+
+    public BackDropperEffect(EffectContext context, String name) {
+        super(context, name, mGraphDefinition, "foreground", "output", OneShotScheduler.class);
+
+        Filter replacer = mGraph.getFilter("replacer");
+        replacer.setInputValue("learningDoneListener", mLearningListener);
+    }
+
+    @Override
+    public void setParameter(String parameterKey, Object value) {
+        if (parameterKey.equals("source")) {
+            Filter background = mGraph.getFilter("background");
+            background.setInputValue("sourceUrl", value);
+        }
+    }
+
+    @Override
+    public void setUpdateListener(EffectUpdateListener listener) {
+        mEffectListener = listener;
+    }
+
+}
\ No newline at end of file
diff --git a/media/mca/effect/java/android/media/effect/effects/BitmapOverlayEffect.java b/media/mca/effect/java/android/media/effect/effects/BitmapOverlayEffect.java
new file mode 100644
index 0000000..43f461c
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/BitmapOverlayEffect.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.BitmapOverlayFilter;
+
+/**
+ * @hide
+ */
+public class BitmapOverlayEffect extends SingleFilterEffect {
+    public BitmapOverlayEffect(EffectContext context, String name) {
+        super(context, name, BitmapOverlayFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/BlackWhiteEffect.java b/media/mca/effect/java/android/media/effect/effects/BlackWhiteEffect.java
new file mode 100644
index 0000000..771afff
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/BlackWhiteEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.BlackWhiteFilter;
+
+/**
+ * @hide
+ */
+public class BlackWhiteEffect extends SingleFilterEffect {
+    public BlackWhiteEffect(EffectContext context, String name) {
+        super(context, name, BlackWhiteFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/BrightnessEffect.java b/media/mca/effect/java/android/media/effect/effects/BrightnessEffect.java
new file mode 100644
index 0000000..774e72f
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/BrightnessEffect.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.BrightnessFilter;
+
+/**
+ * @hide
+ */
+public class BrightnessEffect extends SingleFilterEffect {
+    public BrightnessEffect(EffectContext context, String name) {
+        super(context, name, BrightnessFilter.class, "image", "image");
+    }
+}
+
diff --git a/media/mca/effect/java/android/media/effect/effects/ColorTemperatureEffect.java b/media/mca/effect/java/android/media/effect/effects/ColorTemperatureEffect.java
new file mode 100644
index 0000000..62d98ce
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/ColorTemperatureEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.ColorTemperatureFilter;
+
+/**
+ * @hide
+ */
+public class ColorTemperatureEffect extends SingleFilterEffect {
+    public ColorTemperatureEffect(EffectContext context, String name) {
+        super(context, name, ColorTemperatureFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/ContrastEffect.java b/media/mca/effect/java/android/media/effect/effects/ContrastEffect.java
new file mode 100644
index 0000000..d5bfc21
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/ContrastEffect.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.ContrastFilter;
+
+/**
+ * @hide
+ */
+public class ContrastEffect extends SingleFilterEffect {
+    public ContrastEffect(EffectContext context, String name) {
+        super(context, name, ContrastFilter.class, "image", "image");
+    }
+}
+
diff --git a/media/mca/effect/java/android/media/effect/effects/CropEffect.java b/media/mca/effect/java/android/media/effect/effects/CropEffect.java
new file mode 100644
index 0000000..3e8d78a
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/CropEffect.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SizeChangeEffect;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.CropRectFilter;
+
+/**
+ * @hide
+ */
+//public class CropEffect extends SingleFilterEffect {
+public class CropEffect extends SizeChangeEffect {
+    public CropEffect(EffectContext context, String name) {
+        super(context, name, CropRectFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/CrossProcessEffect.java b/media/mca/effect/java/android/media/effect/effects/CrossProcessEffect.java
new file mode 100644
index 0000000..d7a7df5
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/CrossProcessEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.CrossProcessFilter;
+
+/**
+ * @hide
+ */
+public class CrossProcessEffect extends SingleFilterEffect {
+    public CrossProcessEffect(EffectContext context, String name) {
+        super(context, name, CrossProcessFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/DocumentaryEffect.java b/media/mca/effect/java/android/media/effect/effects/DocumentaryEffect.java
new file mode 100644
index 0000000..1a5ea35
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/DocumentaryEffect.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.DocumentaryFilter;
+
+/**
+ * @hide
+ */
+public class DocumentaryEffect extends SingleFilterEffect {
+    public DocumentaryEffect(EffectContext context, String name) {
+        super(context, name, DocumentaryFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/DuotoneEffect.java b/media/mca/effect/java/android/media/effect/effects/DuotoneEffect.java
new file mode 100644
index 0000000..1391b1f
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/DuotoneEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.DuotoneFilter;
+
+/**
+ * @hide
+ */
+public class DuotoneEffect extends SingleFilterEffect {
+    public DuotoneEffect(EffectContext context, String name) {
+        super(context, name, DuotoneFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/FillLightEffect.java b/media/mca/effect/java/android/media/effect/effects/FillLightEffect.java
new file mode 100644
index 0000000..5260de3
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/FillLightEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.FillLightFilter;
+
+/**
+ * @hide
+ */
+public class FillLightEffect extends SingleFilterEffect {
+    public FillLightEffect(EffectContext context, String name) {
+        super(context, name, FillLightFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/FisheyeEffect.java b/media/mca/effect/java/android/media/effect/effects/FisheyeEffect.java
new file mode 100644
index 0000000..6abfe42
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/FisheyeEffect.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.FisheyeFilter;
+
+/**
+ * @hide
+ */
+public class FisheyeEffect extends SingleFilterEffect {
+    public FisheyeEffect(EffectContext context, String name) {
+        super(context, name, FisheyeFilter.class, "image", "image");
+    }
+}
+
diff --git a/media/mca/effect/java/android/media/effect/effects/FlipEffect.java b/media/mca/effect/java/android/media/effect/effects/FlipEffect.java
new file mode 100644
index 0000000..0f5c421
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/FlipEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.FlipFilter;
+
+/**
+ * @hide
+ */
+public class FlipEffect extends SingleFilterEffect {
+    public FlipEffect(EffectContext context, String name) {
+        super(context, name, FlipFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/GrainEffect.java b/media/mca/effect/java/android/media/effect/effects/GrainEffect.java
new file mode 100644
index 0000000..2fda7e9
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/GrainEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.GrainFilter;
+
+/**
+ * @hide
+ */
+public class GrainEffect extends SingleFilterEffect {
+    public GrainEffect(EffectContext context, String name) {
+        super(context, name, GrainFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/GrayscaleEffect.java b/media/mca/effect/java/android/media/effect/effects/GrayscaleEffect.java
new file mode 100644
index 0000000..26ca081
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/GrayscaleEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.ToGrayFilter;
+
+/**
+ * @hide
+ */
+public class GrayscaleEffect extends SingleFilterEffect {
+    public GrayscaleEffect(EffectContext context, String name) {
+        super(context, name, ToGrayFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/IdentityEffect.java b/media/mca/effect/java/android/media/effect/effects/IdentityEffect.java
new file mode 100644
index 0000000..d07779e
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/IdentityEffect.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.filterfw.core.Frame;
+import android.media.effect.EffectContext;
+import android.media.effect.FilterEffect;
+
+/**
+ * @hide
+ */
+public class IdentityEffect extends FilterEffect {
+
+    public IdentityEffect(EffectContext context, String name) {
+        super(context, name);
+    }
+
+    @Override
+    public void apply(int inputTexId, int width, int height, int outputTexId) {
+        beginGLEffect();
+
+        Frame inputFrame = frameFromTexture(inputTexId, width, height);
+        Frame outputFrame = frameFromTexture(outputTexId, width, height);
+
+        outputFrame.setDataFromFrame(inputFrame);
+
+        inputFrame.release();
+        outputFrame.release();
+
+        endGLEffect();
+    }
+
+    @Override
+    public void setParameter(String parameterKey, Object value) {
+        throw new IllegalArgumentException("Unknown parameter " + parameterKey
+            + " for IdentityEffect!");
+    }
+
+    @Override
+    public void release() {
+    }
+}
+
diff --git a/media/mca/effect/java/android/media/effect/effects/LomoishEffect.java b/media/mca/effect/java/android/media/effect/effects/LomoishEffect.java
new file mode 100644
index 0000000..776e53c
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/LomoishEffect.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.LomoishFilter;
+
+/**
+ * @hide
+ */
+public class LomoishEffect extends SingleFilterEffect {
+    public LomoishEffect(EffectContext context, String name) {
+        super(context, name, LomoishFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/NegativeEffect.java b/media/mca/effect/java/android/media/effect/effects/NegativeEffect.java
new file mode 100644
index 0000000..29fc94a
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/NegativeEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.NegativeFilter;
+
+/**
+ * @hide
+ */
+public class NegativeEffect extends SingleFilterEffect {
+    public NegativeEffect(EffectContext context, String name) {
+        super(context, name, NegativeFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/PosterizeEffect.java b/media/mca/effect/java/android/media/effect/effects/PosterizeEffect.java
new file mode 100644
index 0000000..20a8a37
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/PosterizeEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.PosterizeFilter;
+
+/**
+ * @hide
+ */
+public class PosterizeEffect extends SingleFilterEffect {
+    public PosterizeEffect(EffectContext context, String name) {
+        super(context, name, PosterizeFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/RedEyeEffect.java b/media/mca/effect/java/android/media/effect/effects/RedEyeEffect.java
new file mode 100644
index 0000000..8ed9909
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/RedEyeEffect.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.RedEyeFilter;
+
+/**
+ * @hide
+ */
+public class RedEyeEffect extends SingleFilterEffect {
+    public RedEyeEffect(EffectContext context, String name) {
+        super(context, name, RedEyeFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/RotateEffect.java b/media/mca/effect/java/android/media/effect/effects/RotateEffect.java
new file mode 100644
index 0000000..2340015
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/RotateEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SizeChangeEffect;
+import android.filterpacks.imageproc.RotateFilter;
+
+/**
+ * @hide
+ */
+public class RotateEffect extends SizeChangeEffect {
+    public RotateEffect(EffectContext context, String name) {
+        super(context, name, RotateFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/SaturateEffect.java b/media/mca/effect/java/android/media/effect/effects/SaturateEffect.java
new file mode 100644
index 0000000..fe9250a
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/SaturateEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.SaturateFilter;
+
+/**
+ * @hide
+ */
+public class SaturateEffect extends SingleFilterEffect {
+    public SaturateEffect(EffectContext context, String name) {
+        super(context, name, SaturateFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/SepiaEffect.java b/media/mca/effect/java/android/media/effect/effects/SepiaEffect.java
new file mode 100644
index 0000000..de85b2d
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/SepiaEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.SepiaFilter;
+
+/**
+ * @hide
+ */
+public class SepiaEffect extends SingleFilterEffect {
+    public SepiaEffect(EffectContext context, String name) {
+        super(context, name, SepiaFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/SharpenEffect.java b/media/mca/effect/java/android/media/effect/effects/SharpenEffect.java
new file mode 100644
index 0000000..46776eb
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/SharpenEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.SharpenFilter;
+
+/**
+ * @hide
+ */
+public class SharpenEffect extends SingleFilterEffect {
+    public SharpenEffect(EffectContext context, String name) {
+        super(context, name, SharpenFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/StraightenEffect.java b/media/mca/effect/java/android/media/effect/effects/StraightenEffect.java
new file mode 100644
index 0000000..49253a0
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/StraightenEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.StraightenFilter;
+
+/**
+ * @hide
+ */
+public class StraightenEffect extends SingleFilterEffect {
+    public StraightenEffect(EffectContext context, String name) {
+        super(context, name, StraightenFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/TintEffect.java b/media/mca/effect/java/android/media/effect/effects/TintEffect.java
new file mode 100644
index 0000000..6de9ea8
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/TintEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.TintFilter;
+
+/**
+ * @hide
+ */
+public class TintEffect extends SingleFilterEffect {
+    public TintEffect(EffectContext context, String name) {
+        super(context, name, TintFilter.class, "image", "image");
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/VignetteEffect.java b/media/mca/effect/java/android/media/effect/effects/VignetteEffect.java
new file mode 100644
index 0000000..b143d77
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/effects/VignetteEffect.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.effect.effects;
+
+import android.media.effect.EffectContext;
+import android.media.effect.SingleFilterEffect;
+import android.filterpacks.imageproc.VignetteFilter;
+
+/**
+ * @hide
+ */
+public class VignetteEffect extends SingleFilterEffect {
+    public VignetteEffect(EffectContext context, String name) {
+        super(context, name, VignetteFilter.class, "image", "image");
+    }
+}
diff --git a/native/include/android/rect.h b/media/mca/effect/java/android/media/effect/package-info.java
similarity index 60%
copy from native/include/android/rect.h
copy to media/mca/effect/java/android/media/effect/package-info.java
index 64d487d..b2c14ff 100644
--- a/native/include/android/rect.h
+++ b/media/mca/effect/java/android/media/effect/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,25 +15,14 @@
  */
 
 
-#ifndef ANDROID_RECT_H
-#define ANDROID_RECT_H
+package android.media.effect;
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct ARect {
-#ifdef __cplusplus
-    typedef int32_t value_type;
-#endif
-    int32_t left;
-    int32_t top;
-    int32_t right;
-    int32_t bottom;
-} ARect;
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif // ANDROID_RECT_H
+/**
+ * <h1>Effect Framework</h1>
+ *
+ * This package includes a collection of high-performance visual effects that make use of the
+ * mobile filter framework subsystem.
+ *
+ * TODO: More Documentation
+ *
+ */
diff --git a/media/mca/effect/java/android/media/effect/package.html b/media/mca/effect/java/android/media/effect/package.html
new file mode 100644
index 0000000..8a210fd
--- /dev/null
+++ b/media/mca/effect/java/android/media/effect/package.html
@@ -0,0 +1,54 @@
+<HTML>
+<BODY>
+<p>Provides classes that allow you to apply a variety of visual effects to images and
+videos. For example, you can easily fix red-eye, convert an image to grayscale,
+adjust brightness, adjust saturation, rotate an image, apply a fisheye effect, and much more. The
+system performs all effects processing on the GPU to obtain maximum performance.</p>
+
+<p>For maximum performance, effects are applied directly to OpenGL textures, so your application
+must have a valid OpenGL context before it can use the effects APIs. The textures to which you apply
+effects may be from bitmaps, videos or even the camera. However, there are certain restrictions that
+textures must meet:</p>
+<ol>
+<li>They must be bound to a {@link android.opengl.GLES20#GL_TEXTURE_2D} texture image</li>
+<li>They must contain at least one mipmap level</li>
+</ol>
+
+<p>An {@link android.media.effect.Effect} object defines a single media effect that you can apply to
+an image frame. The basic workflow to create an {@link android.media.effect.Effect} is:</p>
+
+<ol>
+<li>Call {@link android.media.effect.EffectContext#createWithCurrentGlContext
+EffectContext.createWithCurrentGlContext()} from your OpenGL ES 2.0 context.</li>
+<li>Use the returned {@link android.media.effect.EffectContext} to call {@link
+android.media.effect.EffectContext#getFactory EffectContext.getFactory()}, which returns an instance
+of {@link android.media.effect.EffectFactory}.</li>
+<li>Call {@link android.media.effect.EffectFactory#createEffect createEffect()}, passing it an
+effect name from @link android.media.effect.EffectFactory}, such as {@link
+android.media.effect.EffectFactory#EFFECT_FISHEYE} or {@link
+android.media.effect.EffectFactory#EFFECT_VIGNETTE}.</li>
+</ol>
+
+<p>You can adjust an effect’s parameters by calling {@link android.media.effect.Effect#setParameter
+setParameter()} and passing a parameter name and parameter value. Each type of effect accepts
+different parameters, which are documented with the effect name. For example, {@link
+android.media.effect.EffectFactory#EFFECT_FISHEYE} has one parameter for the {@code scale} of the
+distortion.</p>
+
+<p>To apply an effect on a texture, call {@link android.media.effect.Effect#apply apply()} on the
+{@link
+android.media.effect.Effect} and pass in the input texture, its width and height, and the output
+texture. The input texture  must be bound to a {@link android.opengl.GLES20#GL_TEXTURE_2D} texture
+image (usually done by calling the {@link android.opengl.GLES20#glTexImage2D glTexImage2D()}
+function). You may provide multiple mipmap levels. If the output texture has not been bound to a
+texture image, it will be automatically bound by the effect as a {@link
+android.opengl.GLES20#GL_TEXTURE_2D} and with one mipmap level (0), which will have the same
+size as the input.</p> 
+
+<p class="note"><strong>Note:</strong> All effects listed in {@link
+android.media.effect.EffectFactory} are guaranteed to be supported. However, some additional effects
+available from external libraries are not supported by all devices, so you must first check if the
+desired effect from the external library is supported by calling {@link
+android.media.effect.EffectFactory#isEffectSupported isEffectSupported()}.</p>
+</BODY>
+</HTML>
diff --git a/media/mca/filterfw/Android.mk b/media/mca/filterfw/Android.mk
new file mode 100644
index 0000000..b822e99
--- /dev/null
+++ b/media/mca/filterfw/Android.mk
@@ -0,0 +1,53 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#####################
+# Build native sublibraries
+
+include $(all-subdir-makefiles)
+
+#####################
+# Build main libfilterfw
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libfilterfw
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_WHOLE_STATIC_LIBRARIES := libfilterfw_jni \
+                                libfilterfw_native
+
+LOCAL_SHARED_LIBRARIES := libstlport \
+                          libGLESv2 \
+                          libEGL \
+                          libgui \
+                          libdl \
+                          libcutils \
+                          libutils \
+                          libandroid \
+                          libjnigraphics \
+                          libmedia \
+                          libmedia_native
+
+# Don't prelink this library.  For more efficient code, you may want
+# to add this library to the prelink map and set this to true. However,
+# it's difficult to do this for applications that are not supplied as
+# part of a system image.
+LOCAL_PRELINK_MODULE := false
+
+include $(BUILD_SHARED_LIBRARY)
+
+
diff --git a/media/mca/filterfw/java/android/filterfw/FilterFunctionEnvironment.java b/media/mca/filterfw/java/android/filterfw/FilterFunctionEnvironment.java
new file mode 100644
index 0000000..3f36d98
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/FilterFunctionEnvironment.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterFactory;
+import android.filterfw.core.FilterFunction;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameManager;
+
+/**
+ * A FilterFunctionEnvironment provides a simple functional front-end to manually executing
+ * filters. Use this environment if a graph-based approach is not convenient for your case.
+ * Typically, a FilterFunctionEnvironment is used as follows:
+ *   1. Instantiate a new FilterFunctionEnvironment instance.
+ *   2. Perform any configuration, such as setting a GL environment.
+ *   3. Wrap Filters into FilterFunctions by calling createFunction().
+ *   4. Execute FilterFunctions individually and use the results for further processing.
+ * Additionally, there is a convenience method to execute a number of filters in sequence.
+ * @hide
+ */
+public class FilterFunctionEnvironment extends MffEnvironment {
+
+    /**
+     * Create a new FilterFunctionEnvironment with the default components.
+     */
+    public FilterFunctionEnvironment() {
+        super(null);
+    }
+
+    /**
+     * Create a new FilterFunctionEnvironment with a custom FrameManager. Pass null to auto-create
+     * a FrameManager.
+     *
+     * @param frameManager The FrameManager to use, or null to auto-create one.
+     */
+    public FilterFunctionEnvironment(FrameManager frameManager) {
+        super(frameManager);
+    }
+
+    /**
+     * Create a new FilterFunction from a specific filter class. The function is initialized with
+     * the given key-value list of parameters. Note, that this function uses the default shared
+     * FilterFactory to create the filter instance.
+     *
+     * @param filterClass   The class of the filter to wrap. This must be a Filter subclass.
+     * @param parameters    An argument list of alternating key-value filter parameters.
+     * @return             A new FilterFunction instance.
+     */
+    public FilterFunction createFunction(Class filterClass, Object... parameters) {
+        String filterName = "FilterFunction(" + filterClass.getSimpleName() + ")";
+        Filter filter = FilterFactory.sharedFactory().createFilterByClass(filterClass, filterName);
+        filter.initWithAssignmentList(parameters);
+        return new FilterFunction(getContext(), filter);
+    }
+
+    /**
+     * Convenience method to execute a sequence of filter functions. Note that every function in
+     * the list MUST have one input and one output port, except the first filter (which must not
+     * have any input ports) and the last filter (which may not have any output ports).
+     *
+     * @param functions A list of filter functions. The first filter must be a source filter.
+     * @return         The result of the last filter executed, or null if the last filter did not
+                        produce any output.
+     *
+    public Frame executeSequence(FilterFunction[] functions) {
+        Frame oldFrame = null;
+        Frame newFrame = null;
+        for (FilterFunction filterFunction : functions) {
+            if (oldFrame == null) {
+                newFrame = filterFunction.executeWithArgList();
+            } else {
+                newFrame = filterFunction.executeWithArgList(oldFrame);
+                oldFrame.release();
+            }
+            oldFrame = newFrame;
+        }
+        if (oldFrame != null) {
+            oldFrame.release();
+        }
+        return newFrame;
+    }*/
+
+}
diff --git a/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java
new file mode 100644
index 0000000..5f6d45c
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw;
+
+import android.content.Context;
+import android.filterfw.core.AsyncRunner;
+import android.filterfw.core.FilterGraph;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.FrameManager;
+import android.filterfw.core.GraphRunner;
+import android.filterfw.core.RoundRobinScheduler;
+import android.filterfw.core.SyncRunner;
+import android.filterfw.io.GraphIOException;
+import android.filterfw.io.GraphReader;
+import android.filterfw.io.TextGraphReader;
+
+import java.util.ArrayList;
+
+/**
+ * A GraphEnvironment provides a simple front-end to filter graph setup and execution using the
+ * mobile filter framework. Typically, you use a GraphEnvironment in the following fashion:
+ *   1. Instantiate a new GraphEnvironment instance.
+ *   2. Perform any configuration, such as adding graph references and setting a GL environment.
+ *   3. Load a graph file using loadGraph() or add a graph using addGraph().
+ *   4. Obtain a GraphRunner instance using getRunner().
+ *   5. Execute the obtained runner.
+ * Note that it is possible to add multiple graphs and runners to a single GraphEnvironment.
+ *
+ * @hide
+ */
+public class GraphEnvironment extends MffEnvironment {
+
+    public static final int MODE_ASYNCHRONOUS = 1;
+    public static final int MODE_SYNCHRONOUS  = 2;
+
+    private GraphReader mGraphReader;
+    private ArrayList<GraphHandle> mGraphs = new ArrayList<GraphHandle>();
+
+    private class GraphHandle {
+        private FilterGraph mGraph;
+        private AsyncRunner mAsyncRunner;
+        private SyncRunner mSyncRunner;
+
+        public GraphHandle(FilterGraph graph) {
+            mGraph = graph;
+        }
+
+        public FilterGraph getGraph() {
+            return mGraph;
+        }
+
+        public AsyncRunner getAsyncRunner(FilterContext environment) {
+            if (mAsyncRunner == null) {
+                mAsyncRunner = new AsyncRunner(environment, RoundRobinScheduler.class);
+                mAsyncRunner.setGraph(mGraph);
+            }
+            return mAsyncRunner;
+        }
+
+        public GraphRunner getSyncRunner(FilterContext environment) {
+            if (mSyncRunner == null) {
+                mSyncRunner = new SyncRunner(environment, mGraph, RoundRobinScheduler.class);
+            }
+            return mSyncRunner;
+        }
+    }
+
+    /**
+     * Create a new GraphEnvironment with default components.
+     */
+    public GraphEnvironment() {
+        super(null);
+    }
+
+    /**
+     * Create a new GraphEnvironment with a custom FrameManager and GraphReader. Specifying null
+     * for either of these, will auto-create a default instance.
+     *
+     * @param frameManager The FrameManager to use, or null to auto-create one.
+     * @param reader        The GraphReader to use for graph loading, or null to auto-create one.
+     *                      Note, that the reader will not be created until it is required. Pass
+     *                      null if you will not load any graph files.
+     */
+    public GraphEnvironment(FrameManager frameManager, GraphReader reader) {
+        super(frameManager);
+        mGraphReader = reader;
+    }
+
+    /**
+     * Returns the used graph reader. This will create one, if a reader has not been set already.
+     */
+    public GraphReader getGraphReader() {
+        if (mGraphReader == null) {
+            mGraphReader = new TextGraphReader();
+        }
+        return mGraphReader;
+    }
+
+    /**
+     * Add graph references to resolve during graph reading. The references added here are shared
+     * among all graphs.
+     *
+     * @param references An alternating argument list of keys (Strings) and values.
+     */
+    public void addReferences(Object... references) {
+        getGraphReader().addReferencesByKeysAndValues(references);
+    }
+
+    /**
+     * Loads a graph file from the specified resource and adds it to this environment.
+     *
+     * @param context       The context in which to read the resource.
+     * @param resourceId    The ID of the graph resource to load.
+     * @return              A unique ID for the graph.
+     */
+    public int loadGraph(Context context, int resourceId) {
+        // Read the file into a graph
+        FilterGraph graph = null;
+        try {
+            graph = getGraphReader().readGraphResource(context, resourceId);
+        } catch (GraphIOException e) {
+            throw new RuntimeException("Could not read graph: " + e.getMessage());
+        }
+
+        // Add graph to our list of graphs
+        return addGraph(graph);
+    }
+
+    /**
+     * Add a graph to the environment. Consider using loadGraph() if you are loading a graph from
+     * a graph file.
+     *
+     * @param graph The graph to add to the environment.
+     * @return      A unique ID for the added graph.
+     */
+    public int addGraph(FilterGraph graph) {
+        GraphHandle graphHandle = new GraphHandle(graph);
+        mGraphs.add(graphHandle);
+        return mGraphs.size() - 1;
+    }
+
+    /**
+     * Access a specific graph of this environment given a graph ID (previously returned from
+     * loadGraph() or addGraph()). Throws an InvalidArgumentException if no graph with the
+     * specified ID could be found.
+     *
+     * @param graphId   The ID of the graph to get.
+     * @return          The graph with the specified ID.
+     */
+    public FilterGraph getGraph(int graphId) {
+        if (graphId < 0 || graphId >= mGraphs.size()) {
+            throw new IllegalArgumentException(
+                "Invalid graph ID " + graphId + " specified in runGraph()!");
+        }
+        return mGraphs.get(graphId).getGraph();
+    }
+
+    /**
+     * Get a GraphRunner instance for the graph with the specified ID. The GraphRunner instance can
+     * be used to execute the graph. Throws an InvalidArgumentException if no graph with the
+     * specified ID could be found.
+     *
+     * @param graphId       The ID of the graph to get.
+     * @param executionMode The mode of graph execution. Currently this can be either
+                            MODE_SYNCHRONOUS or MODE_ASYNCHRONOUS.
+     * @return              A GraphRunner instance for this graph.
+     */
+    public GraphRunner getRunner(int graphId, int executionMode) {
+        switch (executionMode) {
+            case MODE_ASYNCHRONOUS:
+                return mGraphs.get(graphId).getAsyncRunner(getContext());
+
+            case MODE_SYNCHRONOUS:
+                return mGraphs.get(graphId).getSyncRunner(getContext());
+
+            default:
+                throw new RuntimeException(
+                    "Invalid execution mode " + executionMode + " specified in getRunner()!");
+        }
+    }
+
+}
diff --git a/media/mca/filterfw/java/android/filterfw/MffEnvironment.java b/media/mca/filterfw/java/android/filterfw/MffEnvironment.java
new file mode 100644
index 0000000..1ab416a
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/MffEnvironment.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw;
+
+import android.filterfw.core.CachedFrameManager;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.FrameManager;
+import android.filterfw.core.GLEnvironment;
+
+/**
+ * Base class for mobile filter framework (MFF) frontend environments. These convenience classes
+ * allow using the filter framework without the requirement of performing manual setup of its
+ * required components.
+ *
+ * @hide
+ */
+public class MffEnvironment {
+
+    private FilterContext mContext;
+
+    /**
+     * Protected constructor to initialize the environment's essential components. These are the
+     * frame-manager and the filter-context. Passing in null for the frame-manager causes this
+     * to be auto-created.
+     *
+     * @param frameManager The FrameManager to use or null to auto-create one.
+     */
+    protected MffEnvironment(FrameManager frameManager) {
+        // Get or create the frame manager
+        if (frameManager == null) {
+            frameManager = new CachedFrameManager();
+        }
+
+        // Setup the environment
+        mContext = new FilterContext();
+        mContext.setFrameManager(frameManager);
+
+    }
+
+    /**
+     * Returns the environment's filter-context.
+     */
+    public FilterContext getContext() {
+        return mContext;
+    }
+
+    /**
+     * Set the environment's GL environment to the specified environment. This does not activate
+     * the environment.
+     */
+    public void setGLEnvironment(GLEnvironment glEnvironment) {
+        mContext.initGLEnvironment(glEnvironment);
+    }
+
+    /**
+     * Create and activate a new GL environment for use in this filter context.
+     */
+    public void createGLEnvironment() {
+        GLEnvironment glEnvironment = new GLEnvironment();
+        glEnvironment.initWithNewContext();
+        setGLEnvironment(glEnvironment);
+    }
+
+    /**
+     * Activate the GL environment for use in the current thread. A GL environment must have been
+     * previously set or created using setGLEnvironment() or createGLEnvironment()! Call this after
+     * having switched to a new thread for GL filter execution.
+     */
+    public void activateGLEnvironment() {
+        GLEnvironment glEnv = mContext.getGLEnvironment();
+        if (glEnv != null) {
+            mContext.getGLEnvironment().activate();
+        } else {
+            throw new NullPointerException("No GLEnvironment in place to activate!");
+        }
+    }
+
+    /**
+     * Deactivate the GL environment from use in the current thread. A GL environment must have been
+     * previously set or created using setGLEnvironment() or createGLEnvironment()! Call this before
+     * running GL filters in another thread.
+     */
+    public void deactivateGLEnvironment() {
+        GLEnvironment glEnv = mContext.getGLEnvironment();
+        if (glEnv != null) {
+            mContext.getGLEnvironment().deactivate();
+        } else {
+            throw new NullPointerException("No GLEnvironment in place to deactivate!");
+        }
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/AsyncRunner.java b/media/mca/filterfw/java/android/filterfw/core/AsyncRunner.java
new file mode 100644
index 0000000..70cbad4
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/AsyncRunner.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.os.AsyncTask;
+import android.os.Handler;
+
+import android.util.Log;
+
+import java.lang.InterruptedException;
+import java.lang.Runnable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @hide
+ */
+public class AsyncRunner extends GraphRunner{
+
+    private Class mSchedulerClass;
+    private SyncRunner mRunner;
+    private AsyncRunnerTask mRunTask;
+
+    private OnRunnerDoneListener mDoneListener;
+    private boolean isProcessing;
+
+    private Exception mException;
+
+    private class RunnerResult {
+        public int status = RESULT_UNKNOWN;
+        public Exception exception;
+    }
+
+    private class AsyncRunnerTask extends AsyncTask<SyncRunner, Void, RunnerResult> {
+
+        private static final String TAG = "AsyncRunnerTask";
+
+        @Override
+        protected RunnerResult doInBackground(SyncRunner... runner) {
+            RunnerResult result = new RunnerResult();
+            try {
+                if (runner.length > 1) {
+                    throw new RuntimeException("More than one runner received!");
+                }
+
+                runner[0].assertReadyToStep();
+
+                // Preparation
+                if (mLogVerbose) Log.v(TAG, "Starting background graph processing.");
+                activateGlContext();
+
+                if (mLogVerbose) Log.v(TAG, "Preparing filter graph for processing.");
+                runner[0].beginProcessing();
+
+                if (mLogVerbose) Log.v(TAG, "Running graph.");
+
+                // Run loop
+                result.status = RESULT_RUNNING;
+                while (!isCancelled() && result.status == RESULT_RUNNING) {
+                    if (!runner[0].performStep()) {
+                        result.status = runner[0].determinePostRunState();
+                        if (result.status == GraphRunner.RESULT_SLEEPING) {
+                            runner[0].waitUntilWake();
+                            result.status = RESULT_RUNNING;
+                        }
+                    }
+                }
+
+                // Cleanup
+                if (isCancelled()) {
+                    result.status = RESULT_STOPPED;
+                }
+            } catch (Exception exception) {
+                result.exception = exception;
+                result.status = RESULT_ERROR;
+            }
+
+            // Deactivate context.
+            try {
+                deactivateGlContext();
+            } catch (Exception exception) {
+                result.exception = exception;
+                result.status = RESULT_ERROR;
+            }
+
+            if (mLogVerbose) Log.v(TAG, "Done with background graph processing.");
+            return result;
+        }
+
+        @Override
+        protected void onCancelled(RunnerResult result) {
+            onPostExecute(result);
+        }
+
+        @Override
+        protected void onPostExecute(RunnerResult result) {
+            if (mLogVerbose) Log.v(TAG, "Starting post-execute.");
+            setRunning(false);
+            if (result == null) {
+                // Cancelled before got to doInBackground
+                result = new RunnerResult();
+                result.status = RESULT_STOPPED;
+            }
+            setException(result.exception);
+            if (result.status == RESULT_STOPPED || result.status == RESULT_ERROR) {
+                if (mLogVerbose) Log.v(TAG, "Closing filters.");
+                try {
+                    mRunner.close();
+                } catch (Exception exception) {
+                    result.status = RESULT_ERROR;
+                    setException(exception);
+                }
+            }
+            if (mDoneListener != null) {
+                if (mLogVerbose) Log.v(TAG, "Calling graph done callback.");
+                mDoneListener.onRunnerDone(result.status);
+            }
+            if (mLogVerbose) Log.v(TAG, "Completed post-execute.");
+        }
+    }
+
+    private boolean mLogVerbose;
+    private static final String TAG = "AsyncRunner";
+
+    /** Create a new asynchronous graph runner with the given filter
+     * context, and the given scheduler class.
+     *
+     * Must be created on the UI thread.
+     */
+    public AsyncRunner(FilterContext context, Class schedulerClass) {
+        super(context);
+
+        mSchedulerClass = schedulerClass;
+        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    }
+
+    /** Create a new asynchronous graph runner with the given filter
+     * context. Uses a default scheduler.
+     *
+     * Must be created on the UI thread.
+     */
+    public AsyncRunner(FilterContext context) {
+        super(context);
+
+        mSchedulerClass = SimpleScheduler.class;
+        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    }
+
+    /** Set a callback to be called in the UI thread once the AsyncRunner
+     * completes running a graph, whether the completion is due to a stop() call
+     * or the filters running out of data to process.
+     */
+    @Override
+    public void setDoneCallback(OnRunnerDoneListener listener) {
+        mDoneListener = listener;
+    }
+
+    /** Sets the graph to be run. Will call prepare() on graph. Cannot be called
+     * when a graph is already running.
+     */
+    synchronized public void setGraph(FilterGraph graph) {
+        if (isRunning()) {
+            throw new RuntimeException("Graph is already running!");
+        }
+        mRunner = new SyncRunner(mFilterContext, graph, mSchedulerClass);
+    }
+
+    @Override
+    public FilterGraph getGraph() {
+        return mRunner != null ? mRunner.getGraph() : null;
+    }
+
+    /** Execute the graph in a background thread. */
+    @Override
+    synchronized public void run() {
+        if (mLogVerbose) Log.v(TAG, "Running graph.");
+        setException(null);
+
+        if (isRunning()) {
+            throw new RuntimeException("Graph is already running!");
+        }
+        if (mRunner == null) {
+            throw new RuntimeException("Cannot run before a graph is set!");
+        }
+        mRunTask = this.new AsyncRunnerTask();
+
+        setRunning(true);
+        mRunTask.execute(mRunner);
+    }
+
+    /** Stop graph execution. This is an asynchronous call; register a callback
+     * with setDoneCallback to be notified of when the background processing has
+     * been completed. Calling stop will close the filter graph. */
+    @Override
+    synchronized public void stop() {
+        if (mRunTask != null && !mRunTask.isCancelled() ) {
+            if (mLogVerbose) Log.v(TAG, "Stopping graph.");
+            mRunTask.cancel(false);
+        }
+    }
+
+    @Override
+    synchronized public void close() {
+        if (isRunning()) {
+            throw new RuntimeException("Cannot close graph while it is running!");
+        }
+        if (mLogVerbose) Log.v(TAG, "Closing filters.");
+        mRunner.close();
+    }
+
+    /** Check if background processing is happening */
+    @Override
+    synchronized public boolean isRunning() {
+        return isProcessing;
+    }
+
+    @Override
+    synchronized public Exception getError() {
+        return mException;
+    }
+
+    synchronized private void setRunning(boolean running) {
+        isProcessing = running;
+    }
+
+    synchronized private void setException(Exception exception) {
+        mException = exception;
+    }
+
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/CachedFrameManager.java b/media/mca/filterfw/java/android/filterfw/core/CachedFrameManager.java
new file mode 100644
index 0000000..a2cf2a0
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/CachedFrameManager.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.SimpleFrameManager;
+
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * @hide
+ */
+public class CachedFrameManager extends SimpleFrameManager {
+
+    private SortedMap<Integer, Frame> mAvailableFrames;
+    private int mStorageCapacity = 24 * 1024 * 1024; // Cap default storage to 24MB
+    private int mStorageSize = 0;
+    private int mTimeStamp = 0;
+
+    public CachedFrameManager() {
+        super();
+        mAvailableFrames = new TreeMap<Integer, Frame>();
+    }
+
+    @Override
+    public Frame newFrame(FrameFormat format) {
+        Frame result = findAvailableFrame(format, Frame.NO_BINDING, 0);
+        if (result == null) {
+            result = super.newFrame(format);
+        }
+        result.setTimestamp(Frame.TIMESTAMP_NOT_SET);
+        return result;
+    }
+
+    @Override
+    public Frame newBoundFrame(FrameFormat format, int bindingType, long bindingId) {
+        Frame result = findAvailableFrame(format, bindingType, bindingId);
+        if (result == null) {
+            result = super.newBoundFrame(format, bindingType, bindingId);
+        }
+        result.setTimestamp(Frame.TIMESTAMP_NOT_SET);
+        return result;
+    }
+
+    @Override
+    public Frame retainFrame(Frame frame) {
+        return super.retainFrame(frame);
+    }
+
+    @Override
+    public Frame releaseFrame(Frame frame) {
+        if (frame.isReusable()) {
+            int refCount = frame.decRefCount();
+            if (refCount == 0 && frame.hasNativeAllocation()) {
+                if (!storeFrame(frame)) {
+                    frame.releaseNativeAllocation();
+                }
+                return null;
+            } else if (refCount < 0) {
+                throw new RuntimeException("Frame reference count dropped below 0!");
+            }
+        } else {
+            super.releaseFrame(frame);
+        }
+        return frame;
+    }
+
+    public void clearCache() {
+        for (Frame frame : mAvailableFrames.values()) {
+            frame.releaseNativeAllocation();
+        }
+        mAvailableFrames.clear();
+    }
+
+    @Override
+    public void tearDown() {
+        clearCache();
+    }
+
+    private boolean storeFrame(Frame frame) {
+        synchronized(mAvailableFrames) {
+            // Make sure this frame alone does not exceed capacity
+            int frameSize = frame.getFormat().getSize();
+            if (frameSize > mStorageCapacity) {
+                return false;
+            }
+
+            // Drop frames if adding this frame would exceed capacity
+            int newStorageSize = mStorageSize + frameSize;
+            while (newStorageSize > mStorageCapacity) {
+                dropOldestFrame();
+                newStorageSize = mStorageSize + frameSize;
+            }
+
+            // Store new frame
+            frame.onFrameStore();
+            mStorageSize = newStorageSize;
+            mAvailableFrames.put(mTimeStamp, frame);
+            ++mTimeStamp;
+            return true;
+        }
+    }
+
+    private void dropOldestFrame() {
+        int oldest = mAvailableFrames.firstKey();
+        Frame frame = mAvailableFrames.get(oldest);
+        mStorageSize -= frame.getFormat().getSize();
+        frame.releaseNativeAllocation();
+        mAvailableFrames.remove(oldest);
+    }
+
+    private Frame findAvailableFrame(FrameFormat format, int bindingType, long bindingId) {
+        // Look for a frame that is compatible with the requested format
+        synchronized(mAvailableFrames) {
+            for (Map.Entry<Integer, Frame> entry : mAvailableFrames.entrySet()) {
+                Frame frame = entry.getValue();
+                // Check that format is compatible
+                if (frame.getFormat().isReplaceableBy(format)) {
+                    // Check that binding is compatible (if frame is bound)
+                    if ((bindingType == frame.getBindingType())
+                        && (bindingType == Frame.NO_BINDING || bindingId == frame.getBindingId())) {
+                        // We found one! Take it out of the set of available frames and attach the
+                        // requested format to it.
+                        super.retainFrame(frame);
+                        mAvailableFrames.remove(entry.getKey());
+                        frame.onFrameFetch();
+                        frame.reset(format);
+                        mStorageSize -= format.getSize();
+                        return frame;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/FieldPort.java b/media/mca/filterfw/java/android/filterfw/core/FieldPort.java
new file mode 100644
index 0000000..b0350cc
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/FieldPort.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import java.lang.reflect.Field;
+
+/**
+ * @hide
+ */
+public class FieldPort extends InputPort {
+
+    protected Field mField;
+    protected boolean mHasFrame;
+    protected boolean mValueWaiting = false;
+    protected Object mValue;
+
+    public FieldPort(Filter filter, String name, Field field, boolean hasDefault) {
+        super(filter, name);
+        mField = field;
+        mHasFrame = hasDefault;
+    }
+
+    @Override
+    public void clear() {
+    }
+
+    @Override
+    public void pushFrame(Frame frame) {
+        setFieldFrame(frame, false);
+    }
+
+    @Override
+    public void setFrame(Frame frame) {
+        setFieldFrame(frame, true);
+    }
+
+    @Override
+    public Object getTarget() {
+        try {
+            return mField.get(mFilter);
+        } catch (IllegalAccessException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public synchronized void transfer(FilterContext context) {
+        if (mValueWaiting) {
+            try {
+                mField.set(mFilter, mValue);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(
+                    "Access to field '" + mField.getName() + "' was denied!");
+            }
+            mValueWaiting = false;
+            if (context != null) {
+                mFilter.notifyFieldPortValueUpdated(mName, context);
+            }
+        }
+    }
+
+    @Override
+    public synchronized Frame pullFrame() {
+        throw new RuntimeException("Cannot pull frame on " + this + "!");
+    }
+
+    @Override
+    public synchronized boolean hasFrame() {
+        return mHasFrame;
+    }
+
+    @Override
+    public synchronized boolean acceptsFrame() {
+        return !mValueWaiting;
+    }
+
+    @Override
+    public String toString() {
+        return "field " + super.toString();
+    }
+
+    protected synchronized void setFieldFrame(Frame frame, boolean isAssignment) {
+        assertPortIsOpen();
+        checkFrameType(frame, isAssignment);
+
+        // Store the object value
+        Object value = frame.getObjectValue();
+        if ((value == null && mValue != null) || !value.equals(mValue)) {
+            mValue = value;
+            mValueWaiting = true;
+        }
+
+        // Since a frame was set, mark this port as having a frame to pull
+        mHasFrame = true;
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/Filter.java b/media/mca/filterfw/java/android/filterfw/core/Filter.java
new file mode 100644
index 0000000..73b009d
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/Filter.java
@@ -0,0 +1,709 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *            http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.FilterPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.io.TextGraphReader;
+import android.filterfw.io.GraphIOException;
+import android.filterfw.format.ObjectFormat;
+import android.util.Log;
+
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.Thread;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map.Entry;
+import java.util.LinkedList;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public abstract class Filter {
+
+    static final int STATUS_PREINIT               = 0;
+    static final int STATUS_UNPREPARED            = 1;
+    static final int STATUS_PREPARED              = 2;
+    static final int STATUS_PROCESSING            = 3;
+    static final int STATUS_SLEEPING              = 4;
+    static final int STATUS_FINISHED              = 5;
+    static final int STATUS_ERROR                 = 6;
+    static final int STATUS_RELEASED              = 7;
+
+    private String mName;
+
+    private int mInputCount = -1;
+    private int mOutputCount = -1;
+
+    private HashMap<String, InputPort> mInputPorts;
+    private HashMap<String, OutputPort> mOutputPorts;
+
+    private HashSet<Frame> mFramesToRelease;
+    private HashMap<String, Frame> mFramesToSet;
+
+    private int mStatus = 0;
+    private boolean mIsOpen = false;
+    private int mSleepDelay;
+
+    private long mCurrentTimestamp;
+
+    private boolean mLogVerbose;
+    private static final String TAG = "Filter";
+
+    public Filter(String name) {
+        mName = name;
+        mFramesToRelease = new HashSet<Frame>();
+        mFramesToSet = new HashMap<String, Frame>();
+        mStatus = STATUS_PREINIT;
+
+        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    }
+
+    /** Tests to see if a given filter is installed on the system. Requires
+     * full filter package name, including filterpack.
+     */
+    public static final boolean isAvailable(String filterName) {
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        Class filterClass;
+        // First see if a class of that name exists
+        try {
+            filterClass = contextClassLoader.loadClass(filterName);
+        } catch (ClassNotFoundException e) {
+            return false;
+        }
+        // Then make sure it's a subclass of Filter.
+        try {
+            filterClass.asSubclass(Filter.class);
+        } catch (ClassCastException e) {
+            return false;
+        }
+        return true;
+    }
+
+    public final void initWithValueMap(KeyValueMap valueMap) {
+        // Initialization
+        initFinalPorts(valueMap);
+
+        // Setup remaining ports
+        initRemainingPorts(valueMap);
+
+        // This indicates that final ports can no longer be set
+        mStatus = STATUS_UNPREPARED;
+    }
+
+    public final void initWithAssignmentString(String assignments) {
+        try {
+            KeyValueMap valueMap = new TextGraphReader().readKeyValueAssignments(assignments);
+            initWithValueMap(valueMap);
+        } catch (GraphIOException e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+
+    public final void initWithAssignmentList(Object... keyValues) {
+        KeyValueMap valueMap = new KeyValueMap();
+        valueMap.setKeyValues(keyValues);
+        initWithValueMap(valueMap);
+    }
+
+    public final void init() throws ProtocolException {
+        KeyValueMap valueMap = new KeyValueMap();
+        initWithValueMap(valueMap);
+    }
+
+    public String getFilterClassName() {
+        return getClass().getSimpleName();
+    }
+
+    public final String getName() {
+        return mName;
+    }
+
+    public boolean isOpen() {
+        return mIsOpen;
+    }
+
+    public void setInputFrame(String inputName, Frame frame) {
+        FilterPort port = getInputPort(inputName);
+        if (!port.isOpen()) {
+            port.open();
+        }
+        port.setFrame(frame);
+    }
+
+    public final void setInputValue(String inputName, Object value) {
+        setInputFrame(inputName, wrapInputValue(inputName, value));
+    }
+
+    protected void prepare(FilterContext context) {
+    }
+
+    protected void parametersUpdated(Set<String> updated) {
+    }
+
+    protected void delayNextProcess(int millisecs) {
+        mSleepDelay = millisecs;
+        mStatus = STATUS_SLEEPING;
+    }
+
+    public abstract void setupPorts();
+
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return null;
+    }
+
+    public final FrameFormat getInputFormat(String portName) {
+        InputPort inputPort = getInputPort(portName);
+        return inputPort.getSourceFormat();
+    }
+
+    public void open(FilterContext context) {
+    }
+
+    public abstract void process(FilterContext context);
+
+    public final int getSleepDelay() {
+        return 250;
+    }
+
+    public void close(FilterContext context) {
+    }
+
+    public void tearDown(FilterContext context) {
+    }
+
+    public final int getNumberOfConnectedInputs() {
+        int c = 0;
+        for (InputPort inputPort : mInputPorts.values()) {
+            if (inputPort.isConnected()) {
+                ++c;
+            }
+        }
+        return c;
+    }
+
+    public final int getNumberOfConnectedOutputs() {
+        int c = 0;
+        for (OutputPort outputPort : mOutputPorts.values()) {
+            if (outputPort.isConnected()) {
+                ++c;
+            }
+        }
+        return c;
+    }
+
+    public final int getNumberOfInputs() {
+        return mOutputPorts == null ? 0 : mInputPorts.size();
+    }
+
+    public final int getNumberOfOutputs() {
+        return mInputPorts == null ? 0 : mOutputPorts.size();
+    }
+
+    public final InputPort getInputPort(String portName) {
+        if (mInputPorts == null) {
+            throw new NullPointerException("Attempting to access input port '" + portName
+                + "' of " + this + " before Filter has been initialized!");
+        }
+        InputPort result = mInputPorts.get(portName);
+        if (result == null) {
+            throw new IllegalArgumentException("Unknown input port '" + portName + "' on filter "
+                + this + "!");
+        }
+        return result;
+    }
+
+    public final OutputPort getOutputPort(String portName) {
+        if (mInputPorts == null) {
+            throw new NullPointerException("Attempting to access output port '" + portName
+                + "' of " + this + " before Filter has been initialized!");
+        }
+        OutputPort result = mOutputPorts.get(portName);
+        if (result == null) {
+            throw new IllegalArgumentException("Unknown output port '" + portName + "' on filter "
+                + this + "!");
+        }
+        return result;
+    }
+
+    protected final void pushOutput(String name, Frame frame) {
+        if (frame.getTimestamp() == Frame.TIMESTAMP_NOT_SET) {
+            if (mLogVerbose) Log.v(TAG, "Default-setting output Frame timestamp on port " + name + " to " + mCurrentTimestamp);
+            frame.setTimestamp(mCurrentTimestamp);
+        }
+        getOutputPort(name).pushFrame(frame);
+    }
+
+    protected final Frame pullInput(String name) {
+        Frame result = getInputPort(name).pullFrame();
+        if (mCurrentTimestamp == Frame.TIMESTAMP_UNKNOWN) {
+            mCurrentTimestamp = result.getTimestamp();
+            if (mLogVerbose) Log.v(TAG, "Default-setting current timestamp from input port " + name + " to " + mCurrentTimestamp);
+        }
+        // As result is retained, we add it to the release pool here
+        mFramesToRelease.add(result);
+
+        return result;
+    }
+
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+    }
+
+    /**
+     * Transfers any frame from an input port to its destination. This is useful to force a
+     * transfer from a FieldPort or ProgramPort to its connected target (field or program variable).
+     */
+    protected void transferInputPortFrame(String name, FilterContext context) {
+        getInputPort(name).transfer(context);
+    }
+
+    /**
+     * Assigns all program variables to the ports they are connected to. Call this after
+     * constructing a Program instance with attached ProgramPorts.
+     */
+    protected void initProgramInputs(Program program, FilterContext context) {
+        if (program != null) {
+            for (InputPort inputPort : mInputPorts.values()) {
+                if (inputPort.getTarget() == program) {
+                    inputPort.transfer(context);
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds an input port to the filter. You should call this from within setupPorts, if your
+     * filter has input ports. No type-checking is performed on the input. If you would like to
+     * check against a type mask, use
+     * {@link #addMaskedInputPort(String, FrameFormat) addMaskedInputPort} instead.
+     *
+     * @param name the name of the input port
+     */
+    protected void addInputPort(String name) {
+        addMaskedInputPort(name, null);
+    }
+
+    /**
+     * Adds an input port to the filter. You should call this from within setupPorts, if your
+     * filter has input ports. When type-checking is performed, the input format is
+     * checked against the provided format mask. An exception is thrown in case of a conflict.
+     *
+     * @param name the name of the input port
+     * @param formatMask a format mask, which filters the allowable input types
+     */
+    protected void addMaskedInputPort(String name, FrameFormat formatMask) {
+        InputPort port = new StreamPort(this, name);
+        if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port);
+        mInputPorts.put(name, port);
+        port.setPortFormat(formatMask);
+    }
+
+    /**
+     * Adds an output port to the filter with a fixed output format. You should call this from
+     * within setupPorts, if your filter has output ports. You cannot use this method, if your
+     * output format depends on the input format (e.g. in a pass-through filter). In this case, use
+     * {@link #addOutputBasedOnInput(String, String) addOutputBasedOnInput} instead.
+     *
+     * @param name the name of the output port
+     * @param format the fixed output format of this port
+     */
+    protected void addOutputPort(String name, FrameFormat format) {
+        OutputPort port = new OutputPort(this, name);
+        if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port);
+        port.setPortFormat(format);
+        mOutputPorts.put(name, port);
+    }
+
+    /**
+     * Adds an output port to the filter. You should call this from within setupPorts, if your
+     * filter has output ports. Using this method indicates that the output format for this
+     * particular port, depends on the format of an input port. You MUST also override
+     * {@link #getOutputFormat(String, FrameFormat) getOutputFormat} to specify what format your
+     * filter will output for a given input. If the output format of your filter port does not
+     * depend on the input, use {@link #addOutputPort(String, FrameFormat) addOutputPort} instead.
+     *
+     * @param outputName the name of the output port
+     * @param inputName the name of the input port, that this output depends on
+     */
+    protected void addOutputBasedOnInput(String outputName, String inputName) {
+        OutputPort port = new OutputPort(this, outputName);
+        if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port);
+        port.setBasePort(getInputPort(inputName));
+        mOutputPorts.put(outputName, port);
+    }
+
+    protected void addFieldPort(String name,
+                                Field field,
+                                boolean hasDefault,
+                                boolean isFinal) {
+        // Make sure field is accessible
+        field.setAccessible(true);
+
+        // Create port for this input
+        InputPort fieldPort = isFinal
+            ? new FinalPort(this, name, field, hasDefault)
+            : new FieldPort(this, name, field, hasDefault);
+
+        // Create format for this input
+        if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + fieldPort);
+        MutableFrameFormat format = ObjectFormat.fromClass(field.getType(),
+                                                           FrameFormat.TARGET_SIMPLE);
+        fieldPort.setPortFormat(format);
+
+        // Add port
+        mInputPorts.put(name, fieldPort);
+    }
+
+    protected void addProgramPort(String name,
+                                  String varName,
+                                  Field field,
+                                  Class varType,
+                                  boolean hasDefault) {
+        // Make sure field is accessible
+        field.setAccessible(true);
+
+        // Create port for this input
+        InputPort programPort = new ProgramPort(this, name, varName, field, hasDefault);
+
+        // Create format for this input
+        if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + programPort);
+        MutableFrameFormat format = ObjectFormat.fromClass(varType,
+                                                           FrameFormat.TARGET_SIMPLE);
+        programPort.setPortFormat(format);
+
+        // Add port
+        mInputPorts.put(name, programPort);
+    }
+
+    protected void closeOutputPort(String name) {
+        getOutputPort(name).close();
+    }
+
+    /**
+     * Specifies whether the filter should not be scheduled until a frame is available on that
+     * input port. Note, that setting this to false, does not block a new frame from coming in
+     * (though there is no necessity to pull that frame for processing).
+     * @param portName the name of the input port.
+     * @param waits true, if the filter should wait for a frame on this port.
+     */
+    protected void setWaitsOnInputPort(String portName, boolean waits) {
+        getInputPort(portName).setBlocking(waits);
+    }
+
+    /**
+     * Specifies whether the filter should not be scheduled until the output port is free, i.e.
+     * there is no frame waiting on that output.
+     * @param portName the name of the output port.
+     * @param waits true, if the filter should wait for the port to become free.
+     */
+    protected void setWaitsOnOutputPort(String portName, boolean waits) {
+        getOutputPort(portName).setBlocking(waits);
+    }
+
+    public String toString() {
+        return "'" + getName() + "' (" + getFilterClassName() + ")";
+    }
+
+    // Core internal methods ///////////////////////////////////////////////////////////////////////
+    final Collection<InputPort> getInputPorts() {
+        return mInputPorts.values();
+    }
+
+    final Collection<OutputPort> getOutputPorts() {
+        return mOutputPorts.values();
+    }
+
+    final synchronized int getStatus() {
+        return mStatus;
+    }
+
+    final synchronized void unsetStatus(int flag) {
+        mStatus &= ~flag;
+    }
+
+    final synchronized void performOpen(FilterContext context) {
+        if (!mIsOpen) {
+            if (mStatus == STATUS_UNPREPARED) {
+                if (mLogVerbose) Log.v(TAG, "Preparing " + this);
+                prepare(context);
+                mStatus = STATUS_PREPARED;
+            }
+            if (mStatus == STATUS_PREPARED) {
+                if (mLogVerbose) Log.v(TAG, "Opening " + this);
+                open(context);
+                mStatus = STATUS_PROCESSING;
+            }
+            if (mStatus != STATUS_PROCESSING) {
+                throw new RuntimeException("Filter " + this + " was brought into invalid state during "
+                    + "opening (state: " + mStatus + ")!");
+            }
+            mIsOpen = true;
+        }
+    }
+
+    final synchronized void performProcess(FilterContext context) {
+        if (mStatus == STATUS_RELEASED) {
+            throw new RuntimeException("Filter " + this + " is already torn down!");
+        }
+        transferInputFrames(context);
+        if (mStatus < STATUS_PROCESSING) {
+            performOpen(context);
+        }
+        if (mLogVerbose) Log.v(TAG, "Processing " + this);
+        mCurrentTimestamp = Frame.TIMESTAMP_UNKNOWN;
+        process(context);
+        releasePulledFrames(context);
+        if (filterMustClose()) {
+            performClose(context);
+        }
+    }
+
+    final synchronized void performClose(FilterContext context) {
+        if (mIsOpen) {
+            if (mLogVerbose) Log.v(TAG, "Closing " + this);
+            mIsOpen = false;
+            mStatus = STATUS_PREPARED;
+            close(context);
+            closePorts();
+        }
+    }
+
+    final synchronized void performTearDown(FilterContext context) {
+        performClose(context);
+        if (mStatus != STATUS_RELEASED) {
+            tearDown(context);
+            mStatus = STATUS_RELEASED;
+        }
+    }
+
+    synchronized final boolean canProcess() {
+        if (mLogVerbose) Log.v(TAG, "Checking if can process: " + this + " (" + mStatus + ").");
+        if (mStatus <= STATUS_PROCESSING) {
+            return inputConditionsMet() && outputConditionsMet();
+        } else {
+            return false;
+        }
+    }
+
+    final void openOutputs() {
+        if (mLogVerbose) Log.v(TAG, "Opening all output ports on " + this + "!");
+        for (OutputPort outputPort : mOutputPorts.values()) {
+            if (!outputPort.isOpen()) {
+                outputPort.open();
+            }
+        }
+    }
+
+    final void clearInputs() {
+        for (InputPort inputPort : mInputPorts.values()) {
+            inputPort.clear();
+        }
+    }
+
+    final void clearOutputs() {
+        for (OutputPort outputPort : mOutputPorts.values()) {
+            outputPort.clear();
+        }
+    }
+
+    final void notifyFieldPortValueUpdated(String name, FilterContext context) {
+        if (mStatus == STATUS_PROCESSING || mStatus == STATUS_PREPARED) {
+            fieldPortValueUpdated(name, context);
+        }
+    }
+
+    final synchronized void pushInputFrame(String inputName, Frame frame) {
+        FilterPort port = getInputPort(inputName);
+        if (!port.isOpen()) {
+            port.open();
+        }
+        port.pushFrame(frame);
+    }
+
+    final synchronized void pushInputValue(String inputName, Object value) {
+        pushInputFrame(inputName, wrapInputValue(inputName, value));
+    }
+
+    // Filter internal methods /////////////////////////////////////////////////////////////////////
+    private final void initFinalPorts(KeyValueMap values) {
+        mInputPorts = new HashMap<String, InputPort>();
+        mOutputPorts = new HashMap<String, OutputPort>();
+        addAndSetFinalPorts(values);
+    }
+
+    private final void initRemainingPorts(KeyValueMap values) {
+        addAnnotatedPorts();
+        setupPorts();   // TODO: rename to addFilterPorts() ?
+        setInitialInputValues(values);
+    }
+
+    private final void addAndSetFinalPorts(KeyValueMap values) {
+        Class filterClass = getClass();
+        Annotation annotation;
+        for (Field field : filterClass.getDeclaredFields()) {
+            if ((annotation = field.getAnnotation(GenerateFinalPort.class)) != null) {
+                GenerateFinalPort generator = (GenerateFinalPort)annotation;
+                String name = generator.name().isEmpty() ? field.getName() : generator.name();
+                boolean hasDefault = generator.hasDefault();
+                addFieldPort(name, field, hasDefault, true);
+                if (values.containsKey(name)) {
+                    setImmediateInputValue(name, values.get(name));
+                    values.remove(name);
+                } else if (!generator.hasDefault()) {
+                    throw new RuntimeException("No value specified for final input port '"
+                        + name + "' of filter " + this + "!");
+                }
+            }
+        }
+    }
+
+    private final void addAnnotatedPorts() {
+        Class filterClass = getClass();
+        Annotation annotation;
+        for (Field field : filterClass.getDeclaredFields()) {
+            if ((annotation = field.getAnnotation(GenerateFieldPort.class)) != null) {
+                GenerateFieldPort generator = (GenerateFieldPort)annotation;
+                addFieldGenerator(generator, field);
+            } else if ((annotation = field.getAnnotation(GenerateProgramPort.class)) != null) {
+                GenerateProgramPort generator = (GenerateProgramPort)annotation;
+                addProgramGenerator(generator, field);
+            } else if ((annotation = field.getAnnotation(GenerateProgramPorts.class)) != null) {
+                GenerateProgramPorts generators = (GenerateProgramPorts)annotation;
+                for (GenerateProgramPort generator : generators.value()) {
+                    addProgramGenerator(generator, field);
+                }
+            }
+        }
+    }
+
+    private final void addFieldGenerator(GenerateFieldPort generator, Field field) {
+        String name = generator.name().isEmpty() ? field.getName() : generator.name();
+        boolean hasDefault = generator.hasDefault();
+        addFieldPort(name, field, hasDefault, false);
+    }
+
+    private final void addProgramGenerator(GenerateProgramPort generator, Field field) {
+        String name = generator.name();
+        String varName = generator.variableName().isEmpty() ? name
+                                                            : generator.variableName();
+        Class varType = generator.type();
+        boolean hasDefault = generator.hasDefault();
+        addProgramPort(name, varName, field, varType, hasDefault);
+    }
+
+    private final void setInitialInputValues(KeyValueMap values) {
+        for (Entry<String, Object> entry : values.entrySet()) {
+            setInputValue(entry.getKey(), entry.getValue());
+        }
+    }
+
+    private final void setImmediateInputValue(String name, Object value) {
+        if (mLogVerbose) Log.v(TAG, "Setting immediate value " + value + " for port " + name + "!");
+        FilterPort port = getInputPort(name);
+        port.open();
+        port.setFrame(SimpleFrame.wrapObject(value, null));
+    }
+
+    private final void transferInputFrames(FilterContext context) {
+        for (InputPort inputPort : mInputPorts.values()) {
+            inputPort.transfer(context);
+        }
+    }
+
+    private final Frame wrapInputValue(String inputName, Object value) {
+        MutableFrameFormat inputFormat = ObjectFormat.fromObject(value, FrameFormat.TARGET_SIMPLE);
+        if (value == null) {
+            // If the value is null, the format cannot guess the class, so we adjust it to the
+            // class of the input port here
+            FrameFormat portFormat = getInputPort(inputName).getPortFormat();
+            Class portClass = (portFormat == null) ? null : portFormat.getObjectClass();
+            inputFormat.setObjectClass(portClass);
+        }
+
+        // Serialize if serializable, and type is not an immutable primitive.
+        boolean shouldSerialize = !(value instanceof Number)
+            && !(value instanceof Boolean)
+            && !(value instanceof String)
+            && value instanceof Serializable;
+
+        // Create frame wrapper
+        Frame frame = shouldSerialize
+            ? new SerializedFrame(inputFormat, null)
+            : new SimpleFrame(inputFormat, null);
+        frame.setObjectValue(value);
+        return frame;
+    }
+
+    private final void releasePulledFrames(FilterContext context) {
+        for (Frame frame : mFramesToRelease) {
+            context.getFrameManager().releaseFrame(frame);
+        }
+        mFramesToRelease.clear();
+    }
+
+    private final boolean inputConditionsMet() {
+        for (FilterPort port : mInputPorts.values()) {
+            if (!port.isReady()) {
+                if (mLogVerbose) Log.v(TAG, "Input condition not met: " + port + "!");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private final boolean outputConditionsMet() {
+        for (FilterPort port : mOutputPorts.values()) {
+            if (!port.isReady()) {
+                if (mLogVerbose) Log.v(TAG, "Output condition not met: " + port + "!");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private final void closePorts() {
+        if (mLogVerbose) Log.v(TAG, "Closing all ports on " + this + "!");
+        for (InputPort inputPort : mInputPorts.values()) {
+            inputPort.close();
+        }
+        for (OutputPort outputPort : mOutputPorts.values()) {
+            outputPort.close();
+        }
+    }
+
+    private final boolean filterMustClose() {
+        for (InputPort inputPort : mInputPorts.values()) {
+            if (inputPort.filterMustClose()) {
+                if (mLogVerbose) Log.v(TAG, "Filter " + this + " must close due to port " + inputPort);
+                return true;
+            }
+        }
+        for (OutputPort outputPort : mOutputPorts.values()) {
+            if (outputPort.filterMustClose()) {
+                if (mLogVerbose) Log.v(TAG, "Filter " + this + " must close due to port " + outputPort);
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterContext.java b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java
new file mode 100644
index 0000000..3c79d1b
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameManager;
+import android.filterfw.core.GLEnvironment;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class FilterContext {
+
+    private FrameManager mFrameManager;
+    private GLEnvironment mGLEnvironment;
+    private HashMap<String, Frame> mStoredFrames = new HashMap<String, Frame>();
+    private Set<FilterGraph> mGraphs = new HashSet<FilterGraph>();
+
+    public FrameManager getFrameManager() {
+        return mFrameManager;
+    }
+
+    public void setFrameManager(FrameManager manager) {
+        if (manager == null) {
+            throw new NullPointerException("Attempting to set null FrameManager!");
+        } else if (manager.getContext() != null) {
+            throw new IllegalArgumentException("Attempting to set FrameManager which is already "
+                + "bound to another FilterContext!");
+        } else {
+            mFrameManager = manager;
+            mFrameManager.setContext(this);
+        }
+    }
+
+    public GLEnvironment getGLEnvironment() {
+        return mGLEnvironment;
+    }
+
+    public void initGLEnvironment(GLEnvironment environment) {
+        if (mGLEnvironment == null) {
+            mGLEnvironment = environment;
+        } else {
+            throw new RuntimeException("Attempting to re-initialize GL Environment for " +
+                "FilterContext!");
+        }
+    }
+
+    public interface OnFrameReceivedListener {
+        public void onFrameReceived(Filter filter, Frame frame, Object userData);
+    }
+
+    public synchronized void storeFrame(String key, Frame frame) {
+        Frame storedFrame = fetchFrame(key);
+        if (storedFrame != null) {
+            storedFrame.release();
+        }
+        frame.onFrameStore();
+        mStoredFrames.put(key, frame.retain());
+    }
+
+    public synchronized Frame fetchFrame(String key) {
+        Frame frame = mStoredFrames.get(key);
+        if (frame != null) {
+            frame.onFrameFetch();
+        }
+        return frame;
+    }
+
+    public synchronized void removeFrame(String key) {
+        Frame frame = mStoredFrames.get(key);
+        if (frame != null) {
+            mStoredFrames.remove(key);
+            frame.release();
+        }
+    }
+
+    public synchronized void tearDown() {
+        // Release stored frames
+        for (Frame frame : mStoredFrames.values()) {
+            frame.release();
+        }
+        mStoredFrames.clear();
+
+        // Release graphs
+        for (FilterGraph graph : mGraphs) {
+            graph.tearDown(this);
+        }
+        mGraphs.clear();
+
+        // Release frame manager
+        if (mFrameManager != null) {
+            mFrameManager.tearDown();
+            mFrameManager = null;
+        }
+
+        // Release GL context
+        if (mGLEnvironment != null) {
+            mGLEnvironment.tearDown();
+            mGLEnvironment = null;
+        }
+    }
+
+    final void addGraph(FilterGraph graph) {
+        mGraphs.add(graph);
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java b/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java
new file mode 100644
index 0000000..779df99
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *            http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Filter;
+import android.util.Log;
+
+import dalvik.system.PathClassLoader;
+
+import java.lang.reflect.Constructor;
+import java.lang.ClassLoader;
+import java.lang.Thread;
+import java.util.HashSet;
+
+/**
+ * @hide
+ */
+public class FilterFactory {
+
+    private static FilterFactory mSharedFactory;
+    private HashSet<String> mPackages = new HashSet<String>();
+
+    private static ClassLoader mCurrentClassLoader;
+    private static HashSet<String> mLibraries;
+    private static Object mClassLoaderGuard;
+
+    static {
+        mCurrentClassLoader = Thread.currentThread().getContextClassLoader();
+        mLibraries = new HashSet<String>();
+        mClassLoaderGuard = new Object();
+    }
+
+    private static final String TAG = "FilterFactory";
+    private static boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+
+    public static FilterFactory sharedFactory() {
+        if (mSharedFactory == null) {
+            mSharedFactory = new FilterFactory();
+        }
+        return mSharedFactory;
+    }
+
+    /**
+     * Adds a new Java library to the list to be scanned for filters.
+     * libraryPath must be an absolute path of the jar file.  This needs to be
+     * static because only one classloader per process can open a shared native
+     * library, which a filter may well have.
+     */
+    public static void addFilterLibrary(String libraryPath) {
+        if (mLogVerbose) Log.v(TAG, "Adding filter library " + libraryPath);
+        synchronized(mClassLoaderGuard) {
+            if (mLibraries.contains(libraryPath)) {
+                if (mLogVerbose) Log.v(TAG, "Library already added");
+                return;
+            }
+            mLibraries.add(libraryPath);
+            // Chain another path loader to the current chain
+            mCurrentClassLoader = new PathClassLoader(libraryPath, mCurrentClassLoader);
+        }
+    }
+
+    public void addPackage(String packageName) {
+        if (mLogVerbose) Log.v(TAG, "Adding package " + packageName);
+        /* TODO: This should use a getPackage call in the caller's context, but no such method exists.
+        Package pkg = Package.getPackage(packageName);
+        if (pkg == null) {
+            throw new IllegalArgumentException("Unknown filter package '" + packageName + "'!");
+        }
+        */
+        mPackages.add(packageName);
+    }
+
+    public Filter createFilterByClassName(String className, String filterName) {
+        if (mLogVerbose) Log.v(TAG, "Looking up class " + className);
+        Class filterClass = null;
+
+        // Look for the class in the imported packages
+        for (String packageName : mPackages) {
+            try {
+                if (mLogVerbose) Log.v(TAG, "Trying "+packageName + "." + className);
+                synchronized(mClassLoaderGuard) {
+                    filterClass = mCurrentClassLoader.loadClass(packageName + "." + className);
+                }
+            } catch (ClassNotFoundException e) {
+                continue;
+            }
+            // Exit loop if class was found.
+            if (filterClass != null) {
+                break;
+            }
+        }
+        if (filterClass == null) {
+            throw new IllegalArgumentException("Unknown filter class '" + className + "'!");
+        }
+        return createFilterByClass(filterClass, filterName);
+    }
+
+    public Filter createFilterByClass(Class filterClass, String filterName) {
+        // Make sure this is a Filter subclass
+        try {
+            filterClass.asSubclass(Filter.class);
+        } catch (ClassCastException e) {
+            throw new IllegalArgumentException("Attempting to allocate class '" + filterClass
+                + "' which is not a subclass of Filter!");
+        }
+
+        // Look for the correct constructor
+        Constructor filterConstructor = null;
+        try {
+            filterConstructor = filterClass.getConstructor(String.class);
+        } catch (NoSuchMethodException e) {
+            throw new IllegalArgumentException("The filter class '" + filterClass
+                + "' does not have a constructor of the form <init>(String name)!");
+        }
+
+        // Construct the filter
+        Filter filter = null;
+        try {
+            filter = (Filter)filterConstructor.newInstance(filterName);
+        } catch (Throwable t) {
+            // Condition checked below
+        }
+
+        if (filter == null) {
+            throw new IllegalArgumentException("Could not construct the filter '"
+                + filterName + "'!");
+        }
+        return filter;
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterFunction.java b/media/mca/filterfw/java/android/filterfw/core/FilterFunction.java
new file mode 100644
index 0000000..ce81a18
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterFunction.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import java.util.Map.Entry;
+
+/**
+ * @hide
+ */
+public class FilterFunction {
+
+    private Filter mFilter;
+    private FilterContext mFilterContext;
+    private boolean mFilterIsSetup = false;
+    private FrameHolderPort[] mResultHolders;
+
+    private class FrameHolderPort extends StreamPort {
+        public FrameHolderPort() {
+            super(null, "holder");
+        }
+    }
+
+    public FilterFunction(FilterContext context, Filter filter) {
+        mFilterContext = context;
+        mFilter = filter;
+    }
+
+    public Frame execute(KeyValueMap inputMap) {
+        int filterOutCount = mFilter.getNumberOfOutputs();
+
+        // Sanity checks
+        if (filterOutCount > 1) {
+            throw new RuntimeException("Calling execute on filter " + mFilter + " with multiple "
+                + "outputs! Use executeMulti() instead!");
+        }
+
+        // Setup filter
+        if (!mFilterIsSetup) {
+            connectFilterOutputs();
+            mFilterIsSetup = true;
+        }
+
+        // Make sure GL environment is active
+        boolean didActivateGLEnv = false;
+        GLEnvironment glEnv = mFilterContext.getGLEnvironment();
+        if (glEnv != null && !glEnv.isActive()) {
+            glEnv.activate();
+            didActivateGLEnv = true;
+        }
+
+        // Setup the inputs
+        for (Entry<String, Object> entry : inputMap.entrySet()) {
+            if (entry.getValue() instanceof Frame) {
+                mFilter.pushInputFrame(entry.getKey(), (Frame)entry.getValue());
+            } else {
+                mFilter.pushInputValue(entry.getKey(), entry.getValue());
+            }
+        }
+
+        // Process the filter
+        if (mFilter.getStatus() != Filter.STATUS_PROCESSING) {
+            mFilter.openOutputs();
+        }
+
+        mFilter.performProcess(mFilterContext);
+
+        // Create result handle
+        Frame result = null;
+        if (filterOutCount == 1 && mResultHolders[0].hasFrame()) {
+            result = mResultHolders[0].pullFrame();
+        }
+
+        // Deactivate GL environment if activated
+        if (didActivateGLEnv) {
+            glEnv.deactivate();
+        }
+
+        return result;
+    }
+
+    public Frame executeWithArgList(Object... inputs) {
+        return execute(KeyValueMap.fromKeyValues(inputs));
+    }
+
+    public void close() {
+        mFilter.performClose(mFilterContext);
+    }
+
+    public FilterContext getContext() {
+        return mFilterContext;
+    }
+
+    public Filter getFilter() {
+        return mFilter;
+    }
+
+    public void setInputFrame(String input, Frame frame) {
+        mFilter.setInputFrame(input, frame);
+    }
+
+    public void setInputValue(String input, Object value) {
+        mFilter.setInputValue(input, value);
+    }
+
+    public void tearDown() {
+        mFilter.performTearDown(mFilterContext);
+        mFilter = null;
+    }
+
+    @Override
+    public String toString() {
+        return mFilter.getName();
+    }
+
+    private void connectFilterOutputs() {
+        int  i = 0;
+        mResultHolders = new FrameHolderPort[mFilter.getNumberOfOutputs()];
+        for (OutputPort outputPort : mFilter.getOutputPorts()) {
+            mResultHolders[i] = new FrameHolderPort();
+            outputPort.connectTo(mResultHolders[i]);
+            ++i;
+        }
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java
new file mode 100644
index 0000000..12f7892
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.Stack;
+
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.KeyValueMap;
+import android.filterpacks.base.FrameBranch;
+import android.filterpacks.base.NullFilter;
+
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class FilterGraph {
+
+    private HashSet<Filter> mFilters = new HashSet<Filter>();
+    private HashMap<String, Filter> mNameMap = new HashMap<String, Filter>();
+    private HashMap<OutputPort, LinkedList<InputPort>> mPreconnections = new
+            HashMap<OutputPort, LinkedList<InputPort>>();
+
+    public static final int AUTOBRANCH_OFF      = 0;
+    public static final int AUTOBRANCH_SYNCED   = 1;
+    public static final int AUTOBRANCH_UNSYNCED = 2;
+
+    public static final int TYPECHECK_OFF       = 0;
+    public static final int TYPECHECK_DYNAMIC   = 1;
+    public static final int TYPECHECK_STRICT    = 2;
+
+    private boolean mIsReady = false;
+    private int mAutoBranchMode = AUTOBRANCH_OFF;
+    private int mTypeCheckMode = TYPECHECK_STRICT;
+    private boolean mDiscardUnconnectedOutputs = false;
+
+    private boolean mLogVerbose;
+    private String TAG = "FilterGraph";
+
+    public FilterGraph() {
+        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    }
+
+    public boolean addFilter(Filter filter) {
+        if (!containsFilter(filter)) {
+            mFilters.add(filter);
+            mNameMap.put(filter.getName(), filter);
+            return true;
+        }
+        return false;
+    }
+
+    public boolean containsFilter(Filter filter) {
+        return mFilters.contains(filter);
+    }
+
+    public Filter getFilter(String name) {
+        return mNameMap.get(name);
+    }
+
+    public void connect(Filter source,
+                        String outputName,
+                        Filter target,
+                        String inputName) {
+        if (source == null || target == null) {
+            throw new IllegalArgumentException("Passing null Filter in connect()!");
+        } else if (!containsFilter(source) || !containsFilter(target)) {
+            throw new RuntimeException("Attempting to connect filter not in graph!");
+        }
+
+        OutputPort outPort = source.getOutputPort(outputName);
+        InputPort inPort = target.getInputPort(inputName);
+        if (outPort == null) {
+            throw new RuntimeException("Unknown output port '" + outputName + "' on Filter " +
+                                       source + "!");
+        } else if (inPort == null) {
+            throw new RuntimeException("Unknown input port '" + inputName + "' on Filter " +
+                                       target + "!");
+        }
+
+        preconnect(outPort, inPort);
+    }
+
+    public void connect(String sourceName,
+                        String outputName,
+                        String targetName,
+                        String inputName) {
+        Filter source = getFilter(sourceName);
+        Filter target = getFilter(targetName);
+        if (source == null) {
+            throw new RuntimeException(
+                "Attempting to connect unknown source filter '" + sourceName + "'!");
+        } else if (target == null) {
+            throw new RuntimeException(
+                "Attempting to connect unknown target filter '" + targetName + "'!");
+        }
+        connect(source, outputName, target, inputName);
+    }
+
+    public Set<Filter> getFilters() {
+        return mFilters;
+    }
+
+    public void beginProcessing() {
+        if (mLogVerbose) Log.v(TAG, "Opening all filter connections...");
+        for (Filter filter : mFilters) {
+            filter.openOutputs();
+        }
+        mIsReady = true;
+    }
+
+    public void flushFrames() {
+        for (Filter filter : mFilters) {
+            filter.clearOutputs();
+        }
+    }
+
+    public void closeFilters(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Closing all filters...");
+        for (Filter filter : mFilters) {
+            filter.performClose(context);
+        }
+        mIsReady = false;
+    }
+
+    public boolean isReady() {
+        return mIsReady;
+    }
+
+    public void setAutoBranchMode(int autoBranchMode) {
+        mAutoBranchMode = autoBranchMode;
+    }
+
+    public void setDiscardUnconnectedOutputs(boolean discard) {
+        mDiscardUnconnectedOutputs = discard;
+    }
+
+    public void setTypeCheckMode(int typeCheckMode) {
+        mTypeCheckMode = typeCheckMode;
+    }
+
+    public void tearDown(FilterContext context) {
+        if (!mFilters.isEmpty()) {
+            flushFrames();
+            for (Filter filter : mFilters) {
+                filter.performTearDown(context);
+            }
+            mFilters.clear();
+            mNameMap.clear();
+            mIsReady = false;
+        }
+    }
+
+    private boolean readyForProcessing(Filter filter, Set<Filter> processed) {
+        // Check if this has been already processed
+        if (processed.contains(filter)) {
+            return false;
+        }
+
+        // Check if all dependencies have been processed
+        for (InputPort port : filter.getInputPorts()) {
+            Filter dependency = port.getSourceFilter();
+            if (dependency != null && !processed.contains(dependency)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void runTypeCheck() {
+        Stack<Filter> filterStack = new Stack<Filter>();
+        Set<Filter> processedFilters = new HashSet<Filter>();
+        filterStack.addAll(getSourceFilters());
+
+        while (!filterStack.empty()) {
+            // Get current filter and mark as processed
+            Filter filter = filterStack.pop();
+            processedFilters.add(filter);
+
+            // Anchor output formats
+            updateOutputs(filter);
+
+            // Perform type check
+            if (mLogVerbose) Log.v(TAG, "Running type check on " + filter + "...");
+            runTypeCheckOn(filter);
+
+            // Push connected filters onto stack
+            for (OutputPort port : filter.getOutputPorts()) {
+                Filter target = port.getTargetFilter();
+                if (target != null && readyForProcessing(target, processedFilters)) {
+                    filterStack.push(target);
+                }
+            }
+        }
+
+        // Make sure all ports were setup
+        if (processedFilters.size() != getFilters().size()) {
+            throw new RuntimeException("Could not schedule all filters! Is your graph malformed?");
+        }
+    }
+
+    private void updateOutputs(Filter filter) {
+        for (OutputPort outputPort : filter.getOutputPorts()) {
+            InputPort inputPort = outputPort.getBasePort();
+            if (inputPort != null) {
+                FrameFormat inputFormat = inputPort.getSourceFormat();
+                FrameFormat outputFormat = filter.getOutputFormat(outputPort.getName(),
+                                                                  inputFormat);
+                if (outputFormat == null) {
+                    throw new RuntimeException("Filter did not return an output format for "
+                        + outputPort + "!");
+                }
+                outputPort.setPortFormat(outputFormat);
+            }
+        }
+    }
+
+    private void runTypeCheckOn(Filter filter) {
+        for (InputPort inputPort : filter.getInputPorts()) {
+            if (mLogVerbose) Log.v(TAG, "Type checking port " + inputPort);
+            FrameFormat sourceFormat = inputPort.getSourceFormat();
+            FrameFormat targetFormat = inputPort.getPortFormat();
+            if (sourceFormat != null && targetFormat != null) {
+                if (mLogVerbose) Log.v(TAG, "Checking " + sourceFormat + " against " + targetFormat + ".");
+
+                boolean compatible = true;
+                switch (mTypeCheckMode) {
+                    case TYPECHECK_OFF:
+                        inputPort.setChecksType(false);
+                        break;
+                    case TYPECHECK_DYNAMIC:
+                        compatible = sourceFormat.mayBeCompatibleWith(targetFormat);
+                        inputPort.setChecksType(true);
+                        break;
+                    case TYPECHECK_STRICT:
+                        compatible = sourceFormat.isCompatibleWith(targetFormat);
+                        inputPort.setChecksType(false);
+                        break;
+                }
+
+                if (!compatible) {
+                    throw new RuntimeException("Type mismatch: Filter " + filter + " expects a "
+                        + "format of type " + targetFormat + " but got a format of type "
+                        + sourceFormat + "!");
+                }
+            }
+        }
+    }
+
+    private void checkConnections() {
+        // TODO
+    }
+
+    private void discardUnconnectedOutputs() {
+        // Connect unconnected ports to Null filters
+        LinkedList<Filter> addedFilters = new LinkedList<Filter>();
+        for (Filter filter : mFilters) {
+            int id = 0;
+            for (OutputPort port : filter.getOutputPorts()) {
+                if (!port.isConnected()) {
+                    if (mLogVerbose) Log.v(TAG, "Autoconnecting unconnected " + port + " to Null filter.");
+                    NullFilter nullFilter = new NullFilter(filter.getName() + "ToNull" + id);
+                    nullFilter.init();
+                    addedFilters.add(nullFilter);
+                    port.connectTo(nullFilter.getInputPort("frame"));
+                    ++id;
+                }
+            }
+        }
+        // Add all added filters to this graph
+        for (Filter filter : addedFilters) {
+            addFilter(filter);
+        }
+    }
+
+    private void removeFilter(Filter filter) {
+        mFilters.remove(filter);
+        mNameMap.remove(filter.getName());
+    }
+
+    private void preconnect(OutputPort outPort, InputPort inPort) {
+        LinkedList<InputPort> targets;
+        targets = mPreconnections.get(outPort);
+        if (targets == null) {
+            targets = new LinkedList<InputPort>();
+            mPreconnections.put(outPort, targets);
+        }
+        targets.add(inPort);
+    }
+
+    private void connectPorts() {
+        int branchId = 1;
+        for (Entry<OutputPort, LinkedList<InputPort>> connection : mPreconnections.entrySet()) {
+            OutputPort outputPort = connection.getKey();
+            LinkedList<InputPort> inputPorts = connection.getValue();
+            if (inputPorts.size() == 1) {
+                outputPort.connectTo(inputPorts.get(0));
+            } else if (mAutoBranchMode == AUTOBRANCH_OFF) {
+                throw new RuntimeException("Attempting to connect " + outputPort + " to multiple "
+                                         + "filter ports! Enable auto-branching to allow this.");
+            } else {
+                if (mLogVerbose) Log.v(TAG, "Creating branch for " + outputPort + "!");
+                FrameBranch branch = null;
+                if (mAutoBranchMode == AUTOBRANCH_SYNCED) {
+                    branch = new FrameBranch("branch" + branchId++);
+                } else {
+                    throw new RuntimeException("TODO: Unsynced branches not implemented yet!");
+                }
+                KeyValueMap branchParams = new KeyValueMap();
+                branch.initWithAssignmentList("outputs", inputPorts.size());
+                addFilter(branch);
+                outputPort.connectTo(branch.getInputPort("in"));
+                Iterator<InputPort> inputPortIter = inputPorts.iterator();
+                for (OutputPort branchOutPort : ((Filter)branch).getOutputPorts()) {
+                    branchOutPort.connectTo(inputPortIter.next());
+                }
+            }
+        }
+        mPreconnections.clear();
+    }
+
+    private HashSet<Filter> getSourceFilters() {
+        HashSet<Filter> sourceFilters = new HashSet<Filter>();
+        for (Filter filter : getFilters()) {
+            if (filter.getNumberOfConnectedInputs() == 0) {
+                if (mLogVerbose) Log.v(TAG, "Found source filter: " + filter);
+                sourceFilters.add(filter);
+            }
+        }
+        return sourceFilters;
+    }
+
+    // Core internal methods /////////////////////////////////////////////////////////////////////////
+    void setupFilters() {
+        if (mDiscardUnconnectedOutputs) {
+            discardUnconnectedOutputs();
+        }
+        connectPorts();
+        checkConnections();
+        runTypeCheck();
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterPort.java b/media/mca/filterfw/java/android/filterfw/core/FilterPort.java
new file mode 100644
index 0000000..9734b89
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterPort.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FrameFormat;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public abstract class FilterPort {
+
+    protected Filter mFilter;
+    protected String mName;
+    protected FrameFormat mPortFormat;
+    protected boolean mIsBlocking = true;
+    protected boolean mIsOpen = false;
+    protected boolean mChecksType = false;
+    private boolean mLogVerbose;
+    private static final String TAG = "FilterPort";
+
+    public FilterPort(Filter filter, String name) {
+        mName = name;
+        mFilter = filter;
+        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    }
+
+    public boolean isAttached() {
+        return mFilter != null;
+    }
+
+    public FrameFormat getPortFormat() {
+        return mPortFormat;
+    }
+
+    public void setPortFormat(FrameFormat format) {
+        mPortFormat = format;
+    }
+
+    public Filter getFilter() {
+        return mFilter;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void setBlocking(boolean blocking) {
+        mIsBlocking = blocking;
+    }
+
+    public void setChecksType(boolean checksType) {
+        mChecksType = checksType;
+    }
+
+    public void open() {
+        if (!mIsOpen) {
+            if (mLogVerbose) Log.v(TAG, "Opening " + this);
+        }
+        mIsOpen = true;
+    }
+
+    public void close() {
+        if (mIsOpen) {
+            if (mLogVerbose) Log.v(TAG, "Closing " + this);
+        }
+        mIsOpen = false;
+    }
+
+    public boolean isOpen() {
+        return mIsOpen;
+    }
+
+    public boolean isBlocking() {
+        return mIsBlocking;
+    }
+
+    public abstract boolean filterMustClose();
+
+    public abstract boolean isReady();
+
+    public abstract void pushFrame(Frame frame);
+
+    public abstract void setFrame(Frame frame);
+
+    public abstract Frame pullFrame();
+
+    public abstract boolean hasFrame();
+
+    public abstract void clear();
+
+    public String toString() {
+        return "port '" + mName + "' of " + mFilter;
+    }
+
+    protected void assertPortIsOpen() {
+        if (!isOpen()) {
+            throw new RuntimeException("Illegal operation on closed " + this + "!");
+        }
+    }
+
+    protected void checkFrameType(Frame frame, boolean forceCheck) {
+        if ((mChecksType || forceCheck)
+            && mPortFormat != null
+            && !frame.getFormat().isCompatibleWith(mPortFormat)) {
+            throw new RuntimeException("Frame passed to " + this + " is of incorrect type! "
+                + "Expected " + mPortFormat + " but got " + frame.getFormat());
+        }
+    }
+
+    protected void checkFrameManager(Frame frame, FilterContext context) {
+        if (frame.getFrameManager() != null
+            && frame.getFrameManager() != context.getFrameManager()) {
+            throw new RuntimeException("Frame " + frame + " is managed by foreign FrameManager! ");
+        }
+    }
+}
+
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterSurfaceView.java b/media/mca/filterfw/java/android/filterfw/core/FilterSurfaceView.java
new file mode 100644
index 0000000..49306b2
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterSurfaceView.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+/**
+ * @hide
+ */
+public class FilterSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
+
+    private static int STATE_ALLOCATED      = 0;
+    private static int STATE_CREATED        = 1;
+    private static int STATE_INITIALIZED    = 2;
+
+    private int mState = STATE_ALLOCATED;
+    private SurfaceHolder.Callback mListener;
+    private GLEnvironment mGLEnv;
+    private int mFormat;
+    private int mWidth;
+    private int mHeight;
+    private int mSurfaceId = -1;
+
+    public FilterSurfaceView(Context context) {
+        super(context);
+        getHolder().addCallback(this);
+    }
+
+    public FilterSurfaceView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        getHolder().addCallback(this);
+    }
+
+    public synchronized void bindToListener(SurfaceHolder.Callback listener, GLEnvironment glEnv) {
+        // Make sure we are not bound already
+        if (listener == null) {
+            throw new NullPointerException("Attempting to bind null filter to SurfaceView!");
+        } else if (mListener != null && mListener != listener) {
+            throw new RuntimeException(
+                "Attempting to bind filter " + listener + " to SurfaceView with another open "
+                + "filter " + mListener + " attached already!");
+        }
+
+        // Set listener
+        mListener = listener;
+
+        // Set GLEnv
+        if (mGLEnv != null && mGLEnv != glEnv) {
+            mGLEnv.unregisterSurfaceId(mSurfaceId);
+        }
+        mGLEnv = glEnv;
+
+        // Check if surface has been created already
+        if (mState >= STATE_CREATED) {
+            // Register with env (double registration will be ignored by GLEnv, so we can simply
+            // try to do it here).
+            registerSurface();
+
+            // Forward surface created to listener
+            mListener.surfaceCreated(getHolder());
+
+            // Forward surface changed to listener
+            if (mState == STATE_INITIALIZED) {
+                mListener.surfaceChanged(getHolder(), mFormat, mWidth, mHeight);
+            }
+        }
+    }
+
+    public synchronized void unbind() {
+        mListener = null;
+    }
+
+    public synchronized int getSurfaceId() {
+        return mSurfaceId;
+    }
+
+    public synchronized GLEnvironment getGLEnv() {
+        return mGLEnv;
+    }
+
+    @Override
+    public synchronized void surfaceCreated(SurfaceHolder holder) {
+        mState = STATE_CREATED;
+
+        // Register with GLEnvironment if we have it already
+        if (mGLEnv != null) {
+            registerSurface();
+        }
+
+        // Forward callback to listener
+        if (mListener != null) {
+            mListener.surfaceCreated(holder);
+        }
+    }
+
+    @Override
+    public synchronized void surfaceChanged(SurfaceHolder holder,
+                                            int format,
+                                            int width,
+                                            int height) {
+        // Remember these values
+        mFormat = format;
+        mWidth = width;
+        mHeight = height;
+        mState = STATE_INITIALIZED;
+
+        // Forward to renderer
+        if (mListener != null) {
+            mListener.surfaceChanged(holder, format, width, height);
+        }
+    }
+
+    @Override
+    public synchronized void surfaceDestroyed(SurfaceHolder holder) {
+        mState = STATE_ALLOCATED;
+
+        // Forward to renderer
+        if (mListener != null) {
+            mListener.surfaceDestroyed(holder);
+        }
+
+        // Get rid of internal objects associated with this surface
+        unregisterSurface();
+    }
+
+    private void registerSurface() {
+        mSurfaceId = mGLEnv.registerSurface(getHolder().getSurface());
+        if (mSurfaceId < 0) {
+            throw new RuntimeException("Could not register Surface: " + getHolder().getSurface() +
+                                       " in FilterSurfaceView!");
+        }
+    }
+    private void unregisterSurface() {
+        if (mGLEnv != null && mSurfaceId > 0) {
+            mGLEnv.unregisterSurfaceId(mSurfaceId);
+        }
+    }
+
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/FinalPort.java b/media/mca/filterfw/java/android/filterfw/core/FinalPort.java
new file mode 100644
index 0000000..ad65169
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/FinalPort.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import java.lang.reflect.Field;
+
+/**
+ * @hide
+ */
+public class FinalPort extends FieldPort {
+
+    public FinalPort(Filter filter, String name, Field field, boolean hasDefault) {
+        super(filter, name, field, hasDefault);
+    }
+
+    @Override
+    protected synchronized void setFieldFrame(Frame frame, boolean isAssignment) {
+        assertPortIsOpen();
+        checkFrameType(frame, isAssignment);
+        if (mFilter.getStatus() != Filter.STATUS_PREINIT) {
+            throw new RuntimeException("Attempting to modify " + this + "!");
+        } else {
+            super.setFieldFrame(frame, isAssignment);
+            super.transfer(null);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "final " + super.toString();
+    }
+
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/Frame.java b/media/mca/filterfw/java/android/filterfw/core/Frame.java
new file mode 100644
index 0000000..ef8c542
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/Frame.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.FrameManager;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+
+/**
+ * @hide
+ */
+public abstract class Frame {
+
+    public final static int NO_BINDING = 0;
+
+    public final static long TIMESTAMP_NOT_SET = -2;
+    public final static long TIMESTAMP_UNKNOWN = -1;
+
+    private FrameFormat mFormat;
+    private FrameManager mFrameManager;
+    private boolean mReadOnly = false;
+    private boolean mReusable = false;
+    private int mRefCount = 1;
+    private int mBindingType = NO_BINDING;
+    private long mBindingId = 0;
+    private long mTimestamp = TIMESTAMP_NOT_SET;
+
+    Frame(FrameFormat format, FrameManager frameManager) {
+        mFormat = format.mutableCopy();
+        mFrameManager = frameManager;
+    }
+
+    Frame(FrameFormat format, FrameManager frameManager, int bindingType, long bindingId) {
+        mFormat = format.mutableCopy();
+        mFrameManager = frameManager;
+        mBindingType = bindingType;
+        mBindingId = bindingId;
+    }
+
+    public FrameFormat getFormat() {
+        return mFormat;
+    }
+
+    public int getCapacity() {
+        return getFormat().getSize();
+    }
+
+    public boolean isReadOnly() {
+        return mReadOnly;
+    }
+
+    public int getBindingType() {
+        return mBindingType;
+    }
+
+    public long getBindingId() {
+        return mBindingId;
+    }
+
+    public void setObjectValue(Object object) {
+        assertFrameMutable();
+
+        // Attempt to set the value using a specific setter (which may be more optimized), and
+        // fall back to the setGenericObjectValue(...) in case of no match.
+        if (object instanceof int[]) {
+            setInts((int[])object);
+        } else if (object instanceof float[]) {
+            setFloats((float[])object);
+        } else if (object instanceof ByteBuffer) {
+            setData((ByteBuffer)object);
+        } else if (object instanceof Bitmap) {
+            setBitmap((Bitmap)object);
+        } else {
+            setGenericObjectValue(object);
+        }
+    }
+
+    public abstract Object getObjectValue();
+
+    public abstract void setInts(int[] ints);
+
+    public abstract int[] getInts();
+
+    public abstract void setFloats(float[] floats);
+
+    public abstract float[] getFloats();
+
+    public abstract void setData(ByteBuffer buffer, int offset, int length);
+
+    public void setData(ByteBuffer buffer) {
+        setData(buffer, 0, buffer.limit());
+    }
+
+    public void setData(byte[] bytes, int offset, int length) {
+        setData(ByteBuffer.wrap(bytes, offset, length));
+    }
+
+    public abstract ByteBuffer getData();
+
+    public abstract void setBitmap(Bitmap bitmap);
+
+    public abstract Bitmap getBitmap();
+
+    public void setTimestamp(long timestamp) {
+        mTimestamp = timestamp;
+    }
+
+    public long getTimestamp() {
+        return mTimestamp;
+    }
+
+    public void setDataFromFrame(Frame frame) {
+        setData(frame.getData());
+    }
+
+    protected boolean requestResize(int[] newDimensions) {
+        return false;
+    }
+
+    public int getRefCount() {
+        return mRefCount;
+    }
+
+    public Frame release() {
+        if (mFrameManager != null) {
+            return mFrameManager.releaseFrame(this);
+        } else {
+            return this;
+        }
+    }
+
+    public Frame retain() {
+        if (mFrameManager != null) {
+            return mFrameManager.retainFrame(this);
+        } else {
+            return this;
+        }
+    }
+
+    public FrameManager getFrameManager() {
+        return mFrameManager;
+    }
+
+    protected void assertFrameMutable() {
+        if (isReadOnly()) {
+            throw new RuntimeException("Attempting to modify read-only frame!");
+        }
+    }
+
+    protected void setReusable(boolean reusable) {
+        mReusable = reusable;
+    }
+
+    protected void setFormat(FrameFormat format) {
+        mFormat = format.mutableCopy();
+    }
+
+    protected void setGenericObjectValue(Object value) {
+        throw new RuntimeException(
+            "Cannot set object value of unsupported type: " + value.getClass());
+    }
+
+    protected static Bitmap convertBitmapToRGBA(Bitmap bitmap) {
+        if (bitmap.getConfig() == Bitmap.Config.ARGB_8888) {
+            return bitmap;
+        } else {
+            Bitmap result = bitmap.copy(Bitmap.Config.ARGB_8888, false);
+            if (result == null) {
+                throw new RuntimeException("Error converting bitmap to RGBA!");
+            } else if (result.getRowBytes() != result.getWidth() * 4) {
+                throw new RuntimeException("Unsupported row byte count in bitmap!");
+            }
+            return result;
+        }
+    }
+
+    protected void reset(FrameFormat newFormat) {
+        mFormat = newFormat.mutableCopy();
+        mReadOnly = false;
+        mRefCount = 1;
+    }
+
+    /**
+     * Called just before a frame is stored, such as when storing to a cache or context.
+     */
+    protected void onFrameStore() {
+    }
+
+    /**
+     * Called when a frame is fetched from an internal store such as a cache.
+     */
+    protected void onFrameFetch() {
+    }
+
+    // Core internal methods ///////////////////////////////////////////////////////////////////////
+    protected abstract boolean hasNativeAllocation();
+
+    protected abstract void releaseNativeAllocation();
+
+    final int incRefCount() {
+        ++mRefCount;
+        return mRefCount;
+    }
+
+    final int decRefCount() {
+        --mRefCount;
+        return mRefCount;
+    }
+
+    final boolean isReusable() {
+        return mReusable;
+    }
+
+    final void markReadOnly() {
+        mReadOnly = true;
+    }
+
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java
new file mode 100644
index 0000000..8f619be
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+
+import java.util.Arrays;
+import java.util.Map.Entry;
+
+/**
+ * @hide
+ */
+public class FrameFormat {
+
+    public static final int TYPE_UNSPECIFIED = 0;
+    public static final int TYPE_BIT         = 1;
+    public static final int TYPE_BYTE        = 2;
+    public static final int TYPE_INT16       = 3;
+    public static final int TYPE_INT32       = 4;
+    public static final int TYPE_FLOAT       = 5;
+    public static final int TYPE_DOUBLE      = 6;
+    public static final int TYPE_POINTER     = 7;
+    public static final int TYPE_OBJECT      = 8;
+
+    public static final int TARGET_UNSPECIFIED  = 0;
+    public static final int TARGET_SIMPLE       = 1;
+    public static final int TARGET_NATIVE       = 2;
+    public static final int TARGET_GPU          = 3;
+    public static final int TARGET_VERTEXBUFFER = 4;
+    public static final int TARGET_RS           = 5;
+
+    public static final int SIZE_UNSPECIFIED = 0;
+
+    // TODO: When convenience formats are used, consider changing this to 0 and have the convenience
+    // intializers use a proper BPS.
+    public static final int BYTES_PER_SAMPLE_UNSPECIFIED = 1;
+
+    protected static final int SIZE_UNKNOWN = -1;
+
+    protected int mBaseType = TYPE_UNSPECIFIED;
+    protected int mBytesPerSample = 1;
+    protected int mSize = SIZE_UNKNOWN;
+    protected int mTarget = TARGET_UNSPECIFIED;
+    protected int[] mDimensions;
+    protected KeyValueMap mMetaData;
+    protected Class mObjectClass;
+
+    protected FrameFormat() {
+    }
+
+    public FrameFormat(int baseType, int target) {
+        mBaseType = baseType;
+        mTarget = target;
+        initDefaults();
+    }
+
+    public static FrameFormat unspecified() {
+        return new FrameFormat(TYPE_UNSPECIFIED, TARGET_UNSPECIFIED);
+    }
+
+    public int getBaseType() {
+        return mBaseType;
+    }
+
+    public boolean isBinaryDataType() {
+        return mBaseType >= TYPE_BIT && mBaseType <= TYPE_DOUBLE;
+    }
+
+    public int getBytesPerSample() {
+        return mBytesPerSample;
+    }
+
+    public int getValuesPerSample() {
+        return mBytesPerSample / bytesPerSampleOf(mBaseType);
+    }
+
+    public int getTarget() {
+        return mTarget;
+    }
+
+    public int[] getDimensions() {
+        return mDimensions;
+    }
+
+    public int getDimension(int i) {
+        return mDimensions[i];
+    }
+
+    public int getDimensionCount() {
+        return mDimensions == null ? 0 : mDimensions.length;
+    }
+
+    public boolean hasMetaKey(String key) {
+        return mMetaData != null ? mMetaData.containsKey(key) : false;
+    }
+
+    public boolean hasMetaKey(String key, Class expectedClass) {
+        if (mMetaData != null && mMetaData.containsKey(key)) {
+            if (!expectedClass.isAssignableFrom(mMetaData.get(key).getClass())) {
+                throw new RuntimeException(
+                    "FrameFormat meta-key '" + key + "' is of type " +
+                    mMetaData.get(key).getClass() + " but expected to be of type " +
+                    expectedClass + "!");
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public Object getMetaValue(String key) {
+        return mMetaData != null ? mMetaData.get(key) : null;
+    }
+
+    public int getNumberOfDimensions() {
+        return mDimensions != null ? mDimensions.length : 0;
+    }
+
+    public int getLength() {
+        return (mDimensions != null && mDimensions.length >= 1) ? mDimensions[0] : -1;
+    }
+
+    public int getWidth() {
+        return getLength();
+    }
+
+    public int getHeight() {
+        return (mDimensions != null && mDimensions.length >= 2) ? mDimensions[1] : -1;
+    }
+
+    public int getDepth() {
+        return (mDimensions != null && mDimensions.length >= 3) ? mDimensions[2] : -1;
+    }
+
+    public int getSize() {
+        if (mSize == SIZE_UNKNOWN) mSize = calcSize(mDimensions);
+        return mSize;
+    }
+
+    public Class getObjectClass() {
+        return mObjectClass;
+    }
+
+    public MutableFrameFormat mutableCopy() {
+        MutableFrameFormat result = new MutableFrameFormat();
+        result.setBaseType(getBaseType());
+        result.setTarget(getTarget());
+        result.setBytesPerSample(getBytesPerSample());
+        result.setDimensions(getDimensions());
+        result.setObjectClass(getObjectClass());
+        result.mMetaData = mMetaData == null ? null : (KeyValueMap)mMetaData.clone();
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+
+        if (!(object instanceof FrameFormat)) {
+            return false;
+        }
+
+        FrameFormat format = (FrameFormat)object;
+        return format.mBaseType == mBaseType &&
+                format.mTarget == mTarget &&
+                format.mBytesPerSample == mBytesPerSample &&
+                Arrays.equals(format.mDimensions, mDimensions) &&
+                format.mMetaData.equals(mMetaData);
+    }
+
+    @Override
+    public int hashCode() {
+        return 4211 ^ mBaseType ^ mBytesPerSample ^ getSize();
+    }
+
+    public boolean isCompatibleWith(FrameFormat specification) {
+        // Check base type
+        if (specification.getBaseType() != TYPE_UNSPECIFIED
+            && getBaseType() != specification.getBaseType()) {
+            return false;
+        }
+
+        // Check target
+        if (specification.getTarget() != TARGET_UNSPECIFIED
+            && getTarget() != specification.getTarget()) {
+            return false;
+        }
+
+        // Check bytes per sample
+        if (specification.getBytesPerSample() != BYTES_PER_SAMPLE_UNSPECIFIED
+            && getBytesPerSample() != specification.getBytesPerSample()) {
+            return false;
+        }
+
+        // Check number of dimensions
+        if (specification.getDimensionCount() > 0
+            && getDimensionCount() != specification.getDimensionCount()) {
+            return false;
+        }
+
+        // Check dimensions
+        for (int i = 0; i < specification.getDimensionCount(); ++i) {
+            int specDim = specification.getDimension(i);
+            if (specDim != SIZE_UNSPECIFIED && getDimension(i) != specDim) {
+                return false;
+            }
+        }
+
+        // Check class
+        if (specification.getObjectClass() != null) {
+            if (getObjectClass() == null
+                || !specification.getObjectClass().isAssignableFrom(getObjectClass())) {
+                return false;
+            }
+        }
+
+        // Check meta-data
+        if (specification.mMetaData != null) {
+            for (String specKey : specification.mMetaData.keySet()) {
+                if (mMetaData == null
+                || !mMetaData.containsKey(specKey)
+                || !mMetaData.get(specKey).equals(specification.mMetaData.get(specKey))) {
+                    return false;
+                }
+            }
+        }
+
+        // Passed all the tests
+        return true;
+    }
+
+    public boolean mayBeCompatibleWith(FrameFormat specification) {
+        // Check base type
+        if (specification.getBaseType() != TYPE_UNSPECIFIED
+            && getBaseType() != TYPE_UNSPECIFIED
+            && getBaseType() != specification.getBaseType()) {
+            return false;
+        }
+
+        // Check target
+        if (specification.getTarget() != TARGET_UNSPECIFIED
+            && getTarget() != TARGET_UNSPECIFIED
+            && getTarget() != specification.getTarget()) {
+            return false;
+        }
+
+        // Check bytes per sample
+        if (specification.getBytesPerSample() != BYTES_PER_SAMPLE_UNSPECIFIED
+            && getBytesPerSample() != BYTES_PER_SAMPLE_UNSPECIFIED
+            && getBytesPerSample() != specification.getBytesPerSample()) {
+            return false;
+        }
+
+        // Check number of dimensions
+        if (specification.getDimensionCount() > 0
+            && getDimensionCount() > 0
+            && getDimensionCount() != specification.getDimensionCount()) {
+            return false;
+        }
+
+        // Check dimensions
+        for (int i = 0; i < specification.getDimensionCount(); ++i) {
+            int specDim = specification.getDimension(i);
+            if (specDim != SIZE_UNSPECIFIED
+                && getDimension(i) != SIZE_UNSPECIFIED
+                && getDimension(i) != specDim) {
+                return false;
+            }
+        }
+
+        // Check class
+        if (specification.getObjectClass() != null && getObjectClass() != null) {
+            if (!specification.getObjectClass().isAssignableFrom(getObjectClass())) {
+                return false;
+            }
+        }
+
+        // Check meta-data
+        if (specification.mMetaData != null && mMetaData != null) {
+            for (String specKey : specification.mMetaData.keySet()) {
+                if (mMetaData.containsKey(specKey)
+                    && !mMetaData.get(specKey).equals(specification.mMetaData.get(specKey))) {
+                    return false;
+                }
+            }
+        }
+
+        // Passed all the tests
+        return true;
+    }
+
+    public static int bytesPerSampleOf(int baseType) {
+        // Defaults based on base-type
+        switch (baseType) {
+            case TYPE_BIT:
+            case TYPE_BYTE:
+                return 1;
+            case TYPE_INT16:
+                return 2;
+            case TYPE_INT32:
+            case TYPE_FLOAT:
+            case TYPE_POINTER:
+                return 4;
+            case TYPE_DOUBLE:
+                return 8;
+            default:
+                return 1;
+        }
+    }
+
+    public static String dimensionsToString(int[] dimensions) {
+        StringBuffer buffer = new StringBuffer();
+        if (dimensions != null) {
+            int n = dimensions.length;
+            for (int i = 0; i < n; ++i) {
+                if (dimensions[i] == SIZE_UNSPECIFIED) {
+                    buffer.append("[]");
+                } else {
+                    buffer.append("[" + String.valueOf(dimensions[i]) + "]");
+                }
+            }
+        }
+        return buffer.toString();
+    }
+
+    public static String baseTypeToString(int baseType) {
+        switch (baseType) {
+            case TYPE_UNSPECIFIED: return "unspecified";
+            case TYPE_BIT:         return "bit";
+            case TYPE_BYTE:        return "byte";
+            case TYPE_INT16:       return "int";
+            case TYPE_INT32:       return "int";
+            case TYPE_FLOAT:       return "float";
+            case TYPE_DOUBLE:      return "double";
+            case TYPE_POINTER:     return "pointer";
+            case TYPE_OBJECT:      return "object";
+            default:               return "unknown";
+        }
+    }
+
+    public static String targetToString(int target) {
+        switch (target) {
+            case TARGET_UNSPECIFIED:  return "unspecified";
+            case TARGET_SIMPLE:       return "simple";
+            case TARGET_NATIVE:       return "native";
+            case TARGET_GPU:          return "gpu";
+            case TARGET_VERTEXBUFFER: return "vbo";
+            case TARGET_RS:           return "renderscript";
+            default:                  return "unknown";
+        }
+    }
+
+    public static String metaDataToString(KeyValueMap metaData) {
+        if (metaData == null) {
+            return "";
+        } else {
+            StringBuffer buffer = new StringBuffer();
+            buffer.append("{ ");
+            for (Entry<String, Object> entry : metaData.entrySet()) {
+                buffer.append(entry.getKey() + ": " + entry.getValue() + " ");
+            }
+            buffer.append("}");
+            return buffer.toString();
+        }
+    }
+
+    public static int readTargetString(String targetString) {
+        if (targetString.equalsIgnoreCase("CPU") || targetString.equalsIgnoreCase("NATIVE")) {
+            return FrameFormat.TARGET_NATIVE;
+        } else if (targetString.equalsIgnoreCase("GPU")) {
+            return FrameFormat.TARGET_GPU;
+        } else if (targetString.equalsIgnoreCase("SIMPLE")) {
+            return FrameFormat.TARGET_SIMPLE;
+        } else if (targetString.equalsIgnoreCase("VERTEXBUFFER")) {
+            return FrameFormat.TARGET_VERTEXBUFFER;
+        } else if (targetString.equalsIgnoreCase("UNSPECIFIED")) {
+            return FrameFormat.TARGET_UNSPECIFIED;
+        } else {
+            throw new RuntimeException("Unknown target type '" + targetString + "'!");
+        }
+    }
+
+    // TODO: FromString
+
+    public String toString() {
+        int valuesPerSample = getValuesPerSample();
+        String sampleCountString = valuesPerSample == 1 ? "" : String.valueOf(valuesPerSample);
+        String targetString = mTarget == TARGET_UNSPECIFIED ? "" : (targetToString(mTarget) + " ");
+        String classString = mObjectClass == null
+            ? ""
+            : (" class(" + mObjectClass.getSimpleName() + ") ");
+
+        return targetString
+            + baseTypeToString(mBaseType)
+            + sampleCountString
+            + dimensionsToString(mDimensions)
+            + classString
+            + metaDataToString(mMetaData);
+    }
+
+    private void initDefaults() {
+        mBytesPerSample = bytesPerSampleOf(mBaseType);
+    }
+
+    // Core internal methods ///////////////////////////////////////////////////////////////////////
+    int calcSize(int[] dimensions) {
+        if (dimensions != null && dimensions.length > 0) {
+            int size = getBytesPerSample();
+            for (int dim : dimensions) {
+                size *= dim;
+            }
+            return size;
+        }
+        return 0;
+    }
+
+    boolean isReplaceableBy(FrameFormat format) {
+        return mTarget == format.mTarget
+            && getSize() == format.getSize()
+            && Arrays.equals(format.mDimensions, mDimensions);
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameManager.java b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java
new file mode 100644
index 0000000..8d6c483
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.MutableFrameFormat;
+
+/**
+ * @hide
+ */
+public abstract class FrameManager {
+
+    private FilterContext mContext;
+
+    public abstract Frame newFrame(FrameFormat format);
+
+    public abstract Frame newBoundFrame(FrameFormat format, int bindingType, long bindingId);
+
+    public Frame duplicateFrame(Frame frame) {
+        Frame result = newFrame(frame.getFormat());
+        result.setDataFromFrame(frame);
+        return result;
+    }
+
+    public Frame duplicateFrameToTarget(Frame frame, int newTarget) {
+        MutableFrameFormat newFormat = frame.getFormat().mutableCopy();
+        newFormat.setTarget(newTarget);
+        Frame result = newFrame(newFormat);
+        result.setDataFromFrame(frame);
+        return result;
+    }
+
+    public abstract Frame retainFrame(Frame frame);
+
+    public abstract Frame releaseFrame(Frame frame);
+
+    public FilterContext getContext() {
+        return mContext;
+    }
+
+    public GLEnvironment getGLEnvironment() {
+        return mContext != null ? mContext.getGLEnvironment() : null;
+    }
+
+    public void tearDown() {
+    }
+
+    void setContext(FilterContext context) {
+        mContext = context;
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java
new file mode 100644
index 0000000..fcf5f5d
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.NativeAllocatorTag;
+import android.graphics.SurfaceTexture;
+import android.os.Looper;
+import android.util.Log;
+import android.view.Surface;
+import android.media.MediaRecorder;
+
+/**
+ * @hide
+ */
+public class GLEnvironment {
+
+    private int glEnvId;
+
+    public GLEnvironment() {
+        nativeAllocate();
+    }
+
+    private GLEnvironment(NativeAllocatorTag tag) {
+    }
+
+    public synchronized void tearDown() {
+        if (glEnvId != -1) {
+            nativeDeallocate();
+            glEnvId = -1;
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        tearDown();
+    }
+
+    public void initWithNewContext() {
+        if (!nativeInitWithNewContext()) {
+            throw new RuntimeException("Could not initialize GLEnvironment with new context!");
+        }
+    }
+
+    public void initWithCurrentContext() {
+        if (!nativeInitWithCurrentContext()) {
+            throw new RuntimeException("Could not initialize GLEnvironment with current context!");
+        }
+    }
+
+    public boolean isActive() {
+        return nativeIsActive();
+    }
+
+    public boolean isContextActive() {
+        return nativeIsContextActive();
+    }
+
+    public static boolean isAnyContextActive() {
+        return nativeIsAnyContextActive();
+    }
+
+    public void activate() {
+        if (Looper.myLooper() != null && Looper.myLooper().equals(Looper.getMainLooper())) {
+            Log.e("FilterFramework", "Activating GL context in UI thread!");
+        }
+        if (!nativeActivate()) {
+            throw new RuntimeException("Could not activate GLEnvironment!");
+        }
+    }
+
+    public void deactivate() {
+        if (!nativeDeactivate()) {
+            throw new RuntimeException("Could not deactivate GLEnvironment!");
+        }
+    }
+
+    public void swapBuffers() {
+        if (!nativeSwapBuffers()) {
+            throw new RuntimeException("Error swapping EGL buffers!");
+        }
+    }
+
+    public int registerSurface(Surface surface) {
+        int result = nativeAddSurface(surface);
+        if (result < 0) {
+            throw new RuntimeException("Error registering surface " + surface + "!");
+        }
+        return result;
+    }
+
+    public int registerSurfaceTexture(SurfaceTexture surfaceTexture, int width, int height) {
+        Surface surface = new Surface(surfaceTexture);
+        int result = nativeAddSurfaceWidthHeight(surface, width, height);
+        surface.release();
+        if (result < 0) {
+            throw new RuntimeException("Error registering surfaceTexture " + surfaceTexture + "!");
+        }
+        return result;
+    }
+
+    public int registerSurfaceFromMediaRecorder(MediaRecorder mediaRecorder) {
+        int result = nativeAddSurfaceFromMediaRecorder(mediaRecorder);
+        if (result < 0) {
+            throw new RuntimeException("Error registering surface from "
+                                    + "MediaRecorder" + mediaRecorder + "!");
+        }
+        return result;
+    }
+
+    public void activateSurfaceWithId(int surfaceId) {
+        if (!nativeActivateSurfaceId(surfaceId)) {
+            throw new RuntimeException("Could not activate surface " + surfaceId + "!");
+        }
+    }
+
+    public void unregisterSurfaceId(int surfaceId) {
+        if (!nativeRemoveSurfaceId(surfaceId)) {
+            throw new RuntimeException("Could not unregister surface " + surfaceId + "!");
+        }
+    }
+
+    public void setSurfaceTimestamp(long timestamp) {
+        if (!nativeSetSurfaceTimestamp(timestamp)) {
+            throw new RuntimeException("Could not set timestamp for current surface!");
+        }
+    }
+
+    static {
+        System.loadLibrary("filterfw");
+    }
+
+    private native boolean nativeInitWithNewContext();
+
+    private native boolean nativeInitWithCurrentContext();
+
+    private native boolean nativeIsActive();
+
+    private native boolean nativeIsContextActive();
+
+    private static native boolean nativeIsAnyContextActive();
+
+    private native boolean nativeActivate();
+
+    private native boolean nativeDeactivate();
+
+    private native boolean nativeSwapBuffers();
+
+    private native boolean nativeAllocate();
+
+    private native boolean nativeDeallocate();
+
+    private native int nativeAddSurface(Surface surface);
+
+    private native int nativeAddSurfaceWidthHeight(Surface surface, int width, int height);
+
+    private native int nativeAddSurfaceFromMediaRecorder(MediaRecorder mediaRecorder);
+
+    private native boolean  nativeDisconnectSurfaceMediaSource(MediaRecorder mediaRecorder);
+
+    private native boolean nativeActivateSurfaceId(int surfaceId);
+
+    private native boolean nativeRemoveSurfaceId(int surfaceId);
+
+    private native boolean nativeSetSurfaceTimestamp(long timestamp);
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/GLFrame.java b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java
new file mode 100644
index 0000000..1558cc6
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.FrameManager;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.StopWatchMap;
+import android.graphics.Bitmap;
+import android.opengl.GLES20;
+import android.graphics.Rect;
+
+import java.nio.ByteBuffer;
+
+class GLFrameTimer {
+
+    private static StopWatchMap mTimer = null;
+
+    public static StopWatchMap get() {
+        if (mTimer == null) {
+            mTimer = new StopWatchMap();
+        }
+        return mTimer;
+    }
+
+}
+
+/**
+ * @hide
+ */
+public class GLFrame extends Frame {
+
+    // GL-related binding types
+    public final static int EXISTING_TEXTURE_BINDING = 100;
+    public final static int EXISTING_FBO_BINDING     = 101;
+    public final static int NEW_TEXTURE_BINDING      = 102; // TODO: REMOVE THIS
+    public final static int NEW_FBO_BINDING          = 103; // TODO: REMOVE THIS
+    public final static int EXTERNAL_TEXTURE         = 104;
+
+    private int glFrameId = -1;
+
+    /**
+     * Flag whether we own the texture or not. If we do not, we must be careful when caching or
+     * storing the frame, as the user may delete, and regenerate it.
+     */
+    private boolean mOwnsTexture = true;
+
+    /**
+     * Keep a reference to the GL environment, so that it does not get deallocated while there
+     * are still frames living in it.
+     */
+    private GLEnvironment mGLEnvironment;
+
+    GLFrame(FrameFormat format, FrameManager frameManager) {
+        super(format, frameManager);
+    }
+
+    GLFrame(FrameFormat format, FrameManager frameManager, int bindingType, long bindingId) {
+        super(format, frameManager, bindingType, bindingId);
+    }
+
+    void init(GLEnvironment glEnv) {
+        FrameFormat format = getFormat();
+        mGLEnvironment = glEnv;
+
+        // Check that we have a valid format
+        if (format.getBytesPerSample() != 4) {
+            throw new IllegalArgumentException("GL frames must have 4 bytes per sample!");
+        } else if (format.getDimensionCount() != 2) {
+            throw new IllegalArgumentException("GL frames must be 2-dimensional!");
+        } else if (getFormat().getSize() < 0) {
+            throw new IllegalArgumentException("Initializing GL frame with zero size!");
+        }
+
+        // Create correct frame
+        int bindingType = getBindingType();
+        boolean reusable = true;
+        if (bindingType == Frame.NO_BINDING) {
+            initNew(false);
+        } else if (bindingType == EXTERNAL_TEXTURE) {
+            initNew(true);
+            reusable = false;
+        } else if (bindingType == EXISTING_TEXTURE_BINDING) {
+            initWithTexture((int)getBindingId());
+        } else if (bindingType == EXISTING_FBO_BINDING) {
+            initWithFbo((int)getBindingId());
+        } else if (bindingType == NEW_TEXTURE_BINDING) {
+            initWithTexture((int)getBindingId());
+        } else if (bindingType == NEW_FBO_BINDING) {
+            initWithFbo((int)getBindingId());
+        } else {
+            throw new RuntimeException("Attempting to create GL frame with unknown binding type "
+                + bindingType + "!");
+        }
+        setReusable(reusable);
+    }
+
+    private void initNew(boolean isExternal) {
+        if (isExternal) {
+            if (!nativeAllocateExternal(mGLEnvironment)) {
+                throw new RuntimeException("Could not allocate external GL frame!");
+            }
+        } else {
+            if (!nativeAllocate(mGLEnvironment, getFormat().getWidth(), getFormat().getHeight())) {
+                throw new RuntimeException("Could not allocate GL frame!");
+            }
+        }
+    }
+
+    private void initWithTexture(int texId) {
+        int width = getFormat().getWidth();
+        int height = getFormat().getHeight();
+        if (!nativeAllocateWithTexture(mGLEnvironment, texId, width, height)) {
+            throw new RuntimeException("Could not allocate texture backed GL frame!");
+        }
+        mOwnsTexture = false;
+        markReadOnly();
+    }
+
+    private void initWithFbo(int fboId) {
+        int width = getFormat().getWidth();
+        int height = getFormat().getHeight();
+        if (!nativeAllocateWithFbo(mGLEnvironment, fboId, width, height)) {
+            throw new RuntimeException("Could not allocate FBO backed GL frame!");
+        }
+    }
+
+    void flushGPU(String message) {
+        StopWatchMap timer = GLFrameTimer.get();
+        if (timer.LOG_MFF_RUNNING_TIMES) {
+          timer.start("glFinish " + message);
+          GLES20.glFinish();
+          timer.stop("glFinish " + message);
+        }
+    }
+
+    @Override
+    protected synchronized boolean hasNativeAllocation() {
+        return glFrameId != -1;
+    }
+
+    @Override
+    protected synchronized void releaseNativeAllocation() {
+        nativeDeallocate();
+        glFrameId = -1;
+    }
+
+    public GLEnvironment getGLEnvironment() {
+        return mGLEnvironment;
+    }
+
+    @Override
+    public Object getObjectValue() {
+        assertGLEnvValid();
+        return ByteBuffer.wrap(getNativeData());
+    }
+
+    @Override
+    public void setInts(int[] ints) {
+        assertFrameMutable();
+        assertGLEnvValid();
+        if (!setNativeInts(ints)) {
+            throw new RuntimeException("Could not set int values for GL frame!");
+        }
+    }
+
+    @Override
+    public int[] getInts() {
+        assertGLEnvValid();
+        flushGPU("getInts");
+        return getNativeInts();
+    }
+
+    @Override
+    public void setFloats(float[] floats) {
+        assertFrameMutable();
+        assertGLEnvValid();
+        if (!setNativeFloats(floats)) {
+            throw new RuntimeException("Could not set int values for GL frame!");
+        }
+    }
+
+    @Override
+    public float[] getFloats() {
+        assertGLEnvValid();
+        flushGPU("getFloats");
+        return getNativeFloats();
+    }
+
+    @Override
+    public void setData(ByteBuffer buffer, int offset, int length) {
+        assertFrameMutable();
+        assertGLEnvValid();
+        byte[] bytes = buffer.array();
+        if (getFormat().getSize() != bytes.length) {
+            throw new RuntimeException("Data size in setData does not match GL frame size!");
+        } else if (!setNativeData(bytes, offset, length)) {
+            throw new RuntimeException("Could not set GL frame data!");
+        }
+    }
+
+    @Override
+    public ByteBuffer getData() {
+        assertGLEnvValid();
+        flushGPU("getData");
+        return ByteBuffer.wrap(getNativeData());
+    }
+
+    @Override
+    public void setBitmap(Bitmap bitmap) {
+        assertFrameMutable();
+        assertGLEnvValid();
+        if (getFormat().getWidth()  != bitmap.getWidth() ||
+            getFormat().getHeight() != bitmap.getHeight()) {
+            throw new RuntimeException("Bitmap dimensions do not match GL frame dimensions!");
+        } else {
+            Bitmap rgbaBitmap = convertBitmapToRGBA(bitmap);
+            if (!setNativeBitmap(rgbaBitmap, rgbaBitmap.getByteCount())) {
+                throw new RuntimeException("Could not set GL frame bitmap data!");
+            }
+        }
+    }
+
+    @Override
+    public Bitmap getBitmap() {
+        assertGLEnvValid();
+        flushGPU("getBitmap");
+        Bitmap result = Bitmap.createBitmap(getFormat().getWidth(),
+                                            getFormat().getHeight(),
+                                            Bitmap.Config.ARGB_8888);
+        if (!getNativeBitmap(result)) {
+            throw new RuntimeException("Could not get bitmap data from GL frame!");
+        }
+        return result;
+    }
+
+    @Override
+    public void setDataFromFrame(Frame frame) {
+        assertGLEnvValid();
+
+        // Make sure frame fits
+        if (getFormat().getSize() < frame.getFormat().getSize()) {
+            throw new RuntimeException(
+                "Attempting to assign frame of size " + frame.getFormat().getSize() + " to " +
+                "smaller GL frame of size " + getFormat().getSize() + "!");
+        }
+
+        // Invoke optimized implementations if possible
+        if (frame instanceof NativeFrame) {
+            nativeCopyFromNative((NativeFrame)frame);
+        } else if (frame instanceof GLFrame) {
+            nativeCopyFromGL((GLFrame)frame);
+        } else if (frame instanceof SimpleFrame) {
+            setObjectValue(frame.getObjectValue());
+        } else {
+            super.setDataFromFrame(frame);
+        }
+    }
+
+    public void setViewport(int x, int y, int width, int height) {
+        assertFrameMutable();
+        setNativeViewport(x, y, width, height);
+    }
+
+    public void setViewport(Rect rect) {
+        assertFrameMutable();
+        setNativeViewport(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
+    }
+
+    public void generateMipMap() {
+        assertFrameMutable();
+        assertGLEnvValid();
+        if (!generateNativeMipMap()) {
+            throw new RuntimeException("Could not generate mip-map for GL frame!");
+        }
+    }
+
+    public void setTextureParameter(int param, int value) {
+        assertFrameMutable();
+        assertGLEnvValid();
+        if (!setNativeTextureParam(param, value)) {
+            throw new RuntimeException("Could not set texture value " + param + " = " + value + " " +
+                                       "for GLFrame!");
+        }
+    }
+
+    public int getTextureId() {
+        return getNativeTextureId();
+    }
+
+    public int getFboId() {
+        return getNativeFboId();
+    }
+
+    public void focus() {
+        if (!nativeFocus()) {
+            throw new RuntimeException("Could not focus on GLFrame for drawing!");
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "GLFrame id: " + glFrameId + " (" + getFormat() + ") with texture ID "
+            + getTextureId() + ", FBO ID " + getFboId();
+    }
+
+    @Override
+    protected void reset(FrameFormat newFormat) {
+        if (!nativeResetParams()) {
+            throw new RuntimeException("Could not reset GLFrame texture parameters!");
+        }
+        super.reset(newFormat);
+    }
+
+    @Override
+    protected void onFrameStore() {
+        if (!mOwnsTexture) {
+            // Detach texture from FBO in case user manipulates it.
+            nativeDetachTexFromFbo();
+        }
+    }
+
+    @Override
+    protected void onFrameFetch() {
+        if (!mOwnsTexture) {
+            // Reattach texture to FBO when using frame again. This may reallocate the texture
+            // in case it has become invalid.
+            nativeReattachTexToFbo();
+        }
+    }
+
+    private void assertGLEnvValid() {
+        if (!mGLEnvironment.isContextActive()) {
+            if (GLEnvironment.isAnyContextActive()) {
+                throw new RuntimeException("Attempting to access " + this + " with foreign GL " +
+                    "context active!");
+            } else {
+                throw new RuntimeException("Attempting to access " + this + " with no GL context " +
+                    " active!");
+            }
+        }
+    }
+
+    static {
+        System.loadLibrary("filterfw");
+    }
+
+    private native boolean nativeAllocate(GLEnvironment env, int width, int height);
+
+    private native boolean nativeAllocateWithTexture(GLEnvironment env,
+                                               int textureId,
+                                               int width,
+                                               int height);
+
+    private native boolean nativeAllocateWithFbo(GLEnvironment env,
+                                           int fboId,
+                                           int width,
+                                           int height);
+
+    private native boolean nativeAllocateExternal(GLEnvironment env);
+
+    private native boolean nativeDeallocate();
+
+    private native boolean setNativeData(byte[] data, int offset, int length);
+
+    private native byte[] getNativeData();
+
+    private native boolean setNativeInts(int[] ints);
+
+    private native boolean setNativeFloats(float[] floats);
+
+    private native int[] getNativeInts();
+
+    private native float[] getNativeFloats();
+
+    private native boolean setNativeBitmap(Bitmap bitmap, int size);
+
+    private native boolean getNativeBitmap(Bitmap bitmap);
+
+    private native boolean setNativeViewport(int x, int y, int width, int height);
+
+    private native int getNativeTextureId();
+
+    private native int getNativeFboId();
+
+    private native boolean generateNativeMipMap();
+
+    private native boolean setNativeTextureParam(int param, int value);
+
+    private native boolean nativeResetParams();
+
+    private native boolean nativeCopyFromNative(NativeFrame frame);
+
+    private native boolean nativeCopyFromGL(GLFrame frame);
+
+    private native boolean nativeFocus();
+
+    private native boolean nativeReattachTexToFbo();
+
+    private native boolean nativeDetachTexFromFbo();
+}
diff --git a/native/include/android/rect.h b/media/mca/filterfw/java/android/filterfw/core/GenerateFieldPort.java
similarity index 60%
copy from native/include/android/rect.h
copy to media/mca/filterfw/java/android/filterfw/core/GenerateFieldPort.java
index 64d487d..3e37d4f 100644
--- a/native/include/android/rect.h
+++ b/media/mca/filterfw/java/android/filterfw/core/GenerateFieldPort.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,25 +15,16 @@
  */
 
 
-#ifndef ANDROID_RECT_H
-#define ANDROID_RECT_H
+package android.filterfw.core;
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+import java.lang.annotation.*;
 
-typedef struct ARect {
-#ifdef __cplusplus
-    typedef int32_t value_type;
-#endif
-    int32_t left;
-    int32_t top;
-    int32_t right;
-    int32_t bottom;
-} ARect;
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif // ANDROID_RECT_H
+/**
+ * @hide
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface GenerateFieldPort {
+    String  name() default "";
+    boolean hasDefault() default false;
+}
diff --git a/native/include/android/rect.h b/media/mca/filterfw/java/android/filterfw/core/GenerateFinalPort.java
similarity index 60%
copy from native/include/android/rect.h
copy to media/mca/filterfw/java/android/filterfw/core/GenerateFinalPort.java
index 64d487d..0dec8cc 100644
--- a/native/include/android/rect.h
+++ b/media/mca/filterfw/java/android/filterfw/core/GenerateFinalPort.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,25 +15,16 @@
  */
 
 
-#ifndef ANDROID_RECT_H
-#define ANDROID_RECT_H
+package android.filterfw.core;
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+import java.lang.annotation.*;
 
-typedef struct ARect {
-#ifdef __cplusplus
-    typedef int32_t value_type;
-#endif
-    int32_t left;
-    int32_t top;
-    int32_t right;
-    int32_t bottom;
-} ARect;
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif // ANDROID_RECT_H
+/**
+ * @hide
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface GenerateFinalPort {
+    String  name() default "";
+    boolean hasDefault() default false;
+}
diff --git a/native/include/android/rect.h b/media/mca/filterfw/java/android/filterfw/core/GenerateProgramPort.java
similarity index 60%
rename from native/include/android/rect.h
rename to media/mca/filterfw/java/android/filterfw/core/GenerateProgramPort.java
index 64d487d..fb40416 100644
--- a/native/include/android/rect.h
+++ b/media/mca/filterfw/java/android/filterfw/core/GenerateProgramPort.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,25 +15,18 @@
  */
 
 
-#ifndef ANDROID_RECT_H
-#define ANDROID_RECT_H
+package android.filterfw.core;
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+import java.lang.annotation.*;
 
-typedef struct ARect {
-#ifdef __cplusplus
-    typedef int32_t value_type;
-#endif
-    int32_t left;
-    int32_t top;
-    int32_t right;
-    int32_t bottom;
-} ARect;
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif // ANDROID_RECT_H
+/**
+ * @hide
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface GenerateProgramPort {
+    String  name();
+    Class   type();
+    String  variableName() default "";
+    boolean hasDefault() default false;
+}
diff --git a/native/include/android/rect.h b/media/mca/filterfw/java/android/filterfw/core/GenerateProgramPorts.java
similarity index 60%
copy from native/include/android/rect.h
copy to media/mca/filterfw/java/android/filterfw/core/GenerateProgramPorts.java
index 64d487d..354126d 100644
--- a/native/include/android/rect.h
+++ b/media/mca/filterfw/java/android/filterfw/core/GenerateProgramPorts.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,25 +15,15 @@
  */
 
 
-#ifndef ANDROID_RECT_H
-#define ANDROID_RECT_H
+package android.filterfw.core;
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+import java.lang.annotation.*;
 
-typedef struct ARect {
-#ifdef __cplusplus
-    typedef int32_t value_type;
-#endif
-    int32_t left;
-    int32_t top;
-    int32_t right;
-    int32_t bottom;
-} ARect;
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif // ANDROID_RECT_H
+/**
+ * @hide
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface GenerateProgramPorts {
+    GenerateProgramPort[] value();
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java
new file mode 100644
index 0000000..b496c54
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+/**
+ * @hide
+ */
+public abstract class GraphRunner {
+
+    protected FilterContext mFilterContext = null;
+
+    /** Interface for listeners waiting for the runner to complete. */
+    public interface OnRunnerDoneListener {
+        /** Callback method to be called when the runner completes a
+         * {@link #run()} call.
+         *
+         * @param result will be RESULT_FINISHED if the graph finished running
+         *        on its own, RESULT_STOPPED if the runner was stopped by a call
+         *        to stop(), RESULT_BLOCKED if no filters could run due to lack
+         *        of inputs or outputs or due to scheduling policies, and
+         *        RESULT_SLEEPING if a filter node requested sleep.
+         */
+        public void onRunnerDone(int result);
+    }
+
+    public static final int RESULT_UNKNOWN  = 0;
+    public static final int RESULT_RUNNING  = 1;
+    public static final int RESULT_FINISHED = 2;
+    public static final int RESULT_SLEEPING = 3;
+    public static final int RESULT_BLOCKED  = 4;
+    public static final int RESULT_STOPPED  = 5;
+    public static final int RESULT_ERROR    = 6;
+
+    public GraphRunner(FilterContext context) {
+        mFilterContext = context;
+    }
+
+    public abstract FilterGraph getGraph();
+
+    public FilterContext getContext() {
+        return mFilterContext;
+    }
+
+    /**
+     * Helper function for subclasses to activate the GL environment before running.
+     * @return true, if the GL environment was activated. Returns false, if the GL environment
+     *         was already active.
+     */
+    protected boolean activateGlContext() {
+        GLEnvironment glEnv = mFilterContext.getGLEnvironment();
+        if (glEnv != null && !glEnv.isActive()) {
+            glEnv.activate();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Helper function for subclasses to deactivate the GL environment after running.
+     */
+    protected void deactivateGlContext() {
+        GLEnvironment glEnv = mFilterContext.getGLEnvironment();
+        if (glEnv != null) {
+            glEnv.deactivate();
+        }
+    }
+
+    /** Starts running the graph. Will open the filters in the graph if they are not already open. */
+    public abstract void run();
+
+    public abstract void setDoneCallback(OnRunnerDoneListener listener);
+    public abstract boolean isRunning();
+
+    /** Stops graph execution. As part of stopping, also closes the graph nodes. */
+    public abstract void stop();
+
+    /** Closes the filters in a graph. Can only be called if the graph is not running. */
+    public abstract void close();
+
+    /**
+     * Returns the last exception that happened during an asynchronous run. Returns null if
+     * there is nothing to report.
+     */
+    public abstract Exception getError();
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/InputPort.java b/media/mca/filterfw/java/android/filterfw/core/InputPort.java
new file mode 100644
index 0000000..de5cccc
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/InputPort.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+/**
+ * @hide
+ */
+public abstract class InputPort extends FilterPort {
+
+    protected OutputPort mSourcePort;
+
+    public InputPort(Filter filter, String name) {
+        super(filter, name);
+    }
+
+    public void setSourcePort(OutputPort source) {
+        if (mSourcePort != null) {
+            throw new RuntimeException(this + " already connected to " + mSourcePort + "!");
+        }
+        mSourcePort = source;
+    }
+
+    public boolean isConnected() {
+        return mSourcePort != null;
+    }
+
+    public void open() {
+        super.open();
+        if (mSourcePort != null && !mSourcePort.isOpen()) {
+            mSourcePort.open();
+        }
+    }
+
+    public void close() {
+        if (mSourcePort != null && mSourcePort.isOpen()) {
+            mSourcePort.close();
+        }
+        super.close();
+    }
+
+    public OutputPort getSourcePort() {
+        return mSourcePort;
+    }
+
+    public Filter getSourceFilter() {
+        return mSourcePort == null ? null : mSourcePort.getFilter();
+    }
+
+    public FrameFormat getSourceFormat() {
+        return mSourcePort != null ? mSourcePort.getPortFormat() : getPortFormat();
+    }
+
+    public Object getTarget() {
+        return null;
+    }
+
+    public boolean filterMustClose() {
+        return !isOpen() && isBlocking() && !hasFrame();
+    }
+
+    public boolean isReady() {
+        return hasFrame() || !isBlocking();
+    }
+
+    public boolean acceptsFrame() {
+        return !hasFrame();
+    }
+
+    public abstract void transfer(FilterContext context);
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java b/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java
new file mode 100644
index 0000000..8cf9a13
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+public class KeyValueMap extends HashMap<String, Object> {
+
+    public void setKeyValues(Object... keyValues) {
+        if (keyValues.length % 2 != 0) {
+            throw new RuntimeException("Key-Value arguments passed into setKeyValues must be "
+            + "an alternating list of keys and values!");
+        }
+        for (int i = 0; i < keyValues.length; i += 2) {
+            if (!(keyValues[i] instanceof String)) {
+                throw new RuntimeException("Key-value argument " + i + " must be a key of type "
+                    + "String, but found an object of type " + keyValues[i].getClass() + "!");
+            }
+            String key = (String)keyValues[i];
+            Object value = keyValues[i+1];
+            put(key, value);
+        }
+    }
+
+    public static KeyValueMap fromKeyValues(Object... keyValues) {
+        KeyValueMap result = new KeyValueMap();
+        result.setKeyValues(keyValues);
+        return result;
+    }
+
+    public String getString(String key) {
+        Object result = get(key);
+        return result != null ? (String)result : null;
+    }
+
+    public int getInt(String key) {
+        Object result = get(key);
+        return result != null ? (Integer)result : null;
+    }
+
+    public float getFloat(String key) {
+        Object result = get(key);
+        return result != null ? (Float)result : null;
+    }
+
+    @Override
+    public String toString() {
+        StringWriter writer = new StringWriter();
+        for (Map.Entry<String, Object> entry : entrySet()) {
+            String valueString;
+            Object value = entry.getValue();
+            if (value instanceof String) {
+                valueString = "\"" + value + "\"";
+            } else {
+                valueString = value.toString();
+            }
+            writer.write(entry.getKey() + " = " + valueString + ";\n");
+        }
+        return writer.toString();
+    }
+
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java
new file mode 100644
index 0000000..8c78975
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.KeyValueMap;
+
+import java.util.Arrays;
+
+/**
+ * @hide
+ */
+public class MutableFrameFormat extends FrameFormat {
+
+    public MutableFrameFormat() {
+        super();
+    }
+
+    public MutableFrameFormat(int baseType, int target) {
+        super(baseType, target);
+    }
+
+    public void setBaseType(int baseType) {
+        mBaseType = baseType;
+        mBytesPerSample = bytesPerSampleOf(baseType);
+    }
+
+    public void setTarget(int target) {
+        mTarget = target;
+    }
+
+    public void setBytesPerSample(int bytesPerSample) {
+        mBytesPerSample = bytesPerSample;
+        mSize = SIZE_UNKNOWN;
+    }
+
+    public void setDimensions(int[] dimensions) {
+        mDimensions = (dimensions == null) ? null : Arrays.copyOf(dimensions, dimensions.length);
+        mSize = SIZE_UNKNOWN;
+    }
+
+    public void setDimensions(int size) {
+        int[] dimensions = new int[1];
+        dimensions[0] = size;
+        mDimensions = dimensions;
+        mSize = SIZE_UNKNOWN;
+    }
+
+    public void setDimensions(int width, int height) {
+        int[] dimensions = new int[2];
+        dimensions[0] = width;
+        dimensions[1] = height;
+        mDimensions = dimensions;
+        mSize = SIZE_UNKNOWN;
+    }
+
+    public void setDimensions(int width, int height, int depth) {
+        int[] dimensions = new int[3];
+        dimensions[0] = width;
+        dimensions[1] = height;
+        dimensions[2] = depth;
+        mDimensions = dimensions;
+        mSize = SIZE_UNKNOWN;
+    }
+
+    public void setDimensionCount(int count) {
+        mDimensions = new int[count];
+    }
+
+    public void setObjectClass(Class objectClass) {
+        mObjectClass = objectClass;
+    }
+
+    public void setMetaValue(String key, Object value) {
+        if (mMetaData == null) {
+            mMetaData = new KeyValueMap();
+        }
+        mMetaData.put(key, value);
+    }
+
+}
diff --git a/native/include/android/rect.h b/media/mca/filterfw/java/android/filterfw/core/NativeAllocatorTag.java
similarity index 60%
copy from native/include/android/rect.h
copy to media/mca/filterfw/java/android/filterfw/core/NativeAllocatorTag.java
index 64d487d..4d43d7c 100644
--- a/native/include/android/rect.h
+++ b/media/mca/filterfw/java/android/filterfw/core/NativeAllocatorTag.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,25 +15,14 @@
  */
 
 
-#ifndef ANDROID_RECT_H
-#define ANDROID_RECT_H
+package android.filterfw.core;
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct ARect {
-#ifdef __cplusplus
-    typedef int32_t value_type;
-#endif
-    int32_t left;
-    int32_t top;
-    int32_t right;
-    int32_t bottom;
-} ARect;
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif // ANDROID_RECT_H
+/**
+ * This class is simply a place-holder type used to identify calls coming
+ * from the native layer. This way method signatures can be selected
+ * that are to be accessed from the native layer only.
+ *
+ * @hide
+ **/
+public class NativeAllocatorTag {
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/NativeBuffer.java b/media/mca/filterfw/java/android/filterfw/core/NativeBuffer.java
new file mode 100644
index 0000000..80da5ea
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/NativeBuffer.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Frame;
+
+/**
+ * @hide
+ */
+public class NativeBuffer {
+
+    // These are set by the native layer
+    private long mDataPointer = 0;
+    private int mSize = 0;
+
+    private Frame mAttachedFrame;
+
+    private boolean mOwnsData = false;
+    private int mRefCount = 1;
+
+    public NativeBuffer() {
+    }
+
+    public NativeBuffer(int count) {
+        allocate(count * getElementSize());
+        mOwnsData = true;
+    }
+
+    public NativeBuffer mutableCopy() {
+        NativeBuffer result = null;
+        try {
+            Class myClass = getClass();
+            result = (NativeBuffer)myClass.newInstance();
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to allocate a copy of " + getClass() + "! Make " +
+                                       "sure the class has a default constructor!");
+        }
+        if (mSize > 0 && !nativeCopyTo(result)) {
+            throw new RuntimeException("Failed to copy NativeBuffer to mutable instance!");
+        }
+        return result;
+    }
+
+    public int size() {
+        return mSize;
+    }
+
+    public int count() {
+        return (mDataPointer != 0) ? mSize / getElementSize() : 0;
+    }
+
+    public int getElementSize() {
+        return 1;
+    }
+
+    public NativeBuffer retain() {
+        if (mAttachedFrame != null) {
+            mAttachedFrame.retain();
+        } else if (mOwnsData) {
+            ++mRefCount;
+        }
+        return this;
+    }
+
+    public NativeBuffer release() {
+        // Decrement refcount
+        boolean doDealloc = false;
+        if (mAttachedFrame != null) {
+            doDealloc = (mAttachedFrame.release() == null);
+        } else if (mOwnsData) {
+            --mRefCount;
+            doDealloc = (mRefCount == 0);
+        }
+
+        // Deallocate if necessary
+        if (doDealloc) {
+            deallocate(mOwnsData);
+            return null;
+        } else {
+            return this;
+        }
+    }
+
+    public boolean isReadOnly() {
+        return (mAttachedFrame != null) ? mAttachedFrame.isReadOnly() : false;
+    }
+
+    static {
+        System.loadLibrary("filterfw");
+    }
+
+    void attachToFrame(Frame frame) {
+        // We do not auto-retain. We expect the user to call retain() if they want to hold on to
+        // the frame.
+        mAttachedFrame = frame;
+    }
+
+    protected void assertReadable() {
+        if (mDataPointer == 0 || mSize == 0
+        || (mAttachedFrame != null && !mAttachedFrame.hasNativeAllocation())) {
+            throw new NullPointerException("Attempting to read from null data frame!");
+        }
+    }
+
+    protected void assertWritable() {
+        if (isReadOnly()) {
+            throw new RuntimeException("Attempting to modify read-only native (structured) data!");
+        }
+    }
+
+    private native boolean allocate(int size);
+    private native boolean deallocate(boolean ownsData);
+    private native boolean nativeCopyTo(NativeBuffer buffer);
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/NativeFrame.java b/media/mca/filterfw/java/android/filterfw/core/NativeFrame.java
new file mode 100644
index 0000000..bfd09ba
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/NativeFrame.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.FrameManager;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.NativeBuffer;
+import android.graphics.Bitmap;
+
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+
+/**
+ * @hide
+ */
+public class NativeFrame extends Frame {
+
+    private int nativeFrameId = -1;
+
+    NativeFrame(FrameFormat format, FrameManager frameManager) {
+        super(format, frameManager);
+        int capacity = format.getSize();
+        nativeAllocate(capacity);
+        setReusable(capacity != 0);
+    }
+
+    @Override
+    protected synchronized void releaseNativeAllocation() {
+        nativeDeallocate();
+        nativeFrameId = -1;
+    }
+
+    @Override
+    protected synchronized boolean hasNativeAllocation() {
+        return nativeFrameId != -1;
+    }
+
+    @Override
+    public int getCapacity() {
+        return getNativeCapacity();
+    }
+
+    /**
+     * Returns the native frame's Object value.
+     *
+     * If the frame's base-type is not TYPE_OBJECT, this returns a data buffer containing the native
+     * data (this is equivalent to calling getData().
+     * If the frame is based on an object type, this type is expected to be a subclass of
+     * NativeBuffer. The NativeBuffer returned is only valid for as long as the frame is alive. If
+     * you need to hold on to the returned value, you must retain it.
+     */
+    @Override
+    public Object getObjectValue() {
+        // If this is not a structured frame, return our data
+        if (getFormat().getBaseType() != FrameFormat.TYPE_OBJECT) {
+            return getData();
+        }
+
+        // Get the structure class
+        Class structClass = getFormat().getObjectClass();
+        if (structClass == null) {
+            throw new RuntimeException("Attempting to get object data from frame that does " +
+                                       "not specify a structure object class!");
+        }
+
+        // Make sure it is a NativeBuffer subclass
+        if (!NativeBuffer.class.isAssignableFrom(structClass)) {
+            throw new RuntimeException("NativeFrame object class must be a subclass of " +
+                                       "NativeBuffer!");
+        }
+
+        // Instantiate a new empty instance of this class
+        NativeBuffer structData = null;
+        try {
+          structData = (NativeBuffer)structClass.newInstance();
+        } catch (Exception e) {
+          throw new RuntimeException("Could not instantiate new structure instance of type '" +
+                                     structClass + "'!");
+        }
+
+        // Wrap it around our data
+        if (!getNativeBuffer(structData)) {
+            throw new RuntimeException("Could not get the native structured data for frame!");
+        }
+
+        // Attach this frame to it
+        structData.attachToFrame(this);
+
+        return structData;
+    }
+
+    @Override
+    public void setInts(int[] ints) {
+        assertFrameMutable();
+        if (ints.length * nativeIntSize() > getFormat().getSize()) {
+            throw new RuntimeException(
+                "NativeFrame cannot hold " + ints.length + " integers. (Can only hold " +
+                (getFormat().getSize() / nativeIntSize()) + " integers).");
+        } else if (!setNativeInts(ints)) {
+            throw new RuntimeException("Could not set int values for native frame!");
+        }
+    }
+
+    @Override
+    public int[] getInts() {
+        return getNativeInts(getFormat().getSize());
+    }
+
+    @Override
+    public void setFloats(float[] floats) {
+        assertFrameMutable();
+        if (floats.length * nativeFloatSize() > getFormat().getSize()) {
+            throw new RuntimeException(
+                "NativeFrame cannot hold " + floats.length + " floats. (Can only hold " +
+                (getFormat().getSize() / nativeFloatSize()) + " floats).");
+        } else if (!setNativeFloats(floats)) {
+            throw new RuntimeException("Could not set int values for native frame!");
+        }
+    }
+
+    @Override
+    public float[] getFloats() {
+        return getNativeFloats(getFormat().getSize());
+    }
+
+    // TODO: This function may be a bit confusing: Is the offset the target or source offset? Maybe
+    // we should allow specifying both? (May be difficult for other frame types).
+    @Override
+    public void setData(ByteBuffer buffer, int offset, int length) {
+        assertFrameMutable();
+        byte[] bytes = buffer.array();
+        if ((length + offset) > buffer.limit()) {
+            throw new RuntimeException("Offset and length exceed buffer size in native setData: " +
+                                       (length + offset) + " bytes given, but only " + buffer.limit() +
+                                       " bytes available!");
+        } else if (getFormat().getSize() != length) {
+            throw new RuntimeException("Data size in setData does not match native frame size: " +
+                                       "Frame size is " + getFormat().getSize() + " bytes, but " +
+                                       length + " bytes given!");
+        } else if (!setNativeData(bytes, offset, length)) {
+            throw new RuntimeException("Could not set native frame data!");
+        }
+    }
+
+    @Override
+    public ByteBuffer getData() {
+        byte[] data = getNativeData(getFormat().getSize());
+        return data == null ? null : ByteBuffer.wrap(data);
+    }
+
+    @Override
+    public void setBitmap(Bitmap bitmap) {
+        assertFrameMutable();
+        if (getFormat().getNumberOfDimensions() != 2) {
+            throw new RuntimeException("Attempting to set Bitmap for non 2-dimensional native frame!");
+        } else if (getFormat().getWidth()  != bitmap.getWidth() ||
+                   getFormat().getHeight() != bitmap.getHeight()) {
+            throw new RuntimeException("Bitmap dimensions do not match native frame dimensions!");
+        } else {
+            Bitmap rgbaBitmap = convertBitmapToRGBA(bitmap);
+            int byteCount = rgbaBitmap.getByteCount();
+            int bps = getFormat().getBytesPerSample();
+            if (!setNativeBitmap(rgbaBitmap, byteCount, bps)) {
+                throw new RuntimeException("Could not set native frame bitmap data!");
+            }
+        }
+    }
+
+    @Override
+    public Bitmap getBitmap() {
+        if (getFormat().getNumberOfDimensions() != 2) {
+            throw new RuntimeException("Attempting to get Bitmap for non 2-dimensional native frame!");
+        }
+        Bitmap result = Bitmap.createBitmap(getFormat().getWidth(),
+                                            getFormat().getHeight(),
+                                            Bitmap.Config.ARGB_8888);
+        int byteCount = result.getByteCount();
+        int bps = getFormat().getBytesPerSample();
+        if (!getNativeBitmap(result, byteCount, bps)) {
+            throw new RuntimeException("Could not get bitmap data from native frame!");
+        }
+        return result;
+    }
+
+    @Override
+    public void setDataFromFrame(Frame frame) {
+        // Make sure frame fits
+        if (getFormat().getSize() < frame.getFormat().getSize()) {
+            throw new RuntimeException(
+                "Attempting to assign frame of size " + frame.getFormat().getSize() + " to " +
+                "smaller native frame of size " + getFormat().getSize() + "!");
+        }
+
+        // Invoke optimized implementations if possible
+        if (frame instanceof NativeFrame) {
+            nativeCopyFromNative((NativeFrame)frame);
+        } else if (frame instanceof GLFrame) {
+            nativeCopyFromGL((GLFrame)frame);
+        } else if (frame instanceof SimpleFrame) {
+            setObjectValue(frame.getObjectValue());
+        } else {
+            super.setDataFromFrame(frame);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "NativeFrame id: " + nativeFrameId + " (" + getFormat() + ") of size "
+            + getCapacity();
+    }
+
+    static {
+        System.loadLibrary("filterfw");
+    }
+
+    private native boolean nativeAllocate(int capacity);
+
+    private native boolean nativeDeallocate();
+
+    private native int getNativeCapacity();
+
+    private static native int nativeIntSize();
+
+    private static native int nativeFloatSize();
+
+    private native boolean setNativeData(byte[] data, int offset, int length);
+
+    private native byte[] getNativeData(int byteCount);
+
+    private native boolean getNativeBuffer(NativeBuffer buffer);
+
+    private native boolean setNativeInts(int[] ints);
+
+    private native boolean setNativeFloats(float[] floats);
+
+    private native int[] getNativeInts(int byteCount);
+
+    private native float[] getNativeFloats(int byteCount);
+
+    private native boolean setNativeBitmap(Bitmap bitmap, int size, int bytesPerSample);
+
+    private native boolean getNativeBitmap(Bitmap bitmap, int size, int bytesPerSample);
+
+    private native boolean nativeCopyFromNative(NativeFrame frame);
+
+    private native boolean nativeCopyFromGL(GLFrame frame);
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/NativeProgram.java b/media/mca/filterfw/java/android/filterfw/core/NativeProgram.java
new file mode 100644
index 0000000..791ab3c
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/NativeProgram.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Frame;
+import android.filterfw.core.Program;
+
+/**
+ * @hide
+ */
+public class NativeProgram extends Program {
+
+    private int nativeProgramId;
+    private boolean mHasInitFunction     = false;
+    private boolean mHasTeardownFunction = false;
+    private boolean mHasSetValueFunction = false;
+    private boolean mHasGetValueFunction = false;
+    private boolean mHasResetFunction = false;
+    private boolean mTornDown = false;
+
+    public NativeProgram(String nativeLibName, String nativeFunctionPrefix) {
+        // Allocate the native instance
+        allocate();
+
+        // Open the native library
+        String fullLibName = "lib" + nativeLibName + ".so";
+        if (!openNativeLibrary(fullLibName)) {
+            throw new RuntimeException("Could not find native library named '" + fullLibName + "' " +
+                                       "required for native program!");
+        }
+
+        // Bind the native functions
+        String processFuncName = nativeFunctionPrefix + "_process";
+        if (!bindProcessFunction(processFuncName)) {
+            throw new RuntimeException("Could not find native program function name " +
+                                       processFuncName + " in library " + fullLibName + "! " +
+                                       "This function is required!");
+        }
+
+        String initFuncName = nativeFunctionPrefix + "_init";
+        mHasInitFunction = bindInitFunction(initFuncName);
+
+        String teardownFuncName = nativeFunctionPrefix + "_teardown";
+        mHasTeardownFunction = bindTeardownFunction(teardownFuncName);
+
+        String setValueFuncName = nativeFunctionPrefix + "_setvalue";
+        mHasSetValueFunction = bindSetValueFunction(setValueFuncName);
+
+        String getValueFuncName = nativeFunctionPrefix + "_getvalue";
+        mHasGetValueFunction = bindGetValueFunction(getValueFuncName);
+
+        String resetFuncName = nativeFunctionPrefix + "_reset";
+        mHasResetFunction = bindResetFunction(resetFuncName);
+
+        // Initialize the native code
+        if (mHasInitFunction && !callNativeInit()) {
+            throw new RuntimeException("Could not initialize NativeProgram!");
+        }
+    }
+
+    public void tearDown() {
+        if (mTornDown) return;
+        if (mHasTeardownFunction && !callNativeTeardown()) {
+            throw new RuntimeException("Could not tear down NativeProgram!");
+        }
+        deallocate();
+        mTornDown = true;
+    }
+
+    @Override
+    public void reset() {
+        if (mHasResetFunction && !callNativeReset()) {
+            throw new RuntimeException("Could not reset NativeProgram!");
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        tearDown();
+    }
+
+    @Override
+    public void process(Frame[] inputs, Frame output) {
+        if (mTornDown) {
+            throw new RuntimeException("NativeProgram already torn down!");
+        }
+        NativeFrame[] nativeInputs = new NativeFrame[inputs.length];
+        for (int i = 0; i < inputs.length; ++i) {
+            if (inputs[i] == null || inputs[i] instanceof NativeFrame) {
+                nativeInputs[i] = (NativeFrame)inputs[i];
+            } else {
+                throw new RuntimeException("NativeProgram got non-native frame as input "+ i +"!");
+            }
+        }
+
+        // Get the native output frame
+        NativeFrame nativeOutput = null;
+        if (output == null || output instanceof NativeFrame) {
+            nativeOutput = (NativeFrame)output;
+        } else {
+            throw new RuntimeException("NativeProgram got non-native output frame!");
+        }
+
+        // Process!
+        if (!callNativeProcess(nativeInputs, nativeOutput)) {
+            throw new RuntimeException("Calling native process() caused error!");
+        }
+    }
+
+    @Override
+    public void setHostValue(String variableName, Object value) {
+        if (mTornDown) {
+            throw new RuntimeException("NativeProgram already torn down!");
+        }
+        if (!mHasSetValueFunction) {
+            throw new RuntimeException("Attempting to set native variable, but native code does not " +
+                                       "define native setvalue function!");
+        }
+        if (!callNativeSetValue(variableName, value.toString())) {
+            throw new RuntimeException("Error setting native value for variable '" + variableName + "'!");
+        }
+    }
+
+    @Override
+    public Object getHostValue(String variableName) {
+        if (mTornDown) {
+            throw new RuntimeException("NativeProgram already torn down!");
+        }
+        if (!mHasGetValueFunction) {
+            throw new RuntimeException("Attempting to get native variable, but native code does not " +
+                                       "define native getvalue function!");
+        }
+        return callNativeGetValue(variableName);
+    }
+
+    static {
+        System.loadLibrary("filterfw");
+    }
+
+    private native boolean allocate();
+
+    private native boolean deallocate();
+
+    private native boolean nativeInit();
+
+    private native boolean openNativeLibrary(String libName);
+
+    private native boolean bindInitFunction(String funcName);
+    private native boolean bindSetValueFunction(String funcName);
+    private native boolean bindGetValueFunction(String funcName);
+    private native boolean bindProcessFunction(String funcName);
+    private native boolean bindResetFunction(String funcName);
+    private native boolean bindTeardownFunction(String funcName);
+
+    private native boolean callNativeInit();
+    private native boolean callNativeSetValue(String key, String value);
+    private native String  callNativeGetValue(String key);
+    private native boolean callNativeProcess(NativeFrame[] inputs, NativeFrame output);
+    private native boolean callNativeReset();
+    private native boolean callNativeTeardown();
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/OneShotScheduler.java b/media/mca/filterfw/java/android/filterfw/core/OneShotScheduler.java
new file mode 100644
index 0000000..dbc8d16
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/OneShotScheduler.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.Scheduler;
+import android.filterfw.core.RoundRobinScheduler;
+import android.util.Log;
+
+import java.util.HashMap;
+
+/**
+ * This OneShotScheduler only schedules source filters at most once. All other
+ * filters will be scheduled, and possibly repeatedly, until there is no filter
+ * that can be scheduled.
+ *
+ * @hide
+ */
+public class OneShotScheduler extends RoundRobinScheduler {
+    private HashMap <String, Integer> scheduled;
+
+    private final boolean mLogVerbose;
+    private static final String TAG = "OneShotScheduler";
+
+    public OneShotScheduler(FilterGraph graph) {
+        super(graph);
+        scheduled = new HashMap<String, Integer>();
+        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    }
+
+    @Override
+    public void reset() {
+        super.reset();
+        scheduled.clear();
+    }
+
+    @Override
+    public Filter scheduleNextNode() {
+        Filter first = null;
+        // return the first filter that is not scheduled before.
+        while (true) {
+            Filter filter = super.scheduleNextNode();
+            if (filter == null) {
+                if (mLogVerbose) Log.v(TAG, "No filters available to run.");
+                return null;
+            }
+            if (!scheduled.containsKey(filter.getName())) {
+                if (filter.getNumberOfConnectedInputs() == 0)
+                    scheduled.put(filter.getName(),1);
+                if (mLogVerbose) Log.v(TAG, "Scheduling filter \"" + filter.getName() + "\" of type " + filter.getFilterClassName());
+                return filter;
+            }
+            // if loop back, nothing available
+            if (first == filter) {
+                break;
+            }
+            // save the first scheduled one
+            if (first == null) first = filter;
+        }
+        if (mLogVerbose) Log.v(TAG, "One pass through graph completed.");
+        return null;
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/OutputPort.java b/media/mca/filterfw/java/android/filterfw/core/OutputPort.java
new file mode 100644
index 0000000..872dbdd
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/OutputPort.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+/**
+ * @hide
+ */
+public class OutputPort extends FilterPort {
+
+    protected InputPort mTargetPort;
+    protected InputPort mBasePort;
+
+    public OutputPort(Filter filter, String name) {
+        super(filter, name);
+    }
+
+    public void connectTo(InputPort target) {
+        if (mTargetPort != null) {
+            throw new RuntimeException(this + " already connected to " + mTargetPort + "!");
+        }
+        mTargetPort = target;
+        mTargetPort.setSourcePort(this);
+    }
+
+    public boolean isConnected() {
+        return mTargetPort != null;
+    }
+
+    public void open() {
+        super.open();
+        if (mTargetPort != null && !mTargetPort.isOpen()) {
+            mTargetPort.open();
+        }
+    }
+
+    public void close() {
+        super.close();
+        if (mTargetPort != null && mTargetPort.isOpen()) {
+            mTargetPort.close();
+        }
+    }
+
+    public InputPort getTargetPort() {
+        return mTargetPort;
+    }
+
+    public Filter getTargetFilter() {
+        return mTargetPort == null ? null : mTargetPort.getFilter();
+    }
+
+    public void setBasePort(InputPort basePort) {
+        mBasePort = basePort;
+    }
+
+    public InputPort getBasePort() {
+        return mBasePort;
+    }
+
+    public boolean filterMustClose() {
+        return !isOpen() && isBlocking();
+    }
+
+    public boolean isReady() {
+        return (isOpen() && mTargetPort.acceptsFrame()) || !isBlocking();
+    }
+
+    @Override
+    public void clear() {
+        if (mTargetPort != null) {
+            mTargetPort.clear();
+        }
+    }
+
+    @Override
+    public void pushFrame(Frame frame) {
+        if (mTargetPort == null) {
+            throw new RuntimeException(
+                "Attempting to push frame on unconnected port: " + this + "!");
+        }
+        mTargetPort.pushFrame(frame);
+    }
+
+    @Override
+    public void setFrame(Frame frame) {
+        assertPortIsOpen();
+        if (mTargetPort == null) {
+            throw new RuntimeException(
+                "Attempting to set frame on unconnected port: " + this + "!");
+        }
+        mTargetPort.setFrame(frame);
+    }
+
+    @Override
+    public Frame pullFrame() {
+        throw new RuntimeException("Cannot pull frame on " + this + "!");
+    }
+
+    @Override
+    public boolean hasFrame() {
+        return mTargetPort == null ? false : mTargetPort.hasFrame();
+    }
+
+    @Override
+    public String toString() {
+        return "output " + super.toString();
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/Program.java b/media/mca/filterfw/java/android/filterfw/core/Program.java
new file mode 100644
index 0000000..1930648
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/Program.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Frame;
+
+/**
+ * @hide
+ */
+public abstract class Program {
+
+    public abstract void process(Frame[] inputs, Frame output);
+
+    public void process(Frame input, Frame output) {
+        Frame[] inputs = new Frame[1];
+        inputs[0] = input;
+        process(inputs, output);
+    }
+
+    public abstract void setHostValue(String variableName, Object value);
+
+    public abstract Object getHostValue(String variableName);
+
+    public void reset() {
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/ProgramPort.java b/media/mca/filterfw/java/android/filterfw/core/ProgramPort.java
new file mode 100644
index 0000000..3cab26d
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/ProgramPort.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import java.lang.reflect.Field;
+
+/**
+ * @hide
+ */
+public class ProgramPort extends FieldPort {
+
+    protected String mVarName;
+
+    public ProgramPort(Filter filter,
+                       String name,
+                       String varName,
+                       Field field,
+                       boolean hasDefault) {
+        super(filter, name, field, hasDefault);
+        mVarName = varName;
+    }
+
+    @Override
+    public String toString() {
+        return "Program " + super.toString();
+    }
+
+    @Override
+    public synchronized void transfer(FilterContext context) {
+        if (mValueWaiting) {
+            try {
+                Object fieldValue = mField.get(mFilter);
+                if (fieldValue != null) {
+                    Program program = (Program)fieldValue;
+                    program.setHostValue(mVarName, mValue);
+                    mValueWaiting = false;
+                }
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(
+                    "Access to program field '" + mField.getName() + "' was denied!");
+            } catch (ClassCastException e) {
+                throw new RuntimeException("Non Program field '" + mField.getName()
+                    + "' annotated with ProgramParameter!");
+            }
+        }
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/ProgramVariable.java b/media/mca/filterfw/java/android/filterfw/core/ProgramVariable.java
new file mode 100644
index 0000000..5592d37
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/ProgramVariable.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+/**
+ * @hide
+ */
+public class ProgramVariable {
+
+    private Program mProgram;
+    private String mVarName;
+
+    public ProgramVariable(Program program, String varName) {
+        mProgram = program;
+        mVarName = varName;
+    }
+
+    public Program getProgram() {
+        return mProgram;
+    }
+
+    public String getVariableName() {
+        return mVarName;
+    }
+
+    public void setValue(Object value) {
+        if (mProgram == null) {
+            throw new RuntimeException("Attempting to set program variable '" + mVarName
+                + "' but the program is null!");
+        }
+        mProgram.setHostValue(mVarName, value);
+    }
+
+    public Object getValue() {
+        if (mProgram == null) {
+            throw new RuntimeException("Attempting to get program variable '" + mVarName
+                + "' but the program is null!");
+        }
+        return mProgram.getHostValue(mVarName);
+    }
+
+}
diff --git a/native/include/android/rect.h b/media/mca/filterfw/java/android/filterfw/core/ProtocolException.java
similarity index 60%
copy from native/include/android/rect.h
copy to media/mca/filterfw/java/android/filterfw/core/ProtocolException.java
index 64d487d..2c7a29a 100644
--- a/native/include/android/rect.h
+++ b/media/mca/filterfw/java/android/filterfw/core/ProtocolException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,25 +15,19 @@
  */
 
 
-#ifndef ANDROID_RECT_H
-#define ANDROID_RECT_H
+package android.filterfw.core;
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+/**
+ * @hide
+ */
+public class ProtocolException extends RuntimeException {
 
-typedef struct ARect {
-#ifdef __cplusplus
-    typedef int32_t value_type;
-#endif
-    int32_t left;
-    int32_t top;
-    int32_t right;
-    int32_t bottom;
-} ARect;
+    public ProtocolException() {
+        super();
+    }
 
-#ifdef __cplusplus
-};
-#endif
+    public ProtocolException(String message) {
+        super(message);
+    }
 
-#endif // ANDROID_RECT_H
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/RandomScheduler.java b/media/mca/filterfw/java/android/filterfw/core/RandomScheduler.java
new file mode 100644
index 0000000..087f5db
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/RandomScheduler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import java.util.Random;
+import java.util.Vector;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.Scheduler;
+
+/**
+ * @hide
+ */
+public class RandomScheduler extends Scheduler {
+
+    private Random mRand = new Random();
+
+    public RandomScheduler(FilterGraph graph) {
+        super(graph);
+    }
+
+    @Override
+    public void reset() {
+    }
+
+    @Override
+    public Filter scheduleNextNode() {
+        Vector<Filter> candidates = new Vector<Filter>();
+        for (Filter filter : getGraph().getFilters()) {
+            if (filter.canProcess())
+                candidates.add(filter);
+        }
+        if (candidates.size() > 0) {
+          int r = mRand.nextInt(candidates.size());
+          return candidates.elementAt(r);
+        }
+        return null;
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/RoundRobinScheduler.java b/media/mca/filterfw/java/android/filterfw/core/RoundRobinScheduler.java
new file mode 100644
index 0000000..12cbf19
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/RoundRobinScheduler.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import java.util.Set;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.Scheduler;
+
+/**
+ * @hide
+ */
+public class RoundRobinScheduler extends Scheduler {
+
+    private int mLastPos = -1;
+
+    public RoundRobinScheduler(FilterGraph graph) {
+        super(graph);
+    }
+
+    @Override
+    public void reset() {
+        mLastPos = -1;
+    }
+
+    @Override
+    public Filter scheduleNextNode() {
+        Set<Filter> all_filters = getGraph().getFilters();
+        if (mLastPos >= all_filters.size()) mLastPos = -1;
+        int pos = 0;
+        Filter first = null;
+        int firstNdx = -1;
+        for (Filter filter : all_filters) {
+            if (filter.canProcess()) {
+                if (pos <= mLastPos) {
+                    if (first == null) {
+                        // store the first available filter
+                        first = filter;
+                        firstNdx = pos;
+                    }
+                } else {
+                    // return the next available filter since last
+                    mLastPos = pos;
+                    return filter;
+                }
+            }
+            pos ++;
+        }
+        // going around from the beginning
+        if (first != null ) {
+            mLastPos = firstNdx;
+            return first;
+        }
+        // if there is nothing to be scheduled, still keep the previous
+        // position.
+        return null;
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/Scheduler.java b/media/mca/filterfw/java/android/filterfw/core/Scheduler.java
new file mode 100644
index 0000000..6f0864a
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/Scheduler.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterGraph;
+
+/**
+ * @hide
+ */
+public abstract class Scheduler {
+    // All methods are core internal methods as Scheduler internals are only used by the GraphRunner.
+
+    private FilterGraph mGraph;
+
+    Scheduler(FilterGraph graph) {
+        mGraph = graph;
+    }
+
+    FilterGraph getGraph() {
+        return mGraph;
+    }
+
+    abstract void reset();
+
+    abstract Filter scheduleNextNode();
+
+    boolean finished() {
+        // TODO: Check that the state of all nodes is FINISHED.
+        return true;
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/SerializedFrame.java b/media/mca/filterfw/java/android/filterfw/core/SerializedFrame.java
new file mode 100644
index 0000000..f493fd2
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/SerializedFrame.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.FrameManager;
+import android.filterfw.core.NativeBuffer;
+import android.filterfw.format.ObjectFormat;
+import android.graphics.Bitmap;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OptionalDataException;
+import java.io.OutputStream;
+import java.io.StreamCorruptedException;
+import java.lang.reflect.Constructor;
+import java.nio.ByteBuffer;
+
+/**
+ * A frame that serializes any assigned values. Such a frame is used when passing data objects
+ * between threads.
+ *
+ * @hide
+ */
+public class SerializedFrame extends Frame {
+
+    /**
+     * The initial capacity of the serialized data stream.
+     */
+    private final static int INITIAL_CAPACITY = 64;
+
+    /**
+     * The internal data streams.
+     */
+    private DirectByteOutputStream mByteOutputStream;
+    private ObjectOutputStream mObjectOut;
+
+    /**
+     * An unsynchronized output stream that writes data to an accessible byte array. Callers are
+     * responsible for synchronization. This is more efficient than a ByteArrayOutputStream, as
+     * there are no array copies or synchronization involved to read back written data.
+     */
+    private class DirectByteOutputStream extends OutputStream {
+        private byte[] mBuffer = null;
+        private int mOffset = 0;
+        private int mDataOffset = 0;
+
+        public DirectByteOutputStream(int size) {
+            mBuffer = new byte[size];
+        }
+
+        private final void ensureFit(int bytesToWrite) {
+            if (mOffset + bytesToWrite > mBuffer.length) {
+                byte[] oldBuffer = mBuffer;
+                mBuffer = new byte[Math.max(mOffset + bytesToWrite, mBuffer.length * 2)];
+                System.arraycopy(oldBuffer, 0, mBuffer, 0, mOffset);
+                oldBuffer = null;
+            }
+        }
+
+        public final void markHeaderEnd() {
+            mDataOffset = mOffset;
+        }
+
+        public final int getSize() {
+            return mOffset;
+        }
+
+        public byte[] getByteArray() {
+            return mBuffer;
+        }
+
+        @Override
+        public final void write(byte b[]) {
+            write(b, 0, b.length);
+        }
+
+        @Override
+        public final void write(byte b[], int off, int len) {
+            ensureFit(len);
+            System.arraycopy(b, off, mBuffer, mOffset, len);
+            mOffset += len;
+        }
+
+        @Override
+        public final void write(int b) {
+            ensureFit(1);
+            mBuffer[mOffset++] = (byte)b;
+        }
+
+        public final void reset() {
+            mOffset = mDataOffset;
+        }
+
+        public final DirectByteInputStream getInputStream() {
+            return new DirectByteInputStream(mBuffer, mOffset);
+        }
+    }
+
+    /**
+     * An unsynchronized input stream that reads data directly from a provided byte array. Callers
+     * are responsible for synchronization and ensuring that the byte buffer is valid.
+     */
+    private class DirectByteInputStream extends InputStream {
+
+        private byte[] mBuffer;
+        private int mPos = 0;
+        private int mSize;
+
+        public DirectByteInputStream(byte[] buffer, int size) {
+            mBuffer = buffer;
+            mSize = size;
+        }
+
+        @Override
+        public final int available() {
+            return mSize - mPos;
+        }
+
+        @Override
+        public final int read() {
+            return (mPos < mSize) ? (mBuffer[mPos++] & 0xFF) : -1;
+        }
+
+        @Override
+        public final int read(byte[] b, int off, int len) {
+            if (mPos >= mSize) {
+                return -1;
+            }
+            if ((mPos + len) > mSize) {
+                len = mSize - mPos;
+            }
+            System.arraycopy(mBuffer, mPos, b, off, len);
+            mPos += len;
+            return len;
+        }
+
+        @Override
+        public final long skip(long n) {
+            if ((mPos + n) > mSize) {
+                n = mSize - mPos;
+            }
+            if (n < 0) {
+                return 0;
+            }
+            mPos += n;
+            return n;
+        }
+    }
+
+    SerializedFrame(FrameFormat format, FrameManager frameManager) {
+        super(format, frameManager);
+        setReusable(false);
+
+        // Setup streams
+        try {
+            mByteOutputStream = new DirectByteOutputStream(INITIAL_CAPACITY);
+            mObjectOut = new ObjectOutputStream(mByteOutputStream);
+            mByteOutputStream.markHeaderEnd();
+        } catch (IOException e) {
+            throw new RuntimeException("Could not create serialization streams for "
+                + "SerializedFrame!", e);
+        }
+    }
+
+    static SerializedFrame wrapObject(Object object, FrameManager frameManager) {
+        FrameFormat format = ObjectFormat.fromObject(object, FrameFormat.TARGET_SIMPLE);
+        SerializedFrame result = new SerializedFrame(format, frameManager);
+        result.setObjectValue(object);
+        return result;
+    }
+
+    @Override
+    protected boolean hasNativeAllocation() {
+        return false;
+    }
+
+    @Override
+    protected void releaseNativeAllocation() {
+    }
+
+    @Override
+    public Object getObjectValue() {
+        return deserializeObjectValue();
+    }
+
+    @Override
+    public void setInts(int[] ints) {
+        assertFrameMutable();
+        setGenericObjectValue(ints);
+    }
+
+    @Override
+    public int[] getInts() {
+        Object result = deserializeObjectValue();
+        return (result instanceof int[]) ? (int[])result : null;
+    }
+
+    @Override
+    public void setFloats(float[] floats) {
+        assertFrameMutable();
+        setGenericObjectValue(floats);
+    }
+
+    @Override
+    public float[] getFloats() {
+        Object result = deserializeObjectValue();
+        return (result instanceof float[]) ? (float[])result : null;
+    }
+
+    @Override
+    public void setData(ByteBuffer buffer, int offset, int length) {
+        assertFrameMutable();
+        setGenericObjectValue(ByteBuffer.wrap(buffer.array(), offset, length));
+    }
+
+    @Override
+    public ByteBuffer getData() {
+        Object result = deserializeObjectValue();
+        return (result instanceof ByteBuffer) ? (ByteBuffer)result : null;
+    }
+
+    @Override
+    public void setBitmap(Bitmap bitmap) {
+        assertFrameMutable();
+        setGenericObjectValue(bitmap);
+    }
+
+    @Override
+    public Bitmap getBitmap() {
+        Object result = deserializeObjectValue();
+        return (result instanceof Bitmap) ? (Bitmap)result : null;
+    }
+
+    @Override
+    protected void setGenericObjectValue(Object object) {
+        serializeObjectValue(object);
+    }
+
+    private final void serializeObjectValue(Object object) {
+        try {
+            mByteOutputStream.reset();
+            mObjectOut.writeObject(object);
+            mObjectOut.flush();
+            mObjectOut.close();
+        } catch (IOException e) {
+            throw new RuntimeException("Could not serialize object " + object + " in "
+                + this + "!", e);
+        }
+    }
+
+    private final Object deserializeObjectValue() {
+        try {
+            InputStream inputStream = mByteOutputStream.getInputStream();
+            ObjectInputStream objectStream = new ObjectInputStream(inputStream);
+            return objectStream.readObject();
+        } catch (IOException e) {
+            throw new RuntimeException("Could not deserialize object in " + this + "!", e);
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException("Unable to deserialize object of unknown class in "
+                + this + "!", e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "SerializedFrame (" + getFormat() + ")";
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java
new file mode 100644
index 0000000..a971cb6
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Frame;
+import android.filterfw.core.NativeAllocatorTag;
+import android.filterfw.core.Program;
+import android.filterfw.core.StopWatchMap;
+import android.filterfw.core.VertexFrame;
+import android.filterfw.geometry.Quad;
+import android.opengl.GLES20;
+
+/**
+ * @hide
+ */
+public class ShaderProgram extends Program {
+
+    private int shaderProgramId;
+
+    private int mMaxTileSize = 0;
+
+    // Keep a reference to the GL environment, so that it does not get deallocated while there
+    // are still programs living in it.
+    private GLEnvironment mGLEnvironment;
+
+    private StopWatchMap mTimer = null;
+
+    private void setTimer() {
+        mTimer = new StopWatchMap();
+    }
+
+    // Used from native layer for creating empty wrapper only!
+    private ShaderProgram() {
+    }
+
+    private ShaderProgram(NativeAllocatorTag tag) {
+    }
+
+    public ShaderProgram(FilterContext context, String fragmentShader) {
+        mGLEnvironment = getGLEnvironment(context);
+        allocate(mGLEnvironment, null, fragmentShader);
+        if (!compileAndLink()) {
+            throw new RuntimeException("Could not compile and link shader!");
+        }
+        this.setTimer();
+    }
+
+    public ShaderProgram(FilterContext context, String vertexShader, String fragmentShader) {
+        mGLEnvironment = getGLEnvironment(context);
+        allocate(mGLEnvironment, vertexShader, fragmentShader);
+        if (!compileAndLink()) {
+            throw new RuntimeException("Could not compile and link shader!");
+        }
+        this.setTimer();
+    }
+
+    public static ShaderProgram createIdentity(FilterContext context) {
+        ShaderProgram program = nativeCreateIdentity(getGLEnvironment(context));
+        program.setTimer();
+        return program;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        deallocate();
+    }
+
+    public GLEnvironment getGLEnvironment() {
+        return mGLEnvironment;
+    }
+
+    @Override
+    public void process(Frame[] inputs, Frame output) {
+        if (mTimer.LOG_MFF_RUNNING_TIMES) {
+          mTimer.start("glFinish");
+          GLES20.glFinish();
+          mTimer.stop("glFinish");
+        }
+
+        // Get the GL input frames
+        // TODO: We do the same in the NativeProgram... can we find a better way?!
+        GLFrame[] glInputs = new GLFrame[inputs.length];
+        for (int i = 0; i < inputs.length; ++i) {
+            if (inputs[i] instanceof GLFrame) {
+                glInputs[i] = (GLFrame)inputs[i];
+            } else {
+                throw new RuntimeException("ShaderProgram got non-GL frame as input " + i + "!");
+            }
+        }
+
+        // Get the GL output frame
+        GLFrame glOutput = null;
+        if (output instanceof GLFrame) {
+            glOutput = (GLFrame)output;
+        } else {
+            throw new RuntimeException("ShaderProgram got non-GL output frame!");
+        }
+
+        // Adjust tiles to meet maximum tile size requirement
+        if (mMaxTileSize > 0) {
+            int xTiles = (output.getFormat().getWidth() + mMaxTileSize - 1) / mMaxTileSize;
+            int yTiles = (output.getFormat().getHeight() + mMaxTileSize - 1) / mMaxTileSize;
+            setShaderTileCounts(xTiles, yTiles);
+        }
+
+        // Process!
+        if (!shaderProcess(glInputs, glOutput)) {
+            throw new RuntimeException("Error executing ShaderProgram!");
+        }
+
+        if (mTimer.LOG_MFF_RUNNING_TIMES) {
+          GLES20.glFinish();
+        }
+    }
+
+    @Override
+    public void setHostValue(String variableName, Object value) {
+        if (!setUniformValue(variableName, value)) {
+            throw new RuntimeException("Error setting uniform value for variable '" +
+                                       variableName + "'!");
+        }
+    }
+
+    @Override
+    public Object getHostValue(String variableName) {
+        return getUniformValue(variableName);
+    }
+
+    public void setAttributeValues(String attributeName, float[] data, int componentCount) {
+        if (!setShaderAttributeValues(attributeName, data, componentCount)) {
+            throw new RuntimeException("Error setting attribute value for attribute '" +
+                                       attributeName + "'!");
+        }
+    }
+
+    public void setAttributeValues(String attributeName,
+                                   VertexFrame vertexData,
+                                   int type,
+                                   int componentCount,
+                                   int strideInBytes,
+                                   int offsetInBytes,
+                                   boolean normalize) {
+        if (!setShaderAttributeVertexFrame(attributeName,
+                                           vertexData,
+                                           type,
+                                           componentCount,
+                                           strideInBytes,
+                                           offsetInBytes,
+                                           normalize)) {
+            throw new RuntimeException("Error setting attribute value for attribute '" +
+                                       attributeName + "'!");
+        }
+    }
+
+    public void setSourceRegion(Quad region) {
+        setSourceRegion(region.p0.x, region.p0.y,
+                        region.p1.x, region.p1.y,
+                        region.p2.x, region.p2.y,
+                        region.p3.x, region.p3.y);
+    }
+
+    public void setTargetRegion(Quad region) {
+        setTargetRegion(region.p0.x, region.p0.y,
+                        region.p1.x, region.p1.y,
+                        region.p2.x, region.p2.y,
+                        region.p3.x, region.p3.y);
+    }
+
+    public void setSourceRect(float x, float y, float width, float height) {
+        setSourceRegion(x, y, x + width, y, x, y + height, x + width, y + height);
+    }
+
+    public void setTargetRect(float x, float y, float width, float height) {
+        setTargetRegion(x, y, x + width, y, x, y + height, x + width, y + height);
+    }
+
+    public void setClearsOutput(boolean clears) {
+        if (!setShaderClearsOutput(clears)) {
+            throw new RuntimeException("Could not set clears-output flag to " + clears + "!");
+        }
+    }
+
+    public void setClearColor(float r, float g, float b) {
+        if (!setShaderClearColor(r, g, b)) {
+            throw new RuntimeException("Could not set clear color to " + r + "," + g + "," + b + "!");
+        }
+    }
+
+    public void setBlendEnabled(boolean enable) {
+        if (!setShaderBlendEnabled(enable)) {
+            throw new RuntimeException("Could not set Blending " + enable + "!");
+        }
+    }
+
+    public void setBlendFunc(int sfactor, int dfactor) {
+        if (!setShaderBlendFunc(sfactor, dfactor)) {
+            throw new RuntimeException("Could not set BlendFunc " + sfactor +","+ dfactor + "!");
+        }
+    }
+
+    public void setDrawMode(int drawMode) {
+        if (!setShaderDrawMode(drawMode)) {
+            throw new RuntimeException("Could not set GL draw-mode to " + drawMode + "!");
+        }
+    }
+
+    public void setVertexCount(int count) {
+        if (!setShaderVertexCount(count)) {
+            throw new RuntimeException("Could not set GL vertex count to " + count + "!");
+        }
+    }
+
+    public void setMaximumTileSize(int size) {
+        mMaxTileSize = size;
+    }
+
+    public void beginDrawing() {
+        if (!beginShaderDrawing()) {
+            throw new RuntimeException("Could not prepare shader-program for drawing!");
+        }
+    }
+
+    private static GLEnvironment getGLEnvironment(FilterContext context) {
+        GLEnvironment result = context != null ? context.getGLEnvironment() : null;
+        if (result == null) {
+            throw new NullPointerException("Attempting to create ShaderProgram with no GL "
+                + "environment in place!");
+        }
+        return result;
+    }
+
+    static {
+        System.loadLibrary("filterfw");
+    }
+
+    private native boolean allocate(GLEnvironment glEnv,
+                                    String vertexShader,
+                                    String fragmentShader);
+
+    private native boolean deallocate();
+
+    private native boolean compileAndLink();
+
+    private native boolean shaderProcess(GLFrame[] inputs, GLFrame output);
+
+    private native boolean setUniformValue(String name, Object value);
+
+    private native Object getUniformValue(String name);
+
+    public native boolean setSourceRegion(float x0, float y0, float x1, float y1,
+                                          float x2, float y2, float x3, float y3);
+
+    private native boolean setTargetRegion(float x0, float y0, float x1, float y1,
+                                           float x2, float y2, float x3, float y3);
+
+    private static native ShaderProgram nativeCreateIdentity(GLEnvironment glEnv);
+
+    private native boolean setShaderClearsOutput(boolean clears);
+
+    private native boolean setShaderBlendEnabled(boolean enable);
+
+    private native boolean setShaderBlendFunc(int sfactor, int dfactor);
+
+    private native boolean setShaderClearColor(float r, float g, float b);
+
+    private native boolean setShaderDrawMode(int drawMode);
+
+    private native boolean setShaderTileCounts(int xCount, int yCount);
+
+    private native boolean setShaderVertexCount(int vertexCount);
+
+    private native boolean beginShaderDrawing();
+
+    private native boolean setShaderAttributeValues(String attributeName,
+                                                    float[] data,
+                                                    int componentCount);
+
+    private native boolean setShaderAttributeVertexFrame(String attributeName,
+                                                         VertexFrame vertexData,
+                                                         int type,
+                                                         int componentCount,
+                                                         int strideInBytes,
+                                                         int offsetInBytes,
+                                                         boolean normalize);
+
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/SimpleFrame.java b/media/mca/filterfw/java/android/filterfw/core/SimpleFrame.java
new file mode 100644
index 0000000..534a30d
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/SimpleFrame.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.FrameManager;
+import android.filterfw.core.NativeBuffer;
+import android.filterfw.format.ObjectFormat;
+import android.graphics.Bitmap;
+
+import java.lang.reflect.Constructor;
+import java.nio.ByteBuffer;
+
+/**
+ * @hide
+ */
+public class SimpleFrame extends Frame {
+
+    private Object mObject;
+
+    SimpleFrame(FrameFormat format, FrameManager frameManager) {
+        super(format, frameManager);
+        initWithFormat(format);
+        setReusable(false);
+    }
+
+    static SimpleFrame wrapObject(Object object, FrameManager frameManager) {
+        FrameFormat format = ObjectFormat.fromObject(object, FrameFormat.TARGET_SIMPLE);
+        SimpleFrame result = new SimpleFrame(format, frameManager);
+        result.setObjectValue(object);
+        return result;
+    }
+
+    private void initWithFormat(FrameFormat format) {
+        final int count = format.getLength();
+        final int baseType = format.getBaseType();
+        switch (baseType) {
+            case FrameFormat.TYPE_BYTE:
+                mObject = new byte[count];
+                break;
+            case FrameFormat.TYPE_INT16:
+                mObject = new short[count];
+                break;
+            case FrameFormat.TYPE_INT32:
+                mObject = new int[count];
+                break;
+            case FrameFormat.TYPE_FLOAT:
+                mObject = new float[count];
+                break;
+            case FrameFormat.TYPE_DOUBLE:
+                mObject = new double[count];
+                break;
+            default:
+                mObject = null;
+                break;
+        }
+    }
+
+    @Override
+    protected boolean hasNativeAllocation() {
+        return false;
+    }
+
+    @Override
+    protected void releaseNativeAllocation() {
+    }
+
+    @Override
+    public Object getObjectValue() {
+        return mObject;
+    }
+
+    @Override
+    public void setInts(int[] ints) {
+        assertFrameMutable();
+        setGenericObjectValue(ints);
+    }
+
+    @Override
+    public int[] getInts() {
+        return (mObject instanceof int[]) ? (int[])mObject : null;
+    }
+
+    @Override
+    public void setFloats(float[] floats) {
+        assertFrameMutable();
+        setGenericObjectValue(floats);
+    }
+
+    @Override
+    public float[] getFloats() {
+        return (mObject instanceof float[]) ? (float[])mObject : null;
+    }
+
+    @Override
+    public void setData(ByteBuffer buffer, int offset, int length) {
+        assertFrameMutable();
+        setGenericObjectValue(ByteBuffer.wrap(buffer.array(), offset, length));
+    }
+
+    @Override
+    public ByteBuffer getData() {
+        return (mObject instanceof ByteBuffer) ? (ByteBuffer)mObject : null;
+    }
+
+    @Override
+    public void setBitmap(Bitmap bitmap) {
+        assertFrameMutable();
+        setGenericObjectValue(bitmap);
+    }
+
+    @Override
+    public Bitmap getBitmap() {
+        return (mObject instanceof Bitmap) ? (Bitmap)mObject : null;
+    }
+
+    private void setFormatObjectClass(Class objectClass) {
+        MutableFrameFormat format = getFormat().mutableCopy();
+        format.setObjectClass(objectClass);
+        setFormat(format);
+    }
+
+    @Override
+    protected void setGenericObjectValue(Object object) {
+        // Update the FrameFormat class
+        // TODO: Take this out! FrameFormats should not be modified and convenience formats used
+        // instead!
+        FrameFormat format = getFormat();
+        if (format.getObjectClass() == null) {
+            setFormatObjectClass(object.getClass());
+        } else if (!format.getObjectClass().isAssignableFrom(object.getClass())) {
+            throw new RuntimeException(
+                "Attempting to set object value of type '" + object.getClass() + "' on " +
+                "SimpleFrame of type '" + format.getObjectClass() + "'!");
+        }
+
+        // Set the object value
+        mObject = object;
+    }
+
+    @Override
+    public String toString() {
+        return "SimpleFrame (" + getFormat() + ")";
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/SimpleFrameManager.java b/media/mca/filterfw/java/android/filterfw/core/SimpleFrameManager.java
new file mode 100644
index 0000000..e2b9047
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/SimpleFrameManager.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.FrameManager;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.SimpleFrame;
+import android.filterfw.core.VertexFrame;
+
+/**
+ * @hide
+ */
+public class SimpleFrameManager extends FrameManager {
+
+    public SimpleFrameManager() {
+    }
+
+    @Override
+    public Frame newFrame(FrameFormat format) {
+        return createNewFrame(format);
+    }
+
+    @Override
+    public Frame newBoundFrame(FrameFormat format, int bindingType, long bindingId) {
+        Frame result = null;
+        switch(format.getTarget()) {
+            case FrameFormat.TARGET_GPU: {
+                GLFrame glFrame = new GLFrame(format, this, bindingType, bindingId);
+                glFrame.init(getGLEnvironment());
+                result = glFrame;
+                break;
+            }
+
+            default:
+                throw new RuntimeException("Attached frames are not supported for target type: "
+                    + FrameFormat.targetToString(format.getTarget()) + "!");
+        }
+        return result;
+    }
+
+    private Frame createNewFrame(FrameFormat format) {
+        Frame result = null;
+        switch(format.getTarget()) {
+            case FrameFormat.TARGET_SIMPLE:
+                result = new SimpleFrame(format, this);
+                break;
+
+            case FrameFormat.TARGET_NATIVE:
+                result = new NativeFrame(format, this);
+                break;
+
+            case FrameFormat.TARGET_GPU: {
+                GLFrame glFrame = new GLFrame(format, this);
+                glFrame.init(getGLEnvironment());
+                result = glFrame;
+                break;
+            }
+
+            case FrameFormat.TARGET_VERTEXBUFFER: {
+                result = new VertexFrame(format, this);
+                break;
+            }
+
+            default:
+                throw new RuntimeException("Unsupported frame target type: " +
+                                           FrameFormat.targetToString(format.getTarget()) + "!");
+        }
+        return result;
+    }
+
+    @Override
+    public Frame retainFrame(Frame frame) {
+        frame.incRefCount();
+        return frame;
+    }
+
+    @Override
+    public Frame releaseFrame(Frame frame) {
+        int refCount = frame.decRefCount();
+        if (refCount == 0 && frame.hasNativeAllocation()) {
+            frame.releaseNativeAllocation();
+            return null;
+        } else if (refCount < 0) {
+            throw new RuntimeException("Frame reference count dropped below 0!");
+        }
+        return frame;
+    }
+
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/SimpleScheduler.java b/media/mca/filterfw/java/android/filterfw/core/SimpleScheduler.java
new file mode 100644
index 0000000..bb4e5ba
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/SimpleScheduler.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.Scheduler;
+
+/**
+ * @hide
+ */
+public class SimpleScheduler extends Scheduler {
+
+    public SimpleScheduler(FilterGraph graph) {
+        super(graph);
+    }
+
+    @Override
+    public void reset() {
+    }
+
+    @Override
+    public Filter scheduleNextNode() {
+        for (Filter filter : getGraph().getFilters()) {
+            if (filter.canProcess())
+                return filter;
+        }
+        return null;
+    }
+
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/StopWatchMap.java b/media/mca/filterfw/java/android/filterfw/core/StopWatchMap.java
new file mode 100644
index 0000000..444a1fc
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/StopWatchMap.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterfw.core;
+
+import android.os.SystemClock;
+import android.util.Log;
+import java.util.HashMap;
+
+/**
+ * @hide
+ */
+class StopWatch {
+
+    private int STOP_WATCH_LOGGING_PERIOD = 200;
+    private String TAG = "MFF";
+
+    private String mName;
+    private long mStartTime;
+    private long mTotalTime;
+    private int mNumCalls;
+
+    public StopWatch(String name) {
+        mName = name;
+        mStartTime = -1;
+        mTotalTime = 0;
+        mNumCalls = 0;
+    }
+
+    public void start() {
+        if (mStartTime != -1) {
+             throw new RuntimeException(
+                 "Calling start with StopWatch already running");
+        }
+        mStartTime = SystemClock.elapsedRealtime();
+    }
+
+    public void stop() {
+        if (mStartTime == -1) {
+             throw new RuntimeException(
+                 "Calling stop with StopWatch already stopped");
+        }
+        long stopTime = SystemClock.elapsedRealtime();
+        mTotalTime += stopTime - mStartTime;
+        ++mNumCalls;
+        mStartTime = -1;
+        if (mNumCalls % STOP_WATCH_LOGGING_PERIOD == 0) {
+            Log.i(TAG, "AVG ms/call " + mName + ": " +
+                  String.format("%.1f", mTotalTime * 1.0f / mNumCalls));
+            mTotalTime = 0;
+            mNumCalls = 0;
+        }
+    }
+
+}
+
+public class StopWatchMap {
+
+    public boolean LOG_MFF_RUNNING_TIMES = false;
+
+    private HashMap<String, StopWatch> mStopWatches = null;
+
+    public StopWatchMap() {
+        mStopWatches = new HashMap<String, StopWatch>();
+    }
+
+    public void start(String stopWatchName) {
+        if (!LOG_MFF_RUNNING_TIMES) {
+            return;
+        }
+        if (!mStopWatches.containsKey(stopWatchName)) {
+            mStopWatches.put(stopWatchName, new StopWatch(stopWatchName));
+        }
+        mStopWatches.get(stopWatchName).start();
+    }
+
+    public void stop(String stopWatchName) {
+        if (!LOG_MFF_RUNNING_TIMES) {
+            return;
+        }
+        if (!mStopWatches.containsKey(stopWatchName)) {
+            throw new RuntimeException(
+                "Calling stop with unknown stopWatchName: " + stopWatchName);
+        }
+        mStopWatches.get(stopWatchName).stop();
+    }
+
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/StreamPort.java b/media/mca/filterfw/java/android/filterfw/core/StreamPort.java
new file mode 100644
index 0000000..8520a0b
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/StreamPort.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+/**
+ * @hide
+ */
+public class StreamPort extends InputPort {
+
+    private Frame mFrame;
+    private boolean mPersistent;
+
+    public StreamPort(Filter filter, String name) {
+        super(filter, name);
+    }
+
+    @Override
+    public void clear() {
+        if (mFrame != null) {
+            mFrame.release();
+            mFrame = null;
+        }
+    }
+
+    @Override
+    public void setFrame(Frame frame) {
+        assignFrame(frame, true);
+    }
+
+    @Override
+    public void pushFrame(Frame frame) {
+        assignFrame(frame, false);
+    }
+
+    protected synchronized void assignFrame(Frame frame, boolean persistent) {
+        assertPortIsOpen();
+        checkFrameType(frame, persistent);
+
+        if (persistent) {
+            if (mFrame != null) {
+                mFrame.release();
+            }
+        } else if (mFrame != null) {
+            throw new RuntimeException(
+                "Attempting to push more than one frame on port: " + this + "!");
+        }
+        mFrame = frame.retain();
+        mFrame.markReadOnly();
+        mPersistent = persistent;
+    }
+
+    @Override
+    public synchronized Frame pullFrame() {
+        // Make sure we have a frame
+        if (mFrame == null) {
+            throw new RuntimeException("No frame available to pull on port: " + this + "!");
+        }
+
+        // Return a retained result
+        Frame result = mFrame;
+        if (mPersistent) {
+            mFrame.retain();
+        } else {
+            mFrame = null;
+        }
+        return result;
+    }
+
+    @Override
+    public synchronized boolean hasFrame() {
+        return mFrame != null;
+    }
+
+    @Override
+    public String toString() {
+        return "input " + super.toString();
+    }
+
+    @Override
+    public synchronized void transfer(FilterContext context) {
+        if (mFrame != null) {
+            checkFrameManager(mFrame, context);
+        }
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/SyncRunner.java b/media/mca/filterfw/java/android/filterfw/core/SyncRunner.java
new file mode 100644
index 0000000..abbd359b
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/SyncRunner.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.os.ConditionVariable;
+import android.util.Log;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @hide
+ */
+public class SyncRunner extends GraphRunner {
+
+    private Scheduler mScheduler = null;
+
+    private OnRunnerDoneListener mDoneListener = null;
+    private ScheduledThreadPoolExecutor mWakeExecutor = new ScheduledThreadPoolExecutor(1);
+    private ConditionVariable mWakeCondition = new ConditionVariable();
+
+    private StopWatchMap mTimer = null;
+
+    private final boolean mLogVerbose;
+    private final static String TAG = "SyncRunner";
+
+    // TODO: Provide factory based constructor?
+    public SyncRunner(FilterContext context, FilterGraph graph, Class schedulerClass) {
+        super(context);
+
+        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+
+        if (mLogVerbose) Log.v(TAG, "Initializing SyncRunner");
+
+        // Create the scheduler
+        if (Scheduler.class.isAssignableFrom(schedulerClass)) {
+            try {
+                Constructor schedulerConstructor = schedulerClass.getConstructor(FilterGraph.class);
+                mScheduler = (Scheduler)schedulerConstructor.newInstance(graph);
+            } catch (NoSuchMethodException e) {
+                throw new RuntimeException("Scheduler does not have constructor <init>(FilterGraph)!", e);
+            } catch (InstantiationException e) {
+                throw new RuntimeException("Could not instantiate the Scheduler instance!", e);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException("Cannot access Scheduler constructor!", e);
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException("Scheduler constructor threw an exception", e);
+            } catch (Exception e) {
+                throw new RuntimeException("Could not instantiate Scheduler", e);
+            }
+        } else {
+            throw new IllegalArgumentException("Class provided is not a Scheduler subclass!");
+        }
+
+        // Associate this runner and the graph with the context
+        mFilterContext = context;
+        mFilterContext.addGraph(graph);
+
+        mTimer = new StopWatchMap();
+
+        if (mLogVerbose) Log.v(TAG, "Setting up filters");
+
+        // Setup graph filters
+        graph.setupFilters();
+    }
+
+    @Override
+    public FilterGraph getGraph() {
+        return mScheduler != null ? mScheduler.getGraph() : null;
+    }
+
+    public int step() {
+        assertReadyToStep();
+        if (!getGraph().isReady() ) {
+            throw new RuntimeException("Trying to process graph that is not open!");
+        }
+        return performStep() ? RESULT_RUNNING : determinePostRunState();
+    }
+
+    public void beginProcessing() {
+        mScheduler.reset();
+        getGraph().beginProcessing();
+    }
+
+    public void close() {
+        // Close filters
+        if (mLogVerbose) Log.v(TAG, "Closing graph.");
+        getGraph().closeFilters(mFilterContext);
+        mScheduler.reset();
+    }
+
+    @Override
+    public void run() {
+        if (mLogVerbose) Log.v(TAG, "Beginning run.");
+
+        assertReadyToStep();
+
+        // Preparation
+        beginProcessing();
+        boolean glActivated = activateGlContext();
+
+        // Run
+        boolean keepRunning = true;
+        while (keepRunning) {
+            keepRunning = performStep();
+        }
+
+        // Cleanup
+        if (glActivated) {
+            deactivateGlContext();
+        }
+
+        // Call completion callback if set
+        if (mDoneListener != null) {
+            if (mLogVerbose) Log.v(TAG, "Calling completion listener.");
+            mDoneListener.onRunnerDone(determinePostRunState());
+        }
+        if (mLogVerbose) Log.v(TAG, "Run complete");
+    }
+
+    @Override
+    public boolean isRunning() {
+        return false;
+    }
+
+    @Override
+    public void setDoneCallback(OnRunnerDoneListener listener) {
+        mDoneListener = listener;
+    }
+
+    @Override
+    public void stop() {
+        throw new RuntimeException("SyncRunner does not support stopping a graph!");
+    }
+
+    @Override
+    synchronized public Exception getError() {
+        return null;
+    }
+
+    protected void waitUntilWake() {
+        mWakeCondition.block();
+    }
+
+    protected void processFilterNode(Filter filter) {
+        if (mLogVerbose) Log.v(TAG, "Processing filter node");
+        filter.performProcess(mFilterContext);
+        if (filter.getStatus() == Filter.STATUS_ERROR) {
+            throw new RuntimeException("There was an error executing " + filter + "!");
+        } else if (filter.getStatus() == Filter.STATUS_SLEEPING) {
+            if (mLogVerbose) Log.v(TAG, "Scheduling filter wakeup");
+            scheduleFilterWake(filter, filter.getSleepDelay());
+        }
+    }
+
+    protected void scheduleFilterWake(Filter filter, int delay) {
+        // Close the wake condition
+        mWakeCondition.close();
+
+        // Schedule the wake-up
+        final Filter filterToSchedule = filter;
+        final ConditionVariable conditionToWake = mWakeCondition;
+
+        mWakeExecutor.schedule(new Runnable() {
+          @Override
+          public void run() {
+                filterToSchedule.unsetStatus(Filter.STATUS_SLEEPING);
+                conditionToWake.open();
+            }
+        }, delay, TimeUnit.MILLISECONDS);
+    }
+
+    protected int determinePostRunState() {
+        boolean isBlocked = false;
+        for (Filter filter : mScheduler.getGraph().getFilters()) {
+            if (filter.isOpen()) {
+                if (filter.getStatus() == Filter.STATUS_SLEEPING) {
+                    // If ANY node is sleeping, we return our state as sleeping
+                    return RESULT_SLEEPING;
+                } else {
+                    // If a node is still open, it is blocked (by input or output)
+                    return RESULT_BLOCKED;
+                }
+            }
+        }
+        return RESULT_FINISHED;
+    }
+
+    // Core internal methods ///////////////////////////////////////////////////////////////////////
+    boolean performStep() {
+        if (mLogVerbose) Log.v(TAG, "Performing one step.");
+        Filter filter = mScheduler.scheduleNextNode();
+        if (filter != null) {
+            mTimer.start(filter.getName());
+            processFilterNode(filter);
+            mTimer.stop(filter.getName());
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    void assertReadyToStep() {
+        if (mScheduler == null) {
+            throw new RuntimeException("Attempting to run schedule with no scheduler in place!");
+        } else if (getGraph() == null) {
+            throw new RuntimeException("Calling step on scheduler with no graph in place!");
+        }
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/VertexFrame.java b/media/mca/filterfw/java/android/filterfw/core/VertexFrame.java
new file mode 100644
index 0000000..6982ce3
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/VertexFrame.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.core;
+
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.FrameManager;
+import android.graphics.Bitmap;
+
+import java.nio.ByteBuffer;
+
+/**
+ * @hide
+ */
+public class VertexFrame extends Frame {
+
+    private int vertexFrameId = -1;
+
+    VertexFrame(FrameFormat format, FrameManager frameManager) {
+        super(format, frameManager);
+        if (getFormat().getSize() <= 0) {
+            throw new IllegalArgumentException("Initializing vertex frame with zero size!");
+        } else {
+            if (!nativeAllocate(getFormat().getSize())) {
+                throw new RuntimeException("Could not allocate vertex frame!");
+            }
+        }
+    }
+
+    @Override
+    protected synchronized boolean hasNativeAllocation() {
+        return vertexFrameId != -1;
+    }
+
+    @Override
+    protected synchronized void releaseNativeAllocation() {
+        nativeDeallocate();
+        vertexFrameId = -1;
+    }
+
+    @Override
+    public Object getObjectValue() {
+        throw new RuntimeException("Vertex frames do not support reading data!");
+    }
+
+    @Override
+    public void setInts(int[] ints) {
+        assertFrameMutable();
+        if (!setNativeInts(ints)) {
+            throw new RuntimeException("Could not set int values for vertex frame!");
+        }
+    }
+
+    @Override
+    public int[] getInts() {
+        throw new RuntimeException("Vertex frames do not support reading data!");
+    }
+
+    @Override
+    public void setFloats(float[] floats) {
+        assertFrameMutable();
+        if (!setNativeFloats(floats)) {
+            throw new RuntimeException("Could not set int values for vertex frame!");
+        }
+    }
+
+    @Override
+    public float[] getFloats() {
+        throw new RuntimeException("Vertex frames do not support reading data!");
+    }
+
+    @Override
+    public void setData(ByteBuffer buffer, int offset, int length) {
+        assertFrameMutable();
+        byte[] bytes = buffer.array();
+        if (getFormat().getSize() != bytes.length) {
+            throw new RuntimeException("Data size in setData does not match vertex frame size!");
+        } else if (!setNativeData(bytes, offset, length)) {
+            throw new RuntimeException("Could not set vertex frame data!");
+        }
+    }
+
+    @Override
+    public ByteBuffer getData() {
+        throw new RuntimeException("Vertex frames do not support reading data!");
+    }
+
+    @Override
+    public void setBitmap(Bitmap bitmap) {
+        throw new RuntimeException("Unsupported: Cannot set vertex frame bitmap value!");
+    }
+
+    @Override
+    public Bitmap getBitmap() {
+        throw new RuntimeException("Vertex frames do not support reading data!");
+    }
+
+    @Override
+    public void setDataFromFrame(Frame frame) {
+        // TODO: Optimize
+        super.setDataFromFrame(frame);
+    }
+
+    public int getVboId() {
+        return getNativeVboId();
+    }
+
+    @Override
+    public String toString() {
+        return "VertexFrame (" + getFormat() + ") with VBO ID " + getVboId();
+    }
+
+    static {
+        System.loadLibrary("filterfw");
+    }
+
+    private native boolean nativeAllocate(int size);
+
+    private native boolean nativeDeallocate();
+
+    private native boolean setNativeData(byte[] data, int offset, int length);
+
+    private native boolean setNativeInts(int[] ints);
+
+    private native boolean setNativeFloats(float[] floats);
+
+    private native int getNativeVboId();
+}
diff --git a/media/mca/filterfw/java/android/filterfw/core/package-info.java b/media/mca/filterfw/java/android/filterfw/core/package-info.java
new file mode 100644
index 0000000..4afda1b
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/core/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * @hide
+ */
+package android.filterfw.core;
diff --git a/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java
new file mode 100644
index 0000000..d57f47c
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.format;
+
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.MutableFrameFormat;
+import android.graphics.Bitmap;
+
+/**
+ * @hide
+ */
+public class ImageFormat {
+
+    public static final String COLORSPACE_KEY = "colorspace";
+
+    public static final int COLORSPACE_GRAY  = 1;
+    public static final int COLORSPACE_RGB   = 2;
+    public static final int COLORSPACE_RGBA  = 3;
+    public static final int COLORSPACE_YUV   = 4;
+
+    public static MutableFrameFormat create(int width,
+                                            int height,
+                                            int colorspace,
+                                            int bytesPerSample,
+                                            int target) {
+        MutableFrameFormat result = new MutableFrameFormat(FrameFormat.TYPE_BYTE, target);
+        result.setDimensions(width, height);
+        result.setBytesPerSample(bytesPerSample);
+        result.setMetaValue(COLORSPACE_KEY, colorspace);
+        if (target == FrameFormat.TARGET_SIMPLE) {
+            result.setObjectClass(Bitmap.class);
+        }
+        return result;
+    }
+
+    public static MutableFrameFormat create(int width,
+                                            int height,
+                                            int colorspace,
+                                            int target) {
+        return create(width,
+                      height,
+                      colorspace,
+                      bytesPerSampleForColorspace(colorspace),
+                      target);
+    }
+
+    public static MutableFrameFormat create(int colorspace, int target) {
+        return create(FrameFormat.SIZE_UNSPECIFIED,
+                      FrameFormat.SIZE_UNSPECIFIED,
+                      colorspace,
+                      bytesPerSampleForColorspace(colorspace),
+                      target);
+    }
+
+    public static MutableFrameFormat create(int colorspace) {
+        return create(FrameFormat.SIZE_UNSPECIFIED,
+                      FrameFormat.SIZE_UNSPECIFIED,
+                      colorspace,
+                      bytesPerSampleForColorspace(colorspace),
+                      FrameFormat.TARGET_UNSPECIFIED);
+    }
+
+    public static int bytesPerSampleForColorspace(int colorspace) {
+        switch (colorspace) {
+            case COLORSPACE_GRAY:
+                return 1;
+            case COLORSPACE_RGB:
+                return 3;
+            case COLORSPACE_RGBA:
+                return 4;
+            case COLORSPACE_YUV:
+                return 3;
+            default:
+                throw new RuntimeException("Unknown colorspace id " + colorspace + "!");
+        }
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/format/ObjectFormat.java b/media/mca/filterfw/java/android/filterfw/format/ObjectFormat.java
new file mode 100644
index 0000000..ae39628
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/format/ObjectFormat.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.format;
+
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeBuffer;
+
+/**
+ * @hide
+ */
+public class ObjectFormat {
+
+    public static MutableFrameFormat fromClass(Class clazz, int count, int target) {
+        // Create frame format
+        MutableFrameFormat result = new MutableFrameFormat(FrameFormat.TYPE_OBJECT, target);
+        result.setObjectClass(getBoxedClass(clazz));
+        if (count != FrameFormat.SIZE_UNSPECIFIED) {
+            result.setDimensions(count);
+        }
+        result.setBytesPerSample(bytesPerSampleForClass(clazz, target));
+        return result;
+    }
+
+    public static MutableFrameFormat fromClass(Class clazz, int target) {
+        return fromClass(clazz, FrameFormat.SIZE_UNSPECIFIED, target);
+    }
+
+    public static MutableFrameFormat fromObject(Object object, int target) {
+        return object == null
+            ? new MutableFrameFormat(FrameFormat.TYPE_OBJECT, target)
+            : fromClass(object.getClass(), FrameFormat.SIZE_UNSPECIFIED, target);
+    }
+
+    public static MutableFrameFormat fromObject(Object object, int count, int target) {
+        return object == null
+            ? new MutableFrameFormat(FrameFormat.TYPE_OBJECT, target)
+            : fromClass(object.getClass(), count, target);
+    }
+
+    private static int bytesPerSampleForClass(Class clazz, int target) {
+        // Native targets have objects manifested in a byte buffer. Thus it is important to
+        // correctly determine the size of single element here.
+        if (target == FrameFormat.TARGET_NATIVE) {
+            if (!NativeBuffer.class.isAssignableFrom(clazz)) {
+                throw new IllegalArgumentException("Native object-based formats must be of a " +
+                    "NativeBuffer subclass! (Received class: " + clazz + ").");
+            }
+            try {
+                return ((NativeBuffer)clazz.newInstance()).getElementSize();
+            } catch (Exception e) {
+                throw new RuntimeException("Could not determine the size of an element in a "
+                    + "native object-based frame of type " + clazz + "! Perhaps it is missing a "
+                    + "default constructor?");
+            }
+        } else {
+            return FrameFormat.BYTES_PER_SAMPLE_UNSPECIFIED;
+        }
+    }
+
+    private static Class getBoxedClass(Class type) {
+        // Check if type is primitive
+        if (type.isPrimitive()) {
+            // Yes -> box it
+            if (type == boolean.class) {
+                return java.lang.Boolean.class;
+            } else if (type == byte.class) {
+                return java.lang.Byte.class;
+            } else if (type == char.class) {
+                return java.lang.Character.class;
+            } else if (type == short.class) {
+                return java.lang.Short.class;
+            } else if (type == int.class) {
+                return java.lang.Integer.class;
+            } else if (type == long.class) {
+                return java.lang.Long.class;
+            } else if (type == float.class) {
+                return java.lang.Float.class;
+            } else if (type == double.class) {
+                return java.lang.Double.class;
+            } else {
+                throw new IllegalArgumentException(
+                    "Unknown primitive type: " + type.getSimpleName() + "!");
+            }
+        } else {
+            // No -> return it
+            return type;
+        }
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/format/PrimitiveFormat.java b/media/mca/filterfw/java/android/filterfw/format/PrimitiveFormat.java
new file mode 100644
index 0000000..40f07aa
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/format/PrimitiveFormat.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.format;
+
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.MutableFrameFormat;
+
+/**
+ * @hide
+ */
+public class PrimitiveFormat {
+
+    public static MutableFrameFormat createByteFormat(int count, int target) {
+        return createFormat(FrameFormat.TYPE_BYTE, count, target);
+    }
+
+    public static MutableFrameFormat createInt16Format(int count, int target) {
+        return createFormat(FrameFormat.TYPE_INT16, count, target);
+    }
+
+    public static MutableFrameFormat createInt32Format(int count, int target) {
+        return createFormat(FrameFormat.TYPE_INT32, count, target);
+    }
+
+    public static MutableFrameFormat createFloatFormat(int count, int target) {
+        return createFormat(FrameFormat.TYPE_FLOAT, count, target);
+    }
+
+    public static MutableFrameFormat createDoubleFormat(int count, int target) {
+        return createFormat(FrameFormat.TYPE_DOUBLE, count, target);
+    }
+
+    public static MutableFrameFormat createByteFormat(int target) {
+        return createFormat(FrameFormat.TYPE_BYTE, target);
+    }
+
+    public static MutableFrameFormat createInt16Format(int target) {
+        return createFormat(FrameFormat.TYPE_INT16, target);
+    }
+
+    public static MutableFrameFormat createInt32Format(int target) {
+        return createFormat(FrameFormat.TYPE_INT32, target);
+    }
+
+    public static MutableFrameFormat createFloatFormat(int target) {
+        return createFormat(FrameFormat.TYPE_FLOAT, target);
+    }
+
+    public static MutableFrameFormat createDoubleFormat(int target) {
+        return createFormat(FrameFormat.TYPE_DOUBLE, target);
+    }
+
+    private static MutableFrameFormat createFormat(int baseType, int count, int target) {
+        MutableFrameFormat result = new MutableFrameFormat(baseType, target);
+        result.setDimensions(count);
+        return result;
+    }
+
+    private static MutableFrameFormat createFormat(int baseType, int target) {
+        MutableFrameFormat result = new MutableFrameFormat(baseType, target);
+        result.setDimensionCount(1);
+        return result;
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/format/package-info.java b/media/mca/filterfw/java/android/filterfw/format/package-info.java
new file mode 100644
index 0000000..dfd9a3f
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/format/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * @hide
+ */
+package android.filterfw.format;
diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Point.java b/media/mca/filterfw/java/android/filterfw/geometry/Point.java
new file mode 100644
index 0000000..8207c72c
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/geometry/Point.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.geometry;
+
+import java.lang.Math;
+
+/**
+ * @hide
+ */
+public class Point {
+
+    public float x;
+    public float y;
+
+    public Point() {
+    }
+
+    public Point(float x, float y) {
+        this.x = x;
+        this.y = y;
+    }
+
+    public void set(float x, float y) {
+        this.x = x;
+        this.y = y;
+    }
+
+    public boolean IsInUnitRange() {
+        return x >= 0.0f && x <= 1.0f &&
+               y >= 0.0f && y <= 1.0f;
+    }
+
+    public Point plus(float x, float y) {
+        return new Point(this.x + x, this.y + y);
+    }
+
+    public Point plus(Point point) {
+        return this.plus(point.x, point.y);
+    }
+
+    public Point minus(float x, float y) {
+        return new Point(this.x - x, this.y - y);
+    }
+
+    public Point minus(Point point) {
+        return this.minus(point.x, point.y);
+    }
+
+    public Point times(float s) {
+        return new Point(this.x * s, this.y * s);
+    }
+
+    public Point mult(float x, float y) {
+        return new Point(this.x * x, this.y * y);
+    }
+
+    public float length() {
+        return (float)Math.sqrt(x*x + y*y);
+    }
+
+    public float distanceTo(Point p) {
+        return p.minus(this).length();
+    }
+
+    public Point scaledTo(float length) {
+        return this.times(length / this.length());
+    }
+
+    public Point normalize() {
+        return this.scaledTo(1.0f);
+    }
+
+    public Point rotated90(int count) {
+        float nx = this.x;
+        float ny = this.y;
+        for (int i = 0; i < count; ++i) {
+            float ox = nx;
+            nx = ny;
+            ny = -ox;
+        }
+        return new Point(nx, ny);
+    }
+
+    public Point rotated(float radians) {
+        // TODO(renn): Optimize: Keep cache of cos/sin values
+        return new Point((float)(Math.cos(radians) * x - Math.sin(radians) * y),
+                         (float)(Math.sin(radians) * x + Math.cos(radians) * y));
+    }
+
+    public Point rotatedAround(Point center, float radians) {
+        return this.minus(center).rotated(radians).plus(center);
+    }
+
+    @Override
+    public String toString() {
+        return "(" + x + ", " + y + ")";
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Quad.java b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java
new file mode 100644
index 0000000..ee092fd
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.geometry;
+
+import android.filterfw.geometry.Point;
+
+import java.lang.Float;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class Quad {
+
+    public Point p0;
+    public Point p1;
+    public Point p2;
+    public Point p3;
+
+    public Quad() {
+    }
+
+    public Quad(Point p0, Point p1, Point p2, Point p3) {
+        this.p0 = p0;
+        this.p1 = p1;
+        this.p2 = p2;
+        this.p3 = p3;
+    }
+
+    public boolean IsInUnitRange() {
+        return p0.IsInUnitRange() &&
+               p1.IsInUnitRange() &&
+               p2.IsInUnitRange() &&
+               p3.IsInUnitRange();
+    }
+
+    public Quad translated(Point t) {
+        return new Quad(p0.plus(t), p1.plus(t), p2.plus(t), p3.plus(t));
+    }
+
+    public Quad translated(float x, float y) {
+        return new Quad(p0.plus(x, y), p1.plus(x, y), p2.plus(x, y), p3.plus(x, y));
+    }
+
+    public Quad scaled(float s) {
+        return new Quad(p0.times(s), p1.times(s), p2.times(s), p3.times(s));
+    }
+
+    public Quad scaled(float x, float y) {
+        return new Quad(p0.mult(x, y), p1.mult(x, y), p2.mult(x, y), p3.mult(x, y));
+    }
+
+    public Rectangle boundingBox() {
+        List<Float> xs = Arrays.asList(p0.x, p1.x, p2.x, p3.x);
+        List<Float> ys = Arrays.asList(p0.y, p1.y, p2.y, p3.y);
+        float x0 = Collections.min(xs);
+        float y0 = Collections.min(ys);
+        float x1 = Collections.max(xs);
+        float y1 = Collections.max(ys);
+        return new Rectangle(x0, y0, x1 - x0, y1 - y0);
+    }
+
+    public float getBoundingWidth() {
+        List<Float> xs = Arrays.asList(p0.x, p1.x, p2.x, p3.x);
+        return Collections.max(xs) - Collections.min(xs);
+    }
+
+    public float getBoundingHeight() {
+        List<Float> ys = Arrays.asList(p0.y, p1.y, p2.y, p3.y);
+        return Collections.max(ys) - Collections.min(ys);
+    }
+
+    @Override
+    public String toString() {
+        return "{" + p0 + ", " + p1 + ", " + p2 + ", " + p3 + "}";
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Rectangle.java b/media/mca/filterfw/java/android/filterfw/geometry/Rectangle.java
new file mode 100644
index 0000000..e4bd622
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/geometry/Rectangle.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.geometry;
+
+import android.filterfw.geometry.Point;
+import android.filterfw.geometry.Quad;
+
+/**
+ * @hide
+ */
+public class Rectangle extends Quad {
+
+    public Rectangle() {
+    }
+
+    public Rectangle(float x, float y, float width, float height) {
+        super(new Point(x, y),
+              new Point(x + width, y),
+              new Point(x, y + height),
+              new Point(x + width, y + height));
+    }
+
+    public Rectangle(Point origin, Point size) {
+        super(origin,
+              origin.plus(size.x, 0.0f),
+              origin.plus(0.0f, size.y),
+              origin.plus(size.x, size.y));
+    }
+
+    public static Rectangle fromRotatedRect(Point center, Point size, float rotation) {
+        Point p0 = new Point(center.x - size.x/2f, center.y - size.y/2f);
+        Point p1 = new Point(center.x + size.x/2f, center.y - size.y/2f);
+        Point p2 = new Point(center.x - size.x/2f, center.y + size.y/2f);
+        Point p3 = new Point(center.x + size.x/2f, center.y + size.y/2f);
+        return new Rectangle(p0.rotatedAround(center, rotation),
+                             p1.rotatedAround(center, rotation),
+                             p2.rotatedAround(center, rotation),
+                             p3.rotatedAround(center, rotation));
+    }
+
+    private Rectangle(Point p0, Point p1, Point p2, Point p3) {
+        super(p0, p1, p2, p3);
+    }
+
+    public static Rectangle fromCenterVerticalAxis(Point center, Point vAxis, Point size) {
+        Point dy = vAxis.scaledTo(size.y / 2.0f);
+        Point dx = vAxis.rotated90(1).scaledTo(size.x / 2.0f);
+        return new Rectangle(center.minus(dx).minus(dy),
+                             center.plus(dx).minus(dy),
+                             center.minus(dx).plus(dy),
+                             center.plus(dx).plus(dy));
+    }
+
+    public float getWidth() {
+        return p1.minus(p0).length();
+    }
+
+    public float getHeight() {
+        return p2.minus(p0).length();
+    }
+
+    public Point center() {
+        return p0.plus(p1).plus(p2).plus(p3).times(0.25f);
+    }
+
+    @Override
+    public Rectangle scaled(float s) {
+        return new Rectangle(p0.times(s), p1.times(s), p2.times(s), p3.times(s));
+    }
+
+    @Override
+    public Rectangle scaled(float x, float y) {
+        return new Rectangle(p0.mult(x, y), p1.mult(x, y), p2.mult(x, y), p3.mult(x, y));
+    }
+
+    //public Rectangle rotated(float radians) {
+      // TODO: Implement this.
+    //}
+
+}
diff --git a/media/mca/filterfw/java/android/filterfw/geometry/package-info.java b/media/mca/filterfw/java/android/filterfw/geometry/package-info.java
new file mode 100644
index 0000000..5547622
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/geometry/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * @hide
+ */
+package android.filterfw.geometry;
diff --git a/native/include/android/rect.h b/media/mca/filterfw/java/android/filterfw/io/GraphIOException.java
similarity index 60%
copy from native/include/android/rect.h
copy to media/mca/filterfw/java/android/filterfw/io/GraphIOException.java
index 64d487d..940b393 100644
--- a/native/include/android/rect.h
+++ b/media/mca/filterfw/java/android/filterfw/io/GraphIOException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,25 +15,19 @@
  */
 
 
-#ifndef ANDROID_RECT_H
-#define ANDROID_RECT_H
+package android.filterfw.io;
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+/**
+ * @hide
+ */
+public class GraphIOException extends Exception {
 
-typedef struct ARect {
-#ifdef __cplusplus
-    typedef int32_t value_type;
-#endif
-    int32_t left;
-    int32_t top;
-    int32_t right;
-    int32_t bottom;
-} ARect;
+    public GraphIOException() {
+        super();
+    }
 
-#ifdef __cplusplus
-};
-#endif
+    public GraphIOException(String message) {
+        super(message);
+    }
 
-#endif // ANDROID_RECT_H
+}
diff --git a/media/mca/filterfw/java/android/filterfw/io/GraphReader.java b/media/mca/filterfw/java/android/filterfw/io/GraphReader.java
new file mode 100644
index 0000000..deb06e2
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/io/GraphReader.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.io;
+
+import android.content.Context;
+import android.filterfw.core.FilterGraph;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.io.GraphIOException;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.StringWriter;
+
+/**
+ * @hide
+ */
+public abstract class GraphReader {
+
+    protected KeyValueMap mReferences = new KeyValueMap();
+
+    public abstract FilterGraph readGraphString(String graphString) throws GraphIOException;
+
+    public abstract KeyValueMap readKeyValueAssignments(String assignments) throws GraphIOException;
+
+    public FilterGraph readGraphResource(Context context, int resourceId) throws GraphIOException {
+        InputStream inputStream = context.getResources().openRawResource(resourceId);
+        InputStreamReader reader = new InputStreamReader(inputStream);
+        StringWriter writer = new StringWriter();
+        char[] buffer = new char[1024];
+        try {
+            int bytesRead;
+            while ((bytesRead = reader.read(buffer, 0, 1024)) > 0) {
+                writer.write(buffer, 0, bytesRead);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("Could not read specified resource file!");
+        }
+        return readGraphString(writer.toString());
+    }
+
+    public void addReference(String name, Object object) {
+        mReferences.put(name, object);
+    }
+
+    public void addReferencesByMap(KeyValueMap refs) {
+        mReferences.putAll(refs);
+    }
+
+    public void addReferencesByKeysAndValues(Object... references) {
+        mReferences.setKeyValues(references);
+    }
+
+}
diff --git a/media/mca/filterfw/java/android/filterfw/io/PatternScanner.java b/media/mca/filterfw/java/android/filterfw/io/PatternScanner.java
new file mode 100644
index 0000000..4f1df02
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/io/PatternScanner.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.io;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @hide
+ */
+public class PatternScanner {
+
+    private String mInput;
+    private Pattern mIgnorePattern;
+    private int mOffset = 0;
+    private int mLineNo = 0;
+    private int mStartOfLine = 0;
+
+    public PatternScanner(String input) {
+        mInput = input;
+    }
+
+    public PatternScanner(String input, Pattern ignorePattern) {
+        mInput = input;
+        mIgnorePattern = ignorePattern;
+        skip(mIgnorePattern);
+    }
+
+    public String tryEat(Pattern pattern) {
+        // Skip ignore pattern
+        if (mIgnorePattern != null) {
+            skip(mIgnorePattern);
+        }
+
+        // Create the matcher
+        Matcher matcher = pattern.matcher(mInput);
+        matcher.region(mOffset, mInput.length());
+
+        // Attempt to match
+        String result = null;
+        if (matcher.lookingAt()) {
+            updateLineCount(mOffset, matcher.end());
+            mOffset = matcher.end();
+            result = mInput.substring(matcher.start(), matcher.end());
+        }
+
+        // Skip ignore pattern
+        if (result != null && mIgnorePattern != null) {
+            skip(mIgnorePattern);
+        }
+
+        return result;
+    }
+
+    public String eat(Pattern pattern, String tokenName) {
+        String result = tryEat(pattern);
+        if (result == null) {
+            throw new RuntimeException(unexpectedTokenMessage(tokenName));
+        }
+        return result;
+    }
+
+    public boolean peek(Pattern pattern) {
+        // Skip ignore pattern
+        if (mIgnorePattern != null) {
+            skip(mIgnorePattern);
+        }
+
+        // Create the matcher
+        Matcher matcher = pattern.matcher(mInput);
+        matcher.region(mOffset, mInput.length());
+
+        // Attempt to match
+        return matcher.lookingAt();
+    }
+
+    public void skip(Pattern pattern) {
+        Matcher matcher = pattern.matcher(mInput);
+        matcher.region(mOffset, mInput.length());
+        if (matcher.lookingAt()) {
+            updateLineCount(mOffset, matcher.end());
+            mOffset = matcher.end();
+        }
+    }
+
+    public boolean atEnd() {
+        return mOffset >= mInput.length();
+    }
+
+    public int lineNo() {
+        return mLineNo;
+    }
+
+    public String unexpectedTokenMessage(String tokenName) {
+        String line = mInput.substring(mStartOfLine, mOffset);
+        return "Unexpected token on line " + (mLineNo + 1) + " after '" + line + "' <- Expected " +
+                tokenName + "!";
+    }
+
+    public void updateLineCount(int start, int end) {
+        for (int i = start; i < end; ++i) {
+            if (mInput.charAt(i) == '\n') {
+                ++mLineNo;
+                mStartOfLine = i + 1;
+            }
+        }
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/io/TextGraphReader.java b/media/mca/filterfw/java/android/filterfw/io/TextGraphReader.java
new file mode 100644
index 0000000..366ef82
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/io/TextGraphReader.java
@@ -0,0 +1,489 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterfw.io;
+
+import java.lang.Float;
+import java.lang.Integer;
+import java.lang.String;
+
+import java.util.ArrayList;
+import java.util.regex.Pattern;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterFactory;
+import android.filterfw.core.FilterGraph;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.ProtocolException;
+import android.filterfw.io.GraphReader;
+import android.filterfw.io.GraphIOException;
+import android.filterfw.io.PatternScanner;
+
+/**
+ * @hide
+ */
+public class TextGraphReader extends GraphReader {
+
+    private ArrayList<Command> mCommands = new ArrayList<Command>();
+    private Filter mCurrentFilter;
+    private FilterGraph mCurrentGraph;
+    private KeyValueMap mBoundReferences;
+    private KeyValueMap mSettings;
+    private FilterFactory mFactory;
+
+    private interface Command {
+        public void execute(TextGraphReader reader) throws GraphIOException;
+    }
+
+    private class ImportPackageCommand implements Command {
+        private String mPackageName;
+
+        public ImportPackageCommand(String packageName) {
+            mPackageName = packageName;
+        }
+
+        @Override
+        public void execute(TextGraphReader reader) throws GraphIOException {
+            try {
+                reader.mFactory.addPackage(mPackageName);
+            } catch (IllegalArgumentException e) {
+                throw new GraphIOException(e.getMessage());
+            }
+        }
+    }
+
+    private class AddLibraryCommand implements Command {
+        private String mLibraryName;
+
+        public AddLibraryCommand(String libraryName) {
+            mLibraryName = libraryName;
+        }
+
+        @Override
+        public void execute(TextGraphReader reader) {
+            reader.mFactory.addFilterLibrary(mLibraryName);
+        }
+    }
+
+    private class AllocateFilterCommand implements Command {
+        private String mClassName;
+        private String mFilterName;
+
+        public AllocateFilterCommand(String className, String filterName) {
+            mClassName = className;
+            mFilterName = filterName;
+        }
+
+        public void execute(TextGraphReader reader) throws GraphIOException {
+            // Create the filter
+            Filter filter = null;
+            try {
+                filter = reader.mFactory.createFilterByClassName(mClassName, mFilterName);
+            } catch (IllegalArgumentException e) {
+                throw new GraphIOException(e.getMessage());
+            }
+
+            // Set it as the current filter
+            reader.mCurrentFilter = filter;
+        }
+    }
+
+    private class InitFilterCommand implements Command {
+        private KeyValueMap mParams;
+
+        public InitFilterCommand(KeyValueMap params) {
+            mParams = params;
+        }
+
+        @Override
+        public void execute(TextGraphReader reader) throws GraphIOException {
+            Filter filter = reader.mCurrentFilter;
+            try {
+                filter.initWithValueMap(mParams);
+            } catch (ProtocolException e) {
+                throw new GraphIOException(e.getMessage());
+            }
+            reader.mCurrentGraph.addFilter(mCurrentFilter);
+        }
+    }
+
+    private class ConnectCommand implements Command {
+        private String mSourceFilter;
+        private String mSourcePort;
+        private String mTargetFilter;
+        private String mTargetName;
+
+        public ConnectCommand(String sourceFilter,
+                              String sourcePort,
+                              String targetFilter,
+                              String targetName) {
+            mSourceFilter = sourceFilter;
+            mSourcePort = sourcePort;
+            mTargetFilter = targetFilter;
+            mTargetName = targetName;
+        }
+
+        @Override
+        public void execute(TextGraphReader reader) {
+            reader.mCurrentGraph.connect(mSourceFilter, mSourcePort, mTargetFilter, mTargetName);
+        }
+    }
+
+    @Override
+    public FilterGraph readGraphString(String graphString) throws GraphIOException {
+        FilterGraph result = new FilterGraph();
+
+        reset();
+        mCurrentGraph = result;
+        parseString(graphString);
+        applySettings();
+        executeCommands();
+        reset();
+
+        return result;
+    }
+
+    private void reset() {
+        mCurrentGraph = null;
+        mCurrentFilter = null;
+        mCommands.clear();
+        mBoundReferences = new KeyValueMap();
+        mSettings = new KeyValueMap();
+        mFactory = new FilterFactory();
+    }
+
+    private void parseString(String graphString) throws GraphIOException {
+        final Pattern commandPattern = Pattern.compile("@[a-zA-Z]+");
+        final Pattern curlyClosePattern = Pattern.compile("\\}");
+        final Pattern curlyOpenPattern = Pattern.compile("\\{");
+        final Pattern ignorePattern = Pattern.compile("(\\s+|//[^\\n]*\\n)+");
+        final Pattern packageNamePattern = Pattern.compile("[a-zA-Z\\.]+");
+        final Pattern libraryNamePattern = Pattern.compile("[a-zA-Z\\./:]+");
+        final Pattern portPattern = Pattern.compile("\\[[a-zA-Z0-9\\-_]+\\]");
+        final Pattern rightArrowPattern = Pattern.compile("=>");
+        final Pattern semicolonPattern = Pattern.compile(";");
+        final Pattern wordPattern = Pattern.compile("[a-zA-Z0-9\\-_]+");
+
+        final int STATE_COMMAND           = 0;
+        final int STATE_IMPORT_PKG        = 1;
+        final int STATE_ADD_LIBRARY       = 2;
+        final int STATE_FILTER_CLASS      = 3;
+        final int STATE_FILTER_NAME       = 4;
+        final int STATE_CURLY_OPEN        = 5;
+        final int STATE_PARAMETERS        = 6;
+        final int STATE_CURLY_CLOSE       = 7;
+        final int STATE_SOURCE_FILTERNAME = 8;
+        final int STATE_SOURCE_PORT       = 9;
+        final int STATE_RIGHT_ARROW       = 10;
+        final int STATE_TARGET_FILTERNAME = 11;
+        final int STATE_TARGET_PORT       = 12;
+        final int STATE_ASSIGNMENT        = 13;
+        final int STATE_EXTERNAL          = 14;
+        final int STATE_SETTING           = 15;
+        final int STATE_SEMICOLON         = 16;
+
+        int state = STATE_COMMAND;
+        PatternScanner scanner = new PatternScanner(graphString, ignorePattern);
+
+        String curClassName = null;
+        String curSourceFilterName = null;
+        String curSourcePortName = null;
+        String curTargetFilterName = null;
+        String curTargetPortName = null;
+
+        // State machine main loop
+        while (!scanner.atEnd()) {
+            switch (state) {
+                case STATE_COMMAND: {
+                    String curCommand = scanner.eat(commandPattern, "<command>");
+                    if (curCommand.equals("@import")) {
+                        state = STATE_IMPORT_PKG;
+                    } else if (curCommand.equals("@library")) {
+                        state = STATE_ADD_LIBRARY;
+                    } else if (curCommand.equals("@filter")) {
+                        state = STATE_FILTER_CLASS;
+                    } else if (curCommand.equals("@connect")) {
+                        state = STATE_SOURCE_FILTERNAME;
+                    } else if (curCommand.equals("@set")) {
+                        state = STATE_ASSIGNMENT;
+                    } else if (curCommand.equals("@external")) {
+                        state = STATE_EXTERNAL;
+                    } else if (curCommand.equals("@setting")) {
+                        state = STATE_SETTING;
+                    } else {
+                        throw new GraphIOException("Unknown command '" + curCommand + "'!");
+                    }
+                    break;
+                }
+
+                case STATE_IMPORT_PKG: {
+                    String packageName = scanner.eat(packageNamePattern, "<package-name>");
+                    mCommands.add(new ImportPackageCommand(packageName));
+                    state = STATE_SEMICOLON;
+                    break;
+                }
+
+                case STATE_ADD_LIBRARY: {
+                    String libraryName = scanner.eat(libraryNamePattern, "<library-name>");
+                    mCommands.add(new AddLibraryCommand(libraryName));
+                    state = STATE_SEMICOLON;
+                    break;
+                }
+
+                case STATE_FILTER_CLASS:
+                    curClassName = scanner.eat(wordPattern, "<class-name>");
+                    state = STATE_FILTER_NAME;
+                    break;
+
+                case STATE_FILTER_NAME: {
+                    String curFilterName = scanner.eat(wordPattern, "<filter-name>");
+                    mCommands.add(new AllocateFilterCommand(curClassName, curFilterName));
+                    state = STATE_CURLY_OPEN;
+                    break;
+                }
+
+                case STATE_CURLY_OPEN:
+                    scanner.eat(curlyOpenPattern, "{");
+                    state = STATE_PARAMETERS;
+                    break;
+
+                case STATE_PARAMETERS: {
+                    KeyValueMap params = readKeyValueAssignments(scanner, curlyClosePattern);
+                    mCommands.add(new InitFilterCommand(params));
+                    state = STATE_CURLY_CLOSE;
+                    break;
+                }
+
+                case STATE_CURLY_CLOSE:
+                    scanner.eat(curlyClosePattern, "}");
+                    state = STATE_COMMAND;
+                    break;
+
+                case STATE_SOURCE_FILTERNAME:
+                    curSourceFilterName = scanner.eat(wordPattern, "<source-filter-name>");
+                    state = STATE_SOURCE_PORT;
+                    break;
+
+                case STATE_SOURCE_PORT: {
+                    String portString = scanner.eat(portPattern, "[<source-port-name>]");
+                    curSourcePortName = portString.substring(1, portString.length() - 1);
+                    state = STATE_RIGHT_ARROW;
+                    break;
+                }
+
+                case STATE_RIGHT_ARROW:
+                    scanner.eat(rightArrowPattern, "=>");
+                    state = STATE_TARGET_FILTERNAME;
+                    break;
+
+                case STATE_TARGET_FILTERNAME:
+                    curTargetFilterName = scanner.eat(wordPattern, "<target-filter-name>");
+                    state = STATE_TARGET_PORT;
+                    break;
+
+                case STATE_TARGET_PORT: {
+                    String portString = scanner.eat(portPattern, "[<target-port-name>]");
+                    curTargetPortName = portString.substring(1, portString.length() - 1);
+                    mCommands.add(new ConnectCommand(curSourceFilterName,
+                                                     curSourcePortName,
+                                                     curTargetFilterName,
+                                                     curTargetPortName));
+                    state = STATE_SEMICOLON;
+                    break;
+                }
+
+                case STATE_ASSIGNMENT: {
+                    KeyValueMap assignment = readKeyValueAssignments(scanner, semicolonPattern);
+                    mBoundReferences.putAll(assignment);
+                    state = STATE_SEMICOLON;
+                    break;
+                }
+
+                case STATE_EXTERNAL: {
+                    String externalName = scanner.eat(wordPattern, "<external-identifier>");
+                    bindExternal(externalName);
+                    state = STATE_SEMICOLON;
+                    break;
+                }
+
+                case STATE_SETTING: {
+                    KeyValueMap setting = readKeyValueAssignments(scanner, semicolonPattern);
+                    mSettings.putAll(setting);
+                    state = STATE_SEMICOLON;
+                    break;
+                }
+
+                case STATE_SEMICOLON:
+                    scanner.eat(semicolonPattern, ";");
+                    state = STATE_COMMAND;
+                    break;
+            }
+        }
+
+        // Make sure end of input was expected
+        if (state != STATE_SEMICOLON && state != STATE_COMMAND) {
+            throw new GraphIOException("Unexpected end of input!");
+        }
+    }
+
+    @Override
+    public KeyValueMap readKeyValueAssignments(String assignments) throws GraphIOException {
+        final Pattern ignorePattern = Pattern.compile("\\s+");
+        PatternScanner scanner = new PatternScanner(assignments, ignorePattern);
+        return readKeyValueAssignments(scanner, null);
+    }
+
+    private KeyValueMap readKeyValueAssignments(PatternScanner scanner,
+                                                Pattern endPattern) throws GraphIOException {
+        // Our parser is a state-machine, and these are our states
+        final int STATE_IDENTIFIER = 0;
+        final int STATE_EQUALS     = 1;
+        final int STATE_VALUE      = 2;
+        final int STATE_POST_VALUE = 3;
+
+        final Pattern equalsPattern = Pattern.compile("=");
+        final Pattern semicolonPattern = Pattern.compile(";");
+        final Pattern wordPattern = Pattern.compile("[a-zA-Z]+[a-zA-Z0-9]*");
+        final Pattern stringPattern = Pattern.compile("'[^']*'|\\\"[^\\\"]*\\\"");
+        final Pattern intPattern = Pattern.compile("[0-9]+");
+        final Pattern floatPattern = Pattern.compile("[0-9]*\\.[0-9]+f?");
+        final Pattern referencePattern = Pattern.compile("\\$[a-zA-Z]+[a-zA-Z0-9]");
+        final Pattern booleanPattern = Pattern.compile("true|false");
+
+        int state = STATE_IDENTIFIER;
+        KeyValueMap newVals = new KeyValueMap();
+        String curKey = null;
+        String curValue = null;
+
+        while (!scanner.atEnd() && !(endPattern != null && scanner.peek(endPattern))) {
+            switch (state) {
+                case STATE_IDENTIFIER:
+                    curKey = scanner.eat(wordPattern, "<identifier>");
+                    state = STATE_EQUALS;
+                    break;
+
+                case STATE_EQUALS:
+                    scanner.eat(equalsPattern, "=");
+                    state = STATE_VALUE;
+                    break;
+
+                case STATE_VALUE:
+                    if ((curValue = scanner.tryEat(stringPattern)) != null) {
+                        newVals.put(curKey, curValue.substring(1, curValue.length() - 1));
+                    } else if ((curValue = scanner.tryEat(referencePattern)) != null) {
+                        String refName = curValue.substring(1, curValue.length());
+                        Object referencedObject = mBoundReferences != null
+                            ? mBoundReferences.get(refName)
+                            : null;
+                        if (referencedObject == null) {
+                            throw new GraphIOException(
+                                "Unknown object reference to '" + refName + "'!");
+                        }
+                        newVals.put(curKey, referencedObject);
+                    } else if ((curValue = scanner.tryEat(booleanPattern)) != null) {
+                        newVals.put(curKey, Boolean.parseBoolean(curValue));
+                    } else if ((curValue = scanner.tryEat(floatPattern)) != null) {
+                        newVals.put(curKey, Float.parseFloat(curValue));
+                    } else if ((curValue = scanner.tryEat(intPattern)) != null) {
+                        newVals.put(curKey, Integer.parseInt(curValue));
+                    } else {
+                        throw new GraphIOException(scanner.unexpectedTokenMessage("<value>"));
+                    }
+                    state = STATE_POST_VALUE;
+                    break;
+
+                case STATE_POST_VALUE:
+                    scanner.eat(semicolonPattern, ";");
+                    state = STATE_IDENTIFIER;
+                    break;
+            }
+        }
+
+        // Make sure end is expected
+        if (state != STATE_IDENTIFIER && state != STATE_POST_VALUE) {
+            throw new GraphIOException(
+                "Unexpected end of assignments on line " + scanner.lineNo() + "!");
+        }
+
+        return newVals;
+    }
+
+    private void bindExternal(String name) throws GraphIOException {
+        if (mReferences.containsKey(name)) {
+            Object value = mReferences.get(name);
+            mBoundReferences.put(name, value);
+        } else {
+            throw new GraphIOException("Unknown external variable '" + name + "'! "
+                + "You must add a reference to this external in the host program using "
+                + "addReference(...)!");
+        }
+    }
+
+    /**
+     * Unused for now: Often you may want to declare references that are NOT in a certain graph,
+     * e.g. when reading multiple graphs with the same reader. We could print a warning, but even
+     * that may be too much.
+     **/
+    private void checkReferences() throws GraphIOException {
+        for (String reference : mReferences.keySet()) {
+            if (!mBoundReferences.containsKey(reference)) {
+                throw new GraphIOException(
+                    "Host program specifies reference to '" + reference + "', which is not "
+                    + "declared @external in graph file!");
+            }
+        }
+    }
+
+    private void applySettings() throws GraphIOException {
+        for (String setting : mSettings.keySet()) {
+            Object value = mSettings.get(setting);
+            if (setting.equals("autoBranch")) {
+                expectSettingClass(setting, value, String.class);
+                if (value.equals("synced")) {
+                    mCurrentGraph.setAutoBranchMode(FilterGraph.AUTOBRANCH_SYNCED);
+                } else if (value.equals("unsynced")) {
+                    mCurrentGraph.setAutoBranchMode(FilterGraph.AUTOBRANCH_UNSYNCED);
+                } else if (value.equals("off")) {
+                    mCurrentGraph.setAutoBranchMode(FilterGraph.AUTOBRANCH_OFF);
+                } else {
+                    throw new GraphIOException("Unknown autobranch setting: " + value + "!");
+                }
+            } else if (setting.equals("discardUnconnectedOutputs")) {
+                expectSettingClass(setting, value, Boolean.class);
+                mCurrentGraph.setDiscardUnconnectedOutputs((Boolean)value);
+            } else {
+                throw new GraphIOException("Unknown @setting '" + setting + "'!");
+            }
+        }
+    }
+
+    private void expectSettingClass(String setting,
+                                    Object value,
+                                    Class expectedClass) throws GraphIOException {
+        if (value.getClass() != expectedClass) {
+            throw new GraphIOException("Setting '" + setting + "' must have a value of type "
+                + expectedClass.getSimpleName() + ", but found a value of type "
+                + value.getClass().getSimpleName() + "!");
+        }
+    }
+
+    private void executeCommands() throws GraphIOException {
+        for (Command command : mCommands) {
+            command.execute(this);
+        }
+    }
+}
diff --git a/media/mca/filterfw/java/android/filterfw/io/package-info.java b/media/mca/filterfw/java/android/filterfw/io/package-info.java
new file mode 100644
index 0000000..ea3e70f
--- /dev/null
+++ b/media/mca/filterfw/java/android/filterfw/io/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * @hide
+ */
+package android.filterfw.io;
diff --git a/media/mca/filterfw/jni/Android.mk b/media/mca/filterfw/jni/Android.mk
new file mode 100644
index 0000000..5aa5af1
--- /dev/null
+++ b/media/mca/filterfw/jni/Android.mk
@@ -0,0 +1,51 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+#####################
+# Build module libfilterfw_jni
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE    = libfilterfw_jni
+
+LOCAL_SRC_FILES = jni_init.cpp \
+                  jni_gl_environment.cpp \
+                  jni_gl_frame.cpp \
+                  jni_native_buffer.cpp \
+                  jni_native_frame.cpp \
+                  jni_native_program.cpp \
+                  jni_shader_program.cpp \
+                  jni_util.cpp \
+                  jni_vertex_frame.cpp
+
+# Need FilterFW lib
+include $(LOCAL_PATH)/../native/libfilterfw.mk
+
+# Also need the JNI headers.
+LOCAL_C_INCLUDES += \
+	$(JNI_H_INCLUDE) \
+	$(LOCAL_PATH)/..
+
+# Don't prelink this library.  For more efficient code, you may want
+# to add this library to the prelink map and set this to true. However,
+# it's difficult to do this for applications that are not supplied as
+# part of a system image.
+LOCAL_PRELINK_MODULE := false
+
+include $(BUILD_STATIC_LIBRARY)
+
diff --git a/media/mca/filterfw/jni/jni_gl_environment.cpp b/media/mca/filterfw/jni/jni_gl_environment.cpp
new file mode 100644
index 0000000..3c596a4
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_gl_environment.cpp
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// #define LOG_NDEBUG 0
+
+#include <stdint.h>
+#include <android/native_window_jni.h>
+
+#include "jni/jni_gl_environment.h"
+#include "jni/jni_util.h"
+#include <media/mediarecorder.h>
+#include "native/core/gl_env.h"
+
+#include <gui/ISurfaceTexture.h>
+#include <gui/SurfaceTextureClient.h>
+#include <utils/Errors.h>
+#include <system/window.h>
+
+
+using android::filterfw::GLEnv;
+using android::filterfw::WindowHandle;
+using android::MediaRecorder;
+using android::sp;
+using android::ISurfaceTexture;
+using android::SurfaceTextureClient;
+
+
+class NativeWindowHandle : public WindowHandle {
+  public:
+    NativeWindowHandle(ANativeWindow* window) : window_(window) {
+    }
+
+    virtual ~NativeWindowHandle() {
+    }
+
+    virtual void Destroy() {
+      ALOGI("Releasing ANativeWindow!");
+      ANativeWindow_release(window_);
+    }
+
+    virtual const void* InternalHandle() const {
+      return window_;
+    }
+
+    virtual void* InternalHandle() {
+      return window_;
+    }
+
+  private:
+    ANativeWindow* window_;
+};
+
+jboolean Java_android_filterfw_core_GLEnvironment_nativeAllocate(JNIEnv* env, jobject thiz) {
+  return ToJBool(WrapObjectInJava(new GLEnv(), env, thiz, true));
+}
+
+jboolean Java_android_filterfw_core_GLEnvironment_nativeDeallocate(JNIEnv* env, jobject thiz) {
+  return ToJBool(DeleteNativeObject<GLEnv>(env, thiz));
+}
+
+jboolean Java_android_filterfw_core_GLEnvironment_nativeInitWithNewContext(JNIEnv* env,
+                                                                           jobject thiz) {
+  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
+  return gl_env ? ToJBool(gl_env->InitWithNewContext()) : JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_GLEnvironment_nativeInitWithCurrentContext(JNIEnv* env,
+                                                                               jobject thiz) {
+  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
+  return gl_env ? ToJBool(gl_env->InitWithCurrentContext()) : JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_GLEnvironment_nativeIsActive(JNIEnv* env, jobject thiz) {
+  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
+  return gl_env ? ToJBool(gl_env->IsActive()) : JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_GLEnvironment_nativeIsContextActive(JNIEnv* env, jobject thiz) {
+  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
+  return gl_env ? ToJBool(gl_env->IsContextActive()) : JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_GLEnvironment_nativeIsAnyContextActive(JNIEnv* env,
+                                                                           jclass clazz) {
+  return ToJBool(GLEnv::IsAnyContextActive());
+}
+
+jboolean Java_android_filterfw_core_GLEnvironment_nativeActivate(JNIEnv* env, jobject thiz) {
+  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
+  return gl_env ? ToJBool(gl_env->Activate()) : JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_GLEnvironment_nativeDeactivate(JNIEnv* env, jobject thiz) {
+  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
+  return gl_env ? ToJBool(gl_env->Deactivate()) : JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_GLEnvironment_nativeSwapBuffers(JNIEnv* env, jobject thiz) {
+  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
+  return gl_env ? ToJBool(gl_env->SwapBuffers()) : JNI_FALSE;
+}
+
+// Get the native mediarecorder object corresponding to the java object
+static sp<MediaRecorder> getMediaRecorder(JNIEnv* env, jobject jmediarecorder) {
+    jclass clazz = env->FindClass("android/media/MediaRecorder");
+    if (clazz == NULL) {
+        return NULL;
+    }
+
+    jfieldID context = env->GetFieldID(clazz, "mNativeContext", "I");
+    if (context == NULL) {
+        return NULL;
+    }
+
+    MediaRecorder* const p = (MediaRecorder*)env->GetIntField(jmediarecorder, context);
+    env->DeleteLocalRef(clazz);
+    return sp<MediaRecorder>(p);
+}
+
+
+jint Java_android_filterfw_core_GLEnvironment_nativeAddSurface(JNIEnv* env,
+                                                               jobject thiz,
+                                                               jobject surface) {
+  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
+  if (!surface) {
+    ALOGE("GLEnvironment: Null Surface passed!");
+    return -1;
+  } else if (gl_env) {
+    // Get the ANativeWindow
+    ANativeWindow* window = ANativeWindow_fromSurface(env, surface);
+    if (!window) {
+      ALOGE("GLEnvironment: Error creating window!");
+      return -1;
+    }
+
+    NativeWindowHandle* winHandle = new NativeWindowHandle(window);
+    int result = gl_env->FindSurfaceIdForWindow(winHandle);
+    if (result == -1) {
+      // Configure surface
+      EGLConfig config;
+      EGLint numConfigs = -1;
+      EGLint configAttribs[] = {
+        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+        EGL_RED_SIZE, 8,
+        EGL_GREEN_SIZE, 8,
+        EGL_BLUE_SIZE, 8,
+        EGL_RECORDABLE_ANDROID, EGL_TRUE,
+        EGL_NONE
+      };
+
+
+
+      eglChooseConfig(gl_env->display(), configAttribs, &config, 1, &numConfigs);
+      if (numConfigs < 1) {
+        ALOGE("GLEnvironment: No suitable EGL configuration found for surface!");
+        return -1;
+      }
+
+      // Create the EGL surface
+      EGLSurface egl_surface = eglCreateWindowSurface(gl_env->display(),
+                                                      config,
+                                                      window,
+                                                      NULL);
+
+      if (GLEnv::CheckEGLError("eglCreateWindowSurface")) {
+        ALOGE("GLEnvironment: Error creating window surface!");
+        return -1;
+      }
+
+      // Add it to GL Env and assign ID
+      result = gl_env->AddWindowSurface(egl_surface, winHandle);
+    } else {
+      delete winHandle;
+    }
+    return result;
+  }
+  return -1;
+}
+
+jint Java_android_filterfw_core_GLEnvironment_nativeAddSurfaceWidthHeight(JNIEnv* env,
+                                                                      jobject thiz,
+                                                                      jobject surface,
+                                                                      jint width,
+                                                                      jint height) {
+  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
+  if (!surface) {
+    ALOGE("GLEnvironment: Null SurfaceTexture passed!");
+    return -1;
+  } else if (gl_env) {
+    // Get the ANativeWindow
+    ANativeWindow* window = ANativeWindow_fromSurface(env, surface);
+    if (!window) {
+      ALOGE("GLEnvironment: Error creating window!");
+      return -1;
+    }
+
+    // Don't care about format (will get overridden by SurfaceTexture
+    // anyway), but do care about width and height
+    // TODO: Probably, this should be just be
+    // ANativeWindow_setBuffersDimensions. The pixel format is
+    // set during the eglCreateWindowSurface
+    ANativeWindow_setBuffersGeometry(window, width, height, 0);
+
+    NativeWindowHandle* winHandle = new NativeWindowHandle(window);
+    int result = gl_env->FindSurfaceIdForWindow(winHandle);
+    if (result == -1) {
+      // Configure surface
+      EGLConfig config;
+      EGLint numConfigs = -1;
+      EGLint configAttribs[] = {
+        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+        EGL_RED_SIZE, 8,
+        EGL_GREEN_SIZE, 8,
+        EGL_BLUE_SIZE, 8,
+        EGL_RECORDABLE_ANDROID, EGL_TRUE,
+        EGL_NONE
+      };
+
+
+
+      eglChooseConfig(gl_env->display(), configAttribs, &config, 1, &numConfigs);
+      if (numConfigs < 1) {
+        ALOGE("GLEnvironment: No suitable EGL configuration found for surface texture!");
+        return -1;
+      }
+
+      // Create the EGL surface
+      EGLSurface egl_surface = eglCreateWindowSurface(gl_env->display(),
+                                                      config,
+                                                      window,
+                                                      NULL);
+
+      if (GLEnv::CheckEGLError("eglCreateWindowSurface")) {
+        ALOGE("GLEnvironment: Error creating window surface!");
+        return -1;
+      }
+
+      // Add it to GL Env and assign ID
+      result = gl_env->AddWindowSurface(egl_surface, winHandle);
+    } else {
+      delete winHandle;
+    }
+    return result;
+  }
+  return -1;
+}
+
+// nativeAddSurfaceFromMediaRecorder gets an EGLSurface
+// using a MediaRecorder object.
+// When Mediarecorder is used for recording GL Frames, it
+// will have a reference to a Native Handle (a SurfaceTexureClient)
+// which talks to the StageFrightRecorder in mediaserver via
+// a binder interface.
+jint Java_android_filterfw_core_GLEnvironment_nativeAddSurfaceFromMediaRecorder(
+                                                      JNIEnv* env,
+                                                      jobject thiz,
+                                                      jobject jmediarecorder) {
+    ALOGV("GLEnv Jni: nativeAddSurfaceFromMediaRecorder");
+    GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
+    if (!gl_env) {
+        return -1;
+    }
+    // get a native mediarecorder object from the java object
+    sp<MediaRecorder> mr = getMediaRecorder(env, jmediarecorder);
+    if (mr == NULL) {
+        ALOGE("GLEnvironment: Error- MediaRecorder could not be initialized!");
+        return -1;
+    }
+
+    // Ask the mediarecorder to return a handle to a surfacemediasource
+    // This will talk to the StageFrightRecorder via MediaRecorderClient
+    // over binder calls
+    sp<ISurfaceTexture> surfaceMS = mr->querySurfaceMediaSourceFromMediaServer();
+    if (surfaceMS == NULL) {
+      ALOGE("GLEnvironment: Error- MediaRecorder returned a null \
+              <ISurfaceTexture> handle.");
+      return -1;
+    }
+    sp<SurfaceTextureClient> surfaceTC = new SurfaceTextureClient(surfaceMS);
+    // Get the ANativeWindow
+    sp<ANativeWindow> window = surfaceTC;
+
+
+    if (window == NULL) {
+      ALOGE("GLEnvironment: Error creating window!");
+      return -1;
+    }
+    window->incStrong((void*)ANativeWindow_acquire);
+
+    // In case of encoding, no need to set the dimensions
+    // on the buffers. The dimensions for the final encoding are set by
+    // the consumer side.
+    // The pixel format is dictated by the GL, and set during the
+    // eglCreateWindowSurface
+
+    NativeWindowHandle* winHandle = new NativeWindowHandle(window.get());
+    int result = gl_env->FindSurfaceIdForWindow(winHandle);
+    // If we find a surface with that window handle, just return that id
+    if (result != -1) {
+        delete winHandle;
+        return result;
+    }
+    // If we do not find a surface with that window handle, create
+    // one and assign to it the handle
+    // Configure surface
+    EGLConfig config;
+    EGLint numConfigs = -1;
+    EGLint configAttribs[] = {
+          EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+          EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+          EGL_RED_SIZE, 8,
+          EGL_GREEN_SIZE, 8,
+          EGL_BLUE_SIZE, 8,
+          EGL_RECORDABLE_ANDROID, EGL_TRUE,
+          EGL_NONE
+    };
+
+
+    eglChooseConfig(gl_env->display(), configAttribs, &config, 1, &numConfigs);
+    if (numConfigs < 1) {
+      ALOGE("GLEnvironment: No suitable EGL configuration found for surface texture!");
+      delete winHandle;
+      return -1;
+    }
+
+    // Create the EGL surface
+    EGLSurface egl_surface = eglCreateWindowSurface(gl_env->display(),
+                                                    config,
+                                                    window.get(),
+                                                    NULL);
+
+    if (GLEnv::CheckEGLError("eglCreateWindowSurface")) {
+      ALOGE("GLEnvironment: Error creating window surface!");
+      delete winHandle;
+      return -1;
+    }
+
+    // Add it to GL Env and assign ID
+    result = gl_env->AddWindowSurface(egl_surface, winHandle);
+    return result;
+}
+
+jboolean Java_android_filterfw_core_GLEnvironment_nativeActivateSurfaceId(JNIEnv* env,
+                                                                          jobject thiz,
+                                                                          jint surfaceId) {
+  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
+  return gl_env ? ToJBool(gl_env->SwitchToSurfaceId(surfaceId) && gl_env->Activate()) : JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_GLEnvironment_nativeRemoveSurfaceId(JNIEnv* env,
+                                                                        jobject thiz,
+                                                                        jint surfaceId) {
+  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
+  return gl_env ? ToJBool(gl_env->ReleaseSurfaceId(surfaceId)) : JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_GLEnvironment_nativeSetSurfaceTimestamp(JNIEnv* env,
+                                                                            jobject thiz,
+                                                                            jlong timestamp) {
+  GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz);
+  int64_t timestamp_native = timestamp;
+  return gl_env ? ToJBool(gl_env->SetSurfaceTimestamp(timestamp_native)) : JNI_FALSE;
+}
diff --git a/media/mca/filterfw/jni/jni_gl_environment.h b/media/mca/filterfw/jni/jni_gl_environment.h
new file mode 100644
index 0000000..af9c744
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_gl_environment.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_JNI_GL_ENVIRONMENT_H
+#define ANDROID_FILTERFW_JNI_GL_ENVIRONMENT_H
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLEnvironment_nativeAllocate(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLEnvironment_nativeDeallocate(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLEnvironment_nativeInitWithNewContext(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLEnvironment_nativeInitWithCurrentContext(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLEnvironment_nativeIsActive(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLEnvironment_nativeIsContextActive(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLEnvironment_nativeIsAnyContextActive(JNIEnv* env, jclass clazz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLEnvironment_nativeActivate(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLEnvironment_nativeDeactivate(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLEnvironment_nativeSwapBuffers(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jint JNICALL
+Java_android_filterfw_core_GLEnvironment_nativeAddSurface(JNIEnv* env,
+                                                          jobject thiz,
+                                                          jobject surface);
+
+JNIEXPORT jint JNICALL
+Java_android_filterfw_core_GLEnvironment_nativeAddSurfaceWidthHeight(JNIEnv* env,
+                                                                 jobject thiz,
+                                                                 jobject surface,
+                                                                 jint width,
+                                                                 jint height);
+
+// The call to hook up the SurfaceMediaSource (in MediaServer)  to the GL.
+// We get a sp<ISurfaceTexure> from the MediaServer and talks to MediaServer
+// over a binder interface. GL hooked up to the MediaServer by using the native
+// window created using the <ISurfaceTexture> handle
+JNIEXPORT jint JNICALL
+Java_android_filterfw_core_GLEnvironment_nativeAddSurfaceFromMediaRecorder(
+                                                      JNIEnv* env,
+                                                      jobject thiz,
+                                                      jobject mediarecorder);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLEnvironment_nativeActivateSurfaceId(JNIEnv* env,
+                                                                 jobject thiz,
+                                                                 jint surfaceId);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLEnvironment_nativeRemoveSurfaceId(JNIEnv* env,
+                                                               jobject thiz,
+                                                               jint surfaceId);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLEnvironment_nativeSetSurfaceTimestamp(JNIEnv* env,
+                                                                   jobject thiz,
+                                                                   jlong timestamp);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_FILTERFW_JNI_GL_ENVIRONMENT_H
diff --git a/media/mca/filterfw/jni/jni_gl_frame.cpp b/media/mca/filterfw/jni/jni_gl_frame.cpp
new file mode 100644
index 0000000..61340f9
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_gl_frame.cpp
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "android/bitmap.h"
+
+#include "jni/jni_gl_frame.h"
+#include "jni/jni_util.h"
+
+#include "native/core/gl_env.h"
+#include "native/core/gl_frame.h"
+#include "native/core/native_frame.h"
+
+using android::filterfw::GLEnv;
+using android::filterfw::GLFrame;
+using android::filterfw::NativeFrame;
+
+// Helper functions ////////////////////////////////////////////////////////////////////////////////
+void ConvertFloatsToRGBA(const float* floats, int length, uint8_t* result) {
+  for (int i = 0; i < length; ++i) {
+    result[i] = static_cast<uint8_t>(floats[i] * 255.0);
+  }
+}
+
+void ConvertRGBAToFloats(const uint8_t* rgba, int length, float* result) {
+  for (int i = 0; i < length; ++i) {
+    result[i] = rgba[i] / 255.0;
+  }
+}
+
+// GLFrame JNI implementation //////////////////////////////////////////////////////////////////////
+jboolean Java_android_filterfw_core_GLFrame_nativeAllocate(JNIEnv* env,
+                                                           jobject thiz,
+                                                           jobject gl_env,
+                                                           jint width,
+                                                           jint height) {
+  GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
+  if (!gl_env_ptr) return JNI_FALSE;
+  GLFrame* frame = new GLFrame(gl_env_ptr);
+  if (frame->Init(width, height)) {
+    return ToJBool(WrapObjectInJava(frame, env, thiz, true));
+  } else {
+    delete frame;
+    return JNI_FALSE;
+  }
+}
+
+jboolean Java_android_filterfw_core_GLFrame_nativeAllocateWithTexture(JNIEnv* env,
+                                                                      jobject thiz,
+                                                                      jobject gl_env,
+                                                                      jint tex_id,
+                                                                      jint width,
+                                                                      jint height) {
+  GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
+  if (!gl_env_ptr) return JNI_FALSE;
+  GLFrame* frame = new GLFrame(gl_env_ptr);
+  if (frame->InitWithTexture(tex_id, width, height)) {
+    return ToJBool(WrapObjectInJava(frame, env, thiz, true));
+  } else {
+    delete frame;
+    return JNI_FALSE;
+  }
+}
+
+jboolean Java_android_filterfw_core_GLFrame_nativeAllocateWithFbo(JNIEnv* env,
+                                                                  jobject thiz,
+                                                                  jobject gl_env,
+                                                                  jint fbo_id,
+                                                                  jint width,
+                                                                  jint height) {
+  GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
+  if (!gl_env_ptr) return JNI_FALSE;
+  GLFrame* frame = new GLFrame(gl_env_ptr);
+  if (frame->InitWithFbo(fbo_id, width, height)) {
+    return ToJBool(WrapObjectInJava(frame, env, thiz, true));
+  } else {
+    delete frame;
+    return JNI_FALSE;
+  }
+}
+
+jboolean Java_android_filterfw_core_GLFrame_nativeAllocateExternal(JNIEnv* env,
+                                                                   jobject thiz,
+                                                                   jobject gl_env) {
+  GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
+  if (!gl_env_ptr) return JNI_FALSE;
+  GLFrame* frame = new GLFrame(gl_env_ptr);
+  if (frame->InitWithExternalTexture()) {
+    return ToJBool(WrapObjectInJava(frame, env, thiz, true));
+  } else {
+    delete frame;
+    return JNI_FALSE;
+  }
+}
+
+jboolean Java_android_filterfw_core_GLFrame_nativeDeallocate(JNIEnv* env, jobject thiz) {
+  return ToJBool(DeleteNativeObject<GLFrame>(env, thiz));
+}
+
+jboolean Java_android_filterfw_core_GLFrame_setNativeData(JNIEnv* env,
+                                                          jobject thiz,
+                                                          jbyteArray data,
+                                                          jint offset,
+                                                          jint length) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  if (frame && data) {
+    jbyte* bytes = env->GetByteArrayElements(data, NULL);
+    if (bytes) {
+      const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(bytes + offset), length);
+      env->ReleaseByteArrayElements(data, bytes, JNI_ABORT);
+      return ToJBool(success);
+    }
+  }
+  return JNI_FALSE;
+}
+
+jbyteArray Java_android_filterfw_core_GLFrame_getNativeData(JNIEnv* env, jobject thiz) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  if (frame && frame->Size() > 0) {
+    jbyteArray result = env->NewByteArray(frame->Size());
+    jbyte* data = env->GetByteArrayElements(result, NULL);
+    frame->CopyDataTo(reinterpret_cast<uint8_t*>(data), frame->Size());
+    env->ReleaseByteArrayElements(result, data, 0);
+    return result;
+  }
+  return NULL;
+}
+
+jboolean Java_android_filterfw_core_GLFrame_setNativeInts(JNIEnv* env,
+                                                          jobject thiz,
+                                                          jintArray ints) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  if (frame && ints) {
+    jint* int_ptr = env->GetIntArrayElements(ints, NULL);
+    const int length = env->GetArrayLength(ints);
+    if (int_ptr) {
+      const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(int_ptr),
+                                            length * sizeof(jint));
+      env->ReleaseIntArrayElements(ints, int_ptr, JNI_ABORT);
+      return ToJBool(success);
+    }
+  }
+  return JNI_FALSE;
+}
+
+jintArray Java_android_filterfw_core_GLFrame_getNativeInts(JNIEnv* env, jobject thiz) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  if (frame && frame->Size() > 0 && (frame->Size() % sizeof(jint) == 0)) {
+    jintArray result = env->NewIntArray(frame->Size() / sizeof(jint));
+    jint* data = env->GetIntArrayElements(result, NULL);
+    frame->CopyDataTo(reinterpret_cast<uint8_t*>(data), frame->Size());
+    env->ReleaseIntArrayElements(result, data, 0);
+    return result;
+   }
+   return NULL;
+}
+
+jboolean Java_android_filterfw_core_GLFrame_setNativeFloats(JNIEnv* env,
+                                                            jobject thiz,
+                                                            jfloatArray floats) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  if (frame && floats) {
+    jfloat* float_ptr = env->GetFloatArrayElements(floats, NULL);
+    const int length = env->GetArrayLength(floats);
+    if (float_ptr) {
+      // Convert floats to RGBA buffer
+      uint8_t* rgba_buffer = new uint8_t[length];
+      ConvertFloatsToRGBA(float_ptr, length, rgba_buffer);
+      env->ReleaseFloatArrayElements(floats, float_ptr, JNI_ABORT);
+
+      // Write RGBA buffer to frame
+      const bool success = frame->WriteData(rgba_buffer, length);
+
+      // Clean-up
+      delete[] rgba_buffer;
+      return ToJBool(success);
+    }
+  }
+  return JNI_FALSE;
+}
+
+jfloatArray Java_android_filterfw_core_GLFrame_getNativeFloats(JNIEnv* env, jobject thiz) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  if (frame && frame->Size() > 0) {
+    // Create the result array
+    jfloatArray result = env->NewFloatArray(frame->Size());
+    jfloat* float_array = env->GetFloatArrayElements(result, NULL);
+
+    // Read the frame pixels
+    uint8_t* pixels = new uint8_t[frame->Size()];
+    frame->CopyDataTo(pixels, frame->Size());
+
+    // Convert them to floats
+    ConvertRGBAToFloats(pixels, frame->Size(), float_array);
+
+    // Clean-up
+    delete[] pixels;
+    env->ReleaseFloatArrayElements(result, float_array, 0);
+    return result;
+  }
+  return NULL;
+}
+
+jboolean Java_android_filterfw_core_GLFrame_setNativeBitmap(JNIEnv* env,
+                                                            jobject thiz,
+                                                            jobject bitmap,
+                                                            jint size) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  if (frame && bitmap) {
+    uint8_t* pixels;
+    const int result = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void**>(&pixels));
+    if (result == ANDROID_BITMAP_RESUT_SUCCESS) {
+      const bool success = frame->WriteData(pixels, size);
+      return ToJBool(success &&
+                     AndroidBitmap_unlockPixels(env, bitmap) == ANDROID_BITMAP_RESUT_SUCCESS);
+    }
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_GLFrame_getNativeBitmap(JNIEnv* env,
+                                                            jobject thiz,
+                                                            jobject bitmap) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  if (frame && bitmap) {
+    uint8_t* pixels;
+    const int result = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void**>(&pixels));
+    if (result == ANDROID_BITMAP_RESUT_SUCCESS) {
+      frame->CopyDataTo(pixels, frame->Size());
+      return (AndroidBitmap_unlockPixels(env, bitmap) == ANDROID_BITMAP_RESUT_SUCCESS);
+    }
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_GLFrame_setNativeViewport(JNIEnv* env,
+                                                              jobject thiz,
+                                                              jint x,
+                                                              jint y,
+                                                              jint width,
+                                                              jint height) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  return frame ? ToJBool(frame->SetViewport(x, y, width, height)) : JNI_FALSE;
+}
+
+jint Java_android_filterfw_core_GLFrame_getNativeTextureId(JNIEnv* env, jobject thiz) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  return frame ? frame->GetTextureId() : -1;
+}
+
+jint Java_android_filterfw_core_GLFrame_getNativeFboId(JNIEnv* env, jobject thiz) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  return frame ? frame->GetFboId() : -1;
+}
+
+jboolean Java_android_filterfw_core_GLFrame_generateNativeMipMap(JNIEnv* env, jobject thiz) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  return frame ? ToJBool(frame->GenerateMipMap()) : JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_GLFrame_setNativeTextureParam(JNIEnv* env,
+                                                                  jobject thiz,
+                                                                  jint param,
+                                                                  jint value) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  return frame ? ToJBool(frame->SetTextureParameter(param, value)) : JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_GLFrame_nativeResetParams(JNIEnv* env, jobject thiz) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  return frame ? ToJBool(frame->ResetTexParameters()) : JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_GLFrame_nativeCopyFromNative(JNIEnv* env,
+                                                                 jobject thiz,
+                                                                 jobject frame) {
+  GLFrame* this_frame = ConvertFromJava<GLFrame>(env, thiz);
+  NativeFrame* other_frame = ConvertFromJava<NativeFrame>(env, frame);
+  if (this_frame && other_frame) {
+    return ToJBool(this_frame->WriteData(other_frame->Data(), other_frame->Size()));
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_GLFrame_nativeCopyFromGL(JNIEnv* env,
+                                                             jobject thiz,
+                                                             jobject frame) {
+  GLFrame* this_frame = ConvertFromJava<GLFrame>(env, thiz);
+  GLFrame* other_frame = ConvertFromJava<GLFrame>(env, frame);
+  if (this_frame && other_frame) {
+    return ToJBool(this_frame->CopyPixelsFrom(other_frame));
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_GLFrame_nativeFocus(JNIEnv* env, jobject thiz) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  return ToJBool(frame && frame->FocusFrameBuffer());
+}
+
+jboolean Java_android_filterfw_core_GLFrame_nativeReattachTexToFbo(JNIEnv* env, jobject thiz) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  return ToJBool(frame && frame->ReattachTextureToFbo());
+}
+
+jboolean Java_android_filterfw_core_GLFrame_nativeDetachTexFromFbo(JNIEnv* env, jobject thiz) {
+  GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz);
+  return ToJBool(frame && frame->DetachTextureFromFbo());
+}
+
diff --git a/media/mca/filterfw/jni/jni_gl_frame.h b/media/mca/filterfw/jni/jni_gl_frame.h
new file mode 100644
index 0000000..8a25aea
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_gl_frame.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_JNI_GL_FRAME_H
+#define ANDROID_FILTERFW_JNI_GL_FRAME_H
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_nativeAllocate(JNIEnv* env,
+                                                  jobject thiz,
+                                                  jobject gl_env,
+                                                  jint width,
+                                                  jint height);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_nativeAllocateWithTexture(JNIEnv* env,
+                                                             jobject thiz,
+                                                             jobject gl_env,
+                                                             jint tex_id,
+                                                             jint width,
+                                                             jint height);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_nativeAllocateWithFbo(JNIEnv* env,
+                                                         jobject thiz,
+                                                         jobject gl_env,
+                                                         jint fbo_id,
+                                                         jint width,
+                                                         jint height);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_nativeAllocateExternal(JNIEnv* env,
+                                                          jobject thiz,
+                                                          jobject gl_env);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_nativeDeallocate(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_setNativeInts(JNIEnv* env, jobject thiz, jintArray ints);
+
+JNIEXPORT jintArray JNICALL
+Java_android_filterfw_core_GLFrame_getNativeInts(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_setNativeFloats(JNIEnv* env, jobject thiz, jfloatArray ints);
+
+JNIEXPORT jfloatArray JNICALL
+Java_android_filterfw_core_GLFrame_getNativeFloats(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_setNativeData(JNIEnv* env,
+                                                 jobject thiz,
+                                                 jbyteArray data,
+                                                 jint offset,
+                                                 jint length);
+
+JNIEXPORT jbyteArray JNICALL
+Java_android_filterfw_core_GLFrame_getNativeData(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_setNativeBitmap(JNIEnv* env,
+                                                   jobject thiz,
+                                                   jobject bitmap,
+                                                   jint size);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_getNativeBitmap(JNIEnv* env, jobject thiz, jobject bitmap);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_setNativeViewport(JNIEnv* env,
+                                                     jobject thiz,
+                                                     jint x,
+                                                     jint y,
+                                                     jint width,
+                                                     jint height);
+
+JNIEXPORT jint JNICALL
+Java_android_filterfw_core_GLFrame_getNativeTextureId(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jint JNICALL
+Java_android_filterfw_core_GLFrame_getNativeFboId(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_generateNativeMipMap(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_setNativeTextureParam(JNIEnv* env,
+                                                         jobject thiz,
+                                                         jint param,
+                                                         jint value);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_nativeResetParams(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_nativeCopyFromNative(JNIEnv* env,
+                                                        jobject thiz,
+                                                        jobject frame);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_nativeCopyFromGL(JNIEnv* env,
+                                                    jobject thiz,
+                                                    jobject frame);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_nativeFocus(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_nativeReattachTexToFbo(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_GLFrame_nativeDetachTexFromFbo(JNIEnv* env, jobject thiz);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ANDROID_FILTERFW_JNI_GL_FRAME_H */
diff --git a/media/mca/filterfw/jni/jni_init.cpp b/media/mca/filterfw/jni/jni_init.cpp
new file mode 100644
index 0000000..3b131f1
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_init.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jni/jni_util.h"
+
+#include "native/core/native_frame.h"
+#include "native/core/native_program.h"
+#include "native/core/gl_env.h"
+#include "native/core/gl_frame.h"
+#include "native/core/shader_program.h"
+#include "native/core/vertex_frame.h"
+
+using namespace android::filterfw;
+
+JavaVM* g_current_java_vm_ = NULL;
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+  // Set the current vm pointer
+  g_current_java_vm_ = vm;
+
+  // Initialize object pools
+  ObjectPool<NativeFrame>::Setup("android/filterfw/core/NativeFrame", "nativeFrameId");
+  ObjectPool<NativeProgram>::Setup("android/filterfw/core/NativeProgram", "nativeProgramId");
+  ObjectPool<GLFrame>::Setup("android/filterfw/core/GLFrame", "glFrameId");
+  ObjectPool<ShaderProgram>::Setup("android/filterfw/core/ShaderProgram", "shaderProgramId");
+  ObjectPool<GLEnv>::Setup("android/filterfw/core/GLEnvironment", "glEnvId");
+  ObjectPool<VertexFrame>::Setup("android/filterfw/core/VertexFrame", "vertexFrameId");
+
+  return JNI_VERSION_1_4;
+}
diff --git a/media/mca/filterfw/jni/jni_native_buffer.cpp b/media/mca/filterfw/jni/jni_native_buffer.cpp
new file mode 100644
index 0000000..097c145
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_native_buffer.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jni/jni_native_buffer.h"
+#include "jni/jni_util.h"
+
+char* GetJBufferData(JNIEnv* env, jobject buffer, int* size) {
+  jclass base_class = env->FindClass("android/filterfw/core/NativeBuffer");
+
+  // Get fields
+  jfieldID ptr_field = env->GetFieldID(base_class, "mDataPointer", "J");
+  jfieldID size_field = env->GetFieldID(base_class, "mSize", "I");
+
+  // Get their values
+  char* data = reinterpret_cast<char*>(env->GetLongField(buffer, ptr_field));
+  if (size) {
+    *size = env->GetIntField(buffer, size_field);
+  }
+
+  // Clean-up
+  env->DeleteLocalRef(base_class);
+
+  return data;
+}
+
+bool AttachDataToJBuffer(JNIEnv* env, jobject buffer, char* data, int size) {
+  jclass base_class = env->FindClass("android/filterfw/core/NativeBuffer");
+
+  // Get fields
+  jfieldID ptr_field = env->GetFieldID(base_class, "mDataPointer", "J");
+  jfieldID size_field = env->GetFieldID(base_class, "mSize", "I");
+
+  // Set their values
+  env->SetLongField(buffer, ptr_field, reinterpret_cast<jlong>(data));
+  env->SetIntField(buffer, size_field, size);
+
+  return true;
+}
+
+jboolean Java_android_filterfw_core_NativeBuffer_allocate(JNIEnv* env, jobject thiz, jint size) {
+  char* data = new char[size];
+  return ToJBool(AttachDataToJBuffer(env, thiz, data, size));
+}
+
+jboolean Java_android_filterfw_core_NativeBuffer_deallocate(JNIEnv* env,
+                                                            jobject thiz,
+                                                            jboolean owns_data) {
+  if (ToCppBool(owns_data)) {
+    char* data = GetJBufferData(env, thiz, NULL);
+    delete[] data;
+  }
+  return JNI_TRUE;
+}
+
+jboolean Java_android_filterfw_core_NativeBuffer_nativeCopyTo(JNIEnv* env,
+                                                              jobject thiz,
+                                                              jobject new_buffer) {
+  // Get source buffer
+  int size;
+  char* source_data = GetJBufferData(env, thiz, &size);
+
+  // Make copy
+  char* target_data = new char[size];
+  memcpy(target_data, source_data, size);
+
+  // Attach it to new buffer
+  AttachDataToJBuffer(env, new_buffer, target_data, size);
+
+  return JNI_TRUE;
+}
+
diff --git a/media/mca/filterfw/jni/jni_native_buffer.h b/media/mca/filterfw/jni/jni_native_buffer.h
new file mode 100644
index 0000000..73c12be
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_native_buffer.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTEFW_JNI_NATIVE_BUFFER_H
+#define ANDROID_FILTEFW_JNI_NATIVE_BUFFER_H
+
+#include <jni.h>
+
+// Internal Buffer Unwrapping functions ////////////////////////////////////////////////////////////
+/**
+ * Given a Java NativeBuffer instance, get access to the underlying C pointer and its size. The
+ * size argument may be NULL, in which case the object is not queried for its size.
+ **/
+char* GetJBufferData(JNIEnv* env, jobject buffer, int* size);
+
+/**
+ * Attach a given C data buffer and its size to a given allocated Java NativeBuffer instance. After
+ * this call, the java instance will have the given C buffer as its backing. Note, that the Java
+ * instance contains the flag on whether or not it owns the buffer or not, so make sure it is what
+ * you expect.
+ **/
+bool AttachDataToJBuffer(JNIEnv* env, jobject buffer, char* data, int size);
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// JNI Wrappers ////////////////////////////////////////////////////////////////////////////////////
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeBuffer_allocate(JNIEnv* env, jobject thiz, jint size);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeBuffer_deallocate(JNIEnv* env, jobject thiz, jboolean owns_data);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeBuffer_nativeCopyTo(JNIEnv* env, jobject thiz, jobject new_buffer);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_FILTEFW_JNI_NATIVE_BUFFER_H
diff --git a/media/mca/filterfw/jni/jni_native_frame.cpp b/media/mca/filterfw/jni/jni_native_frame.cpp
new file mode 100644
index 0000000..1dfa3e6
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_native_frame.cpp
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "android/bitmap.h"
+
+#include "jni/jni_native_frame.h"
+#include "jni/jni_native_buffer.h"
+#include "jni/jni_util.h"
+
+#include "native/base/logging.h"
+#include "native/core/gl_frame.h"
+#include "native/core/native_frame.h"
+
+using android::filterfw::NativeFrame;
+using android::filterfw::GLFrame;
+
+typedef union {
+    uint32_t value;
+    uint8_t  rgba[4];
+} Pixel;
+
+jboolean Java_android_filterfw_core_NativeFrame_nativeAllocate(JNIEnv* env,
+                                                               jobject thiz,
+                                                               jint size) {
+  return ToJBool(WrapObjectInJava(new NativeFrame(size), env, thiz, true));
+}
+
+jboolean Java_android_filterfw_core_NativeFrame_nativeDeallocate(JNIEnv* env, jobject thiz) {
+  return ToJBool(DeleteNativeObject<NativeFrame>(env, thiz));
+}
+
+jint Java_android_filterfw_core_NativeFrame_nativeIntSize(JNIEnv*, jclass) {
+  return sizeof(jint);
+}
+
+jint Java_android_filterfw_core_NativeFrame_nativeFloatSize(JNIEnv*, jclass) {
+  return sizeof(jfloat);
+}
+
+jboolean Java_android_filterfw_core_NativeFrame_setNativeData(JNIEnv* env,
+                                                              jobject thiz,
+                                                              jbyteArray data,
+                                                              jint offset,
+                                                              jint length) {
+  NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz);
+  if (frame && data) {
+    jbyte* bytes = env->GetByteArrayElements(data, NULL);
+    if (bytes) {
+      const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(bytes + offset),
+                                            0,
+                                            length);
+      env->ReleaseByteArrayElements(data, bytes, JNI_ABORT);
+      return ToJBool(success);
+    }
+  }
+  return JNI_FALSE;
+}
+
+jbyteArray Java_android_filterfw_core_NativeFrame_getNativeData(JNIEnv* env,
+                                                                jobject thiz,
+                                                                jint size) {
+  NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz);
+  if (frame) {
+    const uint8_t* data = frame->Data();
+    if (!data || size > frame->Size())
+      return NULL;
+    jbyteArray result = env->NewByteArray(size);
+    env->SetByteArrayRegion(result, 0, size, reinterpret_cast<const jbyte*>(data));
+    return result;
+  }
+  return NULL;
+}
+
+jboolean Java_android_filterfw_core_NativeFrame_getNativeBuffer(JNIEnv* env,
+                                                                jobject thiz,
+                                                                jobject buffer) {
+  NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz);
+  if (frame) {
+    char* data = reinterpret_cast<char*>(frame->MutableData());
+    return ToJBool(AttachDataToJBuffer(env, buffer, data, frame->Size()));
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_NativeFrame_setNativeInts(JNIEnv* env,
+                                                              jobject thiz,
+                                                              jintArray ints) {
+  NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz);
+  if (frame && ints) {
+    jint* int_ptr = env->GetIntArrayElements(ints, NULL);
+    const int length = env->GetArrayLength(ints);
+    if (int_ptr) {
+      const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(int_ptr),
+                                            0,
+                                            length * sizeof(jint));
+      env->ReleaseIntArrayElements(ints, int_ptr, JNI_ABORT);
+      return ToJBool(success);
+    }
+  }
+  return JNI_FALSE;
+}
+
+jintArray Java_android_filterfw_core_NativeFrame_getNativeInts(JNIEnv* env,
+                                                               jobject thiz,
+                                                               jint size) {
+  NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz);
+  if (frame) {
+    const uint8_t* data = frame->Data();
+    if (!data || size > frame->Size() || (size % sizeof(jint)) != 0)
+      return NULL;
+    const int count = size / sizeof(jint);
+    jintArray result = env->NewIntArray(count);
+    env->SetIntArrayRegion(result, 0, count, reinterpret_cast<const jint*>(data));
+    return result;
+  }
+  return NULL;
+}
+
+jboolean Java_android_filterfw_core_NativeFrame_setNativeFloats(JNIEnv* env,
+                                                                jobject thiz,
+                                                                jfloatArray floats) {
+  NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz);
+  if (frame && floats) {
+    jfloat* float_ptr = env->GetFloatArrayElements(floats, NULL);
+    const int length = env->GetArrayLength(floats);
+    if (float_ptr) {
+      const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(float_ptr),
+                                            0,
+                                            length * sizeof(jfloat));
+      env->ReleaseFloatArrayElements(floats, float_ptr, JNI_ABORT);
+      return ToJBool(success);
+    }
+  }
+  return JNI_FALSE;
+}
+
+jfloatArray Java_android_filterfw_core_NativeFrame_getNativeFloats(JNIEnv* env,
+                                                                   jobject thiz,
+                                                                   jint size) {
+  NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz);
+  if (frame) {
+    const uint8_t* data = frame->Data();
+    if (!data || size > frame->Size() || (size % sizeof(jfloat)) != 0)
+      return NULL;
+    const int count = size / sizeof(jfloat);
+    jfloatArray result = env->NewFloatArray(count);
+    env->SetFloatArrayRegion(result, 0, count, reinterpret_cast<const jfloat*>(data));
+    return result;
+  }
+  return NULL;
+}
+
+jboolean Java_android_filterfw_core_NativeFrame_setNativeBitmap(JNIEnv* env,
+                                                                jobject thiz,
+                                                                jobject bitmap,
+                                                                jint size,
+                                                                jint bytes_per_sample) {
+  NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz);
+  if (frame && bitmap) {
+    // Make sure frame size matches bitmap size
+    if ((size / 4) != (frame->Size() / bytes_per_sample)) {
+      ALOGE("Size mismatch in native setBitmap()!");
+      return JNI_FALSE;
+    }
+
+    Pixel* src_ptr;
+    const int result = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void**>(&src_ptr));
+    if (result == ANDROID_BITMAP_RESUT_SUCCESS) {
+      // Create destination pointers
+      uint8_t* dst_ptr = reinterpret_cast<uint8_t*>(frame->MutableData());
+      const uint8_t* end_ptr = dst_ptr + frame->Size();
+      switch (bytes_per_sample) {
+        case 1: { // RGBA -> GRAY
+          while (dst_ptr < end_ptr) {
+            const Pixel pixel = *(src_ptr++);
+            *(dst_ptr++) = (pixel.rgba[0] + pixel.rgba[1] + pixel.rgba[2]) / 3;
+          }
+          break;
+        }
+        case 3: { // RGBA -> RGB
+          while (dst_ptr < end_ptr) {
+            const Pixel pixel = *(src_ptr++);
+            *(dst_ptr++) = pixel.rgba[0];
+            *(dst_ptr++) = pixel.rgba[1];
+            *(dst_ptr++) = pixel.rgba[2];
+          }
+          break;
+        }
+        case 4: { // RGBA -> RGBA
+          memcpy(dst_ptr, src_ptr, frame->Size());
+          break;
+        }
+        default:
+          ALOGE("Unsupported bytes-per-pixel %d in setBitmap!", bytes_per_sample);
+          break;
+      }
+      return (AndroidBitmap_unlockPixels(env, bitmap) == ANDROID_BITMAP_RESUT_SUCCESS);
+    }
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_NativeFrame_getNativeBitmap(JNIEnv* env,
+                                                                jobject thiz,
+                                                                jobject bitmap,
+                                                                jint size,
+                                                                jint bytes_per_sample) {
+  NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz);
+  if (frame && bitmap) {
+    Pixel* dst_ptr;
+    const int result = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void**>(&dst_ptr));
+    if (result == ANDROID_BITMAP_RESUT_SUCCESS) {
+      // Make sure frame size matches bitmap size
+      if ((size / 4) != (frame->Size() / bytes_per_sample)) {
+        ALOGE("Size mismatch in native getBitmap()!");
+        return JNI_FALSE;
+      }
+
+      const uint8_t* src_ptr = frame->Data();
+      const uint8_t* end_ptr = src_ptr + frame->Size();
+      switch (bytes_per_sample) {
+        case 1: { // GRAY -> RGBA
+          while (src_ptr < end_ptr) {
+            const uint8_t value = *(src_ptr++);
+            dst_ptr->rgba[0] = dst_ptr->rgba[1] = dst_ptr->rgba[2] = value;
+            dst_ptr->rgba[3] = 255;
+            ++dst_ptr;
+          }
+          break;
+        }
+        case 3: { // RGB -> RGBA
+          while (src_ptr < end_ptr) {
+            dst_ptr->rgba[0] = *(src_ptr++);
+            dst_ptr->rgba[1] = *(src_ptr++);
+            dst_ptr->rgba[2] = *(src_ptr++);
+            dst_ptr->rgba[3] = 255;
+            ++dst_ptr;
+          }
+          break;
+        }
+        case 4: { // RGBA -> RGBA
+          memcpy(dst_ptr, src_ptr, frame->Size());
+          break;
+        }
+        default:
+          ALOGE("Unsupported bytes-per-pixel %d in getBitmap!", bytes_per_sample);
+          break;
+      }
+      return (AndroidBitmap_unlockPixels(env, bitmap) == ANDROID_BITMAP_RESUT_SUCCESS);
+    }
+  }
+  return JNI_FALSE;
+}
+
+jint Java_android_filterfw_core_NativeFrame_getNativeCapacity(JNIEnv* env, jobject thiz) {
+  NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz);
+  return frame ? frame->Capacity() : -1;
+}
+
+jboolean Java_android_filterfw_core_NativeFrame_nativeCopyFromNative(JNIEnv* env,
+                                                                     jobject thiz,
+                                                                     jobject frame) {
+  NativeFrame* this_frame = ConvertFromJava<NativeFrame>(env, thiz);
+  NativeFrame* other_frame = ConvertFromJava<NativeFrame>(env, frame);
+  if (this_frame && other_frame) {
+    return ToJBool(this_frame->WriteData(other_frame->Data(), 0, other_frame->Size()));
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_NativeFrame_nativeCopyFromGL(JNIEnv* env,
+                                                                 jobject thiz,
+                                                                 jobject frame) {
+  NativeFrame* this_frame = ConvertFromJava<NativeFrame>(env, thiz);
+  GLFrame* other_frame = ConvertFromJava<GLFrame>(env, frame);
+  if (this_frame && other_frame) {
+    return ToJBool(other_frame->CopyDataTo(this_frame->MutableData(), this_frame->Size()));
+  }
+  return JNI_FALSE;
+}
diff --git a/media/mca/filterfw/jni/jni_native_frame.h b/media/mca/filterfw/jni/jni_native_frame.h
new file mode 100644
index 0000000..ecd9f82
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_native_frame.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_JNI_NATIVE_FRAME_H
+#define ANDROID_FILTERFW_JNI_NATIVE_FRAME_H
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeFrame_nativeAllocate(JNIEnv* env, jobject thiz, jint size);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeFrame_nativeDeallocate(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jint JNICALL
+Java_android_filterfw_core_NativeFrame_nativeIntSize(JNIEnv* env, jclass clazz);
+
+JNIEXPORT jint JNICALL
+Java_android_filterfw_core_NativeFrame_nativeFloatSize(JNIEnv* env, jclass clazz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeFrame_setNativeInts(JNIEnv* env, jobject thiz, jintArray ints);
+
+JNIEXPORT jintArray JNICALL
+Java_android_filterfw_core_NativeFrame_getNativeInts(JNIEnv* env, jobject thiz, jint size);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeFrame_setNativeFloats(JNIEnv* env, jobject thiz, jfloatArray ints);
+
+JNIEXPORT jfloatArray JNICALL
+Java_android_filterfw_core_NativeFrame_getNativeFloats(JNIEnv* env, jobject thiz, jint size);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeFrame_setNativeData(JNIEnv* env,
+                                                     jobject thiz,
+                                                     jbyteArray data,
+                                                     jint offset,
+                                                     jint length);
+
+JNIEXPORT jbyteArray JNICALL
+Java_android_filterfw_core_NativeFrame_getNativeData(JNIEnv* env, jobject thiz, jint size);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeFrame_getNativeBuffer(JNIEnv* env, jobject thiz, jobject buffer);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeFrame_setNativeBitmap(JNIEnv* env,
+                                                       jobject thiz,
+                                                       jobject bitmap,
+                                                       jint size,
+                                                       jint bytes_per_sample);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeFrame_getNativeBitmap(JNIEnv* env,
+                                                       jobject thiz,
+                                                       jobject bitmap,
+                                                       jint size,
+                                                       jint bytes_per_sample);
+
+JNIEXPORT jint JNICALL
+Java_android_filterfw_core_NativeFrame_getNativeCapacity(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeFrame_nativeCopyFromNative(JNIEnv* env,
+                                                            jobject thiz,
+                                                            jobject frame);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeFrame_nativeCopyFromGL(JNIEnv* env,
+                                                        jobject thiz,
+                                                        jobject frame);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_FILTERFW_JNI_NATIVE_FRAME_H
diff --git a/media/mca/filterfw/jni/jni_native_program.cpp b/media/mca/filterfw/jni/jni_native_program.cpp
new file mode 100644
index 0000000..b30b769
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_native_program.cpp
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <vector>
+
+#include "jni/jni_native_program.h"
+#include "jni/jni_util.h"
+
+#include "native/base/logging.h"
+#include "native/core/native_frame.h"
+#include "native/core/native_program.h"
+
+using android::filterfw::NativeFrame;
+using android::filterfw::NativeProgram;
+
+jboolean Java_android_filterfw_core_NativeProgram_allocate(JNIEnv* env, jobject thiz) {
+  return ToJBool(WrapObjectInJava(new NativeProgram(), env, thiz, true));
+}
+
+jboolean Java_android_filterfw_core_NativeProgram_deallocate(JNIEnv* env, jobject thiz) {
+  return ToJBool(DeleteNativeObject<NativeProgram>(env, thiz));
+}
+
+jboolean Java_android_filterfw_core_NativeProgram_nativeInit(JNIEnv* env, jobject thiz) {
+  NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz);
+  return ToJBool(program && program->CallInit());
+}
+
+jboolean Java_android_filterfw_core_NativeProgram_openNativeLibrary(JNIEnv* env,
+                                                                    jobject thiz,
+                                                                    jstring lib_name) {
+  NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz);
+  return ToJBool(program && lib_name && program->OpenLibrary(ToCppString(env, lib_name)));
+}
+
+jboolean Java_android_filterfw_core_NativeProgram_bindInitFunction(JNIEnv* env,
+                                                                   jobject thiz,
+                                                                   jstring func_name) {
+  NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz);
+  return ToJBool(program && func_name && program->BindInitFunction(ToCppString(env, func_name)));
+}
+
+jboolean Java_android_filterfw_core_NativeProgram_bindSetValueFunction(JNIEnv* env,
+                                                                       jobject thiz,
+                                                                       jstring func_name) {
+  NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz);
+  return ToJBool(program &&
+                 func_name &&
+                 program->BindSetValueFunction(ToCppString(env, func_name)));
+}
+
+jboolean Java_android_filterfw_core_NativeProgram_bindGetValueFunction(JNIEnv* env,
+                                                                       jobject thiz,
+                                                                       jstring func_name) {
+  NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz);
+  return ToJBool(program &&
+                 func_name &&
+                 program->BindGetValueFunction(ToCppString(env, func_name)));
+}
+
+jboolean Java_android_filterfw_core_NativeProgram_bindProcessFunction(JNIEnv* env,
+                                                                      jobject thiz,
+                                                                      jstring func_name) {
+  NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz);
+  return ToJBool(program && func_name && program->BindProcessFunction(ToCppString(env, func_name)));
+}
+
+jboolean Java_android_filterfw_core_NativeProgram_bindResetFunction(JNIEnv* env,
+                                                                    jobject thiz,
+                                                                    jstring func_name) {
+  NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz);
+  return ToJBool(program &&
+                 func_name &&
+                 program->BindResetFunction(ToCppString(env, func_name)));
+}
+
+jboolean Java_android_filterfw_core_NativeProgram_bindTeardownFunction(JNIEnv* env,
+                                                                       jobject thiz,
+                                                                       jstring func_name) {
+  NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz);
+  return ToJBool(program &&
+                 func_name &&
+                 program->BindTeardownFunction(ToCppString(env, func_name)));
+}
+
+jboolean Java_android_filterfw_core_NativeProgram_callNativeInit(JNIEnv* env, jobject thiz) {
+  NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz);
+  return ToJBool(program && program->CallInit());
+}
+
+jboolean Java_android_filterfw_core_NativeProgram_callNativeSetValue(JNIEnv* env,
+                                                                     jobject thiz,
+                                                                     jstring key,
+                                                                     jstring value) {
+  if (!value) {
+    ALOGE("Native Program: Attempting to set null value for key %s!",
+         ToCppString(env, key).c_str());
+  }
+  NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz);
+  const std::string c_value = ToCppString(env, value);
+  const std::string c_key = ToCppString(env, key);
+  return ToJBool(program && program->CallSetValue(c_key, c_value));
+}
+
+jstring Java_android_filterfw_core_NativeProgram_callNativeGetValue(JNIEnv* env,
+                                                                    jobject thiz,
+                                                                    jstring key) {
+  NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz);
+  const std::string c_key = ToCppString(env, key);
+  if (program) {
+    return ToJString(env, program->CallGetValue(c_key));
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_NativeProgram_callNativeProcess(JNIEnv* env,
+                                                                    jobject thiz,
+                                                                    jobjectArray inputs,
+                                                                    jobject output) {
+  NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz);
+
+  // Sanity checks
+  if (!program || !inputs) {
+    return JNI_FALSE;
+  }
+
+  // Get the input buffers
+  const int input_count = env->GetArrayLength(inputs);
+  std::vector<const char*> input_buffers(input_count, NULL);
+  std::vector<int> input_sizes(input_count, 0);
+  for (int i = 0 ; i < input_count; ++i) {
+    const char* input_data = NULL;
+    int input_size = 0;
+    jobject input = env->GetObjectArrayElement(inputs, i);
+    if (input) {
+        NativeFrame* native_frame = ConvertFromJava<NativeFrame>(env, input);
+        if (!native_frame) {
+          ALOGE("NativeProgram: Could not grab NativeFrame input %d!", i);
+          return JNI_FALSE;
+        }
+        input_data = reinterpret_cast<const char*>(native_frame->Data());
+        input_size = native_frame->Size();
+    }
+    input_buffers[i] = input_data;
+    input_sizes[i] = input_size;
+  }
+
+  // Get the output buffer
+  char* output_data = NULL;
+  int output_size = 0;
+  if (output) {
+    NativeFrame* output_frame = ConvertFromJava<NativeFrame>(env, output);
+    if (!output_frame) {
+      ALOGE("NativeProgram: Could not grab NativeFrame output!");
+      return JNI_FALSE;
+    }
+    output_data = reinterpret_cast<char*>(output_frame->MutableData());
+    output_size = output_frame->Size();
+  }
+
+  // Process the frames!
+  return ToJBool(program->CallProcess(input_buffers, input_sizes, output_data, output_size));
+}
+
+jboolean Java_android_filterfw_core_NativeProgram_callNativeReset(JNIEnv* env, jobject thiz) {
+  NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz);
+  return ToJBool(program && program->CallReset());
+}
+
+jboolean Java_android_filterfw_core_NativeProgram_callNativeTeardown(JNIEnv* env, jobject thiz) {
+  NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz);
+  return ToJBool(program && program->CallTeardown());
+}
diff --git a/media/mca/filterfw/jni/jni_native_program.h b/media/mca/filterfw/jni/jni_native_program.h
new file mode 100644
index 0000000..fa97c39
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_native_program.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_JNI_NATIVE_PROGRAM_H
+#define ANDROID_FILTERFW_JNI_NATIVE_PROGRAM_H
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeProgram_allocate(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeProgram_deallocate(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeProgram_nativeInit(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeProgram_openNativeLibrary(JNIEnv* env,
+                                                           jobject thiz,
+                                                           jstring lib_name);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeProgram_bindInitFunction(JNIEnv* env,
+                                                          jobject thiz,
+                                                          jstring func_name);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeProgram_bindSetValueFunction(JNIEnv* env,
+                                                              jobject thiz,
+                                                              jstring func_name);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeProgram_bindGetValueFunction(JNIEnv* env,
+                                                              jobject thiz,
+                                                              jstring func_name);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeProgram_bindProcessFunction(JNIEnv* env,
+                                                             jobject thiz,
+                                                             jstring func_name);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeProgram_bindResetFunction(JNIEnv* env,
+                                                           jobject thiz,
+                                                           jstring func_name);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeProgram_bindTeardownFunction(JNIEnv* env,
+                                                              jobject thiz,
+                                                              jstring func_name);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeProgram_callNativeInit(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeProgram_callNativeSetValue(JNIEnv* env,
+                                                            jobject thiz,
+                                                            jstring key,
+                                                            jstring value);
+
+JNIEXPORT jstring JNICALL
+Java_android_filterfw_core_NativeProgram_callNativeGetValue(JNIEnv* env,
+                                                            jobject thiz,
+                                                            jstring key);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeProgram_callNativeProcess(JNIEnv* env,
+                                                           jobject thiz,
+                                                           jobjectArray inputs,
+                                                           jobject output);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeProgram_callNativeReset(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_NativeProgram_callNativeTeardown(JNIEnv* env, jobject thiz);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_FILTERFW_JNI_NATIVE_PROGRAM_H
diff --git a/media/mca/filterfw/jni/jni_shader_program.cpp b/media/mca/filterfw/jni/jni_shader_program.cpp
new file mode 100644
index 0000000..19f43cd
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_shader_program.cpp
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <vector>
+
+#include "jni/jni_shader_program.h"
+#include "jni/jni_util.h"
+
+#include "native/base/logging.h"
+#include "native/core/geometry.h"
+#include "native/core/gl_env.h"
+#include "native/core/gl_frame.h"
+#include "native/core/shader_program.h"
+#include "native/core/vertex_frame.h"
+
+using android::filterfw::GLEnv;
+using android::filterfw::GLFrame;
+using android::filterfw::Point;
+using android::filterfw::ProgramVar;
+using android::filterfw::Quad;
+using android::filterfw::ShaderProgram;
+using android::filterfw::VertexFrame;
+
+jboolean Java_android_filterfw_core_ShaderProgram_allocate(JNIEnv* env,
+                                                           jobject thiz,
+                                                           jobject gl_env,
+                                                           jstring vertex_shader,
+                                                           jstring fragment_shader) {
+  // Get the GLEnv pointer
+  GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
+
+  // Create the shader
+  if (!fragment_shader || !gl_env_ptr)
+    return false;
+  else if (!vertex_shader)
+    return ToJBool(WrapObjectInJava(new ShaderProgram(
+      gl_env_ptr,
+      ToCppString(env, fragment_shader)),
+      env,
+      thiz,
+      true));
+  else
+    return ToJBool(WrapObjectInJava(new ShaderProgram(
+      gl_env_ptr,
+      ToCppString(env, vertex_shader),
+      ToCppString(env, fragment_shader)),
+      env,
+      thiz,
+      true));
+}
+
+jboolean Java_android_filterfw_core_ShaderProgram_deallocate(JNIEnv* env, jobject thiz) {
+  return ToJBool(DeleteNativeObject<ShaderProgram>(env, thiz));
+}
+
+jboolean Java_android_filterfw_core_ShaderProgram_compileAndLink(JNIEnv* env, jobject thiz) {
+  ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz);
+  return program ? ToJBool(program->CompileAndLink()) : JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_ShaderProgram_setUniformValue(JNIEnv* env,
+                                                                  jobject thiz,
+                                                                  jstring key,
+                                                                  jobject value) {
+  ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz);
+  const Value c_value = ToCValue(env, value);
+  const std::string c_key = ToCppString(env, key);
+  if (c_value.value) {
+    return ToJBool(program && program->SetUniformValue(c_key, c_value));
+  } else {
+    ALOGE("ShaderProgram: Could not convert java object value passed for key '%s'!", c_key.c_str());
+    return JNI_FALSE;
+  }
+}
+
+jobject Java_android_filterfw_core_ShaderProgram_getUniformValue(JNIEnv* env,
+                                                                 jobject thiz,
+                                                                 jstring key) {
+  ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz);
+  const std::string c_key = ToCppString(env, key);
+  return program ? ToJObject(env, program->GetUniformValue(c_key)) : JNI_NULL;
+}
+
+jboolean Java_android_filterfw_core_ShaderProgram_shaderProcess(JNIEnv* env,
+                                                                jobject thiz,
+                                                                jobjectArray inputs,
+                                                                jobject output) {
+  ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz);
+  std::vector<const GLFrame*> input_frames;
+  if (program && inputs && output) {
+    // Get the input frames
+    const int input_count = env->GetArrayLength(inputs);
+    for (int i = 0; i < input_count; ++i) {
+      jobject input = env->GetObjectArrayElement(inputs, i);
+      const GLFrame* input_frame = ConvertFromJava<GLFrame>(env, input);
+      if (!input || !input_frame) {
+        ALOGE("ShaderProgram: invalid input frame %d!", i);
+        return JNI_FALSE;
+      }
+      input_frames.push_back(input_frame);
+    }
+
+    // Get the output frame
+    GLFrame* output_frame = ConvertFromJava<GLFrame>(env, output);
+    if (!output_frame) {
+      ALOGE("ShaderProgram: no output frame found!");
+      return JNI_FALSE;
+    }
+
+    // Process the frames!
+    if (!program->Process(input_frames, output_frame)) {
+      ALOGE("ShaderProgram: error processing shader!");
+      return JNI_FALSE;
+    }
+
+    return JNI_TRUE;
+  }
+  return JNI_FALSE;
+}
+
+jobject Java_android_filterfw_core_ShaderProgram_nativeCreateIdentity(JNIEnv* env,
+                                                                      jclass,
+                                                                      jobject gl_env) {
+  GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
+  ShaderProgram* program = gl_env_ptr ? ShaderProgram::CreateIdentity(gl_env_ptr) : NULL;
+  return program ? WrapNewObjectInJava(program, env, false) : NULL;
+}
+
+jboolean Java_android_filterfw_core_ShaderProgram_setSourceRegion(JNIEnv* env,
+                                                                  jobject thiz,
+                                                                  jfloat x0,
+                                                                  jfloat y0,
+                                                                  jfloat x1,
+                                                                  jfloat y1,
+                                                                  jfloat x2,
+                                                                  jfloat y2,
+                                                                  jfloat x3,
+                                                                  jfloat y3) {
+  ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz);
+  if (program) {
+    program->SetSourceRegion(Quad(Point(x0, y0), Point(x1, y1), Point(x2, y2), Point(x3, y3)));
+    return JNI_TRUE;
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_ShaderProgram_setTargetRegion(JNIEnv* env,
+                                                                  jobject thiz,
+                                                                  jfloat x0,
+                                                                  jfloat y0,
+                                                                  jfloat x1,
+                                                                  jfloat y1,
+                                                                  jfloat x2,
+                                                                  jfloat y2,
+                                                                  jfloat x3,
+                                                                  jfloat y3) {
+  ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz);
+  if (program) {
+    program->SetTargetRegion(Quad(Point(x0, y0), Point(x1, y1), Point(x2, y2), Point(x3, y3)));
+    return JNI_TRUE;
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_ShaderProgram_setShaderClearsOutput(JNIEnv* env,
+                                                                        jobject thiz,
+                                                                        jboolean clears) {
+  ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz);
+  if (program) {
+    program->SetClearsOutput(ToCppBool(clears));
+    return JNI_TRUE;
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_ShaderProgram_setShaderBlendEnabled(JNIEnv* env,
+                                                                        jobject thiz,
+                                                                        jboolean enable) {
+  ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz);
+  if (program) {
+    program->SetBlendEnabled(ToCppBool(enable));
+    return JNI_TRUE;
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_ShaderProgram_setShaderBlendFunc(JNIEnv* env,
+                                                                     jobject thiz,
+                                                                     jint sfactor,
+                                                                     jint dfactor) {
+  ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz);
+  if (program) {
+    program->SetBlendFunc(sfactor, dfactor);
+    return JNI_TRUE;
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_ShaderProgram_setShaderClearColor(JNIEnv* env,
+                                                                      jobject thiz,
+                                                                      jfloat r,
+                                                                      jfloat g,
+                                                                      jfloat b) {
+  ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz);
+  if (program) {
+    program->SetClearColor(r, g, b, 1.0f);
+    return JNI_TRUE;
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_ShaderProgram_setShaderDrawMode(JNIEnv* env,
+                                                                    jobject thiz,
+                                                                    jint draw_mode) {
+  ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz);
+  if (program) {
+    program->SetDrawMode(draw_mode);
+    return JNI_TRUE;
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_ShaderProgram_setShaderTileCounts(JNIEnv* env,
+                                                                      jobject thiz,
+                                                                      jint x_count,
+                                                                      jint y_count) {
+  ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz);
+  if (program) {
+    program->SetTileCounts(x_count, y_count);
+    return JNI_TRUE;
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_ShaderProgram_setShaderVertexCount(JNIEnv* env,
+                                                                       jobject thiz,
+                                                                       jint vertex_count) {
+  ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz);
+  if (program) {
+    program->SetVertexCount(vertex_count);
+    return JNI_TRUE;
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_ShaderProgram_beginShaderDrawing(JNIEnv* env, jobject thiz) {
+    ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz);
+    return ToJBool(program && program->BeginDraw());
+}
+
+jboolean Java_android_filterfw_core_ShaderProgram_setShaderAttributeValues(
+    JNIEnv* env,
+    jobject thiz,
+    jstring attr_name,
+    jfloatArray values,
+    jint component_count) {
+  ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz);
+  if (program) {
+    // Get the floats to set
+    jfloat* float_ptr = env->GetFloatArrayElements(values, NULL);
+    const int length = env->GetArrayLength(values);
+
+    // Get the program variable to set
+    const std::string attr_string = ToCppString(env, attr_name);
+    ProgramVar program_var = program->GetAttribute(attr_string);
+
+    // Set the variable
+    if (float_ptr && ShaderProgram::IsVarValid(program_var)) {
+      const bool success = program->SetAttributeValues(program_var,
+                                                       reinterpret_cast<float*>(float_ptr),
+                                                       length,
+                                                       component_count);
+      env->ReleaseFloatArrayElements(values, float_ptr, JNI_ABORT);
+      return ToJBool(success);
+    }
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_ShaderProgram_setShaderAttributeVertexFrame(
+    JNIEnv* env,
+    jobject thiz,
+    jstring attr_name,
+    jobject vertex_frame,
+    jint type,
+    jint component_count,
+    jint stride,
+    jint offset,
+    jboolean normalize) {
+  ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz);
+  if (program) {
+    // Get the vertex frame
+    VertexFrame* v_frame = ConvertFromJava<VertexFrame>(env, vertex_frame);
+
+    // Get the program variable to set
+    const std::string attr_string = ToCppString(env, attr_name);
+    ProgramVar program_var = program->GetAttribute(attr_string);
+
+    // Set the variable
+    if (v_frame && ShaderProgram::IsVarValid(program_var)) {
+      const bool success = program->SetAttributeValues(program_var,
+                                                       v_frame,
+                                                       type,
+                                                       component_count,
+                                                       stride,
+                                                       offset,
+                                                       ToCppBool(normalize));
+      return ToJBool(success);
+    }
+  }
+  return JNI_FALSE;
+}
diff --git a/media/mca/filterfw/jni/jni_shader_program.h b/media/mca/filterfw/jni/jni_shader_program.h
new file mode 100644
index 0000000..94a1dd4
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_shader_program.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_JNI_SHADER_PROGRAM_H
+#define ANDROID_FILTERFW_JNI_SHADER_PROGRAM_H
+
+#include <jni.h>
+
+#include "native/core/value.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_allocate(JNIEnv* env,
+                                                  jobject thiz,
+                                                  jobject gl_env,
+                                                  jstring vertex_shader,
+                                                  jstring fragment_shader);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_deallocate(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_compileAndLink(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_setUniformValue(JNIEnv* env,
+                                                         jobject thiz,
+                                                         jstring key,
+                                                         jobject value);
+
+JNIEXPORT jobject JNICALL
+Java_android_filterfw_core_ShaderProgram_getUniformValue(JNIEnv* env,
+                                                         jobject thiz,
+                                                         jstring key);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_shaderProcess(JNIEnv* env,
+                                                       jobject thiz,
+                                                       jobjectArray inputs,
+                                                       jobject output);
+
+JNIEXPORT jobject JNICALL
+Java_android_filterfw_core_ShaderProgram_nativeCreateIdentity(JNIEnv* env,
+                                                              jclass clazz,
+                                                              jobject gl_env);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_setSourceRegion(JNIEnv* env,
+                                                         jobject thiz,
+                                                         jfloat x0,
+                                                         jfloat y0,
+                                                         jfloat x1,
+                                                         jfloat y1,
+                                                         jfloat x2,
+                                                         jfloat y2,
+                                                         jfloat x3,
+                                                         jfloat y3);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_setTargetRegion(JNIEnv* env,
+                                                         jobject thiz,
+                                                         jfloat x0,
+                                                         jfloat y0,
+                                                         jfloat x1,
+                                                         jfloat y1,
+                                                         jfloat x2,
+                                                         jfloat y2,
+                                                         jfloat x3,
+                                                         jfloat y3);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_setShaderClearsOutput(JNIEnv* env,
+                                                               jobject thiz,
+                                                               jboolean clears);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_setShaderClearColor(JNIEnv* env,
+                                                             jobject thiz,
+                                                             jfloat r,
+                                                             jfloat g,
+                                                             jfloat b);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_setShaderBlendEnabled(JNIEnv* env,
+                                                               jobject thiz,
+                                                               jboolean enable);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_setShaderBlendFunc(JNIEnv* env,
+                                                               jobject thiz,
+                                                               jint sfactor,
+                                                               jint dfactor);
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_setShaderDrawMode(JNIEnv* env,
+                                                           jobject thiz,
+                                                           jint draw_mode);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_setShaderTileCounts(JNIEnv* env,
+                                                             jobject thiz,
+                                                             jint x_count,
+                                                             jint y_count);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_setShaderVertexCount(JNIEnv* env,
+                                                              jobject thiz,
+                                                              jint vertex_count);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_beginShaderDrawing(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_setShaderAttributeValues(JNIEnv* env,
+                                                                  jobject thiz,
+                                                                  jstring attr_name,
+                                                                  jfloatArray values,
+                                                                  jint component_count);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_ShaderProgram_setShaderAttributeVertexFrame(JNIEnv* env,
+                                                                       jobject thiz,
+                                                                       jstring attr_name,
+                                                                       jobject vertex_frame,
+                                                                       jint type,
+                                                                       jint component_count,
+                                                                       jint stride,
+                                                                       jint offset,
+                                                                       jboolean normalize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_FILTERFW_JNI_SHADER_PROGRAM_H
diff --git a/media/mca/filterfw/jni/jni_util.cpp b/media/mca/filterfw/jni/jni_util.cpp
new file mode 100644
index 0000000..30c0898
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_util.cpp
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+
+#include "jni/jni_util.h"
+
+#include "base/logging.h"
+
+#if 0
+JavaObject::JavaObject()
+    : object_(JNI_NULL),
+      ref_count_(new int(0)) {
+}
+
+JavaObject::JavaObject(const JavaObject& java_obj)
+    : object_(java_obj.object_),
+      ref_count_(java_obj.ref_count_) {
+  Retain();
+}
+
+JavaObject::JavaObject(jobject object, JNIEnv* env)
+    : object_(NULL),
+      ref_count_(new int(0)) {
+  Retain();
+  object_ = env->NewGlobalRef(object_);
+}
+
+JavaObject::~JavaObject() {
+  Release();
+}
+
+JavaObject& JavaObject::operator=(const JavaObject& java_obj) {
+  Release();
+  object_ = java_obj.object_;
+  ref_count_ = java_obj.ref_count_;
+  Retain();
+  return *this;
+}
+
+void JavaObject::Retain() {
+  if (ref_count_)
+    ++(*ref_count_);
+  else
+    ALOGE("JavaObject: Reference count is NULL! JavaObject may be corrupted.");
+}
+
+void JavaObject::Release() {
+  if (ref_count_) {
+    if (*ref_count_ > 0)
+      --(*ref_count_);
+    if (*ref_count_ == 0) {
+      JNIEnv* env = GetCurrentJNIEnv();
+      if (!env)
+        ALOGE("JavaObject: Releasing outside of Java thread. Will just leak!");
+      else if (object_)
+        env->DeleteGlobalRef(object_);
+      delete ref_count_;
+      ref_count_ = NULL;
+    }
+  } else {
+    ALOGE("JavaObject: Reference count is NULL! JavaObject may be corrupted.");
+  }
+}
+
+void JavaObject::Reset() {
+  Release();
+  object_ = NULL;
+  ref_count_ = new int(0);
+}
+
+JavaVM* GetCurrentJavaVM() {
+  return g_current_java_vm_;
+}
+
+JNIEnv* GetCurrentJNIEnv() {
+  JavaVM* vm = GetCurrentJavaVM();
+  JNIEnv* env = NULL;
+  const jint result = vm->GetEnv(reinterpret_cast<void**>(&env),
+                                 JNI_VERSION_1_4);
+  return result == JNI_OK ? env : NULL;
+}
+#endif
+
+jstring ToJString(JNIEnv* env, const std::string& value) {
+  return env->NewStringUTF(value.c_str());
+}
+
+std::string ToCppString(JNIEnv* env, jstring value) {
+  jboolean isCopy;
+  const char* c_value = env->GetStringUTFChars(value, &isCopy);
+  std::string result(c_value);
+  if (isCopy == JNI_TRUE)
+    env->ReleaseStringUTFChars(value, c_value);
+  return result;
+}
+
+jboolean ToJBool(bool value) {
+  return value ? JNI_TRUE : JNI_FALSE;
+}
+
+bool ToCppBool(jboolean value) {
+  return value == JNI_TRUE;
+}
+
+// TODO: We actually shouldn't use such a function as it requires a class name lookup at every
+// invocation. Instead, store the class objects and use those.
+bool IsJavaInstanceOf(JNIEnv* env, jobject object, const std::string& class_name) {
+  jclass clazz = env->FindClass(class_name.c_str());
+  return clazz ? env->IsInstanceOf(object, clazz) == JNI_TRUE : false;
+}
+
+template<typename T>
+jobject CreateJObject(JNIEnv* env, const std::string& class_name, const std::string& signature, T value) {
+  jobject result = JNI_NULL;
+
+  return result;
+}
+
+Value ToCValue(JNIEnv* env, jobject object) {
+  Value result = MakeNullValue();
+  if (object != NULL) {
+    if (IsJavaInstanceOf(env, object, "java/lang/Boolean")) {
+      jmethodID method = env->GetMethodID(env->GetObjectClass(object), "booleanValue", "()Z");
+      result = MakeIntValue(env->CallBooleanMethod(object, method) == JNI_TRUE ? 1 : 0);
+    } else if (IsJavaInstanceOf(env, object, "java/lang/Integer")) {
+      jmethodID method = env->GetMethodID(env->GetObjectClass(object), "intValue", "()I");
+      result = MakeIntValue(env->CallIntMethod(object, method));
+    } else if (IsJavaInstanceOf(env, object, "java/lang/Float")) {
+      jmethodID method = env->GetMethodID(env->GetObjectClass(object), "floatValue", "()F");
+      result = MakeFloatValue(env->CallFloatMethod(object, method));
+    } else if (IsJavaInstanceOf(env, object, "java/lang/String")) {
+      result = MakeStringValue(ToCppString(env, static_cast<jstring>(object)).c_str());
+    } else if (IsJavaInstanceOf(env, object, "[I")) {
+      jint* elems = env->GetIntArrayElements(static_cast<jintArray>(object), NULL);
+      const jint count = env->GetArrayLength(static_cast<jintArray>(object));
+      result = MakeIntArrayValue(elems, count);
+      env->ReleaseIntArrayElements(static_cast<jintArray>(object), elems, JNI_ABORT);
+    } else if (IsJavaInstanceOf(env, object, "[F")) {
+      jfloat* elems = env->GetFloatArrayElements(static_cast<jfloatArray>(object), NULL);
+      const jint count = env->GetArrayLength(static_cast<jfloatArray>(object));
+      result = MakeFloatArrayValue(elems, count);
+      env->ReleaseFloatArrayElements(static_cast<jfloatArray>(object), elems, JNI_ABORT);
+    }
+  }
+  return result;
+}
+
+jobject ToJObject(JNIEnv* env, const Value& value) {
+  jobject result = JNI_NULL;
+  if (ValueIsInt(value)) {
+    jclass clazz = env->FindClass("java/lang/Integer");
+    jmethodID constructorID = env->GetMethodID(clazz, "<init>", "(I)V");
+    result = env->NewObject(clazz, constructorID, GetIntValue(value));
+  } else if (ValueIsFloat(value)) {
+    jclass clazz = env->FindClass("java/lang/Float");
+    jmethodID constructorID = env->GetMethodID(clazz, "<init>", "(F)V");
+    result = env->NewObject(clazz, constructorID, GetFloatValue(value));
+  } else if (ValueIsString(value)) {
+    result = ToJString(env, GetStringValue(value));
+  } else if (ValueIsIntArray(value)) {
+    result = env->NewIntArray(GetValueCount(value));
+    env->SetIntArrayRegion(static_cast<jintArray>(result),
+                           0,
+                           GetValueCount(value),
+                           reinterpret_cast<const jint*>(GetIntArrayValue(value)));
+  } else if (ValueIsFloatArray(value)) {
+    result = env->NewFloatArray(GetValueCount(value));
+    env->SetFloatArrayRegion(static_cast<jfloatArray>(result),
+                             0,
+                             GetValueCount(value),
+                             reinterpret_cast<const jfloat*>(GetFloatArrayValue(value)));
+  }
+  return result;
+}
diff --git a/media/mca/filterfw/jni/jni_util.h b/media/mca/filterfw/jni/jni_util.h
new file mode 100644
index 0000000..68ff653
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_util.h
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+
+#include <hash_map>
+#include <string>
+
+#include "base/utilities.h"
+#include "core/value.h"
+
+#ifndef ANDROID_FILTERFW_JNI_JNI_UTIL_H
+#define ANDROID_FILTERFW_JNI_JNI_UTIL_H
+
+// We add this JNI_NULL macro to allow consistent code separation of Java and
+// C++ types.
+#define JNI_NULL NULL
+
+#if 0
+// Pointer to current JavaVM. Do not use this directly. Instead use the funciton
+// GetCurrentJavaVM().
+extern JavaVM* g_current_java_vm_;
+
+// Wrapper around a java object pointer, which includes the environment
+// pointer in which the object "lives". This is used for passing down Java
+// objects from the Java layer to C++.
+// While an instance of this class does not own the underlying java object, it
+// does hold a global reference to it, so that the Java garbage collector does
+// not destroy it. It uses reference counting to determine when it can destroy
+// the reference.
+// TODO: Add multi-thread support!
+class JavaObject {
+  public:
+    // Creates a NULL JavaObject.
+    JavaObject();
+
+    // Creates a wrapper around the given object in the given JNI environment.
+    JavaObject(jobject object, JNIEnv* env);
+
+    // Copy constructor.
+    JavaObject(const JavaObject& java_obj);
+
+    // Destructor.
+    ~JavaObject();
+
+    // Assignment operator.
+    JavaObject& operator=(const JavaObject& java_obj);
+
+    // Access to the object (non-const as JNI functions are non-const).
+    jobject object() const {
+      return object_;
+    }
+
+    // Resets this object to the NULL JavaObject.
+    void Reset();
+
+  private:
+    // Retain the instance, i.e. increase reference count.
+    void Retain();
+
+    // Release the instance, i.e. decrease reference count.
+    void Release();
+
+    // The object pointer (not owned).
+    jobject object_;
+
+    // The reference count of this object
+    int* ref_count_;
+};
+#endif
+
+// ObjectPool template class. This class keeps track of C++ instances that are
+// coupled to Java objects. This is done by using an "id" field in the Java
+// object, which is then mapped to the correct instance here. It should not be
+// necessary to use this class directly. Instead, the convenience functions
+// below can be used.
+template<class T>
+class ObjectPool {
+  public:
+    // Create a new ObjectPool for a specific object type. Pass the path to the
+    // Java equivalent class of the C++ class, and the name of the java member
+    // field that will store the object's ID.
+    static void Setup(const std::string& jclass_name,
+                      const std::string& id_fld_name) {
+      instance_ = new ObjectPool<T>(jclass_name, id_fld_name);
+    }
+
+    // Return the shared instance to this type's pool.
+    static ObjectPool* Instance() {
+      return instance_;
+    }
+
+    // Delete this type's pool.
+    static void TearDown() {
+      delete instance_;
+    }
+
+    // Register a new C++ object with the pool. This does not affect the Java
+    // layer. Use WrapObject() instead to perform the necessary Java-side
+    // assignments. Pass true to owns if the object pool owns the object.
+    int RegisterObject(T* object, bool owns) {
+      const int id = next_id_;
+      objects_[id] = object;
+      owns_[id] = owns;
+      ++next_id_;
+      return id;
+    }
+
+    // Return the object in the pool with the specified ID.
+    T* ObjectWithID(int obj_id) const {
+      typename CObjMap::const_iterator iter = objects_.find(obj_id);
+      return iter == objects_.end() ? NULL : iter->second;
+    }
+
+    // Get the ID of a Java object. This ID can be used to look-up the C++
+    // object.
+    int GetObjectID(JNIEnv* env, jobject j_object) {
+      jclass cls = env->GetObjectClass(j_object);
+      jfieldID id_field = env->GetFieldID(cls, id_field_name_.c_str(), "I");
+      const int result = env->GetIntField(j_object, id_field);
+      env->DeleteLocalRef(cls);
+      return result;
+    }
+
+    // Take a C++ object and wrap it with a given Java object. This will
+    // essentially set the ID member of the Java object to the ID of the C++
+    // object. Pass true to owns if the object pool owns the object.
+    bool WrapObject(T* c_object, JNIEnv* env, jobject j_object, bool owns) {
+      const int id = RegisterObject(c_object, owns);
+      jclass cls = env->GetObjectClass(j_object);
+      jfieldID id_field = env->GetFieldID(cls, id_field_name_.c_str(), "I");
+      env->SetIntField(j_object, id_field, id);
+      env->DeleteLocalRef(cls);
+      return true;
+    }
+
+    // Remove the object with the given ID from this pool, and delete it. This
+    // does not affect the Java layer.
+    bool DeleteObjectWithID(int obj_id) {
+      typename CObjMap::iterator iter = objects_.find(obj_id);
+      const bool found = iter != objects_.end();
+      if (found) {
+        if (owns_[obj_id])
+          delete iter->second;
+        objects_.erase(iter);
+      }
+      return found;
+    }
+
+    // Instantiates a new java object for this class. The Java class must have
+    // a default constructor for this to succeed.
+    jobject CreateJavaObject(JNIEnv* env) {
+      jclass cls = env->FindClass(jclass_name_.c_str());
+      jmethodID constructor = env->GetMethodID(
+        cls,
+        "<init>",
+        "(Landroid/filterfw/core/NativeAllocatorTag;)V");
+      jobject result = env->NewObject(cls, constructor, JNI_NULL);
+      env->DeleteLocalRef(cls);
+      return result;
+    }
+
+    int GetObjectCount() const {
+      return objects_.size();
+    }
+
+    const std::string& GetJavaClassName() const {
+      return jclass_name_;
+    }
+
+  private:
+    explicit ObjectPool(const std::string& jclass_name,
+                        const std::string& id_fld_name)
+      : jclass_name_(jclass_name),
+        id_field_name_(id_fld_name),
+        next_id_(0) { }
+
+    typedef std::hash_map<int, T*>    CObjMap;
+    typedef std::hash_map<int, bool>  FlagMap;
+    static ObjectPool* instance_;
+    std::string jclass_name_;
+    std::string id_field_name_;
+    int next_id_;
+    CObjMap objects_;
+    FlagMap owns_;
+
+    DISALLOW_COPY_AND_ASSIGN(ObjectPool);
+};
+
+template<typename T> ObjectPool<T>* ObjectPool<T>::instance_ = NULL;
+
+// Convenience Functions ///////////////////////////////////////////////////////
+
+// This function "links" the C++ instance and the Java instance, so that they
+// can be mapped to one another. This must be called for every C++ instance
+// which is wrapped by a Java front-end interface. Pass true to owns, if the
+// Java layer should own the object.
+template<typename T>
+bool WrapObjectInJava(T* c_object, JNIEnv* env, jobject j_object, bool owns) {
+  ObjectPool<T>* pool = ObjectPool<T>::Instance();
+  return pool ? pool->WrapObject(c_object, env, j_object, owns) : false;
+}
+
+// Creates a new Java instance, which wraps the passed C++ instance. Returns
+// the wrapped object or JNI_NULL if there was an error. Pass true to owns, if
+// the Java layer should own the object.
+template<typename T>
+jobject WrapNewObjectInJava(T* c_object, JNIEnv* env, bool owns) {
+  ObjectPool<T>* pool = ObjectPool<T>::Instance();
+  if (pool) {
+    jobject result = pool->CreateJavaObject(env);
+    if (WrapObjectInJava(c_object, env, result, owns))
+      return result;
+  }
+  return JNI_NULL;
+}
+
+// Use ConvertFromJava to obtain a C++ instance given a Java object. This
+// instance must have been wrapped in Java using the WrapObjectInJava()
+// function.
+template<typename T>
+T* ConvertFromJava(JNIEnv* env, jobject j_object) {
+  ObjectPool<T>* pool = ObjectPool<T>::Instance();
+  return pool && j_object
+    ? pool->ObjectWithID(pool->GetObjectID(env, j_object))
+    : NULL;
+}
+
+// Delete the native object given a Java instance. This should be called from
+// the Java object's finalizer.
+template<typename T>
+bool DeleteNativeObject(JNIEnv* env, jobject j_object) {
+  ObjectPool<T>* pool = ObjectPool<T>::Instance();
+  return pool && j_object
+    ? pool->DeleteObjectWithID(pool->GetObjectID(env, j_object))
+    : false;
+}
+
+#if 0
+// Get the current JNI VM, or NULL if there is no current VM
+JavaVM* GetCurrentJavaVM();
+
+// Get the current JNI environment, or NULL if this is not a JNI thread
+JNIEnv* GetCurrentJNIEnv();
+#endif
+
+// Convert C++ boolean to Java boolean.
+jboolean ToJBool(bool value);
+
+// Convert Java boolean to C++ boolean.
+bool ToCppBool(jboolean value);
+
+// Convert Java String to C++ string.
+jstring ToJString(JNIEnv* env, const std::string& value);
+
+// Convert C++ string to Java String.
+std::string ToCppString(JNIEnv* env, jstring value);
+
+// Convert Java object to a (C) Value object.
+Value ToCValue(JNIEnv* env, jobject object);
+
+// Convert a (C) Value object to a Java object.
+jobject ToJObject(JNIEnv* env, const Value& value);
+
+// Returns true, iff the passed object is an instance of the class specified
+// by its fully qualified class name.
+bool IsJavaInstanceOf(JNIEnv* env, jobject object,
+                      const std::string& class_name);
+
+#endif // ANDROID_FILTERFW_JNI_JNI_UTIL_H
diff --git a/media/mca/filterfw/jni/jni_vertex_frame.cpp b/media/mca/filterfw/jni/jni_vertex_frame.cpp
new file mode 100644
index 0000000..caae938
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_vertex_frame.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jni/jni_vertex_frame.h"
+#include "jni/jni_util.h"
+
+#include "native/core/vertex_frame.h"
+
+using android::filterfw::VertexFrame;
+
+jboolean Java_android_filterfw_core_VertexFrame_nativeAllocate(JNIEnv* env,
+                                                               jobject thiz,
+                                                               jint size) {
+  return ToJBool(WrapObjectInJava(new VertexFrame(size), env, thiz, true));
+}
+
+jboolean Java_android_filterfw_core_VertexFrame_nativeDeallocate(JNIEnv* env, jobject thiz) {
+  return ToJBool(DeleteNativeObject<VertexFrame>(env, thiz));
+}
+
+jboolean Java_android_filterfw_core_VertexFrame_setNativeInts(JNIEnv* env,
+                                                              jobject thiz,
+                                                              jintArray ints) {
+
+  VertexFrame* frame = ConvertFromJava<VertexFrame>(env, thiz);
+  if (frame && ints) {
+    jint* int_ptr = env->GetIntArrayElements(ints, NULL);
+    const int length = env->GetArrayLength(ints);
+    if (int_ptr) {
+      const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(int_ptr),
+                                            length * sizeof(jint));
+      env->ReleaseIntArrayElements(ints, int_ptr, JNI_ABORT);
+      return ToJBool(success);
+    }
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_VertexFrame_setNativeFloats(JNIEnv* env,
+                                                                jobject thiz,
+                                                                jfloatArray floats) {
+  VertexFrame* frame = ConvertFromJava<VertexFrame>(env, thiz);
+  if (frame && floats) {
+    jfloat* float_ptr = env->GetFloatArrayElements(floats, NULL);
+    const int length = env->GetArrayLength(floats);
+    if (float_ptr) {
+      const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(float_ptr),
+                                            length * sizeof(jfloat));
+      env->ReleaseFloatArrayElements(floats, float_ptr, JNI_ABORT);
+      return ToJBool(success);
+    }
+  }
+  return JNI_FALSE;
+}
+
+jboolean Java_android_filterfw_core_VertexFrame_setNativeData(JNIEnv* env,
+                                                              jobject thiz,
+                                                              jbyteArray data,
+                                                              jint offset,
+                                                              jint length) {
+  VertexFrame* frame = ConvertFromJava<VertexFrame>(env, thiz);
+  if (frame && data) {
+    jbyte* bytes = env->GetByteArrayElements(data, NULL);
+    if (bytes) {
+      const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(bytes + offset),
+                                            length);
+      env->ReleaseByteArrayElements(data, bytes, JNI_ABORT);
+      return ToJBool(success);
+    }
+  }
+  return JNI_FALSE;
+}
+
+jint Java_android_filterfw_core_VertexFrame_getNativeVboId(JNIEnv* env, jobject thiz) {
+  VertexFrame* frame = ConvertFromJava<VertexFrame>(env, thiz);
+  return frame ? frame->GetVboId() : -1;
+}
diff --git a/media/mca/filterfw/jni/jni_vertex_frame.h b/media/mca/filterfw/jni/jni_vertex_frame.h
new file mode 100644
index 0000000..fcd7ea1
--- /dev/null
+++ b/media/mca/filterfw/jni/jni_vertex_frame.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_JNI_VERTEX_FRAME_H
+#define ANDROID_FILTERFW_JNI_VERTEX_FRAME_H
+
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_VertexFrame_nativeAllocate(JNIEnv* env, jobject thiz, jint size);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_VertexFrame_nativeDeallocate(JNIEnv* env, jobject thiz);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_VertexFrame_setNativeInts(JNIEnv* env, jobject thiz, jintArray ints);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_VertexFrame_setNativeFloats(JNIEnv* env,
+                                                       jobject thiz,
+                                                       jfloatArray floats);
+
+JNIEXPORT jboolean JNICALL
+Java_android_filterfw_core_VertexFrame_setNativeData(JNIEnv* env,
+                                                     jobject thiz,
+                                                     jbyteArray data,
+                                                     jint offset,
+                                                     jint length);
+
+JNIEXPORT jint JNICALL
+Java_android_filterfw_core_VertexFrame_getNativeVboId(JNIEnv* env, jobject thiz);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_FILTERFW_JNI_VERTEX_FRAME_H
diff --git a/media/mca/filterfw/native/Android.mk b/media/mca/filterfw/native/Android.mk
new file mode 100644
index 0000000..46ee283
--- /dev/null
+++ b/media/mca/filterfw/native/Android.mk
@@ -0,0 +1,44 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+#####################
+# Build module libfilterfw_static
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := libfilterfw_native
+
+LOCAL_SRC_FILES += core/geometry.cpp \
+                   core/gl_env.cpp \
+                   core/gl_frame.cpp \
+                   core/native_frame.cpp \
+                   core/native_program.cpp \
+                   core/shader_program.cpp \
+                   core/vertex_frame.cpp \
+                   core/value.cpp
+
+# add local includes
+include $(LOCAL_PATH)/libfilterfw.mk
+
+# gcc should always be placed at the end.
+LOCAL_EXPORT_LDLIBS := -llog -lgcc
+
+# TODO: Build a shared library as well?
+include $(BUILD_STATIC_LIBRARY)
+
diff --git a/native/include/android/rect.h b/media/mca/filterfw/native/base/logging.h
similarity index 60%
copy from native/include/android/rect.h
copy to media/mca/filterfw/native/base/logging.h
index 64d487d..1236d0b 100644
--- a/native/include/android/rect.h
+++ b/media/mca/filterfw/native/base/logging.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,26 +14,14 @@
  * limitations under the License.
  */
 
+#ifndef ANDROID_FILTERFW_BASE_LOGGING_H
+#define ANDROID_FILTERFW_BASE_LOGGING_H
 
-#ifndef ANDROID_RECT_H
-#define ANDROID_RECT_H
+#define  LOG_EVERY_FRAME false
 
-#ifdef __cplusplus
-extern "C" {
-#endif
+#define  LOG_FRAME(...) if (LOG_EVERY_FRAME) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
 
-typedef struct ARect {
-#ifdef __cplusplus
-    typedef int32_t value_type;
-#endif
-    int32_t left;
-    int32_t top;
-    int32_t right;
-    int32_t bottom;
-} ARect;
+#define  LOG_TAG "MCA"
+#include <utils/Log.h>
 
-#ifdef __cplusplus
-};
-#endif
-
-#endif // ANDROID_RECT_H
+#endif //  ANDROID_FILTERFW_BASE_LOGGING_H
diff --git a/media/mca/filterfw/native/base/utilities.h b/media/mca/filterfw/native/base/utilities.h
new file mode 100644
index 0000000..6bb3b7f
--- /dev/null
+++ b/media/mca/filterfw/native/base/utilities.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_BASE_UTILITIES_H
+#define ANDROID_FILTERFW_BASE_UTILITIES_H
+
+#include <set>
+#include <utility>
+
+namespace android {
+namespace filterfw {
+
+// Convenience Macro to make copy constructor and assignment operator private
+// (thereby disallowing copying and assigning).
+#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
+  TypeName(const TypeName&);               \
+  void operator=(const TypeName&)
+
+// A macro to disallow all the implicit constructors, namely the
+// default constructor, copy constructor and operator= functions.
+#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
+  TypeName();                                    \
+  DISALLOW_COPY_AND_ASSIGN(TypeName)
+
+// STLDeleteContainerPointers()
+//  For a range within a container of pointers, calls delete
+//  (non-array version) on these pointers.
+// NOTE: for these three functions, we could just implement a DeleteObject
+// functor and then call for_each() on the range and functor, but this
+// requires us to pull in all of algorithm.h, which seems expensive.
+// For hash_[multi]set, it is important that this deletes behind the iterator
+// because the hash_set may call the hash function on the iterator when it is
+// advanced, which could result in the hash function trying to deference a
+// stale pointer.
+template <class ForwardIterator>
+void STLDeleteContainerPointers(ForwardIterator begin,
+                                ForwardIterator end) {
+  while (begin != end) {
+    ForwardIterator temp = begin;
+    ++begin;
+    delete *temp;
+  }
+}
+
+// Given an STL container consisting of (key, value) pairs, STLDeleteValues
+// deletes all the "value" components and clears the container.  Does nothing
+// in the case it's given a NULL pointer.
+template <class T>
+void STLDeleteValues(T *v) {
+  if (!v) return;
+  for (typename T::iterator i = v->begin(); i != v->end(); ++i) {
+    delete i->second;
+  }
+  v->clear();
+}
+
+// Perform a lookup in a map or hash_map.
+// If the key is present a const pointer to the associated value is returned,
+// otherwise a NULL pointer is returned.
+template <class Collection>
+const typename Collection::value_type::second_type*
+FindOrNull(const Collection& collection,
+           const typename Collection::value_type::first_type& key) {
+  typename Collection::const_iterator it = collection.find(key);
+  if (it == collection.end()) {
+    return 0;
+  }
+  return &it->second;
+}
+
+// A simple class that gives checklist functionality: There are essemtially two
+// operations defined on a CheckList:
+//  - Adding a new (unchecked) item.
+//  - Checking off an item.
+// When checking off the last remaining item CheckItem() returns true.
+template<typename T>
+class CheckList {
+  public:
+    // Add a new unchecked item. Does nothing if item is already in checklist.
+    void AddItem(const T& item);
+
+    // Check off an item in the checklist. Returns true if all items have been
+    // checked.
+    bool CheckItem(const T& item);
+
+    // Clear the checklist.
+    void Clear() {
+      items_.clear();
+    }
+
+  private:
+    std::set<T> items_;
+};
+
+template<typename T>
+void CheckList<T>::AddItem(const T& item) {
+  if (!ContainsKey(items_, item))
+    items_.insert(item);
+}
+
+template<typename T>
+bool CheckList<T>::CheckItem(const T& item) {
+  typename std::set<T>::iterator iter = items_.find(item);
+  if (iter != items_.end())
+    items_.erase(iter);
+  return items_.empty();
+}
+
+// Perform a lookup in a map or hash_map whose values are pointers.
+// If the key is present a const pointer to the associated value is returned,
+// otherwise a NULL pointer is returned.
+// This function does not distinguish between a missing key and a key mapped
+// to a NULL value.
+template <class Collection>
+const typename Collection::value_type::second_type
+FindPtrOrNull(const Collection& collection,
+              const typename Collection::value_type::first_type& key) {
+  typename Collection::const_iterator it = collection.find(key);
+  if (it == collection.end()) {
+    return 0;
+  }
+  return it->second;
+}
+
+// Test to see if a set, map, hash_set or hash_map contains a particular key.
+// Returns true if the key is in the collection.
+template <typename Collection, typename Key>
+bool ContainsKey(const Collection& collection, const Key& key) {
+  return collection.find(key) != collection.end();
+}
+
+// Insert a new key and value into a map or hash_map.
+// If the key is not present in the map the key and value are
+// inserted, otherwise nothing happens. True indicates that an insert
+// took place, false indicates the key was already present.
+template <class Collection, class Key, class Value>
+bool InsertIfNotPresent(Collection * const collection,
+                        const Key& key, const Value& value) {
+  std::pair<typename Collection::iterator, bool> ret =
+    collection->insert(typename Collection::value_type(key, value));
+  return ret.second;
+}
+
+} // namespace filterfw
+} // namespace android
+
+#endif // ANDROID_FILTERFW_BASE_UTILITIES_H
diff --git a/media/mca/filterfw/native/core/geometry.cpp b/media/mca/filterfw/native/core/geometry.cpp
new file mode 100644
index 0000000..677b91d
--- /dev/null
+++ b/media/mca/filterfw/native/core/geometry.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "core/geometry.h"
+
+#include <cmath>
+
+#include "base/logging.h"
+
+namespace android {
+namespace filterfw {
+
+float Point::Length() const {
+  return std::sqrt(x_ * x_ + y_ * y_);
+}
+
+bool Point::ScaleTo(float new_length) {
+  float length = Length();
+  if (length == 0.0f) {
+    return false;
+  }
+  x_ *= new_length / length;
+  y_ *= new_length / length;
+  return true;
+}
+
+float Point::Distance(const Point& p0, const Point& p1) {
+  Point diff = p1 - p0;
+  return diff.Length();
+}
+
+Point Point::operator+(const Point& other) const {
+  Point out;
+  out.x_ = x_ + other.x_;
+  out.y_ = y_ + other.y_;
+  return out;
+}
+
+Point Point::operator-(const Point& other) const {
+  Point out;
+  out.x_ = x_ - other.x_;
+  out.y_ = y_ - other.y_;
+  return out;
+}
+
+Point Point::operator*(float factor) const {
+  Point out;
+  out.x_ = factor * x_;
+  out.y_ = factor * y_;
+  return out;
+}
+
+void Point::Rotate90Clockwise() {
+  const float x = x_;
+  x_ = y_;
+  y_ = -x;
+}
+
+bool Rect::ExpandToAspectRatio(float ratio) {
+  if (width <= 0.0f || height <= 0.0f || ratio <= 0.0f) {
+    return false;
+  }
+
+  const float current_ratio = width / height;
+  if (current_ratio < ratio) {
+    const float dx = width * (ratio / current_ratio - 1.0f);
+    x -= dx / 2.0f;
+    width += dx;
+  } else {
+    const float dy = height * (current_ratio / ratio - 1.0f);
+    y -= dy / 2.0f;
+    height += dy;
+  }
+  return true;
+}
+
+bool Rect::ExpandToMinLength(float length) {
+  if (width <= 0.0f || height <= 0.0f || length <= 0.0f) {
+    return false;
+  }
+
+  const float current_length = width > height ? width : height;
+  if (length > current_length) {
+    const float dx = width * (length / current_length - 1.0f);
+    x -= dx / 2.0f;
+    width += dx;
+    const float dy = height * (length / current_length - 1.0f);
+    y -= dy / 2.0f;
+    height += dy;
+  }
+  return true;
+}
+
+bool Rect::ScaleWithLengthLimit(float factor, float max_length) {
+  if (width <= 0.0f || height <= 0.0f || factor <= 0.0f) {
+    return false;
+  }
+
+  const float current_length = width > height ? width : height;
+  if (current_length >= max_length) {
+    return true;
+  }
+
+  float f = factor;
+  if (current_length * f > max_length) {
+    f *= max_length / (current_length * f);
+  }
+
+  const float dx = width * (f - 1.0f);
+  x -= dx / 2.0f;
+  width += dx;
+  const float dy = height * (f - 1.0f);
+  y -= dy / 2.0f;
+  height += dy;
+  return true;
+}
+
+const Point& Quad::point(int ix) const {
+  ALOG_ASSERT(ix < static_cast<int>(points_.size()), "Access out of bounds");
+  return points_[ix];
+}
+
+} // namespace filterfw
+} // namespace android
diff --git a/media/mca/filterfw/native/core/geometry.h b/media/mca/filterfw/native/core/geometry.h
new file mode 100644
index 0000000..7f89eef
--- /dev/null
+++ b/media/mca/filterfw/native/core/geometry.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_CORE_GEOMETRY_H
+#define ANDROID_FILTERFW_CORE_GEOMETRY_H
+
+#include <vector>
+
+namespace android {
+namespace filterfw {
+
+// This is an initial implementation of some geometrical structures. This is
+// likely to grow and become more sophisticated in the future.
+
+class Point {
+  public:
+    Point() : x_(0.0f), y_(0.0f) {}
+    Point(float x, float y) : x_(x), y_(y) {}
+
+    float x() const { return x_; }
+    float y() const { return y_; }
+
+    float Length() const;
+    bool ScaleTo(float new_length);
+    static float Distance(const Point& p0, const Point& p1);
+
+    // Add more of these as needed:
+    Point operator+(const Point& other) const;
+    Point operator-(const Point& other) const;
+    Point operator*(float factor) const;
+
+    void Rotate90Clockwise();
+
+  private:
+    float x_, y_;
+};
+
+class Quad {
+  public:
+    Quad() : points_(4) {}
+    virtual ~Quad() {}
+
+    Quad(const Point& p0, const Point& p1, const Point& p2, const Point& p3)
+        : points_(4) {
+      points_[0] = p0;
+      points_[1] = p1;
+      points_[2] = p2;
+      points_[3] = p3;
+    }
+
+    const std::vector<Point>& points() const { return points_; }
+    const Point& point(int ix) const;
+
+  protected:
+    std::vector<Point> points_;
+};
+
+struct Rect {
+  float x, y, width, height;
+
+  Rect() {
+    x = y = 0.0f;
+    width = height = 1.0f;
+  }
+
+  Rect(float x, float y, float width, float height) {
+    this->x = x;
+    this->y = y;
+    this->width = width;
+    this->height = height;
+  }
+
+  bool ExpandToAspectRatio(float ratio);
+  bool ExpandToMinLength(float length);
+  bool ScaleWithLengthLimit(float factor, float max_length);
+};
+
+} // namespace filterfw
+} // namespace android
+
+#endif // ANDROID_FILTERFW_CORE_GEOMETRY_H
diff --git a/media/mca/filterfw/native/core/gl_buffer_interface.h b/media/mca/filterfw/native/core/gl_buffer_interface.h
new file mode 100644
index 0000000..24b1db9
--- /dev/null
+++ b/media/mca/filterfw/native/core/gl_buffer_interface.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_CORE_GL_BUFFER_INTERFACE_H
+#define ANDROID_FILTERFW_CORE_GL_BUFFER_INTERFACE_H
+
+#include <GLES2/gl2.h>
+
+namespace android {
+namespace filterfw {
+
+class GLTextureHandle {
+  public:
+    virtual ~GLTextureHandle() { }
+
+    // Returns the held texture id.
+    virtual GLuint GetTextureId() const = 0;
+
+    // Binds the held texture. This may result in creating the texture if it
+    // is not yet available.
+    virtual bool FocusTexture() = 0;
+
+    // Generates the mipmap chain of the held texture. Returns true, iff
+    // generating was successful.
+    virtual bool GenerateMipMap() = 0;
+
+    // Set a texture parameter (see glTextureParameter documentation). Returns
+    // true iff the parameter was set successfully.
+    virtual bool SetTextureParameter(GLenum pname, GLint value) = 0;
+
+    // Returns the texture target used.
+    // Texture Target should be: GL_TEXTURE_2D, GL_TEXTURE_EXTERNAL_OES.
+    virtual GLuint GetTextureTarget() const = 0;
+};
+
+class GLFrameBufferHandle {
+  public:
+    virtual ~GLFrameBufferHandle() { }
+
+    // Returns the held FBO id.
+    virtual GLuint GetFboId() const = 0;
+
+    // Binds the held FBO. This may result in creating the FBO if it
+    // is not yet available.
+    virtual bool FocusFrameBuffer() = 0;
+};
+
+// Interface to instances that hold GL textures and frame-buffer-objects.
+// The GLFrame class implements this interface.
+class GLBufferHandle : public GLTextureHandle, public GLFrameBufferHandle {
+  public:
+    virtual ~GLBufferHandle() { }
+};
+
+} // namespace filterfw
+} // namespace android
+
+#endif  // ANDROID_FILTERFW_CORE_GL_BUFFER_INTERFACE_H
diff --git a/media/mca/filterfw/native/core/gl_env.cpp b/media/mca/filterfw/native/core/gl_env.cpp
new file mode 100644
index 0000000..738b8e0
--- /dev/null
+++ b/media/mca/filterfw/native/core/gl_env.cpp
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// #define LOG_NDEBUG 0
+
+#include "base/logging.h"
+#include "base/utilities.h"
+#include "core/gl_env.h"
+#include "core/shader_program.h"
+#include "core/vertex_frame.h"
+#include "system/window.h"
+
+#include <map>
+#include <string>
+#include <EGL/eglext.h>
+
+namespace android {
+namespace filterfw {
+
+GLEnv::GLEnv()
+  : display_(EGL_NO_DISPLAY),
+    context_id_(0),
+    surface_id_(0),
+    max_surface_id_(0),
+    created_context_(false),
+    created_surface_(false),
+    initialized_(false) {
+}
+
+GLEnv::~GLEnv() {
+  // Destroy surfaces
+  for (std::map<int, SurfaceWindowPair>::iterator it = surfaces_.begin();
+       it != surfaces_.end();
+       ++it) {
+    if (it->first != 0 || created_surface_) {
+      eglDestroySurface(display(), it->second.first);
+      if (it->second.second) {
+        it->second.second->Destroy();
+        delete it->second.second;
+      }
+    }
+  }
+
+  // Destroy contexts
+  for (std::map<int, EGLContext>::iterator it = contexts_.begin();
+       it != contexts_.end();
+       ++it) {
+    if (it->first != 0 || created_context_)
+      eglDestroyContext(display(), it->second);
+  }
+
+  // Destroy attached shaders and frames
+  STLDeleteValues(&attached_shaders_);
+  STLDeleteValues(&attached_vframes_);
+
+  // Destroy display
+  if (initialized_)
+    eglTerminate(display());
+
+  // Log error if this did not work
+  if (CheckEGLError("TearDown!"))
+    ALOGE("GLEnv: Error tearing down GL Environment!");
+}
+
+bool GLEnv::IsInitialized() const {
+  return (contexts_.size() > 0 &&
+          surfaces_.size() > 0 &&
+          display_ != EGL_NO_DISPLAY);
+}
+
+bool GLEnv::Deactivate() {
+  eglMakeCurrent(display(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+  return !CheckEGLError("eglMakeCurrent");
+}
+
+bool GLEnv::Activate() {
+  ALOGV("Activate()");
+  if (display()   != eglGetCurrentDisplay() ||
+      context()   != eglGetCurrentContext() ||
+      surface()   != eglGetCurrentSurface(EGL_DRAW)) {
+    // Make sure we are initialized
+    if (context() == EGL_NO_CONTEXT || surface() == EGL_NO_SURFACE)
+      return false;
+
+    // Make our context current
+    ALOGV("eglMakeCurrent");
+    eglMakeCurrent(display(), surface(), surface(), context());
+
+    return !CheckEGLMakeCurrentError();
+  }
+  return true;
+}
+
+bool GLEnv::SwapBuffers() {
+  const bool result = eglSwapBuffers(display(), surface()) == EGL_TRUE;
+  return !CheckEGLError("eglSwapBuffers") && result;
+}
+
+bool GLEnv::InitWithCurrentContext() {
+  if (IsInitialized())
+    return true;
+
+  display_     = eglGetCurrentDisplay();
+  contexts_[0] = eglGetCurrentContext();
+  surfaces_[0] = SurfaceWindowPair(eglGetCurrentSurface(EGL_DRAW), NULL);
+
+  return (context() != EGL_NO_CONTEXT) &&
+         (display() != EGL_NO_DISPLAY) &&
+         (surface() != EGL_NO_SURFACE);
+}
+
+bool GLEnv::InitWithNewContext() {
+  if (IsInitialized()) {
+    ALOGE("GLEnv: Attempting to reinitialize environment!");
+    return false;
+  }
+
+  display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+  if (CheckEGLError("eglGetDisplay")) return false;
+
+  EGLint majorVersion;
+  EGLint minorVersion;
+  eglInitialize(display(), &majorVersion, &minorVersion);
+  if (CheckEGLError("eglInitialize")) return false;
+  initialized_ = true;
+
+  // Configure context/surface
+  EGLConfig config;
+  EGLint numConfigs = -1;
+
+  // TODO(renn): Do we need the window bit here?
+  // TODO: Currently choosing the config that includes all
+  // This is not needed if the encoding is not being used
+  EGLint configAttribs[] = {
+    EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+    EGL_RED_SIZE, 8,
+    EGL_GREEN_SIZE, 8,
+    EGL_BLUE_SIZE, 8,
+    EGL_RECORDABLE_ANDROID, EGL_TRUE,
+    EGL_NONE
+  };
+
+  eglChooseConfig(display(), configAttribs, &config, 1, &numConfigs);
+  if (numConfigs < 1) {
+    ALOGE("GLEnv::Init: No suitable EGL configuration found!");
+    return false;
+  }
+
+  // Create dummy surface using a SurfaceTexture
+  surfaceTexture_ = new SurfaceTexture(0);
+  window_ = new SurfaceTextureClient(static_cast<sp<ISurfaceTexture> >(
+          surfaceTexture_->getBufferQueue()));
+
+  surfaces_[0] = SurfaceWindowPair(eglCreateWindowSurface(display(), config, window_.get(), NULL), NULL);
+  if (CheckEGLError("eglCreateWindowSurface")) return false;
+
+  // Create context
+  EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
+  contexts_[0] = eglCreateContext(display(),
+                                  config,
+                                  EGL_NO_CONTEXT,
+                                  context_attribs);
+  if (CheckEGLError("eglCreateContext")) return false;
+
+  created_context_ = created_surface_ = true;
+
+  return true;
+}
+
+bool GLEnv::IsActive() const {
+  ALOGV("IsActive()");
+  return context() == eglGetCurrentContext()
+    &&   display() == eglGetCurrentDisplay()
+    &&   surface() == eglGetCurrentSurface(EGL_DRAW);
+}
+
+bool GLEnv::IsContextActive() const {
+  return context() == eglGetCurrentContext();
+}
+
+bool GLEnv::IsAnyContextActive() {
+  return eglGetCurrentContext() != EGL_NO_CONTEXT;
+}
+
+int GLEnv::AddWindowSurface(const EGLSurface& surface, WindowHandle* window_handle) {
+  const int id = ++max_surface_id_;
+  surfaces_[id] = SurfaceWindowPair(surface, window_handle);
+  return id;
+}
+
+int GLEnv::AddSurface(const EGLSurface& surface) {
+  return AddWindowSurface(surface, NULL);
+}
+
+bool GLEnv::SwitchToSurfaceId(int surface_id) {
+  ALOGV("SwitchToSurfaceId");
+  if (surface_id_ != surface_id) {
+    const SurfaceWindowPair* surface = FindOrNull(surfaces_, surface_id);
+    if (surface) {
+      bool wasActive = IsActive();
+      surface_id_ = surface_id;
+      return wasActive ? Activate() : true;
+    }
+    return false;
+  }
+  return true;
+}
+
+bool GLEnv::ReleaseSurfaceId(int surface_id) {
+  if (surface_id > 0) {
+    const SurfaceWindowPair* surface_window_pair = FindOrNull(surfaces_, surface_id);
+    if (surface_window_pair) {
+      if (surface_id_ == surface_id)
+        SwitchToSurfaceId(0);
+      eglDestroySurface(display(), surface_window_pair->first);
+      if (surface_window_pair->second) {
+        surface_window_pair->second->Destroy();
+        delete surface_window_pair->second;
+      }
+      surfaces_.erase(surface_id);
+      return true;
+    }
+  }
+  return false;
+}
+
+bool GLEnv::SetSurfaceTimestamp(int64_t timestamp) {
+  if (surface_id_ > 0) {
+    const SurfaceWindowPair* surface_window_pair = FindOrNull(surfaces_,
+            surface_id_);
+    if (surface_window_pair) {
+      ANativeWindow *window = static_cast<ANativeWindow*>(
+              surface_window_pair->second->InternalHandle());
+      native_window_set_buffers_timestamp(window, timestamp);
+      return true;
+    }
+  }
+  return false;
+}
+
+int GLEnv::FindSurfaceIdForWindow(const WindowHandle* window_handle) {
+  for (std::map<int, SurfaceWindowPair>::iterator it = surfaces_.begin();
+       it != surfaces_.end();
+       ++it) {
+    const WindowHandle* my_handle = it->second.second;
+    if (my_handle && my_handle->Equals(window_handle)) {
+      return it->first;
+    }
+  }
+  return -1;
+}
+
+
+int GLEnv::AddContext(const EGLContext& context) {
+  const int id = contexts_.size();
+  contexts_[id] = context;
+  return id;
+}
+
+bool GLEnv::SwitchToContextId(int context_id) {
+  const EGLContext* context = FindOrNull(contexts_, context_id);
+  if (context) {
+    if (context_id_ != context_id) {
+      context_id_ = context_id;
+      return Activate();
+    }
+    return true;
+  }
+  return false;
+}
+
+void GLEnv::ReleaseContextId(int context_id) {
+  if (context_id > 0) {
+    const EGLContext* context = FindOrNull(contexts_, context_id);
+    if (context) {
+      contexts_.erase(context_id);
+      if (context_id_ == context_id && IsActive())
+        SwitchToContextId(0);
+      eglDestroyContext(display(), *context);
+    }
+  }
+}
+
+bool GLEnv::CheckGLError(const std::string& op) {
+  bool err = false;
+  for (GLint error = glGetError(); error; error = glGetError()) {
+    ALOGE("GL Error: Operation '%s' caused GL error (0x%x)\n",
+         op.c_str(),
+         error);
+    err = true;
+  }
+  return err;
+}
+
+bool GLEnv::CheckEGLError(const std::string& op) {
+  bool err = false;
+  for (EGLint error = eglGetError();
+       error != EGL_SUCCESS;
+       error = eglGetError()) {
+    ALOGE("EGL Error: Operation '%s' caused EGL error (0x%x)\n",
+         op.c_str(),
+         error);
+    err = true;
+  }
+  return err;
+}
+
+bool GLEnv::CheckEGLMakeCurrentError() {
+  bool err = false;
+  for (EGLint error = eglGetError();
+       error != EGL_SUCCESS;
+       error = eglGetError()) {
+    switch (error) {
+      case EGL_BAD_DISPLAY:
+        ALOGE("EGL Error: Attempting to activate context with bad display!");
+        break;
+      case EGL_BAD_SURFACE:
+        ALOGE("EGL Error: Attempting to activate context with bad surface!");
+        break;
+      case EGL_BAD_ACCESS:
+        ALOGE("EGL Error: Attempting to activate context, which is "
+             "already active in another thread!");
+        break;
+      default:
+        ALOGE("EGL Error: Making EGL rendering context current caused "
+             "error: 0x%x\n", error);
+    }
+    err = true;
+  }
+  return err;
+}
+
+GLuint GLEnv::GetCurrentProgram() {
+  GLint result;
+  glGetIntegerv(GL_CURRENT_PROGRAM, &result);
+  ALOG_ASSERT(result >= 0);
+  return static_cast<GLuint>(result);
+}
+
+EGLDisplay GLEnv::GetCurrentDisplay() {
+  return eglGetCurrentDisplay();
+}
+
+int GLEnv::NumberOfComponents(GLenum type) {
+  switch (type) {
+    case GL_BOOL:
+    case GL_FLOAT:
+    case GL_INT:
+      return 1;
+    case GL_BOOL_VEC2:
+    case GL_FLOAT_VEC2:
+    case GL_INT_VEC2:
+      return 2;
+    case GL_INT_VEC3:
+    case GL_FLOAT_VEC3:
+    case GL_BOOL_VEC3:
+      return 3;
+    case GL_BOOL_VEC4:
+    case GL_FLOAT_VEC4:
+    case GL_INT_VEC4:
+    case GL_FLOAT_MAT2:
+      return 4;
+    case GL_FLOAT_MAT3:
+      return 9;
+    case GL_FLOAT_MAT4:
+      return 16;
+    default:
+      return 0;
+  }
+}
+
+void GLEnv::AttachShader(int key, ShaderProgram* shader) {
+  ShaderProgram* existingShader = ShaderWithKey(key);
+  if (existingShader)
+    delete existingShader;
+  attached_shaders_[key] = shader;
+}
+
+void GLEnv::AttachVertexFrame(int key, VertexFrame* frame) {
+  VertexFrame* existingFrame = VertexFrameWithKey(key);
+  if (existingFrame)
+    delete existingFrame;
+  attached_vframes_[key] = frame;
+}
+
+ShaderProgram* GLEnv::ShaderWithKey(int key) {
+  return FindPtrOrNull(attached_shaders_, key);
+}
+
+VertexFrame* GLEnv::VertexFrameWithKey(int key) {
+  return FindPtrOrNull(attached_vframes_, key);
+}
+
+} // namespace filterfw
+} // namespace android
diff --git a/media/mca/filterfw/native/core/gl_env.h b/media/mca/filterfw/native/core/gl_env.h
new file mode 100644
index 0000000..b61785f
--- /dev/null
+++ b/media/mca/filterfw/native/core/gl_env.h
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_CORE_GL_ENV_H
+#define ANDROID_FILTERFW_CORE_GL_ENV_H
+
+#include <string>
+#include <utility>
+#include <map>
+
+#include "base/logging.h"
+#include "base/utilities.h"
+
+#include <GLES2/gl2.h>
+#include <EGL/egl.h>
+
+#include <gui/ISurfaceTexture.h>
+#include <gui/SurfaceTextureClient.h>
+
+namespace android {
+namespace filterfw {
+
+class ShaderProgram;
+class VertexFrame;
+
+class WindowHandle {
+  public:
+    virtual ~WindowHandle() {
+    }
+
+    virtual void Destroy() = 0;
+
+    virtual bool Equals(const WindowHandle* window) const {
+      return InternalHandle() == window->InternalHandle();
+    }
+
+    virtual const void* InternalHandle() const = 0;
+
+    virtual void* InternalHandle() = 0;
+};
+
+// The GLEnv class provides functionality related to the EGL environment, which
+// includes the display, context, and surface. It is possible to either create
+// a new environment or base it off the currently active EGL environment. In
+// order to do the latter, an EGL environment must be setup already (though not
+// necessarily through this class), and have an active display, context, and
+// surface.
+class GLEnv {
+  public:
+    // Constructing and Activating /////////////////////////////////////////////
+    // Constructs a new GLEnv object. This does not create a GL context.
+    GLEnv();
+
+    // Destructor. Tears down and deallocates any GL objects that were created
+    // by this instance.
+    ~GLEnv();
+
+    // Inits a new GL environment, including a new surface and context. You
+    // must call Activate() before performing any GL operations.
+    bool InitWithNewContext();
+
+    // Inits the GL environment from the current GL environment. Use this when
+    // there is already a display, surface and context available (possibly
+    // created by the host application). You do not need to call Activate() as
+    // this context is active already.
+    bool InitWithCurrentContext();
+
+    // Activates the environment, and makes the associated GL context the
+    // current context. Creates the environment, if it has not been created
+    // already. Returns true if the activation was successful.
+    bool Activate();
+
+    // Deactivates the environment. Returns true if the deactivation was
+    // successful. You may want to call this when moving a context to another
+    // thread. In this case, deactivate the GLEnv in the old thread, and
+    // reactivate it in the new thread.
+    bool Deactivate();
+
+    // When rendering to a visible surface, call this to swap between the
+    // offscreen and onscreen buffers. Returns true if the buffer swap was
+    // successful.
+    bool SwapBuffers();
+
+    // Working with Surfaces ///////////////////////////////////////////////////
+
+    // Add a surface to the environment. This surface will now be managed (and
+    // owned) by the GLEnv instance. Returns the id of the surface.
+    int AddSurface(const EGLSurface& surface);
+
+    // Add a window surface to the environment. The window is passed in as
+    // an opaque window handle.
+    // This surface will now be managed (and owned) by the GLEnv instance.
+    // Returns the id of the surface.
+    int AddWindowSurface(const EGLSurface& surface, WindowHandle* window_handle);
+
+    // Switch to the surface with the specified id. This will make the surface
+    // active, if it is not active already. Specify an ID of 0 if you would like
+    // to switch to the default surface. Returns true if successful.
+    bool SwitchToSurfaceId(int surface_id);
+
+    // Release the surface with the specified id. This will deallocate the
+    // surface. If this is the active surface, the environment will switch to
+    // the default surface (0) first. You cannot release the default surface.
+    bool ReleaseSurfaceId(int surface_id);
+
+    // Set the timestamp for the current surface. Must be called
+    // before swapBuffers to associate the timestamp with the frame
+    // resulting from swapBuffers.
+    bool SetSurfaceTimestamp(int64_t timestamp);
+
+    // Looks for a surface with the associated window handle. Returns -1 if no
+    // surface with such a window was found.
+    int FindSurfaceIdForWindow(const WindowHandle* window_handle);
+
+    // Obtain the environment's EGL surface.
+    const EGLSurface& surface() const {
+      return surfaces_.find(surface_id_)->second.first;
+    }
+
+    // Working with Contexts ///////////////////////////////////////////////////
+
+    // Add a context to the environment. This context will now be managed (and
+    // owned) by the GLEnv instance. Returns the id of the context.
+    int AddContext(const EGLContext& context);
+
+    // Switch to the context with the specified id. This will make the context
+    // active, if it is not active already. Specify an ID of 0 if you would like
+    // to switch to the default context. Returns true if successful.
+    bool SwitchToContextId(int context_id);
+
+    // Release the context with the specified id. This will deallocate the
+    // context. If this is the active context, the environment will switch to
+    // the default context (0) first. You cannot release the default context.
+    void ReleaseContextId(int context_id);
+
+    // Obtain the environment's EGL context.
+    const EGLContext& context() const {
+      return contexts_.find(context_id_)->second;
+    }
+
+    // Working with the Display ////////////////////////////////////////////////
+
+    // Obtain the environment's EGL display.
+    const EGLDisplay& display() const {
+      return display_;
+    }
+
+    // Inspecting the environment //////////////////////////////////////////////
+    // Returns true if the environment is active in the current thread.
+    bool IsActive() const;
+
+    // Returns true if the environment's context is active in the curent thread.
+    bool IsContextActive() const;
+
+    // Returns true if there is any EGL context active in the current thread.
+    // This need not be a context created by a GLEnv instance.
+    static bool IsAnyContextActive();
+
+    // Attaching GL objects ////////////////////////////////////////////////////
+
+    // Attach a shader to the environment. The environment takes ownership of
+    // the shader.
+    void AttachShader(int key, ShaderProgram* shader);
+
+    // Attach a vertex frame to the environment. The environment takes ownership
+    // of the frame.
+    void AttachVertexFrame(int key, VertexFrame* frame);
+
+    // Return the shader with the specified key, or NULL if there is no such
+    // shader attached to this environment.
+    ShaderProgram* ShaderWithKey(int key);
+
+    // Return the vertex frame with the specified key, or NULL if there is no
+    // such frame attached to this environment.
+    VertexFrame* VertexFrameWithKey(int key);
+
+    // Static methods //////////////////////////////////////////////////////////
+    // These operate on the currently active environment!
+
+    // Checks if the current environment is in a GL error state. If so, it will
+    // output an error message referencing the given operation string. Returns
+    // true if there was at least one error.
+    static bool CheckGLError(const std::string& operation);
+
+    // Checks if the current environment is in an EGL error state. If so, it
+    // will output an error message referencing the given operation string.
+    // Returns true if there was at least one error.
+    static bool CheckEGLError(const std::string& operation);
+
+    // Get the currently used (shader) program.
+    static GLuint GetCurrentProgram();
+
+    // Get the currently active display.
+    static EGLDisplay GetCurrentDisplay();
+
+    // Returns the number of components for a given GL type. For instance,
+    // returns 4 for vec4, and 16 for mat4.
+    static int NumberOfComponents(GLenum type);
+
+  private:
+    typedef std::pair<EGLSurface, WindowHandle*> SurfaceWindowPair;
+
+    // Initializes a new GL environment.
+    bool Init();
+
+    // Returns true if one of the Inits has been called successfully on this
+    // instance.
+    bool IsInitialized() const;
+
+    // Outputs error messages specific to the operation eglMakeCurrent().
+    // Returns true if there was at least one error.
+    static bool CheckEGLMakeCurrentError();
+
+    // The EGL display, contexts, and surfaces.
+    EGLDisplay display_;
+    std::map<int, EGLContext> contexts_;
+    std::map<int, SurfaceWindowPair> surfaces_;
+
+    // The currently active context and surface ids.
+    int context_id_;
+    int surface_id_;
+
+    // Dummy surface for context
+    sp<ANativeWindow> window_;
+
+    // Dummy SurfaceTexture for context
+    sp<SurfaceTexture> surfaceTexture_;
+
+    // The maximum surface id used.
+    int max_surface_id_;
+
+    // These bools keep track of which objects this GLEnv has created (and
+    // owns).
+    bool created_context_;
+    bool created_surface_;
+    bool initialized_;
+
+    // Attachments that GL objects can add to the environment.
+    std::map<int, ShaderProgram*> attached_shaders_;
+    std::map<int, VertexFrame*> attached_vframes_;
+
+    DISALLOW_COPY_AND_ASSIGN(GLEnv);
+};
+
+} // namespace filterfw
+} // namespace android
+
+#endif  // ANDROID_FILTERFW_CORE_GL_ENV_H
diff --git a/media/mca/filterfw/native/core/gl_frame.cpp b/media/mca/filterfw/native/core/gl_frame.cpp
new file mode 100644
index 0000000..0f8b4a1
--- /dev/null
+++ b/media/mca/filterfw/native/core/gl_frame.cpp
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "base/logging.h"
+
+#include "core/gl_env.h"
+#include "core/gl_frame.h"
+#include "core/shader_program.h"
+
+#include <vector>
+
+namespace android {
+namespace filterfw {
+
+static const int kIdentityShaderKey = 1;
+
+//
+// A GLFrame stores pixel data on the GPU. It uses two kinds of GL data
+// containers for this: Textures and Frame Buffer Objects (FBOs). Textures are
+// used whenever pixel data needs to be read into a shader or the host program,
+// and when pixel data is uploaded to a GLFrame. The FBO is used as a rendering
+// target for shaders.
+//
+
+GLFrame::GLFrame(GLEnv* gl_env)
+  : gl_env_(gl_env),
+    width_(0),
+    height_(0),
+    vp_x_(0),
+    vp_y_(0),
+    vp_width_(0),
+    vp_height_(0),
+    texture_id_(0),
+    fbo_id_(0),
+    texture_target_(GL_TEXTURE_2D),
+    texture_state_(kStateUninitialized),
+    fbo_state_(kStateUninitialized),
+    owns_texture_(false),
+    owns_fbo_(false) {
+  SetDefaultTexParameters();
+}
+
+bool GLFrame::Init(int width, int height) {
+  // Make sure we haven't been initialized already
+  if (width_ == 0 && height_ == 0) {
+    InitDimensions(width, height);
+    return true;
+  }
+  return false;
+}
+
+bool GLFrame::InitWithTexture(GLint texture_id, int width, int height) {
+  texture_id_ = texture_id;
+  texture_state_ = glIsTexture(texture_id) ? kStateComplete : kStateGenerated;
+  InitDimensions(width, height);
+  return true;
+}
+
+bool GLFrame::InitWithFbo(GLint fbo_id, int width, int height) {
+  fbo_id_ = fbo_id;
+  fbo_state_ = glIsFramebuffer(fbo_id) ? kStateComplete : kStateGenerated;
+  texture_state_ = kStateUnmanaged;
+  InitDimensions(width, height);
+  return true;
+}
+
+bool GLFrame::InitWithExternalTexture() {
+  texture_target_ = GL_TEXTURE_EXTERNAL_OES;
+  width_ = 0;
+  height_ = 0;
+  return GenerateTextureName();
+}
+
+void GLFrame::InitDimensions(int width, int height) {
+  width_ = width;
+  height_ = height;
+  vp_width_ = width;
+  vp_height_ = height;
+}
+
+GLFrame::~GLFrame() {
+  // Delete texture
+  if (owns_texture_) {
+    // Bind FBO so that texture is unbound from it during deletion
+    if (fbo_state_ == kStateComplete) {
+      glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);
+    }
+    glDeleteTextures(1, &texture_id_);
+  }
+
+  // Delete FBO
+  if (owns_fbo_) {
+    glDeleteFramebuffers(1, &fbo_id_);
+  }
+}
+
+bool GLFrame::GenerateMipMap() {
+  if (FocusTexture()) {
+    glGenerateMipmap(GL_TEXTURE_2D);
+    return !GLEnv::CheckGLError("Generating MipMap!");
+  }
+  return false;
+}
+
+bool GLFrame::SetTextureParameter(GLenum pname, GLint value) {
+  if (value != tex_params_[pname]) {
+    if (FocusTexture()) {
+      glTexParameteri(GL_TEXTURE_2D, pname, value);
+      if (!GLEnv::CheckGLError("Setting texture parameter!")) {
+        tex_params_[pname] = value;
+        return true;
+      }
+    }
+  } else {
+    return true;
+  }
+  return false;
+}
+
+bool GLFrame::UpdateTexParameters() {
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, tex_params_[GL_TEXTURE_MAG_FILTER]);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tex_params_[GL_TEXTURE_MIN_FILTER]);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, tex_params_[GL_TEXTURE_WRAP_S]);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tex_params_[GL_TEXTURE_WRAP_T]);
+  return !GLEnv::CheckGLError("Resetting texture parameters!");
+}
+
+bool GLFrame::TexParametersModifed() {
+  return tex_params_[GL_TEXTURE_MAG_FILTER] != GL_LINEAR
+    ||   tex_params_[GL_TEXTURE_MIN_FILTER] != GL_LINEAR
+    ||   tex_params_[GL_TEXTURE_WRAP_S] != GL_CLAMP_TO_EDGE
+    ||   tex_params_[GL_TEXTURE_WRAP_T] != GL_CLAMP_TO_EDGE;
+}
+
+void GLFrame::SetDefaultTexParameters() {
+  tex_params_[GL_TEXTURE_MAG_FILTER] = GL_LINEAR;
+  tex_params_[GL_TEXTURE_MIN_FILTER] = GL_LINEAR;
+  tex_params_[GL_TEXTURE_WRAP_S] = GL_CLAMP_TO_EDGE;
+  tex_params_[GL_TEXTURE_WRAP_T] = GL_CLAMP_TO_EDGE;
+}
+
+bool GLFrame::ResetTexParameters() {
+  if (TexParametersModifed()) {
+    if (BindTexture()) {
+      SetDefaultTexParameters();
+      return UpdateTexParameters();
+    }
+    return false;
+  }
+  return true;
+}
+
+bool GLFrame::CopyDataTo(uint8_t* buffer, int size) {
+  return (size >= Size())
+    ? CopyPixelsTo(buffer)
+    : false;
+}
+
+bool GLFrame::CopyPixelsTo(uint8_t* buffer) {
+  // Use one of the pixel reading methods below, ordered from most
+  // efficient to least efficient.
+  if (fbo_state_ == kStateComplete)
+    return ReadFboPixels(buffer);
+  else if (texture_state_ == kStateComplete)
+    return ReadTexturePixels(buffer);
+  else
+    return false;
+}
+
+bool GLFrame::WriteData(const uint8_t* data, int data_size) {
+  return (data_size == Size()) ? UploadTexturePixels(data) : false;
+}
+
+bool GLFrame::SetViewport(int x, int y, int width, int height) {
+  vp_x_ = x;
+  vp_y_ = y;
+  vp_width_ = width;
+  vp_height_ = height;
+  return true;
+}
+
+GLFrame* GLFrame::Clone() const {
+  GLFrame* target = new GLFrame(gl_env_);
+  target->Init(width_, height_);
+  target->CopyPixelsFrom(this);
+  return target;
+}
+
+bool GLFrame::CopyPixelsFrom(const GLFrame* frame) {
+  if (frame == this) {
+    return true;
+  } else if (frame && frame->width_ == width_ && frame->height_ == height_) {
+    std::vector<const GLFrame*> sources;
+    sources.push_back(frame);
+    GetIdentity()->Process(sources, this);
+    return true;
+  }
+  return false;
+}
+
+int GLFrame::Size() const {
+  return width_ * height_ * 4;
+}
+
+ShaderProgram* GLFrame::GetIdentity() const {
+  ShaderProgram* stored_shader = gl_env_->ShaderWithKey(kIdentityShaderKey);
+  if (!stored_shader) {
+    stored_shader = ShaderProgram::CreateIdentity(gl_env_);
+    gl_env_->AttachShader(kIdentityShaderKey, stored_shader);
+  }
+  return stored_shader;
+}
+
+bool GLFrame::BindFrameBuffer() const {
+  // Bind the FBO
+  glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);
+  if (GLEnv::CheckGLError("FBO Binding")) return false;
+
+  // Set viewport
+  glViewport(vp_x_, vp_y_, vp_width_, vp_height_);
+  if (GLEnv::CheckGLError("ViewPort Setup")) return false;
+
+  return true;
+}
+
+bool GLFrame::FocusFrameBuffer() {
+  // Create texture backing if necessary
+  if (texture_state_ == kStateUninitialized) {
+    if (!GenerateTextureName())
+      return false;
+  }
+
+  // Create and bind FBO to texture if necessary
+  if (fbo_state_ != kStateComplete) {
+    if (!GenerateFboName() || !AttachTextureToFbo())
+      return false;
+  }
+
+  // And bind it.
+  return BindFrameBuffer();
+}
+
+bool GLFrame::BindTexture() const {
+  glBindTexture(GL_TEXTURE_2D, texture_id_);
+  return !GLEnv::CheckGLError("Texture Binding");
+}
+
+GLuint GLFrame::GetTextureId() const {
+  return texture_id_;
+}
+
+// Returns the held FBO id. Only call this if the GLFrame holds an FBO. You
+// can check this by calling HoldsFbo().
+GLuint GLFrame::GetFboId() const {
+  return fbo_id_;
+}
+
+bool GLFrame::FocusTexture() {
+  // Make sure we have a texture
+  if (!GenerateTextureName())
+    return false;
+
+  // Bind the texture
+  if (!BindTexture())
+    return false;
+
+  return !GLEnv::CheckGLError("Texture Binding");
+}
+
+bool GLFrame::GenerateTextureName() {
+  if (texture_state_ == kStateUninitialized) {
+    // Make sure texture not in use already
+    if (glIsTexture(texture_id_)) {
+      ALOGE("GLFrame: Cannot generate texture id %d, as it is in use already!", texture_id_);
+      return false;
+    }
+
+    // Generate the texture
+    glGenTextures (1, &texture_id_);
+    if (GLEnv::CheckGLError("Texture Generation"))
+      return false;
+    texture_state_ = kStateGenerated;
+    owns_texture_ = true;
+  }
+
+  return true;
+}
+
+bool GLFrame::AllocateTexture() {
+  // Allocate or re-allocate (if texture was deleted externally).
+  if (texture_state_ == kStateGenerated || TextureWasDeleted()) {
+    LOG_FRAME("GLFrame: Allocating texture: %d", texture_id_);
+    glBindTexture(GL_TEXTURE_2D, texture_id_);
+    glTexImage2D(GL_TEXTURE_2D,
+               0,
+               GL_RGBA,
+               width_,
+               height_,
+               0,
+               GL_RGBA,
+               GL_UNSIGNED_BYTE,
+               NULL);
+    if (!GLEnv::CheckGLError("Texture Allocation")) {
+      UpdateTexParameters();
+      texture_state_ = kStateComplete;
+    }
+  }
+  return texture_state_ == kStateComplete;
+}
+
+bool GLFrame::TextureWasDeleted() const {
+  return texture_state_ == kStateComplete && !glIsTexture(texture_id_);
+}
+
+bool GLFrame::GenerateFboName() {
+  if (fbo_state_ == kStateUninitialized) {
+    // Make sure FBO not in use already
+    if (glIsFramebuffer(fbo_id_)) {
+      ALOGE("GLFrame: Cannot generate FBO id %d, as it is in use already!", fbo_id_);
+      return false;
+    }
+
+    // Create FBO
+    glGenFramebuffers(1, &fbo_id_);
+    if (GLEnv::CheckGLError("FBO Generation"))
+      return false;
+    fbo_state_ = kStateGenerated;
+    owns_fbo_ = true;
+  }
+
+  return true;
+}
+
+bool GLFrame::ReadFboPixels(uint8_t* pixels) const {
+  if (fbo_state_ == kStateComplete) {
+    BindFrameBuffer();
+    glReadPixels(0,
+                 0,
+                 width_,
+                 height_,
+                 GL_RGBA,
+                 GL_UNSIGNED_BYTE,
+                 pixels);
+    return !GLEnv::CheckGLError("FBO Pixel Readout");
+  }
+  return false;
+}
+
+bool GLFrame::ReadTexturePixels(uint8_t* pixels) const {
+  // Read pixels from texture if we do not have an FBO
+  // NOTE: OpenGL ES does NOT support glGetTexImage() for reading out texture
+  // data. The only way for us to get texture data is to create a new FBO and
+  // render the current texture frame into it. As this is quite inefficient,
+  // and unnecessary (this can only happen if the user is reading out data
+  // that was just set, and not run through a filter), we warn the user about
+  // this here.
+  ALOGW("Warning: Reading pixel data from unfiltered GL frame. This is highly "
+        "inefficient. Please consider using your original pixel buffer "
+        "instead!");
+
+  // Create source frame set (unfortunately this requires an ugly const-cast,
+  // as we need to wrap ourselves in a frame-set. Still, as this set is used
+  // as input only, we are certain we will not be modified).
+  std::vector<const GLFrame*> sources;
+  sources.push_back(this);
+
+  // Create target frame
+  GLFrame target(gl_env_);
+  target.Init(width_, height_);
+
+  // Render the texture to the target
+  GetIdentity()->Process(sources, &target);
+
+  // Get the pixel data
+  return target.ReadFboPixels(pixels);
+}
+
+bool GLFrame::AttachTextureToFbo() {
+  // Check FBO and texture state. We do not do anything if we are not managing the texture.
+  if (fbo_state_ == kStateComplete || texture_state_ == kStateUnmanaged) {
+    return true;
+  } else if (fbo_state_ != kStateGenerated) {
+    ALOGE("Attempting to attach texture to FBO with no FBO in place!");
+    return false;
+  }
+
+  // If texture has been generated, make sure it is allocated.
+  if (!AllocateTexture())
+    return false;
+
+  // Bind the frame buffer, and check if we it is already bound
+  glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);
+
+  // Bind the texture to the frame buffer
+  LOG_FRAME("Attaching tex %d w %d h %d to fbo %d", texture_id_, width_, height_, fbo_id_);
+  glFramebufferTexture2D(GL_FRAMEBUFFER,
+                         GL_COLOR_ATTACHMENT0,
+                         GL_TEXTURE_2D,
+                         texture_id_,
+                         0);
+
+  // Cleanup
+  glBindTexture(GL_TEXTURE_2D, 0);
+  glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+  if (GLEnv::CheckGLError("Texture Binding to FBO"))
+    return false;
+  else
+    fbo_state_ = kStateComplete;
+
+  return true;
+}
+
+bool GLFrame::ReattachTextureToFbo() {
+  return (fbo_state_ == kStateGenerated) ? AttachTextureToFbo() : true;
+}
+
+bool GLFrame::DetachTextureFromFbo() {
+  if (fbo_state_ == kStateComplete && texture_state_ == kStateComplete) {
+    LOG_FRAME("Detaching tex %d w %d h %d from fbo %d", texture_id_, width_, height_, fbo_id_);
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);
+    glFramebufferTexture2D(GL_FRAMEBUFFER,
+                           GL_COLOR_ATTACHMENT0,
+                           GL_TEXTURE_2D,
+                           0,
+                           0);
+    if (GLEnv::CheckGLError("Detaching texture to FBO"))
+      return false;
+    else
+      fbo_state_ = kStateGenerated;
+  }
+  return true;
+}
+
+bool GLFrame::UploadTexturePixels(const uint8_t* pixels) {
+  // Bind the texture object
+  FocusTexture();
+
+  // Load mipmap level 0
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, height_,
+               0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+
+  // Set the user specified texture parameters
+  UpdateTexParameters();
+
+  if (GLEnv::CheckGLError("Texture Pixel Upload"))
+    return false;
+
+  texture_state_ = kStateComplete;
+  return true;
+}
+
+} // namespace filterfw
+} // namespace android
diff --git a/media/mca/filterfw/native/core/gl_frame.h b/media/mca/filterfw/native/core/gl_frame.h
new file mode 100644
index 0000000..f2a1ad5
--- /dev/null
+++ b/media/mca/filterfw/native/core/gl_frame.h
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_CORE_GL_FRAME_H
+#define ANDROID_FILTERFW_CORE_GL_FRAME_H
+
+#include <map>
+
+#include <GLES2/gl2.h>
+
+#include "core/gl_buffer_interface.h"
+
+namespace android {
+namespace filterfw {
+
+class GLEnv;
+class ShaderProgram;
+
+// A GLFrame stores pixel data on the GPU. While pixel data may be uploaded to
+// a GLFrame and also read out of a GLFrame (access in place is not supported),
+// it is strongly recommended to use ShaderProgram objects for any kind of
+// processing from one GLFrame to another.
+class GLFrame : public GLBufferHandle {
+  public:
+    // Create an empty GL frame in the specified GL environment. Note, that the GLFrame does NOT
+    // take ownership. The caller must make sure the GLEnv stays valid as long as the GLFrame is
+    // alive.
+    GLFrame(GLEnv* gl_env);
+
+    // Deallocate a GL frame.
+    ~GLFrame();
+
+    // Initialize a GL frame to the given width, height, format. Also specify
+    // whether this is a read-only GL frame or not.
+    bool Init(int width, int height);
+
+    // Initialize as using an external texture.
+    bool InitWithExternalTexture();
+
+    // Initialize using an existing texture.
+    bool InitWithTexture(GLint texture_id, int width, int height);
+
+    // Initialize using an existing FBO.
+    bool InitWithFbo(GLint fbo_id, int width, int height);
+
+    // Write the data with the given size in bytes to the frame. The frame size must match the
+    // size of the data.
+    bool WriteData(const uint8_t* data, int size);
+
+    // Copies the frame data to the given buffer.
+    bool CopyDataTo(uint8_t* buffer, int size);
+
+    // Copies the pixels from another GL frame to this frame.
+    bool CopyPixelsFrom(const GLFrame* frame);
+
+    // Returns the size of the buffer in bytes.
+    int Size() const;
+
+    // Clone the current frame by creating a new GL frame and copying all data to it.
+    GLFrame* Clone() const;
+
+    // Returns the held texture id. Only call this if the GLFrame holds a
+    // texture. You can check this by calling HoldsTexture().
+    // Note, that a texture is created only when needed. If you are creating a
+    // new GLFrame, and you need it to be bound to a texture, upload (zeroed)
+    // data to it first, before calling this method.
+    GLuint GetTextureId() const;
+
+    // Returns the held FBO id. Only call this if the GLFrame holds an FBO. You
+    // can check this by calling HoldsFbo().
+    GLuint GetFboId() const;
+
+    // Returns the texture target: GL_TEXTURE_2D or GL_TEXTURE_EXTERNAL_OES.
+    GLuint GetTextureTarget() const {
+      return texture_target_;
+    }
+
+    // Set the viewport that will be used when focusing this frame for rendering. Defaults to
+    // the dimensions of the frame.
+    bool SetViewport(int x, int y, int width, int height);
+
+    // Binds the held texture. This may result in creating the texture if it
+    // is not yet available.
+    bool FocusTexture();
+
+    // Binds the held FBO. This may result in creating the FBO if it
+    // is not yet available.
+    bool FocusFrameBuffer();
+
+    // Generates the mipmap chain of the held texture. Returns true, iff
+    // generating was successful.
+    bool GenerateMipMap();
+
+    // Set a texture parameter (see glTextureParameter documentation). Returns
+    // true iff the parameter was set successfully.
+    bool SetTextureParameter(GLenum pname, GLint value);
+
+    // Reset any modifed texture parameters.
+    bool ResetTexParameters();
+
+    // Detaches the internal texture from the FBO.
+    bool DetachTextureFromFbo();
+
+    // Reattaches the internal texture to the FBO after detachment.
+    bool ReattachTextureToFbo();
+
+  private:
+    // Type to keep track of texture and FBO states
+    enum GLObjectState {
+      kStateUnmanaged,      // We do not manage this object (externally managed)
+      kStateUninitialized,  // Not yet initialized
+      kStateGenerated,      // Tex/FBO id is generated
+      kStateComplete        // FBO has valid attachment / Tex has valid pixel data
+    };
+
+    // Sets the frame and viewport dimensions.
+    void InitDimensions(int width, int height);
+
+    // Generates the internal texture name.
+    bool GenerateTextureName();
+
+    // Allocates the internal texture.
+    bool AllocateTexture();
+
+    // Creates the internal FBO.
+    bool GenerateFboName();
+
+    // Copies pixels from texture or FBO to the specified buffer.
+    bool CopyPixelsTo(uint8_t* buffer);
+
+    // Reads the pixels from the internal texture to the given buffer.
+    bool ReadTexturePixels(uint8_t* pixels) const;
+
+    // Reads the pixels from the internal FBO to the given buffer.
+    bool ReadFboPixels(uint8_t* pixels) const;
+
+    // Writes the specified pixels to the internal texture.
+    bool UploadTexturePixels(const uint8_t* pixels);
+
+    // Binds the internal texture.
+    bool BindTexture() const;
+
+    // Binds the internal FBO.
+    bool BindFrameBuffer() const;
+
+    // Attaches the internal texture to the internal FBO.
+    bool AttachTextureToFbo();
+
+    // Update the texture parameters to the user specified parameters
+    bool UpdateTexParameters();
+
+    // Returns true if the current texture parameters are not the GLES2
+    // default parameters.
+    bool TexParametersModifed();
+
+    // Sets the current texture parameters to the GLES2 default
+    // parameters. This still requires a call to UpdateTexParameters()
+    // for the changes to take effect.
+    void SetDefaultTexParameters();
+
+    // Returns true if the texture we assume to be allocated has been
+    // deleted externally. In this case we assume the texture name is
+    // still valid (otherwise we were provided with a bad texture id).
+    bool TextureWasDeleted() const;
+
+    // Get the (cached) identity shader.
+    ShaderProgram* GetIdentity() const;
+
+    // The GL environment this frame belongs to
+    GLEnv* gl_env_;
+
+    // The width, height and format of the frame
+    int width_;
+    int height_;
+
+    // The viewport dimensions
+    int vp_x_;
+    int vp_y_;
+    int vp_width_;
+    int vp_height_;
+
+    // The texture and FBO ids
+    GLuint texture_id_;
+    GLuint fbo_id_;
+
+    // The texture target: GL_TEXTURE_2D or GL_TEXTURE_EXTERNAL_OES
+    GLuint texture_target_;
+
+    // Flags whether or not frame holds a texture and FBO
+    GLObjectState texture_state_;
+    GLObjectState fbo_state_;
+
+    // Set of current texture parameters
+    std::map<GLenum, GLint> tex_params_;
+
+    // Flag whether frame owns the texture and FBO
+    bool owns_texture_;
+    bool owns_fbo_;
+};
+
+} // namespace filterfw
+} // namespace android
+
+#endif  // ANDROID_FILTERFW_CORE_GL_FRAME_H
diff --git a/media/mca/filterfw/native/core/native_frame.cpp b/media/mca/filterfw/native/core/native_frame.cpp
new file mode 100644
index 0000000..957ecb1
--- /dev/null
+++ b/media/mca/filterfw/native/core/native_frame.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "core/native_frame.h"
+
+namespace android {
+namespace filterfw {
+
+NativeFrame::NativeFrame(int size) : data_(NULL), size_(size), capacity_(size) {
+  data_ = capacity_ == 0 ? NULL : new uint8_t[capacity_];
+}
+
+NativeFrame::~NativeFrame() {
+  delete[] data_;
+}
+
+bool NativeFrame::WriteData(const uint8_t* data, int offset, int size) {
+  if (size_ >= (offset + size)) {
+    memcpy(data_ + offset, data, size);
+    return true;
+  }
+  return false;
+}
+
+bool NativeFrame::SetData(uint8_t* data, int size) {
+  delete[] data_;
+  size_ = capacity_ = size;
+  data_ = data;
+  return true;
+}
+
+NativeFrame* NativeFrame::Clone() const {
+  NativeFrame* result = new NativeFrame(size_);
+  if (data_)
+    result->WriteData(data_, 0, size_);
+  return result;
+}
+
+bool NativeFrame::Resize(int newSize) {
+  if (newSize <= capacity_ && newSize >= 0) {
+    size_ = newSize;
+    return true;
+  }
+  return false;
+}
+
+} // namespace filterfw
+} // namespace android
diff --git a/media/mca/filterfw/native/core/native_frame.h b/media/mca/filterfw/native/core/native_frame.h
new file mode 100644
index 0000000..0d335b3
--- /dev/null
+++ b/media/mca/filterfw/native/core/native_frame.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_CORE_NATIVE_FRAME_H
+#define ANDROID_FILTERFW_CORE_NATIVE_FRAME_H
+
+#include "base/utilities.h"
+
+namespace android {
+namespace filterfw {
+
+// A NativeFrame stores data in a memory buffer (on the heap). It is used for
+// data processing on the CPU.
+class NativeFrame {
+  public:
+    // Create an empty native frame.
+    NativeFrame(int size);
+
+    ~NativeFrame();
+
+    // Set the frame data and size in bytes. The NativeFrame object takes ownership of the data.
+    // To copy data into an existing frame, use WriteData().
+    bool SetData(uint8_t* data, int size);
+
+    // Write the specified data of the given size to the frame at the specified offset. The
+    // receiver must be large enough to hold the data.
+    bool WriteData(const uint8_t* data, int offset, int size);
+
+    // Returns a pointer to the data, or NULL if no data was set.
+    const uint8_t* Data() const {
+      return data_;
+    }
+
+    // Returns a non-const pointer to the data, or NULL if no data was set.
+    uint8_t* MutableData() {
+      return data_;
+    }
+
+    // Resize the frame. You can only resize to a size that fits within the frame's capacity.
+    // Returns true if the resize was successful.
+    bool Resize(int newSize);
+
+    // Returns the size of the frame in bytes.
+    int Size() {
+      return size_;
+    }
+
+    // Returns the capacity of the frame in bytes.
+    int Capacity() {
+      return capacity_;
+    }
+
+    // Returns a new native frame
+    NativeFrame* Clone() const;
+
+  private:
+    // Pointer to the data. Owned by the frame.
+    uint8_t* data_;
+
+    // Size of data buffer in bytes.
+    int size_;
+
+    // Capacity of data buffer in bytes.
+    int capacity_;
+
+    DISALLOW_COPY_AND_ASSIGN(NativeFrame);
+};
+
+} // namespace filterfw
+} // namespace android
+
+#endif  // ANDROID_FILTERFW_CORE_NATIVE_FRAME_H
diff --git a/media/mca/filterfw/native/core/native_program.cpp b/media/mca/filterfw/native/core/native_program.cpp
new file mode 100644
index 0000000..c46c46f
--- /dev/null
+++ b/media/mca/filterfw/native/core/native_program.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dlfcn.h>
+
+#include "base/logging.h"
+#include "core/native_frame.h"
+#include "core/native_program.h"
+
+#include <string>
+#include <vector>
+
+namespace android {
+namespace filterfw {
+
+NativeProgram::NativeProgram()
+    : lib_handle_(NULL),
+      init_function_(NULL),
+      setvalue_function_(NULL),
+      getvalue_function_(NULL),
+      process_function_(NULL),
+      reset_function_(NULL),
+      teardown_function_(NULL),
+      user_data_(NULL) {
+}
+
+NativeProgram::~NativeProgram() {
+  if (lib_handle_)
+    dlclose(lib_handle_);
+}
+
+bool NativeProgram::OpenLibrary(const std::string& lib_name) {
+  if (!lib_handle_) {
+    lib_handle_ = dlopen(lib_name.c_str(), RTLD_NOW);
+    if (!lib_handle_) {
+      ALOGE("NativeProgram: Error opening library: '%s': %s", lib_name.c_str(), dlerror());
+      return false;
+    }
+    return true;
+  }
+  return false;
+}
+
+bool NativeProgram::BindProcessFunction(const std::string& func_name) {
+  if (!lib_handle_)
+    return false;
+  process_function_ = reinterpret_cast<ProcessFunctionPtr>(dlsym(lib_handle_, func_name.c_str()));
+  if (!process_function_) {
+    ALOGE("NativeProgram: Could not find process function symbol: '%s'!", func_name.c_str());
+    return false;
+  }
+  return true;
+}
+
+bool NativeProgram::BindInitFunction(const std::string& func_name) {
+  if (!lib_handle_)
+    return false;
+  init_function_ = reinterpret_cast<InitFunctionPtr>(dlsym(lib_handle_, func_name.c_str()));
+  return init_function_ != NULL;
+}
+
+bool NativeProgram::BindSetValueFunction(const std::string& func_name) {
+  if (!lib_handle_)
+    return false;
+  setvalue_function_ = reinterpret_cast<SetValueFunctionPtr>(dlsym(lib_handle_, func_name.c_str()));
+  return setvalue_function_ != NULL;
+}
+
+bool NativeProgram::BindGetValueFunction(const std::string& func_name) {
+  if (!lib_handle_)
+    return false;
+  getvalue_function_ = reinterpret_cast<GetValueFunctionPtr>(dlsym(lib_handle_, func_name.c_str()));
+  return getvalue_function_ != NULL;
+}
+
+bool NativeProgram::BindResetFunction(const std::string& func_name) {
+  if (!lib_handle_)
+    return false;
+  reset_function_ = reinterpret_cast<ResetFunctionPtr>(dlsym(lib_handle_, func_name.c_str()));
+  return reset_function_ != NULL;
+}
+
+bool NativeProgram::BindTeardownFunction(const std::string& func_name) {
+  if (!lib_handle_)
+    return false;
+  teardown_function_ = reinterpret_cast<TeardownFunctionPtr>(dlsym(lib_handle_, func_name.c_str()));
+  return teardown_function_ != NULL;
+}
+
+bool NativeProgram::CallProcess(const std::vector<const char*>& inputs,
+                                const std::vector<int>& input_sizes,
+                                char* output,
+                                int output_size) {
+  if (process_function_) {
+    return process_function_(const_cast<const char**>(&inputs[0]),
+                             &input_sizes[0],
+                             inputs.size(),
+                             output,
+                             output_size,
+                             user_data_) == 1;
+  }
+  return false;
+}
+
+bool NativeProgram::CallInit() {
+  if (init_function_) {
+    init_function_(&user_data_);
+    return true;
+  }
+  return false;
+}
+
+bool NativeProgram::CallSetValue(const std::string& key, const std::string& value) {
+  if (setvalue_function_) {
+    setvalue_function_(key.c_str(), value.c_str(), user_data_);
+    return true;
+  }
+  return false;
+}
+
+std::string NativeProgram::CallGetValue(const std::string& key) {
+  if (getvalue_function_) {
+    static const int buffer_size = 1024;
+    char result[buffer_size];
+    result[buffer_size - 1] = '\0';
+    getvalue_function_(key.c_str(), result, buffer_size, user_data_);
+    return std::string(result);
+  }
+  return std::string();
+}
+
+bool NativeProgram::CallReset() {
+  if (reset_function_) {
+    reset_function_(user_data_);
+    return true;
+  }
+  return false;
+}
+
+bool NativeProgram::CallTeardown() {
+  if (teardown_function_) {
+    teardown_function_(user_data_);
+    return true;
+  }
+  return false;
+}
+
+} // namespace filterfw
+} // namespace android
diff --git a/media/mca/filterfw/native/core/native_program.h b/media/mca/filterfw/native/core/native_program.h
new file mode 100644
index 0000000..ce704af
--- /dev/null
+++ b/media/mca/filterfw/native/core/native_program.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_CORE_NATIVE_PROGRAM_H
+#define ANDROID_FILTERFW_CORE_NATIVE_PROGRAM_H
+
+#include <vector>
+#include <string>
+
+#include "base/utilities.h"
+
+namespace android {
+namespace filterfw {
+
+class NativeFrame;
+
+typedef void (*InitFunctionPtr)(void**);
+typedef void (*SetValueFunctionPtr)(const char*, const char*, void*);
+typedef void (*GetValueFunctionPtr)(const char*, char*, int, void*);
+typedef int (*ProcessFunctionPtr)(const char**, const int*, int, char*, int, void*);
+typedef void (*ResetFunctionPtr)(void*);
+typedef void (*TeardownFunctionPtr)(void*);
+
+class NativeProgram {
+  public:
+    // Create an empty native frame.
+    NativeProgram();
+
+    ~NativeProgram();
+
+    bool OpenLibrary(const std::string& lib_name);
+
+    bool BindInitFunction(const std::string& func_name);
+    bool BindSetValueFunction(const std::string& func_name);
+    bool BindGetValueFunction(const std::string& func_name);
+    bool BindProcessFunction(const std::string& func_name);
+    bool BindResetFunction(const std::string& func_name);
+    bool BindTeardownFunction(const std::string& func_name);
+
+    bool CallInit();
+    bool CallSetValue(const std::string& key, const std::string& value);
+    std::string CallGetValue(const std::string& key);
+    bool CallProcess(const std::vector<const char*>& inputs,
+                     const std::vector<int>& input_sizes,
+                     char* output,
+                     int output_size);
+    bool CallReset();
+    bool CallTeardown();
+
+  private:
+    // Pointer to the data. Owned by the frame.
+    void* lib_handle_;
+
+    // The function pointers to the native function implementations.
+    InitFunctionPtr init_function_;
+    SetValueFunctionPtr setvalue_function_;
+    GetValueFunctionPtr getvalue_function_;
+    ProcessFunctionPtr process_function_;
+    ResetFunctionPtr reset_function_;
+    TeardownFunctionPtr teardown_function_;
+
+    // Pointer to user data
+    void* user_data_;
+
+    DISALLOW_COPY_AND_ASSIGN(NativeProgram);
+};
+
+} // namespace filterfw
+} // namespace android
+
+#endif  // ANDROID_FILTERFW_CORE_NATIVE_PROGRAM_H
diff --git a/media/mca/filterfw/native/core/shader_program.cpp b/media/mca/filterfw/native/core/shader_program.cpp
new file mode 100644
index 0000000..d92eb31
--- /dev/null
+++ b/media/mca/filterfw/native/core/shader_program.cpp
@@ -0,0 +1,1122 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "base/logging.h"
+
+#include "core/geometry.h"
+#include "core/gl_buffer_interface.h"
+#include "core/gl_env.h"
+#include "core/gl_frame.h"
+#include "core/shader_program.h"
+#include "core/vertex_frame.h"
+
+#include <string>
+#include <sstream>
+#include <vector>
+
+namespace android {
+namespace filterfw {
+
+// VBO attachment keys
+static const int kDefaultVboKey = 1;
+
+static const char* s_default_vertex_shader_source_ =
+  "attribute vec4 a_position;\n"
+  "attribute vec2 a_texcoord;\n"
+  "varying vec2 v_texcoord;\n"
+  "void main() {\n"
+  "  gl_Position = a_position;\n"
+  "  v_texcoord = a_texcoord;\n"
+  "}\n";
+
+// Helper Functions ////////////////////////////////////////////////////////////
+// Maps coordinates x,y in the unit rectangle over to the quadrangle specified
+// by the four points in b. The result coordinates are written to xt and yt.
+static void GetTileCoords(const float* b, float x, float y, float* xt, float* yt) {
+  const float w0 =  (1.0f - x) * (1.0f - y);
+  const float w1 =  x * (1.0f - y);
+  const float w2 =  (1.0f - x) * y;
+  const float w3 =  x * y;
+
+  *xt = w0 * b[0] + w1 * b[2] + w2 * b[4] + w3 * b[6];
+  *yt = w0 * b[1] + w1 * b[3] + w2 * b[5] + w3 * b[7];
+}
+
+static inline float AdjustRatio(float current, float next) {
+  return (current + next) / 2.0;
+}
+
+// VertexAttrib implementation /////////////////////////////////////////////////
+ShaderProgram::VertexAttrib::VertexAttrib()
+  : is_const(true),
+    index(-1),
+    normalized(false),
+    stride(0),
+    components(0),
+    offset(0),
+    type(GL_FLOAT),
+    vbo(0),
+    values(NULL),
+    owned_data(NULL) {
+}
+
+// ShaderProgram implementation ////////////////////////////////////////////////
+ShaderProgram::ShaderProgram(GLEnv* gl_env, const std::string& fragment_shader)
+  : fragment_shader_source_(fragment_shader),
+    vertex_shader_source_(s_default_vertex_shader_source_),
+    fragment_shader_(0),
+    vertex_shader_(0),
+    program_(0),
+    gl_env_(gl_env),
+    base_texture_unit_(GL_TEXTURE0),
+    source_coords_(NULL),
+    target_coords_(NULL),
+    manage_coordinates_(false),
+    tile_x_count_(1),
+    tile_y_count_(1),
+    vertex_count_(4),
+    draw_mode_(GL_TRIANGLE_STRIP),
+    clears_(false),
+    blending_(false),
+    sfactor_(GL_SRC_ALPHA),
+    dfactor_(GL_ONE_MINUS_SRC_ALPHA) {
+  SetDefaultCoords();
+}
+
+ShaderProgram::ShaderProgram(GLEnv* gl_env,
+                             const std::string& vertex_shader,
+                             const std::string& fragment_shader)
+  : fragment_shader_source_(fragment_shader),
+    vertex_shader_source_(vertex_shader),
+    fragment_shader_(0),
+    vertex_shader_(0),
+    program_(0),
+    gl_env_(gl_env),
+    base_texture_unit_(GL_TEXTURE0),
+    source_coords_(NULL),
+    target_coords_(NULL),
+    manage_coordinates_(false),
+    tile_x_count_(1),
+    tile_y_count_(1),
+    vertex_count_(4),
+    draw_mode_(GL_TRIANGLE_STRIP),
+    clears_(false),
+    blending_(false),
+    sfactor_(GL_SRC_ALPHA),
+    dfactor_(GL_ONE_MINUS_SRC_ALPHA) {
+  SetDefaultCoords();
+}
+
+ShaderProgram::~ShaderProgram() {
+  // Delete our vertex data
+  delete[] source_coords_;
+  delete[] target_coords_;
+
+  // Delete any owned attribute data
+  VertexAttribMap::const_iterator iter;
+  for (iter = attrib_values_.begin(); iter != attrib_values_.end(); ++iter) {
+    const VertexAttrib& attrib = iter->second;
+    if (attrib.owned_data)
+      delete[] attrib.owned_data;
+  }
+}
+
+void ShaderProgram::SetDefaultCoords() {
+  if (!source_coords_)
+    source_coords_ = new float[8];
+  if (!target_coords_)
+    target_coords_ = new float[8];
+
+  source_coords_[0] = 0.0f;
+  source_coords_[1] = 0.0f;
+  source_coords_[2] = 1.0f;
+  source_coords_[3] = 0.0f;
+  source_coords_[4] = 0.0f;
+  source_coords_[5] = 1.0f;
+  source_coords_[6] = 1.0f;
+  source_coords_[7] = 1.0f;
+
+  target_coords_[0] = -1.0f;
+  target_coords_[1] = -1.0f;
+  target_coords_[2] =  1.0f;
+  target_coords_[3] = -1.0f;
+  target_coords_[4] = -1.0f;
+  target_coords_[5] =  1.0f;
+  target_coords_[6] =  1.0f;
+  target_coords_[7] =  1.0f;
+
+}
+
+ShaderProgram* ShaderProgram::CreateIdentity(GLEnv* gl_env) {
+  const char* s_id_fragment_shader =
+    "precision mediump float;\n"
+    "uniform sampler2D tex_sampler_0;\n"
+    "varying vec2 v_texcoord;\n"
+    "void main() {\n"
+    "  gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n"
+    "}\n";
+  ShaderProgram* result = new ShaderProgram(gl_env, s_id_fragment_shader);
+  result->CompileAndLink();
+  return result;
+}
+
+bool ShaderProgram::IsVarValid(ProgramVar var) {
+  return var != -1;
+}
+
+bool ShaderProgram::Process(const std::vector<const GLTextureHandle*>& input,
+                            GLFrameBufferHandle* output) {
+  // TODO: This can be optimized: If the input and output are the same, as in
+  // the last iteration (typical of a multi-pass filter), a lot of this setup
+  // may be skipped.
+
+  // Abort if program did not successfully compile and link
+  if (!IsExecutable()) {
+    ALOGE("ShaderProgram: unexecutable program!");
+    return false;
+  }
+
+  // Focus the FBO of the output
+  if (!output->FocusFrameBuffer()) {
+    ALOGE("Unable to focus frame buffer");
+    return false;
+  }
+
+  // Get all required textures
+  std::vector<GLuint> textures;
+  std::vector<GLenum> targets;
+  for (unsigned i = 0; i < input.size(); ++i) {
+    // Get the current input frame and make sure it is a GL frame
+    if (input[i]) {
+      // Get the texture bound to that frame
+      const GLuint tex_id = input[i]->GetTextureId();
+      const GLenum target = input[i]->GetTextureTarget();
+      if (tex_id == 0) {
+        ALOGE("ShaderProgram: invalid texture id at input: %d!", i);
+        return false;
+      }
+      textures.push_back(tex_id);
+      targets.push_back(target);
+    }
+  }
+
+  // And render!
+  if (!RenderFrame(textures, targets)) {
+    ALOGE("Unable to render frame");
+    return false;
+  }
+  return true;
+}
+
+bool ShaderProgram::Process(const std::vector<const GLFrame*>& input, GLFrame* output) {
+  std::vector<const GLTextureHandle*> textures(input.size());
+  std::copy(input.begin(), input.end(), textures.begin());
+  return Process(textures, output);
+}
+
+void ShaderProgram::SetSourceRect(float x, float y, float width, float height) {
+  Quad quad(Point(x,          y),
+            Point(x + width,  y),
+            Point(x,          y + height),
+            Point(x + width,  y + height));
+  SetSourceRegion(quad);
+}
+
+void ShaderProgram::SetSourceRegion(const Quad& quad) {
+  int index = 0;
+  for (int i = 0; i < 4; ++i, index += 2) {
+    source_coords_[index]   = quad.point(i).x();
+    source_coords_[index+1] = quad.point(i).y();
+  }
+}
+
+void ShaderProgram::SetTargetRect(float x, float y, float width, float height) {
+  Quad quad(Point(x,          y),
+            Point(x + width,  y),
+            Point(x,          y + height),
+            Point(x + width,  y + height));
+  SetTargetRegion(quad);
+}
+
+void ShaderProgram::SetTargetRegion(const Quad& quad) {
+  int index = 0;
+  for (int i = 0; i < 4; ++i, index += 2) {
+    target_coords_[index]   = (quad.point(i).x() * 2.0) - 1.0;
+    target_coords_[index+1] = (quad.point(i).y() * 2.0) - 1.0;
+  }
+}
+
+bool ShaderProgram::CompileAndLink() {
+  // Make sure we haven't compiled and linked already
+  if (vertex_shader_ != 0 || fragment_shader_ != 0 || program_ != 0) {
+    ALOGE("Attempting to re-compile shaders!");
+    return false;
+  }
+
+  // Compile vertex shader
+  vertex_shader_ = CompileShader(GL_VERTEX_SHADER,
+                                 vertex_shader_source_.c_str());
+  if (!vertex_shader_) {
+    ALOGE("Shader compilation failed!");
+    return false;
+  }
+
+  // Compile fragment shader
+  fragment_shader_ = CompileShader(GL_FRAGMENT_SHADER,
+                                   fragment_shader_source_.c_str());
+  if (!fragment_shader_)
+    return false;
+
+  // Link
+  GLuint shaders[2] = { vertex_shader_, fragment_shader_ };
+  program_ = LinkProgram(shaders, 2);
+
+  // Scan for all uniforms in the program
+  ScanUniforms();
+
+  // Check if we manage all coordinates
+  if (program_ != 0) {
+    ProgramVar tex_coord_attr = glGetAttribLocation(program_, TexCoordAttributeName().c_str());
+    ProgramVar pos_coord_attr = glGetAttribLocation(program_, PositionAttributeName().c_str());
+    manage_coordinates_ = (tex_coord_attr >= 0 && pos_coord_attr >= 0);
+  } else {
+    ALOGE("Could not link shader program!");
+    return false;
+  }
+
+  return true;
+}
+
+GLuint ShaderProgram::CompileShader(GLenum shader_type, const char* source) {
+  LOG_FRAME("Compiling source:\n[%s]", source);
+
+  // Create shader
+  GLuint shader = glCreateShader(shader_type);
+  if (shader) {
+    // Compile source
+    glShaderSource(shader, 1, &source, NULL);
+    glCompileShader(shader);
+
+    // Check for compilation errors
+    GLint compiled = 0;
+    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+    if (!compiled) {
+      // Log the compilation error messages
+      ALOGE("Problem compiling shader! Source:");
+      ALOGE("%s", source);
+      std::string src(source);
+      unsigned int cur_pos = 0;
+      unsigned int next_pos = 0;
+      int line_number = 1;
+      while ( (next_pos = src.find_first_of('\n', cur_pos)) != std::string::npos) {
+        ALOGE("%03d : %s", line_number, src.substr(cur_pos, next_pos-cur_pos).c_str());
+        cur_pos = next_pos + 1;
+        line_number++;
+      }
+      ALOGE("%03d : %s", line_number, src.substr(cur_pos, next_pos-cur_pos).c_str());
+
+      GLint log_length = 0;
+      glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
+      if (log_length) {
+        char* error_log = new char[log_length];
+        if (error_log) {
+          glGetShaderInfoLog(shader, log_length, NULL, error_log);
+          ALOGE("Shader compilation error %d:\n%s\n", shader_type, error_log);
+          delete[] error_log;
+        }
+      }
+      glDeleteShader(shader);
+      shader = 0;
+    }
+  }
+  return shader;
+}
+
+GLuint ShaderProgram::LinkProgram(GLuint* shaders, GLuint count) {
+  GLuint program = glCreateProgram();
+  if (program) {
+    // Attach all compiled shaders
+    for (GLuint i = 0; i < count; ++i) {
+      glAttachShader(program, shaders[i]);
+      if (GLEnv::CheckGLError("glAttachShader")) return 0;
+    }
+
+    // Link
+    glLinkProgram(program);
+
+    // Check for linking errors
+    GLint linked = 0;
+    glGetProgramiv(program, GL_LINK_STATUS, &linked);
+    if (linked != GL_TRUE) {
+      // Log the linker error messages
+      GLint log_length = 0;
+      glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
+      if (log_length) {
+        char* error_log = new char[log_length];
+        if (error_log) {
+          glGetProgramInfoLog(program, log_length, NULL, error_log);
+          ALOGE("Program Linker Error:\n%s\n", error_log);
+          delete[] error_log;
+        }
+      }
+      glDeleteProgram(program);
+      program = 0;
+    }
+  }
+  return program;
+}
+
+void ShaderProgram::ScanUniforms() {
+  int uniform_count;
+  int buffer_size;
+  GLenum type;
+  GLint capacity;
+  glGetProgramiv(program_, GL_ACTIVE_UNIFORMS, &uniform_count);
+  glGetProgramiv(program_, GL_ACTIVE_UNIFORM_MAX_LENGTH, &buffer_size);
+  std::vector<GLchar> name(buffer_size);
+  for (int i = 0; i < uniform_count; ++i) {
+    glGetActiveUniform(program_, i, buffer_size, NULL, &capacity, &type, &name[0]);
+    ProgramVar uniform_id = glGetUniformLocation(program_, &name[0]);
+    uniform_indices_[uniform_id] = i;
+  }
+}
+
+bool ShaderProgram::PushCoords(ProgramVar attr, float* coords) {
+  // If the shader does not define these, we simply ignore the coordinates, and assume that the
+  // user is managing coordinates.
+  if (attr >= 0) {
+    const uint8_t* data = reinterpret_cast<const uint8_t*>(coords);
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+    glVertexAttribPointer(attr, 2, GL_FLOAT, false, 2 * sizeof(float), data);
+    glEnableVertexAttribArray(attr);
+    return !GLEnv::CheckGLError("Pushing vertex coordinates");
+  }
+  return true;
+}
+
+bool ShaderProgram::PushSourceCoords(float* coords) {
+  ProgramVar tex_coord_attr = glGetAttribLocation(program_, TexCoordAttributeName().c_str());
+  return PushCoords(tex_coord_attr, coords);
+}
+
+bool ShaderProgram::PushTargetCoords(float* coords) {
+  ProgramVar pos_coord_attr = glGetAttribLocation(program_, PositionAttributeName().c_str());
+  return PushCoords(pos_coord_attr, coords);
+}
+
+std::string ShaderProgram::InputTextureUniformName(int index) {
+  std::stringstream tex_name;
+  tex_name << "tex_sampler_" << index;
+  return tex_name.str();
+}
+
+bool ShaderProgram::BindInputTextures(const std::vector<GLuint>& textures,
+                                      const std::vector<GLenum>& targets) {
+  for (unsigned i = 0; i < textures.size(); ++i) {
+    // Activate texture unit i
+    glActiveTexture(BaseTextureUnit() + i);
+    if (GLEnv::CheckGLError("Activating Texture Unit"))
+      return false;
+
+    // Bind our texture
+    glBindTexture(targets[i], textures[i]);
+    LOG_FRAME("Binding texture %d", textures[i]);
+    if (GLEnv::CheckGLError("Binding Texture"))
+      return false;
+
+    // Set the texture handle in the shader to unit i
+    ProgramVar tex_var = GetUniform(InputTextureUniformName(i));
+    if (tex_var >= 0) {
+      glUniform1i(tex_var, i);
+    } else {
+      ALOGE("ShaderProgram: Shader does not seem to support %d number of "
+           "inputs! Missing uniform 'tex_sampler_%d'!", textures.size(), i);
+      return false;
+    }
+
+    if (GLEnv::CheckGLError("Texture Variable Binding"))
+      return false;
+  }
+
+  return true;
+}
+
+bool ShaderProgram::UseProgram() {
+  if (GLEnv::GetCurrentProgram() != program_) {
+    LOG_FRAME("Using program %d", program_);
+    glUseProgram(program_);
+    return !GLEnv::CheckGLError("Use Program");
+  }
+  return true;
+}
+
+bool ShaderProgram::RenderFrame(const std::vector<GLuint>& textures,
+                                const std::vector<GLenum>& targets) {
+  // Make sure we have enough texture units to accomodate the textures
+  if (textures.size() > static_cast<unsigned>(MaxTextureUnits())) {
+    ALOGE("ShaderProgram: Number of input textures is unsupported on this "
+         "platform!");
+    return false;
+  }
+
+  // Prepare to render
+  if (!BeginDraw()) {
+    ALOGE("ShaderProgram: couldn't initialize gl for drawing!");
+    return false;
+  }
+
+  // Bind input textures
+  if (!BindInputTextures(textures, targets)) {
+    ALOGE("BindInputTextures failed");
+    return false;
+  }
+
+  if (LOG_EVERY_FRAME) {
+    int fbo, program, buffer;
+    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &fbo);
+    glGetIntegerv(GL_CURRENT_PROGRAM, &program);
+    glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &buffer);
+    ALOGV("RenderFrame: fbo %d prog %d buff %d", fbo, program, buffer);
+  }
+
+  // Render!
+  const bool requestTile = (tile_x_count_ != 1 || tile_y_count_ != 1);
+  const bool success = (!requestTile || !manage_coordinates_  || vertex_count_ != 4)
+      ? Draw()
+      : DrawTiled();
+
+  // Pop vertex attributes
+  PopAttributes();
+
+  return success && !GLEnv::CheckGLError("Rendering");
+}
+
+bool ShaderProgram::Draw() {
+  if (PushSourceCoords(source_coords_) && PushTargetCoords(target_coords_)) {
+    glDrawArrays(draw_mode_, 0, vertex_count_);
+    return true;
+  }
+  return false;
+}
+
+bool ShaderProgram::DrawTiled() {
+  // Target coordinate step size
+  float s[8];
+  float t[8];
+
+  // Step sizes
+  const float xs = 1.0f / static_cast<float>(tile_x_count_);
+  const float ys = 1.0f / static_cast<float>(tile_y_count_);
+
+  // Tile drawing loop
+  for (int i = 0; i < tile_x_count_; ++i) {
+    for (int j = 0; j < tile_y_count_; ++j) {
+      // Current coordinates in unit rectangle
+      const float x = i / static_cast<float>(tile_x_count_);
+      const float y = j / static_cast<float>(tile_y_count_);
+
+      // Source coords
+      GetTileCoords(source_coords_, x, y, &s[0], &s[1]);
+      GetTileCoords(source_coords_, x + xs, y, &s[2], &s[3]);
+      GetTileCoords(source_coords_, x, y + ys, &s[4], &s[5]);
+      GetTileCoords(source_coords_, x + xs, y + ys, &s[6], &s[7]);
+
+      // Target coords
+      GetTileCoords(target_coords_, x, y, &t[0], &t[1]);
+      GetTileCoords(target_coords_, x + xs, y, &t[2], &t[3]);
+      GetTileCoords(target_coords_, x, y + ys, &t[4], &t[5]);
+      GetTileCoords(target_coords_, x + xs, y + ys, &t[6], &t[7]);
+
+      if (PushSourceCoords(s) && PushTargetCoords(t)) {
+        glDrawArrays(draw_mode_, 0, vertex_count_);
+        Yield();
+      } else {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+void ShaderProgram::Yield() {
+  glFinish();
+}
+
+bool ShaderProgram::BeginDraw() {
+  // Activate shader program
+  if (!UseProgram())
+    return false;
+
+  // Push vertex attributes
+  PushAttributes();
+
+  // Clear output, if requested
+  if (clears_) {
+    glClearColor(clear_color_.red,
+                 clear_color_.green,
+                 clear_color_.blue,
+                 clear_color_.alpha);
+    glClear(GL_COLOR_BUFFER_BIT);
+  }
+
+  // Enable/Disable blending
+  if (blending_) {
+    glEnable(GL_BLEND);
+    glBlendFunc(sfactor_, dfactor_);
+  } else glDisable(GL_BLEND);
+
+  return true;
+}
+
+int ShaderProgram::MaxVaryingCount() {
+  GLint result;
+  glGetIntegerv(GL_MAX_VARYING_VECTORS, &result);
+  return result;
+}
+
+int ShaderProgram::MaxTextureUnits() {
+  return GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS - 1;
+}
+
+void ShaderProgram::SetDrawMode(GLenum mode) {
+  draw_mode_ = mode;
+}
+
+void ShaderProgram::SetClearsOutput(bool clears) {
+  clears_ = clears;
+}
+
+void ShaderProgram::SetClearColor(float red, float green, float blue, float alpha) {
+  clear_color_.red = red;
+  clear_color_.green = green;
+  clear_color_.blue = blue;
+  clear_color_.alpha = alpha;
+}
+
+void ShaderProgram::SetTileCounts(int x_count, int y_count) {
+  tile_x_count_ = x_count;
+  tile_y_count_ = y_count;
+}
+
+// Variable Value Setting Helpers //////////////////////////////////////////////
+bool ShaderProgram::CheckValueCount(const std::string& var_type,
+                                    const std::string& var_name,
+                                    int expected_count,
+                                    int components,
+                                    int value_size) {
+  if (expected_count != (value_size / components)) {
+    ALOGE("Shader Program: %s Value Error (%s): Expected value length %d "
+         "(%d components), but received length of %d (%d components)!",
+         var_type.c_str(), var_name.c_str(),
+         expected_count, components * expected_count,
+         value_size / components, value_size);
+    return false;
+  }
+  return true;
+}
+
+bool ShaderProgram::CheckValueMult(const std::string& var_type,
+                                   const std::string& var_name,
+                                   int components,
+                                   int value_size) {
+  if (value_size % components != 0) {
+    ALOGE("Shader Program: %s Value Error (%s): Value must be multiple of %d, "
+         "but %d elements were passed!", var_type.c_str(), var_name.c_str(),
+         components, value_size);
+    return false;
+  }
+  return true;
+}
+
+bool ShaderProgram::CheckVarValid(ProgramVar var) {
+  if (!IsVarValid(var)) {
+    ALOGE("Shader Program: Attempting to access invalid variable!");
+    return false;
+  }
+  return true;
+}
+
+// Uniforms ////////////////////////////////////////////////////////////////////
+bool ShaderProgram::CheckUniformValid(ProgramVar var) {
+  if (!IsVarValid(var) || uniform_indices_.find(var) == uniform_indices_.end()) {
+    ALOGE("Shader Program: Attempting to access unknown uniform %d!", var);
+    return false;
+  }
+  return true;
+}
+
+int ShaderProgram::MaxUniformCount() {
+  // Here we return the minimum of the max uniform count for fragment and vertex
+  // shaders.
+  GLint count1, count2;
+  glGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS, &count1);
+  glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &count2);
+  return count1 < count2 ? count1 : count2;
+}
+
+ProgramVar ShaderProgram::GetUniform(const std::string& name) const {
+  if (!IsExecutable()) {
+    ALOGE("ShaderProgram: Error: Must link program before querying uniforms!");
+    return -1;
+  }
+  return glGetUniformLocation(program_, name.c_str());
+}
+
+bool ShaderProgram::SetUniformValue(ProgramVar var, int value) {
+  if (!CheckVarValid(var))
+    return false;
+
+  // Uniforms are local to the currently used program.
+  if (UseProgram()) {
+    glUniform1i(var, value);
+    return !GLEnv::CheckGLError("Set Uniform Value (int)");
+  }
+  return false;
+}
+
+bool ShaderProgram::SetUniformValue(ProgramVar var, float value) {
+  if (!CheckVarValid(var))
+    return false;
+
+  // Uniforms are local to the currently used program.
+  if (UseProgram()) {
+    glUniform1f(var, value);
+    return !GLEnv::CheckGLError("Set Uniform Value (float)");
+  }
+  return false;
+}
+
+bool ShaderProgram::SetUniformValue(ProgramVar var,
+                                    const int* values,
+                                    int count) {
+  if (!CheckUniformValid(var))
+    return false;
+
+  // Make sure we have values at all
+  if (count == 0)
+    return false;
+
+  // Uniforms are local to the currently used program.
+  if (UseProgram()) {
+    // Get uniform information
+    GLint capacity;
+    GLenum type;
+    char name[128];
+    glGetActiveUniform(program_, IndexOfUniform(var), 128, NULL, &capacity, &type, name);
+
+    // Make sure passed values are compatible
+    const int components = GLEnv::NumberOfComponents(type);
+    if (!CheckValueCount("Uniform (int)", name, capacity, components, count)
+    ||  !CheckValueMult ("Uniform (int)", name, components, count))
+      return false;
+
+    // Set value based on type
+    const int n = count / components;
+    switch(type) {
+      case GL_INT:
+        glUniform1iv(var, n, values);
+        break;
+
+      case GL_INT_VEC2:
+        glUniform2iv(var, n, values);
+        break;
+
+      case GL_INT_VEC3:
+        glUniform3iv(var, n, values);
+        break;
+
+      case GL_INT_VEC4:
+        glUniform4iv(var, n, values);
+        break;
+
+      default:
+        return false;
+    };
+    return !GLEnv::CheckGLError("Set Uniform Value");
+  }
+  return false;
+}
+
+bool ShaderProgram::SetUniformValue(ProgramVar var,
+                                    const float* values,
+                                    int count) {
+  if (!CheckUniformValid(var))
+    return false;
+
+  // Make sure we have values at all
+  if (count == 0)
+    return false;
+
+  // Uniforms are local to the currently used program.
+  if (UseProgram()) {
+    // Get uniform information
+    GLint capacity;
+    GLenum type;
+    char name[128];
+    glGetActiveUniform(program_, IndexOfUniform(var), 128, NULL, &capacity, &type, name);
+
+    // Make sure passed values are compatible
+    const int components = GLEnv::NumberOfComponents(type);
+    if (!CheckValueCount("Uniform (float)", name, capacity, components, count)
+    ||  !CheckValueMult ("Uniform (float)", name, components, count))
+      return false;
+
+    // Set value based on type
+    const int n = count / components;
+    switch(type) {
+      case GL_FLOAT:
+        glUniform1fv(var, n, values);
+        break;
+
+      case GL_FLOAT_VEC2:
+        glUniform2fv(var, n, values);
+        break;
+
+      case GL_FLOAT_VEC3:
+        glUniform3fv(var, n, values);
+        break;
+
+      case GL_FLOAT_VEC4:
+        glUniform4fv(var, n, values);
+        break;
+
+      case GL_FLOAT_MAT2:
+        glUniformMatrix2fv(var, n, GL_FALSE, values);
+        break;
+
+      case GL_FLOAT_MAT3:
+        glUniformMatrix3fv(var, n, GL_FALSE, values);
+        break;
+
+      case GL_FLOAT_MAT4:
+        glUniformMatrix4fv(var, n, GL_FALSE, values);
+        break;
+
+      default:
+        return false;
+    };
+    return !GLEnv::CheckGLError("Set Uniform Value");
+  }
+  return false;
+}
+
+bool ShaderProgram::SetUniformValue(ProgramVar var, const std::vector<int>& values) {
+  return SetUniformValue(var, &values[0], values.size());
+}
+
+bool ShaderProgram::SetUniformValue(ProgramVar var,
+                                    const std::vector<float>& values) {
+  return SetUniformValue(var, &values[0], values.size());
+}
+
+bool ShaderProgram::SetUniformValue(const std::string& name, const Value& value) {
+  if (ValueIsFloat(value))
+    return SetUniformValue(GetUniform(name), GetFloatValue(value));
+  else if (ValueIsInt(value))
+    return SetUniformValue(GetUniform(name), GetIntValue(value));
+  else if (ValueIsFloatArray(value))
+    return SetUniformValue(GetUniform(name), GetFloatArrayValue(value), GetValueCount(value));
+  else if (ValueIsIntArray(value))
+    return SetUniformValue(GetUniform(name), GetIntArrayValue(value), GetValueCount(value));
+  else
+    return false;
+}
+
+Value ShaderProgram::GetUniformValue(const std::string& name) {
+  ProgramVar var = GetUniform(name);
+  if (CheckUniformValid(var)) {
+    // Get uniform information
+    GLint capacity;
+    GLenum type;
+    glGetActiveUniform(program_, IndexOfUniform(var), 0, NULL, &capacity, &type, NULL);
+    if (!GLEnv::CheckGLError("Get Active Uniform")) {
+      // Get value based on type, and wrap in value object
+      switch(type) {
+        case GL_INT: {
+          int value;
+          glGetUniformiv(program_, var, &value);
+          return !GLEnv::CheckGLError("GetVariableValue") ? MakeIntValue(value)
+                                                          : MakeNullValue();
+        } break;
+
+        case GL_INT_VEC2: {
+          int value[2];
+          glGetUniformiv(program_, var, &value[0]);
+          return !GLEnv::CheckGLError("GetVariableValue") ? MakeIntArrayValue(value, 2)
+                                                          : MakeNullValue();
+        } break;
+
+        case GL_INT_VEC3: {
+          int value[3];
+          glGetUniformiv(program_, var, &value[0]);
+          return !GLEnv::CheckGLError("GetVariableValue") ? MakeIntArrayValue(value, 3)
+                                                          : MakeNullValue();
+        } break;
+
+        case GL_INT_VEC4: {
+          int value[4];
+          glGetUniformiv(program_, var, &value[0]);
+          return !GLEnv::CheckGLError("GetVariableValue") ? MakeIntArrayValue(value, 4)
+                                                          : MakeNullValue();
+        } break;
+
+        case GL_FLOAT: {
+          float value;
+          glGetUniformfv(program_, var, &value);
+          return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatValue(value)
+                                                          : MakeNullValue();
+        } break;
+
+        case GL_FLOAT_VEC2: {
+          float value[2];
+          glGetUniformfv(program_, var, &value[0]);
+          return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 2)
+                                                          : MakeNullValue();
+        } break;
+
+        case GL_FLOAT_VEC3: {
+          float value[3];
+          glGetUniformfv(program_, var, &value[0]);
+          return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 3)
+                                                          : MakeNullValue();
+        } break;
+
+        case GL_FLOAT_VEC4: {
+          float value[4];
+          glGetUniformfv(program_, var, &value[0]);
+          return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 4)
+                                                          : MakeNullValue();
+        } break;
+
+        case GL_FLOAT_MAT2: {
+          float value[4];
+          glGetUniformfv(program_, var, &value[0]);
+          return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 4)
+                                                          : MakeNullValue();
+        } break;
+
+        case GL_FLOAT_MAT3: {
+          float value[9];
+          glGetUniformfv(program_, var, &value[0]);
+          return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 9)
+                                                          : MakeNullValue();
+        } break;
+
+        case GL_FLOAT_MAT4: {
+          float value[16];
+          glGetUniformfv(program_, var, &value[0]);
+          return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 16)
+                                                          : MakeNullValue();
+        } break;
+      }
+    }
+  }
+  return MakeNullValue();
+}
+
+GLuint ShaderProgram::IndexOfUniform(ProgramVar var) {
+  return uniform_indices_[var];
+}
+
+// Attributes //////////////////////////////////////////////////////////////////////////////////////
+int ShaderProgram::MaxAttributeCount() {
+  GLint result;
+  glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &result);
+  return result;
+}
+
+ProgramVar ShaderProgram::GetAttribute(const std::string& name) const {
+  if (!IsExecutable()) {
+    ALOGE("ShaderProgram: Error: Must link program before querying attributes!");
+    return -1;
+  } else if (name == PositionAttributeName() || name == TexCoordAttributeName()) {
+    ALOGW("ShaderProgram: Attempting to overwrite internal vertex attribute '%s'!", name.c_str());
+  }
+  return glGetAttribLocation(program_, name.c_str());
+}
+
+bool ShaderProgram::SetAttributeValues(ProgramVar var,
+                                       const VertexFrame* vbo,
+                                       GLenum type,
+                                       int components,
+                                       int stride,
+                                       int offset,
+                                       bool normalize) {
+  if (!CheckVarValid(var))
+    return false;
+
+  if (vbo) {
+    VertexAttrib attrib;
+    attrib.is_const = false;
+    attrib.index = var;
+    attrib.components = components;
+    attrib.normalized = normalize;
+    attrib.stride = stride;
+    attrib.type = type;
+    attrib.vbo = vbo->GetVboId();
+    attrib.offset = offset;
+
+    return StoreAttribute(attrib);
+  }
+  return false;
+}
+
+bool ShaderProgram::SetAttributeValues(ProgramVar var,
+                                       const uint8_t* data,
+                                       GLenum type,
+                                       int components,
+                                       int stride,
+                                       int offset,
+                                       bool normalize) {
+  if (!CheckVarValid(var))
+    return false;
+
+  if (data) {
+    VertexAttrib attrib;
+    attrib.is_const = false;
+    attrib.index = var;
+    attrib.components = components;
+    attrib.normalized = normalize;
+    attrib.stride = stride;
+    attrib.type = type;
+    attrib.values = data + offset;
+
+    return StoreAttribute(attrib);
+  }
+  return false;
+}
+
+bool ShaderProgram::SetAttributeValues(ProgramVar var,
+                                       const std::vector<float>& data,
+                                       int components) {
+  return SetAttributeValues(var, &data[0], data.size(), components);
+}
+
+bool ShaderProgram::SetAttributeValues(ProgramVar var,
+                                       const float* data,
+                                       int total,
+                                       int components) {
+  if (!CheckVarValid(var))
+    return false;
+
+  // Make sure the passed data vector has a valid size
+  if (total  % components != 0) {
+    ALOGE("ShaderProgram: Invalid attribute vector given! Specified a component "
+         "count of %d, but passed a non-multiple vector of size %d!",
+         components, total);
+    return false;
+  }
+
+  // Copy the data to a buffer we own
+  float* data_cpy = new float[total];
+  memcpy(data_cpy, data, sizeof(float) * total);
+
+  // Store the attribute
+  VertexAttrib attrib;
+  attrib.is_const = false;
+  attrib.index = var;
+  attrib.components = components;
+  attrib.normalized = false;
+  attrib.stride = components * sizeof(float);
+  attrib.type = GL_FLOAT;
+  attrib.values = data_cpy;
+  attrib.owned_data = data_cpy; // Marks this for deletion later on
+
+  return StoreAttribute(attrib);
+}
+
+bool ShaderProgram::StoreAttribute(VertexAttrib attrib) {
+  if (attrib.index >= 0) {
+    attrib_values_[attrib.index] = attrib;
+    return true;
+  }
+  return false;
+}
+
+bool ShaderProgram::PushAttributes() {
+  for (VertexAttribMap::const_iterator iter = attrib_values_.begin();
+       iter != attrib_values_.end();
+       ++iter) {
+    const VertexAttrib& attrib = iter->second;
+
+    if (attrib.is_const) {
+      // Set constant attribute values (must be specified as host values)
+      if (!attrib.values)
+        return false;
+
+      const float* values = reinterpret_cast<const float*>(attrib.values);
+      switch (attrib.components) {
+        case 1: glVertexAttrib1fv(attrib.index, values); break;
+        case 2: glVertexAttrib2fv(attrib.index, values); break;
+        case 3: glVertexAttrib3fv(attrib.index, values); break;
+        case 4: glVertexAttrib4fv(attrib.index, values); break;
+        default: return false;
+      }
+      glDisableVertexAttribArray(attrib.index);
+    } else {
+      // Set per-vertex values
+      if (attrib.values) {
+        // Make sure no buffer is bound and set attribute
+        glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+        glVertexAttribPointer(attrib.index,
+                              attrib.components,
+                              attrib.type,
+                              attrib.normalized,
+                              attrib.stride,
+                              attrib.values);
+      } else if (attrib.vbo) {
+        // Bind VBO and set attribute
+        glBindBuffer(GL_ARRAY_BUFFER, attrib.vbo);
+
+        glVertexAttribPointer(attrib.index,
+                              attrib.components,
+                              attrib.type,
+                              attrib.normalized,
+                              attrib.stride,
+                              reinterpret_cast<const void*>(attrib.offset));
+      } else {
+        return false;
+      }
+      glEnableVertexAttribArray(attrib.index);
+    }
+
+    // Make sure everything worked
+    if (GLEnv::CheckGLError("Pushing Vertex Attributes"))
+      return false;
+  }
+
+  return true;
+}
+
+bool ShaderProgram::PopAttributes() {
+  // Disable vertex attributes
+  for (VertexAttribMap::const_iterator iter = attrib_values_.begin();
+       iter != attrib_values_.end();
+       ++iter) {
+    glDisableVertexAttribArray(iter->second.index);
+  }
+  // Unbind buffer: Very important as this greatly affects what glVertexAttribPointer does!
+  glBindBuffer(GL_ARRAY_BUFFER, 0);
+  return !GLEnv::CheckGLError("Popping Vertex Attributes");
+}
+
+void ShaderProgram::SetVertexCount(int count) {
+  vertex_count_ = count;
+}
+
+} // namespace filterfw
+} // namespace android
diff --git a/media/mca/filterfw/native/core/shader_program.h b/media/mca/filterfw/native/core/shader_program.h
new file mode 100644
index 0000000..2063175
--- /dev/null
+++ b/media/mca/filterfw/native/core/shader_program.h
@@ -0,0 +1,553 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_CORE_SHADER_PROGRAM_H
+#define ANDROID_FILTERFW_CORE_SHADER_PROGRAM_H
+
+#include <vector>
+#include <map>
+#include <string>
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <EGL/egl.h>
+
+#include "core/gl_env.h"
+#include "core/value.h"
+
+namespace android {
+namespace filterfw {
+
+class GLFrame;
+class GLFrameBufferHandle;
+class GLTextureHandle;
+class Quad;
+class VertexFrame;
+
+typedef GLint ProgramVar;
+
+// A ShaderProgram is a Program object that holds a GLSL shader implementation.
+// It provides functionality for compiling, linking, and executing the shader.
+// On top of that, it provides access to the shaders source code, uniforms,
+// attributes, and other properties.
+// By default a ShaderProgram provides its own vertex shader. However, a custom
+// vertex shader may be passed and used instead.
+// When implementing a vertex shader, the following attribute names have special
+// meaning:
+//
+//  - a_position: The vertex position
+//  - a_texcoord: The texture cooridnates
+//
+// The shader program will bind these attributes to the correct values, if they
+// are present in the vertex shader source code.
+//
+// When implementing the fragment shader, the following variable names must be
+// defined:
+//
+//  - tex_sampler_<n>: The n'th input texture. For instance, use tex_sampler_0
+//                     for the first input texture. Must be a uniform sampler2D.
+//  - v_texcoord: The current texture coordinate.
+//
+// If more input textures are given than the shader can handle, this will result
+// in an error.
+//
+class ShaderProgram {
+  public:
+    // General Functionality ///////////////////////////////////////////////////
+    // Create a new shader program with the given fragment shader source code.
+    // A default vertex shader is used, which renders the input texture to a
+    // rectangular region of the output texture. You can modify the input and
+    // output regions by using the SetSourceRegion(...) and SetTargetRegion(...)
+    // (and related) functions below.
+    // This program will not be executable until you have compiled and linked
+    // it.
+    // Note, that the ShaderProgram does NOT take ownership of the GLEnv. The
+    // caller must make sure the GLEnv stays valid as long as the GLFrame is
+    // alive.
+    explicit ShaderProgram(GLEnv* gl_env, const std::string& fragment_shader);
+
+    // Create a new shader program with the given fragment and vertex shader
+    // source code. This program will not be executable until you have compiled
+    // and linked it.
+    // Note, that the ShaderProgram does NOT take ownership of the GLEnv. The
+    // caller must make sure the GLEnv stays valid as long as the GLFrame is
+    // alive.
+    ShaderProgram(GLEnv* gl_env,
+                  const std::string& vertex_shader,
+                  const std::string& fragment_shader);
+
+    // Destructor.
+    ~ShaderProgram();
+
+    // Process the given input frames and write the result to the output frame.
+    // Returns false if there was an error processing.
+    bool Process(const std::vector<const GLFrame*>& inputs, GLFrame* output);
+
+    // Same as above, but pass GL interfaces rather than frame objects. Use this
+    // only if you are not working on Frame objects, but rather directly on GL
+    // textures and FBOs.
+    bool Process(const std::vector<const GLTextureHandle*>& input,
+                 GLFrameBufferHandle* output);
+
+    // Compile and link the shader source code. Returns true if compilation
+    // and linkage was successful. Compilation and linking error messages are
+    // written to the error log.
+    bool CompileAndLink();
+
+    // Returns true if this Program has been compiled and linked successfully.
+    bool IsExecutable() const {
+      return program_ != 0;
+    }
+
+    // Returns true if the shader program variable is valid.
+    static bool IsVarValid(ProgramVar var);
+
+    // Special ShaderPrograms //////////////////////////////////////////////////
+    // A (compiled) shader program which assigns the sampled pixels from the
+    // input to the output. Note that transformations may be applied to achieve
+    // effects such as cropping, scaling or rotation.
+    // The caller takes ownership of the result!
+    static ShaderProgram* CreateIdentity(GLEnv* env);
+
+    // Geometry ////////////////////////////////////////////////////////////////
+    // These functions modify the source and target regions used during
+    // rasterization. Note, that these functions will ONLY take effect if
+    // the default vertex shader is used, or your custom vertex shader defines
+    // the a_position and a_texcoord attributes.
+
+    // Set the program to read from a subregion of the input frame, given by
+    // the origin (x, y) and dimensions (width, height). Values are considered
+    // normalized between 0.0 and 1.0. If this region exceeds the input frame
+    // dimensions the results are undefined.
+    void SetSourceRect(float x, float y, float width, float height) ;
+
+    // Set the program to read from a subregion of the input frame, given by
+    // the passed Quad. Values are considered normalized between 0.0 and 1.0.
+    // The Quad points are expected to be in the order top-left, top-right,
+    // bottom-left, bottom-right.
+    // If this region exceeds the input frame dimensions the results are
+    // undefined.
+    void SetSourceRegion(const Quad& quad);
+
+    // Set the program to write to a subregion of the output frame, given by
+    // the origin (x, y) and dimensions (width, height). Values are considered
+    // normalized between 0.0 and 1.0. If this region exceeds the output frame
+    // dimensions the image will be clipped.
+    void SetTargetRect(float x, float y, float width, float height);
+
+    // Set the program to write to a subregion of the output frame, given by
+    // the passed Quad. Values are considered normalized between 0.0 and 1.0.
+    // The Quad points are expected to be in the order top-left, top-right,
+    // bottom-left, bottom-right.
+    // If this region exceeds the output frame dimensions the image will be
+    // clipped.
+    void SetTargetRegion(const Quad& quad);
+
+    // Uniform Variable access /////////////////////////////////////////////////
+    // Note: In order to get and set uniforms, the program must have been
+    // successfully compiled and linked. Otherwise, the getters will return an
+    // invalid ProgramVar variable (check with IsVarValid()).
+    // When setting values, the value type must be match the type of the uniform
+    // in the shader. For instance, a vector of 3 elements cannot be assigned to
+    // a vec2. Similarly, an integer value cannot be assigned to a float value.
+    // Such a type mismatch will result in failure to set the value (which will
+    // remain untouched). Check the return value of the setters to determine
+    // success.
+
+    // Returns the maximum number of uniforms supported by this implementation.
+    static int MaxUniformCount();
+
+    // Returns a handle to the uniform with the given name, or invalid if no
+    // such uniform variable exists in the shader.
+    ProgramVar GetUniform(const std::string& name) const;
+
+    // Set the specified uniform value to the given integer value. Returns true
+    // if the assignment was successful.
+    bool SetUniformValue(ProgramVar var, int value);
+
+    // Set the specified uniform value to the given float value. Returns true
+    // if the assignment was successful.
+    bool SetUniformValue(ProgramVar var, float value);
+
+    // Set the specified uniform value to the given values. Returns true
+    // if the assignment was successful.
+    bool SetUniformValue(ProgramVar var, const int* values, int count);
+
+    // Set the specified uniform value to the given values. Returns true
+    // if the assignment was successful.
+    bool SetUniformValue(ProgramVar var, const float* values, int count);
+
+    // Set the specified uniform value to the given vector value. Returns true
+    // if the assignment was successful.
+    bool SetUniformValue(ProgramVar var, const std::vector<int>& values);
+
+    // Set the specified uniform value to the given vector value. Returns true
+    // if the assignment was successful.
+    bool SetUniformValue(ProgramVar var, const std::vector<float>& values);
+
+    // Generic variable setter, which in the case of GL programs always attempts
+    // to set the value of a uniform variable with the given name. Only values
+    // of type float, float array (or vector), and int are supported.
+    bool SetUniformValue(const std::string& name, const Value& value);
+
+    // Generic variable getter, which in the case of GL programs always attempts
+    // to get the value of a uniform variable with the given name.
+    Value GetUniformValue(const std::string& name);
+
+    // Returns the default name of the input texture uniform variable for the
+    // given input index.
+    static std::string InputTextureUniformName(int index);
+
+    // Attribute access ////////////////////////////////////////////////////////
+    // Note: In order to get and set attributes, the program must have been
+    // successfully compiled and linked. Otherwise, the getters will return an
+    // invalid ProgramVar variable (check with IsVarValid()). Constant attribute
+    // values must be floats. Attribute pointers must be associated with a
+    // specific type, which can be any of the following:
+    //   GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_FLOAT,
+    //   GL_FIXED, GL_HALF_FLOAT_OES.
+    // When storing vertex data, it is recommended to use VertexFrames when
+    // possible as these will be kept in GPU memory, and no copying of vertex
+    // attributes between system and GPU memory needs to take place.
+
+    // Returns the maximum number of attributes supported by this
+    // implementation.
+    static int MaxAttributeCount();
+
+    // Returns a handle to the attribute with the given name, or invalid if no
+    // such attribute exists in the vertex shader.
+    ProgramVar GetAttribute(const std::string& name) const;
+
+    // Set an attribute value that will be constant for each vertex. Returns
+    // true if the assignment was successful.
+    bool SetConstAttributeValue(ProgramVar var, float value);
+
+    // Set an attribute vector value that will be constant for each vertex.
+    // Returns true if the assignment was successful.
+    bool SetConstAttributeValue(ProgramVar var, const std::vector<float>& value);
+
+    // Set attribute values that differ across vertexes, using a VertexFrame.
+    // This is the recommended method of specifying vertex data, that does not
+    // change from iteration to iteration. The parameters are as follows:
+    //   var: The shader variable to bind the values to.
+    //   data: The vertex frame which holds the vertex data. This may be a
+    //         superset of the data required for this particular vertex. Use the
+    //         offset and stride to select the correct data portion.
+    //   type: The type of the data values. This may differ from the type of the
+    //         shader variables. See the normalize flag on how values are
+    //         converted.
+    //   components: The number of components per value. Valid values are 1-4.
+    //   stride: The delta of one element to the next in bytes.
+    //   offset: The offset of the first element.
+    //   normalize: True, if not float values should be normalized to the range
+    //              0-1, when converted to a float.
+    // Returns true, if the assignment was successful.
+    bool SetAttributeValues(ProgramVar var,
+                            const VertexFrame* data,
+                            GLenum type,
+                            int components,
+                            int stride,
+                            int offset,
+                            bool normalize);
+
+    // Set attribute values that differ across vertexes, using a data buffer.
+    // This is the recommended method of specifying vertex data, if your data
+    // changes often. Note that this data may need to be copied to GPU memory
+    // for each render pass. Please see above for a description of the
+    // parameters.
+    // Note: The data passed here MUST be valid until all executions of this
+    // Program instance have been completed!
+    bool SetAttributeValues(ProgramVar var,
+                            const uint8_t* data,
+                            GLenum type,
+                            int components,
+                            int stride,
+                            int offset,
+                            bool normalize);
+
+    // Convenience method for setting vertex values using a vector of floats.
+    // The components parameter specifies how many elements per variable should
+    // be assigned (The variable must be able to fit the number of components).
+    // It must be a value between 1 and 4.
+    // While this method is not as flexible as the methods above, this can be
+    // used when more advanced methods are not necessary. Note, that if your
+    // vertex data does not change, it is recommended to use a VertexFrame.
+    bool SetAttributeValues(ProgramVar var,
+                            const std::vector<float>& data,
+                            int components);
+
+    // Same as above, but using a float pointer instead of vector. Pass the
+    // total number of elements in total.
+    bool SetAttributeValues(ProgramVar var,
+                            const float* data,
+                            int total,
+                            int components);
+
+    // By default, rendering only uses the first 4 vertices. You should only
+    // adjust this value if you are providing your own vertex attributes with
+    // a count unequal to 4. Adjust this value before calling Process().
+    void SetVertexCount(int count);
+
+    // Returns the default name of the attribute used to hold the texture
+    // coordinates. Use this when you need to access the texture coordinate
+    // attribute of the shader's default vertex shader.
+    static const std::string& TexCoordAttributeName() {
+      static std::string s_texcoord("a_texcoord");
+      return s_texcoord;
+    }
+
+    // Returns the default name of the attribute used to hold the output
+    // coordinates. Use this when you need to access the output coordinate
+    // attribute of the shader's default vertex shader.
+    static const std::string& PositionAttributeName() {
+      static std::string s_position("a_position");
+      return s_position;
+    }
+
+    // Rendering ///////////////////////////////////////////////////////////////
+    // Set the draw mode, which can be any of GL_POINTS, GL_LINES,
+    // GL_LINE_STRIP, GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_STRIP,
+    // GL_TRIANGLE_FAN. The default is GL_TRIANGLE_STRIP.
+    // Warning: Do NOT change this if you are not specifying your own vertex
+    // data with SetAttributeValues(...).
+    void SetDrawMode(GLenum mode);
+
+    // If you are doing your own drawing you should call this before beginning
+    // to draw. This will activate the program, push all used attributes, and
+    // clear the frame if requested. You do not need to call this if you are
+    // not doing your own GL drawing!
+    bool BeginDraw();
+
+    // Render a single frame with the given input textures. You may override
+    // this, if you need custom rendering behavior. However, you must take
+    // care of the following things when overriding:
+    //   - Use the correct program (e.g. by calling UseProgram()).
+    //   - Bind the given textures
+    //   - Bind vertex attributes
+    //   - Draw
+    bool RenderFrame(const std::vector<GLuint>& textures,
+                     const std::vector<GLenum>& targets);
+
+    // Pass true to clear the output frame before rendering. The color used
+    // to clear is set in SetClearColor().
+    void SetClearsOutput(bool clears);
+
+    // Set the color used to clear the output frame before rendering. You
+    // must activate clearing by calling SetClearsOutput(true).
+    void SetClearColor(float red, float green, float blue, float alpha);
+
+    // Set the number of tiles to split rendering into. Higher tile numbers
+    // will affect performance negatively, but will allow other GPU threads
+    // to render more frequently. Defaults to 1, 1.
+    void SetTileCounts(int x_count, int y_count);
+
+    // Enable or Disable Blending
+    // Set to true to enable, false to disable.
+    void SetBlendEnabled(bool enable) {
+      blending_ = enable;
+    }
+
+    // Specify pixel arithmetic for blending
+    // The values of sfactor and dfactor can be:
+    //  GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA,
+    //  GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA,
+    //  GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA_SATURATE
+    // Default values for blending are set to:
+    //  sfactor = GL_SRC_ALPHA
+    //  dfactor = GL_ONE_MINUS_SRC_ALPHA
+    void SetBlendFunc(int sfactor, int dfactor) {
+      sfactor_ = sfactor;
+      dfactor_ = dfactor;
+    }
+
+    // Accessing the Compiled Program //////////////////////////////////////////
+    // Use the compiled and linked program for rendering. You should not need
+    // to call this, unless you are implementing your own rendering method.
+    bool UseProgram();
+
+    // Other Properties ////////////////////////////////////////////////////////
+    // Returns the maximum number of varyings supported by this implementation.
+    static int MaxVaryingCount();
+
+    // Returns the maximum number of texture units supported by this
+    // implementation.
+    static int MaxTextureUnits();
+
+    // Lower level functionality ///////////////////////////////////////////////
+    // Compile the shader with the given source. The shader_type must be either
+    // GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
+    static GLuint CompileShader(GLenum shader_type, const char* source);
+
+    // Link the compiled shader objects and return the resulting program.
+    static GLuint LinkProgram(GLuint* shaders, GLuint count);
+
+    // Returns the lowest texture unit that will be used to bind textures.
+    GLuint BaseTextureUnit() const {
+      return base_texture_unit_;
+    }
+
+    // Sets the lowest texture unit that will be used to bind textures. The
+    // default value is GL_TEXTURE0.
+    void SetBaseTextureUnit(GLuint texture_unit) {
+      base_texture_unit_ = texture_unit;
+    }
+
+  private:
+    // Structure to store vertex attribute data.
+    struct VertexAttrib {
+      bool          is_const;
+      int           index;
+      bool          normalized;
+      int           stride;
+      int           components;
+      int           offset;
+      GLenum        type;
+      GLuint        vbo;
+      const void*   values;
+      float*        owned_data;
+
+      VertexAttrib();
+    };
+    typedef std::map<ProgramVar, VertexAttrib> VertexAttribMap;
+
+    struct RGBAColor {
+      float red;
+      float green;
+      float blue;
+      float alpha;
+
+      RGBAColor() : red(0), green(0), blue(0), alpha(1) {
+      }
+    };
+
+    // Scans for all uniforms in the shader and creates index -> id map.
+    void ScanUniforms();
+
+    // Returns the index of the given uniform. The caller must make sure
+    // that the variable id passed is valid.
+    GLuint IndexOfUniform(ProgramVar var);
+
+    // Binds the given input textures.
+    bool BindInputTextures(const std::vector<GLuint>& textures,
+                           const std::vector<GLenum>& targets);
+
+    // Sets the default source and target coordinates.
+    void SetDefaultCoords();
+
+    // Pushes the specified coordinates to the shader attribute.
+    bool PushCoords(ProgramVar attr, float* coords);
+
+    // Pushes the source coordinates.
+    bool PushSourceCoords(float* coords);
+
+    // Pushes the target coordinates.
+    bool PushTargetCoords(float* coords);
+
+    // Performs (simple) GL drawing.
+    bool Draw();
+
+    // Performs tiled GL drawing.
+    bool DrawTiled();
+
+    // Yields to other GPU threads.
+    void Yield();
+
+    // Helper method to assert that the variable value passed has the correct
+    // total size.
+    static bool CheckValueCount(const std::string& var_type,
+                                const std::string& var_name,
+                                int expected_count,
+                                int components,
+                                int value_size);
+
+    // Helper method to assert that the variable value passed has a size, that
+    // is compatible with the type size (must be divisible).
+    static bool CheckValueMult(const std::string& var_type,
+                               const std::string& var_name,
+                               int components,
+                               int value_size);
+
+    // Checks that the variable is valid. Logs an error and returns false if
+    // not.
+    static bool CheckVarValid(ProgramVar var);
+
+    // Returns true if the uniform specified by var is an active uniform in the
+    // program.
+    bool CheckUniformValid(ProgramVar var);
+
+    // Store an attribute to use when rendering.
+    bool StoreAttribute(VertexAttrib attrib);
+
+    // Push all assigned attributes before rendering.
+    bool PushAttributes();
+
+    // Pop all assigned attributes after rendering.
+    bool PopAttributes();
+
+    // The shader source code
+    std::string fragment_shader_source_;
+    std::string vertex_shader_source_;
+
+    // The compiled shaders and linked program
+    GLuint fragment_shader_;
+    GLuint vertex_shader_;
+    GLuint program_;
+
+    // The GL environment this shader lives in.
+    GLEnv* gl_env_;
+
+    // The lowest texture unit this program will use
+    GLuint base_texture_unit_;
+
+    // The current source and target coordinates to render from/to.
+    float* source_coords_;
+    float* target_coords_;
+
+    // True, if the program has control over both source and target coordinates.
+    bool manage_coordinates_;
+
+    // The number of tiles to split rendering into.
+    int tile_x_count_;
+    int tile_y_count_;
+
+    // List of attribute data that we need to set before rendering
+    VertexAttribMap attrib_values_;
+
+    // The number of vertices to render
+    int vertex_count_;
+
+    // The draw mode used during rendering
+    GLenum draw_mode_;
+
+    // True, iff the output frame is cleared before rendering
+    bool clears_;
+
+    // The color used to clear the output frame.
+    RGBAColor clear_color_;
+
+    // Set to true to enable blending.
+    bool blending_;
+    int sfactor_;
+    int dfactor_;
+
+    // Map from uniform ids to indices
+    std::map<ProgramVar, GLuint> uniform_indices_;
+};
+
+} // namespace filterfw
+} // namespace android
+
+#endif  // ANDROID_FILTERFW_CORE_SHADER_PROGRAM_H
diff --git a/media/mca/filterfw/native/core/statistics.cpp b/media/mca/filterfw/native/core/statistics.cpp
new file mode 100644
index 0000000..6f7fee7
--- /dev/null
+++ b/media/mca/filterfw/native/core/statistics.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "core/statistics.h"
+
+#include <math.h>
+
+namespace android {
+namespace filterfw {
+
+IncrementalGaussian::IncrementalGaussian()
+    : n_(0),
+      sum_x_(0.0f),
+      sum_x2_(0.0f),
+      mean_(0.0f),
+      var_(0.0f),
+      exp_denom_(0.0f),
+      pdf_denom_(0.0f) {
+}
+
+void IncrementalGaussian::Add(float value) {
+  ++n_;
+  sum_x_ += value;
+  sum_x2_ += value * value;
+
+  mean_ = sum_x_ / n_;
+  var_ = sum_x2_ / n_ - mean_ * mean_;
+
+  exp_denom_ = 2.0f * var_;
+  pdf_denom_ = sqrtf(M_PI * exp_denom_);
+}
+
+float IncrementalGaussian::Std() const {
+  return sqrtf(var_);
+}
+
+float IncrementalGaussian::Pdf(float value) const {
+  if (var_ == 0.0f) { return n_ > 0 ? value == mean_ : 0.0f; }
+  const float diff = value - mean_;
+  return expf(-diff * diff / exp_denom_) / pdf_denom_;
+}
+
+} // namespace filterfw
+} // namespace android
diff --git a/media/mca/filterfw/native/core/statistics.h b/media/mca/filterfw/native/core/statistics.h
new file mode 100644
index 0000000..ce73b2b
--- /dev/null
+++ b/media/mca/filterfw/native/core/statistics.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_CORE_STATISTICS_H
+#define ANDROID_FILTERFW_CORE_STATISTICS_H
+
+namespace android {
+namespace filterfw {
+
+// An incrementally-constructed Normal distribution.
+class IncrementalGaussian {
+ public:
+  IncrementalGaussian();
+
+  void Add(float value);
+
+  float NumSamples() const { return n_; }
+  float Mean() const { return mean_; }
+  float Var() const { return var_; }
+  float Std() const;
+  float Pdf(float value) const;
+
+ private:
+  int n_;
+  float sum_x_;
+  float sum_x2_;
+  float mean_;
+  float var_;
+  float exp_denom_;
+  float pdf_denom_;
+};
+
+// Discrete-time implementation of a simple RC low-pass filter:
+// exponentially-weighted moving average.
+class RCFilter {
+ public:
+  explicit RCFilter(float gain)
+      : gain_(gain), n_(0), value_(0.0f) {}
+
+  void Add(float measurement) {
+    value_ = n_++ ? gain_ * measurement + (1.0f - gain_) * value_ : measurement;
+  }
+
+  void Reset() { n_ = 0; }
+
+  int NumMeasurements() const { return n_; }
+  float Output() const { return value_; }
+
+ private:
+  float gain_;
+  int n_;
+  float value_;
+};
+
+} // namespace filterfw
+} // namespace android
+
+#endif  // ANDROID_FILTERFW_CORE_STATISTICS_H
diff --git a/media/mca/filterfw/native/core/time_util.cpp b/media/mca/filterfw/native/core/time_util.cpp
new file mode 100644
index 0000000..c86c80d
--- /dev/null
+++ b/media/mca/filterfw/native/core/time_util.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "base/logging.h"
+#include "base/utilities.h"
+
+#include "core/time_util.h"
+
+#include <map>
+#include <string>
+#include <sys/time.h>
+
+namespace android {
+namespace filterfw {
+
+uint64_t getTimeUs() {
+    static long basesec;
+    struct timeval tv;
+    uint64_t nowtime;
+    gettimeofday(&tv, 0);
+    if (basesec == 0) {
+        basesec = tv.tv_sec;
+    }
+    nowtime = (uint64_t)(tv.tv_sec - basesec) * (uint64_t)1000000 +
+              (uint64_t)tv.tv_usec;
+    return nowtime;
+}
+
+const uint64_t NamedStopWatch::kDefaultLoggingPeriodInFrames = 100;
+
+NamedStopWatch::NamedStopWatch(const std::string& name)
+      : mName(name),
+        mLoggingPeriodInFrames(kDefaultLoggingPeriodInFrames),
+        mStartUSec(0),
+        mNumCalls(0),
+        mTotalUSec(0) {
+}
+
+void NamedStopWatch::Start() {
+    mStartUSec = getTimeUs();
+}
+
+void NamedStopWatch::Stop() {
+    if (!mStartUSec) {
+        return;
+    }
+    uint64_t stopUSec = getTimeUs();
+    if (stopUSec > mStartUSec) {
+        ++mNumCalls;
+        mTotalUSec += stopUSec - mStartUSec;
+        if (mNumCalls % mLoggingPeriodInFrames == 0) {
+            const float mSec = TotalUSec() * 1.0E-3f / NumCalls();
+            ALOGE("%s: %f ms", Name().c_str(), mSec);
+        }
+    }
+    mStartUSec = 0;
+}
+
+namespace {
+static NamedStopWatch* GetWatchForName(const string& watch_name) {
+    // TODO: this leaks the NamedStopWatch objects. Replace it with a
+    // singleton to avoid that and make it thread safe.
+    static map<string, NamedStopWatch*> watches;
+    NamedStopWatch* watch = FindPtrOrNull(watches, watch_name);
+    if (!watch) {
+        watch = new NamedStopWatch(watch_name);
+        watches[watch_name] = watch;
+    }
+    return watch;
+};
+}  // namespace
+
+ScopedTimer::ScopedTimer(const string& stop_watch_name) {
+    mWatch = GetWatchForName(stop_watch_name);
+    mWatch->Start();
+}
+
+} // namespace filterfw
+} // namespace android
diff --git a/media/mca/filterfw/native/core/time_util.h b/media/mca/filterfw/native/core/time_util.h
new file mode 100644
index 0000000..3cf2ec9
--- /dev/null
+++ b/media/mca/filterfw/native/core/time_util.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_CORE_TIME_UTIL_H
+#define ANDROID_FILTERFW_CORE_TIME_UTIL_H
+
+#include <string>
+#include <utils/RefBase.h>
+
+#define LOG_MFF_RUNNING_TIMES 0
+
+namespace android {
+namespace filterfw {
+
+uint64_t getTimeUs();
+
+class NamedStopWatch : public RefBase {
+  public:
+    static const uint64_t kDefaultLoggingPeriodInFrames;
+
+    explicit NamedStopWatch(const string& name);
+    void Start();
+    void Stop();
+
+    void SetName(const string& name) { mName = name; }
+    void SetLoggingPeriodInFrames(uint64_t numFrames) {
+        mLoggingPeriodInFrames = numFrames;
+    }
+
+    const string& Name() const { return mName; }
+    uint64_t NumCalls() const { return mNumCalls; }
+    uint64_t TotalUSec() const { return mTotalUSec; }
+
+  private:
+    string mName;
+    uint64_t mLoggingPeriodInFrames;
+    uint64_t mStartUSec;
+    uint64_t mNumCalls;
+    uint64_t mTotalUSec;
+};
+
+class ScopedTimer {
+  public:
+    explicit ScopedTimer(const string& stop_watch_name);
+    explicit ScopedTimer(NamedStopWatch* watch)
+        : mWatch(watch) { mWatch->Start(); }
+    ~ScopedTimer() { mWatch->Stop(); }
+
+  private:
+    NamedStopWatch* mWatch;
+};
+
+} // namespace filterfw
+} // namespace android
+
+#endif  // ANDROID_FILTERFW_CORE_TIME_UTIL_H
diff --git a/media/mca/filterfw/native/core/value.cpp b/media/mca/filterfw/native/core/value.cpp
new file mode 100644
index 0000000..04bf0ef
--- /dev/null
+++ b/media/mca/filterfw/native/core/value.cpp
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "value.h"
+
+#define NULL_VALUE_TYPE           0
+#define INT_VALUE_TYPE            1
+#define FLOAT_VALUE_TYPE          2
+#define STRING_VALUE_TYPE         3
+#define BUFFER_VALUE_TYPE         4
+#define MUTABLE_BUFFER_VALUE_TYPE 5
+#define INT_ARRAY_VALUE_TYPE      6
+#define FLOAT_ARRAY_VALUE_TYPE    7
+
+// Templated versions //////////////////////////////////////////////////////////////////////////////
+template<typename POD, int TYPEID>
+POD GetPODValue(Value value) {
+  return value.type == TYPEID ? *reinterpret_cast<POD*>(value.value) : POD();
+}
+
+template<typename PTR, int TYPEID>
+PTR GetPtrValue(Value value) {
+  return value.type == TYPEID ? reinterpret_cast<PTR>(value.value) : NULL;
+}
+
+template<typename POD, int TYPEID>
+Value MakePODValue(POD value) {
+  Value result;
+  result.type = TYPEID;
+  result.value = malloc(sizeof(POD));
+  result.count = 1;
+  *reinterpret_cast<POD*>(result.value) = value;
+  return result;
+}
+
+template<typename BASE, int TYPEID>
+Value MakePtrValue(const BASE* values, int count) {
+  Value result;
+  result.type = TYPEID;
+  result.value = malloc(sizeof(BASE) * count);
+  memcpy(result.value, values, sizeof(BASE) * count);
+  result.count = count;
+  return result;
+}
+
+template<typename POD, int TYPEID>
+int SetPODValue(Value* value, POD new_value) {
+  if (value->type == NULL_VALUE_TYPE) {
+    value->type = TYPEID;
+    value->value = malloc(sizeof(POD));
+    value->count = 1;
+  }
+  if (value->type == TYPEID) {
+    *reinterpret_cast<POD*>(value->value) = new_value;
+    return 1;
+  }
+  return 0;
+}
+
+template<typename BASE, int TYPEID>
+int SetPtrValue(Value* value, const BASE* new_values, int count) {
+  if (value->type == NULL_VALUE_TYPE) {
+    value->type = TYPEID;
+    value->value = malloc(sizeof(BASE) * count);
+    value->count = count;
+  }
+  if (value->type == TYPEID && value->count == count) {
+    memcpy(value->value, new_values, sizeof(BASE) * count);
+    return 1;
+  }
+  return 0;
+}
+
+// C Wrappers //////////////////////////////////////////////////////////////////////////////////////
+int GetIntValue(Value value) {
+  return GetPODValue<int, INT_VALUE_TYPE>(value);
+}
+
+float GetFloatValue(Value value) {
+  return GetPODValue<float, FLOAT_VALUE_TYPE>(value);
+}
+
+const char* GetStringValue(Value value) {
+  return GetPtrValue<const char*, STRING_VALUE_TYPE>(value);
+}
+
+const char* GetBufferValue(Value value) {
+  return (value.type == BUFFER_VALUE_TYPE || value.type == MUTABLE_BUFFER_VALUE_TYPE)
+    ? (const char*)value.value
+    : NULL;
+}
+
+char* GetMutableBufferValue(Value value) {
+  return GetPtrValue<char*, MUTABLE_BUFFER_VALUE_TYPE>(value);
+}
+
+int* GetIntArrayValue(Value value) {
+  return GetPtrValue<int*, INT_ARRAY_VALUE_TYPE>(value);
+}
+
+float* GetFloatArrayValue(Value value) {
+  return GetPtrValue<float*, FLOAT_ARRAY_VALUE_TYPE>(value);
+}
+
+int ValueIsNull(Value value) {
+  return value.type == NULL_VALUE_TYPE;
+}
+
+int ValueIsInt(Value value) {
+  return value.type == INT_VALUE_TYPE;
+}
+
+int ValueIsFloat(Value value) {
+  return value.type == FLOAT_VALUE_TYPE;
+}
+
+int ValueIsString(Value value) {
+  return value.type == STRING_VALUE_TYPE;
+}
+
+int ValueIsBuffer(Value value) {
+  return value.type == BUFFER_VALUE_TYPE || value.type == MUTABLE_BUFFER_VALUE_TYPE;
+}
+
+int ValueIsIntArray(Value value) {
+  return value.type == INT_ARRAY_VALUE_TYPE;
+}
+
+int ValueIsFloatArray(Value value) {
+  return value.type == FLOAT_ARRAY_VALUE_TYPE;
+}
+
+Value MakeNullValue() {
+  Value result;
+  result.type = NULL_VALUE_TYPE;
+  result.value = NULL;
+  result.count = 0;
+  return result;
+}
+
+Value MakeIntValue(int value) {
+  return MakePODValue<int, INT_VALUE_TYPE>(value);
+}
+
+Value MakeFloatValue(float value) {
+  return MakePODValue<float, FLOAT_VALUE_TYPE>(value);
+}
+
+Value MakeStringValue(const char* value) {
+  return MakePtrValue<char, STRING_VALUE_TYPE>(value, strlen(value) + 1);
+}
+
+Value MakeBufferValue(const char* buffer, int size) {
+  return MakePtrValue<char, BUFFER_VALUE_TYPE>(buffer, size);
+}
+
+Value MakeBufferValueNoCopy(const char* buffer, int size) {
+  Value result;
+  result.type = BUFFER_VALUE_TYPE;
+  result.value = (void*)buffer;
+  result.count = size;
+  return result;
+}
+
+Value MakeMutableBufferValue(const char* buffer, int size) {
+  return MakePtrValue<const char, MUTABLE_BUFFER_VALUE_TYPE>(buffer, size);
+}
+
+Value MakeMutableBufferValueNoCopy(char* buffer, int size) {
+  Value result;
+  result.type = MUTABLE_BUFFER_VALUE_TYPE;
+  result.value = (void*)buffer;
+  result.count = size;
+  return result;
+}
+
+Value MakeIntArrayValue(const int* values, int count) {
+  return MakePtrValue<int, INT_ARRAY_VALUE_TYPE>(values, count);
+}
+
+Value MakeFloatArrayValue(const float* values, int count) {
+  return MakePtrValue<float, FLOAT_ARRAY_VALUE_TYPE>(values, count);
+}
+
+int SetIntValue(Value* value, int new_value) {
+  return SetPODValue<int, INT_VALUE_TYPE>(value, new_value);
+}
+
+int SetFloatValue(Value* value, float new_value) {
+  return SetPODValue<float, FLOAT_VALUE_TYPE>(value, new_value);
+}
+
+int SetStringValue(Value* value, const char* new_value) {
+  return SetPtrValue<char, STRING_VALUE_TYPE>(value, new_value, strlen(new_value) + 1);
+}
+
+int SetMutableBufferValue(Value* value, const char* new_data, int size) {
+  return SetPtrValue<char, MUTABLE_BUFFER_VALUE_TYPE>(value, new_data, size);
+}
+
+int SetIntArrayValue(Value* value, const int* new_values, int count) {
+  return SetPtrValue<int, INT_ARRAY_VALUE_TYPE>(value, new_values, count);
+}
+
+int SetFloatArrayValue(Value* value, const float* new_values, int count) {
+  return SetPtrValue<float, FLOAT_ARRAY_VALUE_TYPE>(value, new_values, count);
+}
+
+int GetValueCount(Value value) {
+  return value.count;
+}
+
+void ReleaseValue(Value* value) {
+  if (value && value->value) {
+    free(value->value);
+    value->value = NULL;
+    value->type = NULL_VALUE_TYPE;
+  }
+}
+
diff --git a/media/mca/filterfw/native/core/value.h b/media/mca/filterfw/native/core/value.h
new file mode 100644
index 0000000..37e8800
--- /dev/null
+++ b/media/mca/filterfw/native/core/value.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_CORE_VALUE_H
+#define ANDROID_FILTERFW_CORE_VALUE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// TODO: As this is no longer part of the proposed NDK, should we make this object-oriented (C++)
+// instead? We can also probably clean this up a bit.
+
+// TODO: Change this to an opaque handle?
+typedef struct {
+  void* value;
+  int type;
+  int count;
+} Value;
+
+// TODO: Probably should make these const Value*?
+int GetIntValue(Value value);
+float GetFloatValue(Value value);
+const char* GetStringValue(Value value);
+const char* GetBufferValue(Value value);
+char* GetMutableBufferValue(Value value);
+int* GetIntArrayValue(Value value);
+float* GetFloatArrayValue(Value value);
+
+// TODO: Probably should make these const Value*?
+int ValueIsNull(Value value);
+int ValueIsInt(Value value);
+int ValueIsFloat(Value value);
+int ValueIsString(Value value);
+int ValueIsBuffer(Value value);
+int ValueIsMutableBuffer(Value value);
+int ValueIsIntArray(Value value);
+int ValueIsFloatArray(Value value);
+
+Value MakeNullValue();
+Value MakeIntValue(int value);
+Value MakeFloatValue(float value);
+Value MakeStringValue(const char* value);
+Value MakeBufferValue(const char* data, int size);
+Value MakeBufferValueNoCopy(const char* data, int size);
+Value MakeMutableBufferValue(const char* data, int size);
+Value MakeMutableBufferValueNoCopy(char* data, int size);
+Value MakeIntArrayValue(const int* values, int count);
+Value MakeFloatArrayValue(const float* values, int count);
+
+// Note: These only alloc if value is Null! Otherwise they overwrite, so data must fit!
+int SetIntValue(Value* value, int new_value);
+int SetFloatValue(Value* value, float new_value);
+int SetStringValue(Value* value, const char* new_value);
+int SetMutableBufferValue(Value* value, const char* new_data, int size);
+int SetIntArrayValue(Value* value, const int* new_values, int count);
+int SetFloatArrayValue(Value* value, const float* new_values, int count);
+
+int GetValueCount(Value value);
+
+void ReleaseValue(Value* value);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif  // ANDROID_FILTERFW_FILTER_VALUE_H
diff --git a/media/mca/filterfw/native/core/vertex_frame.cpp b/media/mca/filterfw/native/core/vertex_frame.cpp
new file mode 100644
index 0000000..822573f
--- /dev/null
+++ b/media/mca/filterfw/native/core/vertex_frame.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "base/logging.h"
+
+#include "core/gl_env.h"
+#include "core/vertex_frame.h"
+
+#include <GLES2/gl2ext.h>
+#include <EGL/egl.h>
+
+namespace android {
+namespace filterfw {
+
+// GL Extensions that are dynamically looked up at runtime
+static PFNGLMAPBUFFEROESPROC    GLMapBufferOES    = NULL;
+static PFNGLUNMAPBUFFEROESPROC  GLUnmapBufferOES  = NULL;
+
+VertexFrame::VertexFrame(int size)
+  : vbo_(0),
+    size_(size) {
+}
+
+VertexFrame::~VertexFrame() {
+  glDeleteBuffers(1, &vbo_);
+}
+
+bool VertexFrame::CreateBuffer() {
+  glGenBuffers(1, &vbo_);
+  return !GLEnv::CheckGLError("Generating VBO");
+}
+
+bool VertexFrame::WriteData(const uint8_t* data, int size) {
+  // Create buffer if not created already
+  const bool first_upload = !HasVBO();
+  if (first_upload && !CreateBuffer()) {
+    ALOGE("VertexFrame: Could not create vertex buffer!");
+    return false;
+  }
+
+  // Upload the data
+  glBindBuffer(GL_ARRAY_BUFFER, vbo_);
+  if (GLEnv::CheckGLError("VBO Bind Buffer"))
+    return false;
+
+  if (first_upload && size == size_)
+    glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW);
+  else if (!first_upload && size <= size_)
+    glBufferSubData(GL_ARRAY_BUFFER, 0, size, data);
+  else {
+    ALOGE("VertexFrame: Attempting to upload more data (%d bytes) than fits "
+         "inside the vertex frame (%d bytes)!", size, size_);
+    return false;
+  }
+
+  // Make sure it worked
+  if (GLEnv::CheckGLError("VBO Data Upload"))
+    return false;
+
+  // Subsequent uploads are now bound to the size given here
+  size_ = size;
+
+  return true;
+}
+
+int VertexFrame::Size() const {
+  return size_;
+}
+
+} // namespace filterfw
+} // namespace android
diff --git a/media/mca/filterfw/native/core/vertex_frame.h b/media/mca/filterfw/native/core/vertex_frame.h
new file mode 100644
index 0000000..1205096
--- /dev/null
+++ b/media/mca/filterfw/native/core/vertex_frame.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_CORE_VERTEXFRAME_H
+#define ANDROID_FILTERFW_CORE_VERTEXFRAME_H
+
+#include <GLES2/gl2.h>
+
+namespace android {
+namespace filterfw {
+
+// A VertexFrame stores vertex attribute data in a VBO. Unlike other frames,
+// you often create instances of VertexFrame yourself, to pass vertex data to
+// a ShaderProgram. Note, that any kind of reading from VertexFrames is NOT
+// supported. Once data is uploaded to a VertexFrame, it cannot be read from
+// again.
+class VertexFrame {
+  public:
+    // Create a VertexFrame of the specified size (in bytes).
+    explicit VertexFrame(int size);
+
+    ~VertexFrame();
+
+    // Upload the given data to the vertex buffer. The size must match the size
+    // passed in the constructor for the first upload. Subsequent uploads must
+    // be able to fit within the allocated space (i.e. size must not exceed the
+    // frame's size).
+    bool WriteData(const uint8_t* data, int size);
+
+    // The size of the vertex buffer in bytes.
+    int Size() const;
+
+    // Return the id of the internal VBO. Returns 0 if no VBO has been
+    // generated yet. The internal VBO is generated the first time data is
+    // uploaded.
+    GLuint GetVboId() const {
+      return vbo_;
+    }
+
+    // Returns true if the frame contains an allocated VBO.
+    bool HasBuffer() const {
+      return vbo_ != 0;
+    }
+
+  private:
+    // Create the VBO
+    bool CreateBuffer();
+
+    // Returns true if the VBO has been created.
+    bool HasVBO() const {
+      return vbo_ != 0;
+    }
+
+    // The internal VBO handle
+    GLuint vbo_;
+
+    // The size of this frame in bytes
+    int size_;
+};
+
+} // namespace filterfw
+} // namespace android
+
+#endif  // ANDROID_FILTERFW_CORE_VERTEXFRAME_H
diff --git a/media/mca/filterfw/native/libfilterfw.mk b/media/mca/filterfw/native/libfilterfw.mk
new file mode 100644
index 0000000..4e88e6f
--- /dev/null
+++ b/media/mca/filterfw/native/libfilterfw.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Add include paths for native code.
+FFW_PATH := $(call my-dir)
+
+# Uncomment the requirements below, once we need them:
+
+# STLport
+include external/stlport/libstlport.mk
+
+# Neven FaceDetect SDK
+#LOCAL_C_INCLUDES += external/neven/FaceRecEm/common/src/b_FDSDK \
+#	external/neven/FaceRecEm/common/src \
+#	external/neven/Embedded/common/conf \
+#	external/neven/Embedded/common/src \
+#	external/neven/unix/src
+
+# Finally, add this directory
+LOCAL_C_INCLUDES += $(FFW_PATH)
+
diff --git a/media/mca/filterpacks/Android.mk b/media/mca/filterpacks/Android.mk
new file mode 100644
index 0000000..6166b1e
--- /dev/null
+++ b/media/mca/filterpacks/Android.mk
@@ -0,0 +1,55 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+##
+# base
+##
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := libfilterpack_base
+LOCAL_SRC_FILES := native/base/geometry.cpp \
+                   native/base/time_util.cpp
+
+LOCAL_CFLAGS := -DANDROID
+
+include external/stlport/libstlport.mk
+
+include $(BUILD_STATIC_LIBRARY)
+
+##
+# filterpack_imageproc
+##
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := libfilterpack_imageproc
+
+LOCAL_SRC_FILES += native/imageproc/brightness.c \
+                   native/imageproc/contrast.c \
+                   native/imageproc/invert.c \
+                   native/imageproc/to_rgba.c
+
+LOCAL_SHARED_LIBRARIES := libutils libfilterfw
+
+LOCAL_PRELINK_MODULE := false
+
+include $(BUILD_SHARED_LIBRARY)
+
+
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/CallbackFilter.java b/media/mca/filterpacks/java/android/filterpacks/base/CallbackFilter.java
new file mode 100644
index 0000000..4185343
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/base/CallbackFilter.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.base;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GenerateFinalPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.lang.Runnable;
+
+/**
+ * @hide
+ */
+public class CallbackFilter extends Filter {
+
+    @GenerateFieldPort(name = "listener", hasDefault = true)
+    private FilterContext.OnFrameReceivedListener mListener;
+
+    @GenerateFieldPort(name = "userData", hasDefault = true)
+    private Object mUserData;
+
+    @GenerateFinalPort(name = "callUiThread", hasDefault = true)
+    private boolean mCallbacksOnUiThread = true;
+
+    private Handler mUiThreadHandler;
+
+    private class CallbackRunnable implements Runnable {
+        private Filter mFilter;
+        private Frame mFrame;
+        private Object mUserData;
+        private FilterContext.OnFrameReceivedListener mListener;
+
+        public CallbackRunnable(FilterContext.OnFrameReceivedListener listener, Filter filter, Frame frame, Object userData) {
+            mListener = listener;
+            mFilter = filter;
+            mFrame = frame;
+            mUserData = userData;
+        }
+
+        public void run() {
+            mListener.onFrameReceived(mFilter, mFrame, mUserData);
+            mFrame.release();
+        }
+    }
+
+    public CallbackFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addInputPort("frame");
+    }
+
+    public void prepare(FilterContext context) {
+        if (mCallbacksOnUiThread) {
+            mUiThreadHandler = new Handler(Looper.getMainLooper());
+        }
+    }
+
+    public void process(FilterContext context) {
+        // Get frame and forward to listener
+        final Frame input = pullInput("frame");
+        if (mListener != null) {
+            if (mCallbacksOnUiThread) {
+                input.retain();
+                CallbackRunnable uiRunnable = new CallbackRunnable(mListener, this, input, mUserData);
+                if (!mUiThreadHandler.post(uiRunnable)) {
+                    throw new RuntimeException("Unable to send callback to UI thread!");
+                }
+            } else {
+                mListener.onFrameReceived(this, input, mUserData);
+            }
+        } else {
+            throw new RuntimeException("CallbackFilter received frame, but no listener set!");
+        }
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/FrameBranch.java b/media/mca/filterpacks/java/android/filterpacks/base/FrameBranch.java
new file mode 100644
index 0000000..6b8cbc7
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/base/FrameBranch.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.base;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFinalPort;
+import android.filterfw.core.KeyValueMap;
+
+/**
+ * @hide
+ */
+public class FrameBranch extends Filter {
+
+    @GenerateFinalPort(name = "outputs", hasDefault = true)
+    private int mNumberOfOutputs = 2;
+
+    public FrameBranch(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addInputPort("in");
+        for (int i = 0; i < mNumberOfOutputs; ++i) {
+            addOutputBasedOnInput("out" + i, "in");
+        }
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("in");
+
+        // Push output
+        for (int i = 0; i < mNumberOfOutputs; ++i) {
+            pushOutput("out" + i, input);
+        }
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/FrameFetch.java b/media/mca/filterpacks/java/android/filterpacks/base/FrameFetch.java
new file mode 100644
index 0000000..518b837
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/base/FrameFetch.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.base;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GenerateFinalPort;
+
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class FrameFetch extends Filter {
+
+    @GenerateFinalPort(name = "format", hasDefault = true)
+    private FrameFormat mFormat;
+
+    @GenerateFieldPort(name = "key")
+    private String mKey;
+
+    @GenerateFieldPort(name = "repeatFrame", hasDefault = true)
+    private boolean mRepeatFrame = false;
+
+    public FrameFetch(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addOutputPort("frame", mFormat == null ? FrameFormat.unspecified() : mFormat);
+    }
+
+    public void process(FilterContext context) {
+        Frame output = context.fetchFrame(mKey);
+        if (output != null) {
+            pushOutput("frame", output);
+            if (!mRepeatFrame) {
+                closeOutputPort("frame");
+            }
+        } else {
+            delayNextProcess(250);
+        }
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/FrameSource.java b/media/mca/filterpacks/java/android/filterpacks/base/FrameSource.java
new file mode 100644
index 0000000..1218d1a
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/base/FrameSource.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.base;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GenerateFinalPort;
+
+/**
+ * @hide
+ */
+public class FrameSource extends Filter {
+
+    @GenerateFinalPort(name = "format")
+    private FrameFormat mFormat;
+
+    @GenerateFieldPort(name = "frame", hasDefault = true)
+    private Frame mFrame = null;
+
+    @GenerateFieldPort(name = "repeatFrame", hasDefault = true)
+    private boolean mRepeatFrame = false;
+
+    public FrameSource(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addOutputPort("frame", mFormat);
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        if (mFrame != null) {
+            // Push output
+            pushOutput("frame", mFrame);
+        }
+
+        if (!mRepeatFrame) {
+            // Close output port as we are done here
+            closeOutputPort("frame");
+        }
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/FrameStore.java b/media/mca/filterpacks/java/android/filterpacks/base/FrameStore.java
new file mode 100644
index 0000000..3aadaac
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/base/FrameStore.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.base;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+
+/**
+ * @hide
+ */
+public class FrameStore extends Filter {
+
+    @GenerateFieldPort(name = "key")
+    private String mKey;
+
+    public FrameStore(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addInputPort("frame");
+    }
+
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("frame");
+
+        // Store frame
+        context.storeFrame(mKey, input);
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/GLTextureSource.java b/media/mca/filterpacks/java/android/filterpacks/base/GLTextureSource.java
new file mode 100644
index 0000000..1776820
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/base/GLTextureSource.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.base;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.format.ImageFormat;
+
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class GLTextureSource extends Filter {
+
+    @GenerateFieldPort(name = "texId")
+    private int mTexId;
+
+    @GenerateFieldPort(name = "width")
+    private int mWidth;
+
+    @GenerateFieldPort(name = "height")
+    private int mHeight;
+
+    @GenerateFieldPort(name = "repeatFrame", hasDefault = true)
+    private boolean mRepeatFrame = false;
+
+    /* This timestamp will be used for all output frames from this source.  They
+     * represent nanoseconds, and should be positive and monotonically
+     * increasing.  Set to Frame.TIMESTAMP_UNKNOWN if timestamps are not
+     * meaningful for these textures.
+     */
+    @GenerateFieldPort(name = "timestamp", hasDefault = true)
+    private long mTimestamp = Frame.TIMESTAMP_UNKNOWN;
+
+    private Frame mFrame;
+
+    public GLTextureSource(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addOutputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
+                                                  FrameFormat.TARGET_GPU));
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        // Release frame, so that it is recreated during the next process call
+        if (mFrame != null) {
+            mFrame.release();
+            mFrame = null;
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Generate frame if not generated already
+        if (mFrame == null) {
+            FrameFormat outputFormat = ImageFormat.create(mWidth, mHeight,
+                                                          ImageFormat.COLORSPACE_RGBA,
+                                                          FrameFormat.TARGET_GPU);
+            mFrame = context.getFrameManager().newBoundFrame(outputFormat,
+                                                             GLFrame.EXISTING_TEXTURE_BINDING,
+                                                             mTexId);
+            mFrame.setTimestamp(mTimestamp);
+        }
+
+        // Push output
+        pushOutput("frame", mFrame);
+
+        if (!mRepeatFrame) {
+            // Close output port as we are done here
+            closeOutputPort("frame");
+        }
+    }
+
+    @Override
+    public void tearDown(FilterContext context) {
+        if (mFrame != null) {
+            mFrame.release();
+        }
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/GLTextureTarget.java b/media/mca/filterpacks/java/android/filterpacks/base/GLTextureTarget.java
new file mode 100644
index 0000000..b2285cd
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/base/GLTextureTarget.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.base;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.format.ImageFormat;
+
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class GLTextureTarget extends Filter {
+
+    @GenerateFieldPort(name = "texId")
+    private int mTexId;
+
+    public GLTextureTarget(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("frame");
+
+        FrameFormat format = ImageFormat.create(input.getFormat().getWidth(),
+                                                input.getFormat().getHeight(),
+                                                ImageFormat.COLORSPACE_RGBA,
+                                                FrameFormat.TARGET_GPU);
+
+        Frame frame = context.getFrameManager().newBoundFrame(format, GLFrame.EXISTING_TEXTURE_BINDING, mTexId);
+
+        // Copy to our texture frame
+        frame.setDataFromFrame(input);
+        frame.release();
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/InputStreamSource.java b/media/mca/filterpacks/java/android/filterpacks/base/InputStreamSource.java
new file mode 100644
index 0000000..6c22ee7
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/base/InputStreamSource.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.base;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GenerateFinalPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.format.PrimitiveFormat;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * @hide
+ */
+public class InputStreamSource extends Filter {
+
+    @GenerateFinalPort(name = "target")
+    private String mTarget;
+
+    @GenerateFieldPort(name = "stream")
+    private InputStream mInputStream;
+
+    @GenerateFinalPort(name = "format", hasDefault = true)
+    private MutableFrameFormat mOutputFormat = null;
+
+    public InputStreamSource(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        int target = FrameFormat.readTargetString(mTarget);
+        if (mOutputFormat == null) {
+            mOutputFormat = PrimitiveFormat.createByteFormat(target);
+        }
+        addOutputPort("data", mOutputFormat);
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        int fileSize = 0;
+        ByteBuffer byteBuffer = null;
+
+        // Read the file
+        try {
+            ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+            byte[] buffer = new byte[1024];
+            int bytesRead;
+            while ((bytesRead = mInputStream.read(buffer)) > 0) {
+                byteStream.write(buffer, 0, bytesRead);
+                fileSize += bytesRead;
+            }
+            byteBuffer = ByteBuffer.wrap(byteStream.toByteArray());
+        } catch (IOException exception) {
+            throw new RuntimeException(
+                "InputStreamSource: Could not read stream: " + exception.getMessage() + "!");
+        }
+
+        // Put it into a frame
+        mOutputFormat.setDimensions(fileSize);
+        Frame output = context.getFrameManager().newFrame(mOutputFormat);
+        output.setData(byteBuffer);
+
+        // Push output
+        pushOutput("data", output);
+
+        // Release pushed frame
+        output.release();
+
+        // Close output port as we are done here
+        closeOutputPort("data");
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/NullFilter.java b/media/mca/filterpacks/java/android/filterpacks/base/NullFilter.java
new file mode 100644
index 0000000..f3e08e4
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/base/NullFilter.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.base;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+
+/**
+ * @hide
+ */
+public class NullFilter extends Filter {
+
+    public NullFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addInputPort("frame");
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        pullInput("frame");
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/ObjectSource.java b/media/mca/filterpacks/java/android/filterpacks/base/ObjectSource.java
new file mode 100644
index 0000000..d511e44
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/base/ObjectSource.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.base;
+
+import java.util.Set;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GenerateFinalPort;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.format.ObjectFormat;
+
+/**
+ * @hide
+ */
+public class ObjectSource extends Filter {
+
+    @GenerateFieldPort(name = "object")
+    private Object mObject;
+
+    @GenerateFinalPort(name = "format", hasDefault = true)
+    private FrameFormat mOutputFormat = FrameFormat.unspecified();
+
+    @GenerateFieldPort(name = "repeatFrame", hasDefault = true)
+    boolean mRepeatFrame = false;
+
+    private Frame mFrame;
+
+    public ObjectSource(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addOutputPort("frame", mOutputFormat);
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // If no frame has been created, create one now.
+        if (mFrame == null) {
+            if (mObject == null) {
+                throw new NullPointerException("ObjectSource producing frame with no object set!");
+            }
+            FrameFormat outputFormat = ObjectFormat.fromObject(mObject, FrameFormat.TARGET_SIMPLE);
+            mFrame = context.getFrameManager().newFrame(outputFormat);
+            mFrame.setObjectValue(mObject);
+            mFrame.setTimestamp(Frame.TIMESTAMP_UNKNOWN);
+        }
+
+        // Push output
+        pushOutput("frame", mFrame);
+
+        // Wait for free output
+        if (!mRepeatFrame) {
+            closeOutputPort("frame");
+        }
+    }
+
+    @Override
+    public void tearDown(FilterContext context) {
+        mFrame.release();
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        // Release our internal frame, so that it is regenerated on the next call to process().
+        if (name.equals("object")) {
+            if (mFrame != null) {
+                mFrame.release();
+                mFrame = null;
+            }
+        }
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/OutputStreamTarget.java b/media/mca/filterpacks/java/android/filterpacks/base/OutputStreamTarget.java
new file mode 100644
index 0000000..3d3d0f1
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/base/OutputStreamTarget.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.base;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+
+import java.io.OutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * @hide
+ */
+public class OutputStreamTarget extends Filter {
+
+    @GenerateFieldPort(name = "stream")
+    private OutputStream mOutputStream;
+
+    public OutputStreamTarget(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addInputPort("data");
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        Frame input = pullInput("data");
+        ByteBuffer data;
+
+        if (input.getFormat().getObjectClass() == String.class) {
+            String stringVal = (String)input.getObjectValue();
+            data = ByteBuffer.wrap(stringVal.getBytes());
+        } else {
+            data = input.getData();
+        }
+        try {
+            mOutputStream.write(data.array(), 0, data.limit());
+            mOutputStream.flush();
+        } catch (IOException exception) {
+            throw new RuntimeException(
+                "OutputStreamTarget: Could not write to stream: " + exception.getMessage() + "!");
+        }
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/base/RetargetFilter.java b/media/mca/filterpacks/java/android/filterpacks/base/RetargetFilter.java
new file mode 100644
index 0000000..254167a
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/base/RetargetFilter.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.base;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GenerateFinalPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+
+/**
+ * @hide
+ */
+public class RetargetFilter extends Filter {
+
+    @GenerateFinalPort(name = "target", hasDefault = false)
+    private String mTargetString;
+
+    private MutableFrameFormat mOutputFormat;
+    private int mTarget = -1;
+
+    public RetargetFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        // Setup target
+        mTarget = FrameFormat.readTargetString(mTargetString);
+
+        // Add ports
+        addInputPort("frame");
+        addOutputBasedOnInput("frame", "frame");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        MutableFrameFormat retargeted = inputFormat.mutableCopy();
+        retargeted.setTarget(mTarget);
+        return retargeted;
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("frame");
+
+        // Create output frame
+        Frame output = context.getFrameManager().duplicateFrameToTarget(input, mTarget);
+
+        // Push output
+        pushOutput("frame", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/AlphaBlendFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/AlphaBlendFilter.java
new file mode 100644
index 0000000..473369c
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/AlphaBlendFilter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class AlphaBlendFilter extends ImageCombineFilter {
+
+    private final String mAlphaBlendShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform sampler2D tex_sampler_1;\n" +
+            "uniform sampler2D tex_sampler_2;\n" +
+            "uniform float weight;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 colorL = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  vec4 colorR = texture2D(tex_sampler_1, v_texcoord);\n" +
+            "  float blend = texture2D(tex_sampler_2, v_texcoord).r * weight;\n" +
+            "  gl_FragColor = colorL * (1.0 - blend) + colorR * blend;\n" +
+            "}\n";
+
+    public AlphaBlendFilter(String name) {
+        super(name, new String[] { "source", "overlay", "mask" }, "blended", "weight");
+    }
+
+    @Override
+    protected Program getNativeProgram(FilterContext context) {
+        throw new RuntimeException("TODO: Write native implementation for AlphaBlend!");
+    }
+
+    @Override
+    protected Program getShaderProgram(FilterContext context) {
+        return new ShaderProgram(context, mAlphaBlendShader);
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/AutoFixFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/AutoFixFilter.java
new file mode 100644
index 0000000..c71c1c94
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/AutoFixFilter.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import android.util.Log;
+
+public class AutoFixFilter extends Filter {
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    @GenerateFieldPort(name = "scale")
+    private float mScale;
+
+    private static final int normal_cdf[] = {
+        9, 33, 50, 64, 75, 84, 92, 99, 106, 112, 117, 122, 126, 130, 134, 138, 142,
+        145, 148, 150, 154, 157, 159, 162, 164, 166, 169, 170, 173, 175, 177, 179,
+        180, 182, 184, 186, 188, 189, 190, 192, 194, 195, 197, 198, 199, 200, 202,
+        203, 205, 206, 207, 208, 209, 210, 212, 213, 214, 215, 216, 217, 218, 219,
+        220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 229, 230, 231, 232, 233,
+        234, 235, 236, 236, 237, 238, 239, 239, 240, 240, 242, 242, 243, 244, 245,
+        245, 246, 247, 247, 248, 249, 249, 250, 250, 251, 252, 253, 253, 254, 255,
+        255, 256, 256, 257, 258, 258, 259, 259, 259, 260, 261, 262, 262, 263, 263,
+        264, 264, 265, 265, 266, 267, 267, 268, 268, 269, 269, 269, 270, 270, 271,
+        272, 272, 273, 273, 274, 274, 275, 275, 276, 276, 277, 277, 277, 278, 278,
+        279, 279, 279, 280, 280, 281, 282, 282, 282, 283, 283, 284, 284, 285, 285,
+        285, 286, 286, 287, 287, 288, 288, 288, 289, 289, 289, 290, 290, 290, 291,
+        292, 292, 292, 293, 293, 294, 294, 294, 295, 295, 296, 296, 296, 297, 297,
+        297, 298, 298, 298, 299, 299, 299, 299, 300, 300, 301, 301, 302, 302, 302,
+        303, 303, 304, 304, 304, 305, 305, 305, 306, 306, 306, 307, 307, 307, 308,
+        308, 308, 309, 309, 309, 309, 310, 310, 310, 310, 311, 312, 312, 312, 313,
+        313, 313, 314, 314, 314, 315, 315, 315, 315, 316, 316, 316, 317, 317, 317,
+        318, 318, 318, 319, 319, 319, 319, 319, 320, 320, 320, 321, 321, 322, 322,
+        322, 323, 323, 323, 323, 324, 324, 324, 325, 325, 325, 325, 326, 326, 326,
+        327, 327, 327, 327, 328, 328, 328, 329, 329, 329, 329, 329, 330, 330, 330,
+        330, 331, 331, 332, 332, 332, 333, 333, 333, 333, 334, 334, 334, 334, 335,
+        335, 335, 336, 336, 336, 336, 337, 337, 337, 337, 338, 338, 338, 339, 339,
+        339, 339, 339, 339, 340, 340, 340, 340, 341, 341, 342, 342, 342, 342, 343,
+        343, 343, 344, 344, 344, 344, 345, 345, 345, 345, 346, 346, 346, 346, 347,
+        347, 347, 347, 348, 348, 348, 348, 349, 349, 349, 349, 349, 349, 350, 350,
+        350, 350, 351, 351, 352, 352, 352, 352, 353, 353, 353, 353, 354, 354, 354,
+        354, 355, 355, 355, 355, 356, 356, 356, 356, 357, 357, 357, 357, 358, 358,
+        358, 358, 359, 359, 359, 359, 359, 359, 359, 360, 360, 360, 360, 361, 361,
+        362, 362, 362, 362, 363, 363, 363, 363, 364, 364, 364, 364, 365, 365, 365,
+        365, 366, 366, 366, 366, 366, 367, 367, 367, 367, 368, 368, 368, 368, 369,
+        369, 369, 369, 369, 369, 370, 370, 370, 370, 370, 371, 371, 372, 372, 372,
+        372, 373, 373, 373, 373, 374, 374, 374, 374, 374, 375, 375, 375, 375, 376,
+        376, 376, 376, 377, 377, 377, 377, 378, 378, 378, 378, 378, 379, 379, 379,
+        379, 379, 379, 380, 380, 380, 380, 381, 381, 381, 382, 382, 382, 382, 383,
+        383, 383, 383, 384, 384, 384, 384, 385, 385, 385, 385, 385, 386, 386, 386,
+        386, 387, 387, 387, 387, 388, 388, 388, 388, 388, 389, 389, 389, 389, 389,
+        389, 390, 390, 390, 390, 391, 391, 392, 392, 392, 392, 392, 393, 393, 393,
+        393, 394, 394, 394, 394, 395, 395, 395, 395, 396, 396, 396, 396, 396, 397,
+        397, 397, 397, 398, 398, 398, 398, 399, 399, 399, 399, 399, 399, 400, 400,
+        400, 400, 400, 401, 401, 402, 402, 402, 402, 403, 403, 403, 403, 404, 404,
+        404, 404, 405, 405, 405, 405, 406, 406, 406, 406, 406, 407, 407, 407, 407,
+        408, 408, 408, 408, 409, 409, 409, 409, 409, 409, 410, 410, 410, 410, 411,
+        411, 412, 412, 412, 412, 413, 413, 413, 413, 414, 414, 414, 414, 415, 415,
+        415, 415, 416, 416, 416, 416, 417, 417, 417, 417, 418, 418, 418, 418, 419,
+        419, 419, 419, 419, 419, 420, 420, 420, 420, 421, 421, 422, 422, 422, 422,
+        423, 423, 423, 423, 424, 424, 424, 425, 425, 425, 425, 426, 426, 426, 426,
+        427, 427, 427, 427, 428, 428, 428, 429, 429, 429, 429, 429, 429, 430, 430,
+        430, 430, 431, 431, 432, 432, 432, 433, 433, 433, 433, 434, 434, 434, 435,
+        435, 435, 435, 436, 436, 436, 436, 437, 437, 437, 438, 438, 438, 438, 439,
+        439, 439, 439, 439, 440, 440, 440, 441, 441, 442, 442, 442, 443, 443, 443,
+        443, 444, 444, 444, 445, 445, 445, 446, 446, 446, 446, 447, 447, 447, 448,
+        448, 448, 449, 449, 449, 449, 449, 450, 450, 450, 451, 451, 452, 452, 452,
+        453, 453, 453, 454, 454, 454, 455, 455, 455, 456, 456, 456, 457, 457, 457,
+        458, 458, 458, 459, 459, 459, 459, 460, 460, 460, 461, 461, 462, 462, 462,
+        463, 463, 463, 464, 464, 465, 465, 465, 466, 466, 466, 467, 467, 467, 468,
+        468, 469, 469, 469, 469, 470, 470, 470, 471, 472, 472, 472, 473, 473, 474,
+        474, 474, 475, 475, 476, 476, 476, 477, 477, 478, 478, 478, 479, 479, 479,
+        480, 480, 480, 481, 482, 482, 483, 483, 484, 484, 484, 485, 485, 486, 486,
+        487, 487, 488, 488, 488, 489, 489, 489, 490, 490, 491, 492, 492, 493, 493,
+        494, 494, 495, 495, 496, 496, 497, 497, 498, 498, 499, 499, 499, 500, 501,
+        502, 502, 503, 503, 504, 504, 505, 505, 506, 507, 507, 508, 508, 509, 509,
+        510, 510, 511, 512, 513, 513, 514, 515, 515, 516, 517, 517, 518, 519, 519,
+        519, 520, 521, 522, 523, 524, 524, 525, 526, 526, 527, 528, 529, 529, 530,
+        531, 532, 533, 534, 535, 535, 536, 537, 538, 539, 539, 540, 542, 543, 544,
+        545, 546, 547, 548, 549, 549, 550, 552, 553, 554, 555, 556, 558, 559, 559,
+        561, 562, 564, 565, 566, 568, 569, 570, 572, 574, 575, 577, 578, 579, 582,
+        583, 585, 587, 589, 590, 593, 595, 597, 599, 602, 604, 607, 609, 612, 615,
+        618, 620, 624, 628, 631, 635, 639, 644, 649, 654, 659, 666, 673, 680, 690,
+        700, 714 };
+
+    private final String mAutoFixShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform sampler2D tex_sampler_1;\n" +
+            "uniform sampler2D tex_sampler_2;\n" +
+            "uniform float scale;\n" +
+            "uniform float shift_scale;\n" +
+            "uniform float hist_offset;\n" +
+            "uniform float hist_scale;\n" +
+            "uniform float density_offset;\n" +
+            "uniform float density_scale;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  const vec3 weights = vec3(0.33333, 0.33333, 0.33333);\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  float energy = dot(color.rgb, weights);\n" +
+            "  float mask_value = energy - 0.5;\n" +
+            "  float alpha;\n" +
+            "  if (mask_value > 0.0) {\n" +
+            "    alpha = (pow(2.0 * mask_value, 1.5) - 1.0) * scale + 1.0;\n" +
+            "  } else { \n" +
+            "    alpha = (pow(2.0 * mask_value, 2.0) - 1.0) * scale + 1.0;\n" +
+            "  }\n" +
+            "  float index = energy * hist_scale + hist_offset;\n" +
+            "  vec4 temp = texture2D(tex_sampler_1, vec2(index, 0.5));\n" +
+            "  float value = temp.g + temp.r * shift_scale;\n" +
+            "  index = value * density_scale + density_offset;\n" +
+            "  temp = texture2D(tex_sampler_2, vec2(index, 0.5));\n" +
+            "  value = temp.g + temp.r * shift_scale;\n" +
+            "  float dst_energy = energy * alpha + value * (1.0 - alpha);\n" +
+            "  float max_energy = energy / max(color.r, max(color.g, color.b));\n" +
+            "  if (dst_energy > max_energy) {\n" +
+            "    dst_energy = max_energy;\n" +
+            "  }\n" +
+            "  if (energy == 0.0) {\n" +
+            "    gl_FragColor = color;\n" +
+            "  } else {\n" +
+            "    gl_FragColor = vec4(color.rgb * dst_energy / energy, color.a);\n" +
+            "  }\n" +
+            "}\n";
+
+    private Program mShaderProgram;
+    private Program mNativeProgram;
+
+    private int mWidth = 0;
+    private int mHeight = 0;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private Frame mHistFrame;
+    private Frame mDensityFrame;
+
+    public AutoFixFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mAutoFixShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mShaderProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    private void initParameters() {
+        mShaderProgram.setHostValue("shift_scale", 1.0f / 256f);
+        mShaderProgram.setHostValue("hist_offset", 0.5f / 766f);
+        mShaderProgram.setHostValue("hist_scale", 765f / 766f);
+        mShaderProgram.setHostValue("density_offset", 0.5f / 1024f);
+        mShaderProgram.setHostValue("density_scale", 1023f / 1024f);
+        mShaderProgram.setHostValue("scale", mScale);
+    }
+
+    @Override
+    protected void prepare(FilterContext context) {
+        int densityDim = 1024;
+        int histDim = 255 * 3 + 1;
+        long precision = (256l * 256l - 1l);
+
+        int[] densityTable = new int[densityDim];
+        for (int i = 0; i < densityDim; ++i) {
+            long temp = normal_cdf[i] * precision / histDim;
+            densityTable[i] = (int) temp;
+        }
+
+        FrameFormat densityFormat = ImageFormat.create(densityDim, 1,
+                                                       ImageFormat.COLORSPACE_RGBA,
+                                                       FrameFormat.TARGET_GPU);
+        mDensityFrame = context.getFrameManager().newFrame(densityFormat);
+        mDensityFrame.setInts(densityTable);
+    }
+
+    @Override
+    public void tearDown(FilterContext context) {
+        if (mDensityFrame != null) {
+            mDensityFrame.release();
+            mDensityFrame = null;
+        }
+
+        if (mHistFrame != null) {
+            mHistFrame.release();
+            mHistFrame = null;
+        }
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mShaderProgram != null) {
+            mShaderProgram.setHostValue("scale", mScale);
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create program if not created already
+        if (mShaderProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+            initParameters();
+        }
+
+        // Check if the frame size has changed
+        if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) {
+            mWidth = inputFormat.getWidth();
+            mHeight = inputFormat.getHeight();
+            createHistogramFrame(context, mWidth, mHeight, input.getInts());
+        }
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Process
+        Frame[] inputs = {input, mHistFrame, mDensityFrame};
+        mShaderProgram.process(inputs, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    private void createHistogramFrame(FilterContext context, int width, int height, int[] data) {
+        int histDims = 255 * 3 + 1;
+        int[] histArray = new int[histDims];
+
+        float border_thickness_ratio = 0.05f;
+        int y_border_thickness = (int) (height * border_thickness_ratio);
+        int x_border_thickness = (int) (width * border_thickness_ratio);
+        int pixels = (width - 2 * x_border_thickness) * (height - 2 * y_border_thickness);
+
+        float count = 0f;
+        for (int y = y_border_thickness; y < height - y_border_thickness; ++y) {
+            for (int x = x_border_thickness; x < width - x_border_thickness; ++x) {
+                int index = y * width + x;
+                int energy = (data[index] & 0xFF) + ((data[index] >> 8) & 0xFF) +
+                    ((data[index] >> 16) & 0xFF);
+                histArray[energy] ++;
+            }
+        }
+
+        for (int i = 1; i < histDims; i++) {
+            histArray[i] += histArray[i-1];
+        }
+
+        for (int i = 0; i < histDims; i++) {
+            long temp = (256 * 256 - 1l) * histArray[i] / pixels;
+            histArray[i] =  (int) temp;
+        }
+
+        FrameFormat shaderHistFormat = ImageFormat.create(histDims, 1,
+                                                          ImageFormat.COLORSPACE_RGBA,
+                                                          FrameFormat.TARGET_GPU);
+        if (mHistFrame != null)
+            mHistFrame.release();
+
+        mHistFrame = context.getFrameManager().newFrame(shaderHistFormat);
+        mHistFrame.setInts(histArray);
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapOverlayFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapOverlayFilter.java
new file mode 100644
index 0000000..d4c901f
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapOverlayFilter.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+import android.filterpacks.imageproc.ImageCombineFilter;
+import android.graphics.Bitmap;
+
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class BitmapOverlayFilter extends Filter {
+
+    @GenerateFieldPort(name = "bitmap")
+    private Bitmap mBitmap;
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+    private Frame mFrame;
+
+    private int mWidth = 0;
+    private int mHeight = 0;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private final String mOverlayShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform sampler2D tex_sampler_1;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 original = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  vec4 mask = texture2D(tex_sampler_1, v_texcoord);\n" +
+            "  gl_FragColor = vec4(original.rgb * (1.0 - mask.a) + mask.rgb, 1.0);\n" +
+            "}\n";
+
+    public BitmapOverlayFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mOverlayShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter FisheyeFilter does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    @Override
+    public void tearDown(FilterContext context) {
+        if (mFrame != null) {
+            mFrame.release();
+            mFrame = null;
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+        }
+
+        // Check if the frame size has changed
+        if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) {
+            mWidth = inputFormat.getWidth();
+            mHeight = inputFormat.getHeight();
+
+            createBitmapFrame(context);
+        }
+
+        // Process
+        Frame[] inputs = {input, mFrame};
+        mProgram.process(inputs, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    private void createBitmapFrame(FilterContext context) {
+        if (mBitmap != null) {
+            FrameFormat format = ImageFormat.create(mBitmap.getWidth(),
+                                                    mBitmap.getHeight(),
+                                                    ImageFormat.COLORSPACE_RGBA,
+                                                    FrameFormat.TARGET_GPU);
+
+            if (mFrame != null) {
+                mFrame.release();
+            }
+
+            mFrame = context.getFrameManager().newFrame(format);
+            mFrame.setBitmap(mBitmap);
+
+            mBitmap.recycle();
+            mBitmap = null;
+        }
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapSource.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapSource.java
new file mode 100644
index 0000000..978fc94
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapSource.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.content.Context;
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.FrameManager;
+import android.filterfw.core.GenerateFinalPort;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.format.ImageFormat;
+import android.graphics.Bitmap;
+
+/**
+ * @hide
+ */
+public class BitmapSource extends Filter {
+
+    @GenerateFieldPort(name = "target")
+    String mTargetString;
+
+    @GenerateFieldPort(name = "bitmap")
+    private Bitmap mBitmap;
+
+    @GenerateFieldPort(name = "recycleBitmap", hasDefault = true)
+    private boolean mRecycleBitmap = true;
+
+    @GenerateFieldPort(name = "repeatFrame", hasDefault = true)
+    boolean mRepeatFrame = false;
+
+    private int mTarget;
+    private Frame mImageFrame;
+
+    public BitmapSource(String name) {
+        super(name);
+    }
+
+
+    @Override
+    public void setupPorts() {
+        // Setup output format
+        FrameFormat outputFormat = ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
+                                                      FrameFormat.TARGET_UNSPECIFIED);
+
+        // Add output port
+        addOutputPort("image", outputFormat);
+    }
+
+    public void loadImage(FilterContext filterContext) {
+        // Create frame with bitmap
+        mTarget = FrameFormat.readTargetString(mTargetString);
+        FrameFormat outputFormat = ImageFormat.create(mBitmap.getWidth(),
+                                                      mBitmap.getHeight(),
+                                                      ImageFormat.COLORSPACE_RGBA,
+                                                      mTarget);
+        mImageFrame = filterContext.getFrameManager().newFrame(outputFormat);
+        mImageFrame.setBitmap(mBitmap);
+        mImageFrame.setTimestamp(Frame.TIMESTAMP_UNKNOWN);
+
+        // Free up space used by bitmap
+        if (mRecycleBitmap) {
+            mBitmap.recycle();
+        }
+        mBitmap = null;
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        // Clear image (to trigger reload) in case parameters have been changed
+        if (name.equals("bitmap") || name.equals("target")) {
+            if (mImageFrame != null) {
+                mImageFrame.release();
+                mImageFrame = null;
+            }
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        if (mImageFrame == null) {
+            loadImage(context);
+        }
+
+        pushOutput("image", mImageFrame);
+
+        if (!mRepeatFrame) {
+            closeOutputPort("image");
+        }
+    }
+
+    @Override
+    public void tearDown(FilterContext env) {
+        if (mImageFrame != null) {
+            mImageFrame.release();
+            mImageFrame = null;
+        }
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BlackWhiteFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BlackWhiteFilter.java
new file mode 100644
index 0000000..a1cec01
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BlackWhiteFilter.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import java.util.Random;
+
+public class BlackWhiteFilter extends Filter {
+
+    @GenerateFieldPort(name = "black", hasDefault = true)
+    private float mBlack = 0f;
+
+    @GenerateFieldPort(name = "white", hasDefault = true)
+    private float mWhite = 1f;
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+
+    private int mWidth = 0;
+    private int mHeight = 0;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private Frame mNoiseFrame = null;
+    private Random mRandom;
+
+    private final String mBlackWhiteShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform sampler2D tex_sampler_1;\n" +
+            "uniform float black;\n" +
+            "uniform float scale;\n" +
+            "uniform float stepsize;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  float dither = texture2D(tex_sampler_1, v_texcoord).r;\n" +
+            "  vec3 xform = clamp((color.rgb - black) * scale, 0.0, 1.0);\n" +
+            "  vec3 temp = clamp((color.rgb + stepsize - black) * scale, 0.0, 1.0);\n" +
+            "  vec3 new_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n" +
+            "  gl_FragColor = vec4(new_color, color.a);\n" +
+            "}\n";
+
+    public BlackWhiteFilter(String name) {
+        super(name);
+
+        mRandom = new Random();
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    @Override
+    public void tearDown(FilterContext context) {
+        if (mNoiseFrame != null) {
+            mNoiseFrame.release();
+            mNoiseFrame = null;
+        }
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mBlackWhiteShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                updateParameters();
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    private void updateParameters() {
+        float scale = (mBlack != mWhite) ? 1.0f / (mWhite - mBlack) : 2000f;
+        float stepsize = 1.0f / 255.0f;
+
+        mProgram.setHostValue("black", mBlack);
+        mProgram.setHostValue("scale", scale);
+        mProgram.setHostValue("stepsize", stepsize);
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mProgram != null) {
+            updateParameters();
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+        }
+
+        // Check if the frame size has changed
+        if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) {
+            mWidth = inputFormat.getWidth();
+            mHeight = inputFormat.getHeight();
+
+            if (mNoiseFrame != null) {
+                mNoiseFrame.release();
+            }
+
+            int[] buffer = new int[mWidth * mHeight];
+            for (int i = 0; i < mWidth * mHeight; ++i) {
+              buffer[i] = mRandom.nextInt(255);
+            }
+            FrameFormat format = ImageFormat.create(mWidth, mHeight,
+                                                    ImageFormat.COLORSPACE_RGBA,
+                                                    FrameFormat.TARGET_GPU);
+            mNoiseFrame = context.getFrameManager().newFrame(format);
+            mNoiseFrame.setInts(buffer);
+        }
+
+        if (mNoiseFrame != null && (mNoiseFrame.getFormat().getWidth() != mWidth ||
+                                    mNoiseFrame.getFormat().getHeight() != mHeight)) {
+            throw new RuntimeException("Random map and imput image size mismatch!");
+        }
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Process
+        Frame[] inputs = {input, mNoiseFrame};
+        mProgram.process(inputs, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BlendFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BlendFilter.java
new file mode 100644
index 0000000..29bc8a3
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BlendFilter.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import java.util.Set;
+
+/**
+ * The filter linearly blends "left" and "right" frames. The blending weight is
+ * the multiplication of parameter "blend" and the alpha value in "right" frame.
+ * @hide
+ */
+public class BlendFilter extends ImageCombineFilter {
+
+    private final String mBlendShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform sampler2D tex_sampler_1;\n" +
+            "uniform float blend;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 colorL = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  vec4 colorR = texture2D(tex_sampler_1, v_texcoord);\n" +
+            "  float weight = colorR.a * blend;\n" +
+            "  gl_FragColor = mix(colorL, colorR, weight);\n" +
+            "}\n";
+
+    public BlendFilter(String name) {
+        super(name, new String[] { "left", "right" }, "blended", "blend");
+    }
+
+    @Override
+    protected Program getNativeProgram(FilterContext context) {
+        throw new RuntimeException("TODO: Write native implementation for Blend!");
+    }
+
+    @Override
+    protected Program getShaderProgram(FilterContext context) {
+        return new ShaderProgram(context, mBlendShader);
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BrightnessFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BrightnessFilter.java
new file mode 100644
index 0000000..046e69d
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BrightnessFilter.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+
+/**
+ * @hide
+ */
+public class BrightnessFilter extends SimpleImageFilter {
+
+    private static final String mBrightnessShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform float brightness;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  gl_FragColor = brightness * color;\n" +
+            "}\n";
+
+    public BrightnessFilter(String name) {
+        super(name, "brightness");
+    }
+
+    @Override
+    protected Program getNativeProgram(FilterContext context) {
+        return new NativeProgram("filterpack_imageproc", "brightness");
+    }
+
+    @Override
+    protected Program getShaderProgram(FilterContext context) {
+        return new ShaderProgram(context, mBrightnessShader);
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ColorTemperatureFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ColorTemperatureFilter.java
new file mode 100644
index 0000000..19da006
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ColorTemperatureFilter.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+import android.util.Log;
+
+public class ColorTemperatureFilter extends Filter {
+
+    @GenerateFieldPort(name = "scale", hasDefault = true)
+    private float mScale = 0.5f;
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private final String mColorTemperatureShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform float scale;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  vec3 new_color = color.rgb;\n" +
+            "  new_color.r = color.r + color.r * ( 1.0 - color.r) * scale;\n" +
+            "  new_color.b = color.b - color.b * ( 1.0 - color.b) * scale;\n" +
+            "  if (scale > 0.0) { \n" +
+            "    color.g = color.g + color.g * ( 1.0 - color.g) * scale * 0.25;\n" +
+            "  }\n" +
+            "  float max_value = max(new_color.r, max(new_color.g, new_color.b));\n" +
+            "  if (max_value > 1.0) { \n" +
+            "     new_color /= max_value;\n" +
+            "  } \n" +
+            "  gl_FragColor = vec4(new_color, color.a);\n" +
+            "}\n";
+
+    public ColorTemperatureFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mColorTemperatureShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+            updateParameters();
+        }
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    private void updateParameters() {
+        mProgram.setHostValue("scale", 2.0f * mScale - 1.0f);
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mProgram != null) {
+            updateParameters();
+        }
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ContrastFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ContrastFilter.java
new file mode 100644
index 0000000..70e987f
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ContrastFilter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class ContrastFilter extends SimpleImageFilter {
+
+    private static final String mContrastShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform float contrast;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  color -= 0.5;\n" +
+            "  color *= contrast;\n" +
+            "  color += 0.5;\n" +
+            "  gl_FragColor = color;\n" +  // this will clamp
+            "}\n";
+
+    public ContrastFilter(String name) {
+        super(name, "contrast");
+    }
+
+    @Override
+    protected Program getNativeProgram(FilterContext context) {
+        return new NativeProgram("filterpack_imageproc", "contrast");
+    }
+
+    @Override
+    protected Program getShaderProgram(FilterContext context) {
+        return new ShaderProgram(context, mContrastShader);
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/CropFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/CropFilter.java
new file mode 100644
index 0000000..5222d9c
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/CropFilter.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.geometry.Point;
+import android.filterfw.geometry.Quad;
+import android.filterfw.format.ImageFormat;
+import android.filterfw.format.ObjectFormat;
+
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class CropFilter extends Filter {
+
+    private Program mProgram;
+    private FrameFormat mLastFormat = null;
+
+    @GenerateFieldPort(name = "owidth")
+    private int mOutputWidth = -1;
+
+    @GenerateFieldPort(name = "oheight")
+    private int mOutputHeight = -1;
+
+    @GenerateFieldPort(name = "fillblack")
+    private boolean mFillBlack = false;
+
+    public CropFilter(String name) {
+        super(name);
+    }
+
+    private final String mFragShader =
+      "precision mediump float;\n" +
+      "uniform sampler2D tex_sampler_0;\n" +
+      "varying vec2 v_texcoord;\n" +
+      "void main() {\n" +
+      "  const vec2 lo = vec2(0.0, 0.0);\n" +
+      "  const vec2 hi = vec2(1.0, 1.0);\n" +
+      "  const vec4 black = vec4(0.0, 0.0, 0.0, 1.0);\n" +
+      "  bool out_of_bounds =\n" +
+      "    any(lessThan(v_texcoord, lo)) ||\n" +
+      "    any(greaterThan(v_texcoord, hi));\n" +
+      "  if (out_of_bounds) {\n" +
+      "    gl_FragColor = black;\n" +
+      "  } else {\n" +
+      "    gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" +
+      "  }\n" +
+      "}\n";
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addMaskedInputPort("box", ObjectFormat.fromClass(Quad.class, FrameFormat.TARGET_SIMPLE));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        // Make sure output size is set to unspecified, as we do not know what we will be resizing
+        // to.
+        MutableFrameFormat outputFormat = inputFormat.mutableCopy();
+        outputFormat.setDimensions(FrameFormat.SIZE_UNSPECIFIED, FrameFormat.SIZE_UNSPECIFIED);
+        return outputFormat;
+    }
+
+    protected void createProgram(FilterContext context, FrameFormat format) {
+        // TODO: Add CPU version
+        if (mLastFormat != null && mLastFormat.getTarget() == format.getTarget()) return;
+        mLastFormat = format;
+        mProgram = null;
+        switch (format.getTarget()) {
+            case FrameFormat.TARGET_GPU:
+              if(mFillBlack)
+                mProgram = new ShaderProgram(context, mFragShader);
+              else
+                mProgram = ShaderProgram.createIdentity(context);
+
+                break;
+        }
+        if (mProgram == null) {
+            throw new RuntimeException("Could not create a program for crop filter " + this + "!");
+        }
+    }
+
+    @Override
+    public void process(FilterContext env) {
+        // Get input frame
+        Frame imageFrame = pullInput("image");
+        Frame boxFrame = pullInput("box");
+
+        createProgram(env, imageFrame.getFormat());
+
+        // Get the box
+        Quad box = (Quad)boxFrame.getObjectValue();
+
+        // Create output format
+        MutableFrameFormat outputFormat = imageFrame.getFormat().mutableCopy();
+        outputFormat.setDimensions(mOutputWidth == -1 ? outputFormat.getWidth() : mOutputWidth,
+                                   mOutputHeight == -1 ? outputFormat.getHeight() : mOutputHeight);
+
+        // Create output frame
+        Frame output = env.getFrameManager().newFrame(outputFormat);
+
+        // Set the program parameters
+        if (mProgram instanceof ShaderProgram) {
+            ShaderProgram shaderProgram = (ShaderProgram)mProgram;
+            shaderProgram.setSourceRegion(box);
+        }
+
+        mProgram.process(imageFrame, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/CropRectFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/CropRectFilter.java
new file mode 100644
index 0000000..d423d06
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/CropRectFilter.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class CropRectFilter extends Filter {
+
+    @GenerateFieldPort(name = "xorigin")
+    private int mXorigin;
+
+    @GenerateFieldPort(name = "yorigin")
+    private int mYorigin;
+
+    @GenerateFieldPort(name = "width")
+    private int mOutputWidth;
+
+    @GenerateFieldPort(name = "height")
+    private int mOutputHeight;
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+
+    private int mWidth = 0;
+    private int mHeight = 0;
+
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    public CropRectFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = ShaderProgram.createIdentity(context);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mProgram != null) {
+            updateSourceRect(mWidth, mHeight);
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create output frame
+        FrameFormat outputFormat = ImageFormat.create(mOutputWidth, mOutputHeight,
+                                                      ImageFormat.COLORSPACE_RGBA,
+                                                      FrameFormat.TARGET_GPU);
+        Frame output = context.getFrameManager().newFrame(outputFormat);
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+        }
+
+        // Check if the frame size has changed
+        if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) {
+            updateSourceRect(inputFormat.getWidth(), inputFormat.getHeight());
+        }
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    void updateSourceRect(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+
+        /*
+        Log.e("CropFilter", mWidth + ", " + mHeight + ", " +
+                            (float) mXorigin / mWidth + ", " +
+                            (float) mYorigin / mHeight + ", " +
+                            (float) mOutputWidth / mWidth + ", " +
+                            (float) mOutputHeight / mHeight);
+        */
+
+        ((ShaderProgram) mProgram).setSourceRect((float) mXorigin / mWidth,
+                                                 (float) mYorigin / mHeight,
+                                                 (float) mOutputWidth / mWidth,
+                                                 (float) mOutputHeight / mHeight);
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/CrossProcessFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/CrossProcessFilter.java
new file mode 100644
index 0000000..e0514f8
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/CrossProcessFilter.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import android.util.Log;
+
+public class CrossProcessFilter extends Filter {
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private final String mCrossProcessShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  vec3 ncolor = vec3(0.0, 0.0, 0.0);\n" +
+            "  float value;\n" +
+            "  if (color.r < 0.5) {\n" +
+            "    value = color.r;\n" +
+            "  } else {\n" +
+            "    value = 1.0 - color.r;\n" +
+            "  }\n" +
+            "  float red = 4.0 * value * value * value;\n" +
+            "  if (color.r < 0.5) {\n" +
+            "    ncolor.r = red;\n" +
+            "  } else {\n" +
+            "    ncolor.r = 1.0 - red;\n" +
+            "  }\n" +
+            "  if (color.g < 0.5) {\n" +
+            "    value = color.g;\n" +
+            "  } else {\n" +
+            "    value = 1.0 - color.g;\n" +
+            "  }\n" +
+            "  float green = 2.0 * value * value;\n" +
+            "  if (color.g < 0.5) {\n" +
+            "    ncolor.g = green;\n" +
+            "  } else {\n" +
+            "    ncolor.g = 1.0 - green;\n" +
+            "  }\n" +
+            "  ncolor.b = color.b * 0.5 + 0.25;\n" +
+            "  gl_FragColor = vec4(ncolor.rgb, color.a);\n" +
+            "}\n";
+
+    public CrossProcessFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mCrossProcessShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter CrossProcess does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+        }
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/DocumentaryFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/DocumentaryFilter.java
new file mode 100644
index 0000000..3c7b846
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/DocumentaryFilter.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import java.util.Random;
+
+public class DocumentaryFilter extends Filter {
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+
+    private int mWidth = 0;
+    private int mHeight = 0;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private Frame mNoiseFrame;
+    private Random mRandom;
+
+    private final String mDocumentaryShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform sampler2D tex_sampler_1;\n" +
+            "uniform float stepsize;\n" +
+            "uniform float inv_max_dist;\n" +
+            "uniform vec2 center;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            // black white
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  float dither = texture2D(tex_sampler_1, v_texcoord).r;\n" +
+            "  vec3 xform = clamp(2.0 * color.rgb, 0.0, 1.0);\n" +
+            "  vec3 temp = clamp(2.0 * (color.rgb + stepsize), 0.0, 1.0);\n" +
+            "  vec3 new_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n" +
+            // grayscale
+            "  float gray = dot(new_color, vec3(0.299, 0.587, 0.114));\n" +
+            "  new_color = vec3(gray, gray, gray);\n" +
+            // vignette
+            "  float dist = distance(gl_FragCoord.xy, center);\n" +
+            "  float lumen = 0.85 / (1.0 + exp((dist * inv_max_dist - 0.83) * 20.0)) + 0.15;\n" +
+            "  gl_FragColor = vec4(new_color * lumen, color.a);\n" +
+            "}\n";
+
+    public DocumentaryFilter(String name) {
+        super(name);
+
+        mRandom = new Random();
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    @Override
+    public void tearDown(FilterContext context) {
+        if (mNoiseFrame != null) {
+            mNoiseFrame.release();
+            mNoiseFrame = null;
+        }
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mDocumentaryShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+        }
+
+        // Check if the frame size has changed
+        if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) {
+            mWidth = inputFormat.getWidth();
+            mHeight = inputFormat.getHeight();
+
+            int[] buffer = new int[mWidth * mHeight];
+            for (int i = 0; i < mWidth * mHeight; ++i) {
+              buffer[i] = mRandom.nextInt(255);
+            }
+            FrameFormat format = ImageFormat.create(mWidth, mHeight,
+                                                    ImageFormat.COLORSPACE_RGBA,
+                                                    FrameFormat.TARGET_GPU);
+            if (mNoiseFrame != null) {
+                mNoiseFrame.release();
+            }
+            mNoiseFrame = context.getFrameManager().newFrame(format);
+            mNoiseFrame.setInts(buffer);
+
+            initParameters();
+        }
+
+        if (mNoiseFrame != null && (mNoiseFrame.getFormat().getWidth() != mWidth ||
+                                    mNoiseFrame.getFormat().getHeight() != mHeight)) {
+            throw new RuntimeException("Random map and imput image size mismatch!");
+        }
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Process
+        Frame[] inputs = {input, mNoiseFrame};
+        mProgram.process(inputs, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    private void initParameters() {
+        if (mProgram != null) {
+            float centerX = (float) (0.5 * mWidth);
+            float centerY = (float) (0.5 * mHeight);
+            float center[] = {centerX, centerY};
+            float max_dist = (float) Math.sqrt(centerX * centerX + centerY * centerY);
+
+            mProgram.setHostValue("center", center);
+            mProgram.setHostValue("inv_max_dist", 1.0f / max_dist);
+            mProgram.setHostValue("stepsize", 1.0f / 255.0f);
+        }
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawOverlayFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawOverlayFilter.java
new file mode 100644
index 0000000..3f1711e
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawOverlayFilter.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.geometry.Quad;
+import android.filterfw.format.ImageFormat;
+import android.filterfw.format.ObjectFormat;
+
+import android.opengl.GLES20;
+
+/**
+ * @hide
+ */
+public class DrawOverlayFilter extends Filter {
+
+    private ShaderProgram mProgram;
+
+    public DrawOverlayFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        FrameFormat imageFormatMask = ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
+                                                         FrameFormat.TARGET_GPU);
+        addMaskedInputPort("source", imageFormatMask);
+        addMaskedInputPort("overlay", imageFormatMask);
+        addMaskedInputPort("box", ObjectFormat.fromClass(Quad.class, FrameFormat.TARGET_SIMPLE));
+        addOutputBasedOnInput("image", "source");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    @Override
+    public void prepare(FilterContext context) {
+        mProgram = ShaderProgram.createIdentity(context);
+    }
+
+    @Override
+    public void process(FilterContext env) {
+        // Get input frame
+        Frame sourceFrame = pullInput("source");
+        Frame overlayFrame = pullInput("overlay");
+        Frame boxFrame = pullInput("box");
+
+        // Get the box
+        Quad box = (Quad)boxFrame.getObjectValue();
+        box = box.translated(1.0f, 1.0f).scaled(2.0f);
+
+        mProgram.setTargetRegion(box);
+
+        // Create output frame with copy of input
+        Frame output = env.getFrameManager().newFrame(sourceFrame.getFormat());
+        output.setDataFromFrame(sourceFrame);
+
+        // Draw onto output
+        mProgram.process(overlayFrame, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawRectFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawRectFilter.java
new file mode 100644
index 0000000..83c9348
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawRectFilter.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.geometry.Quad;
+import android.filterfw.format.ImageFormat;
+import android.filterfw.format.ObjectFormat;
+
+import android.opengl.GLES20;
+
+/**
+ * @hide
+ */
+public class DrawRectFilter extends Filter {
+
+    @GenerateFieldPort(name = "colorRed",  hasDefault = true)
+    private float mColorRed = 0.8f;
+
+    @GenerateFieldPort(name = "colorGreen", hasDefault = true)
+    private float mColorGreen = 0.8f;
+
+    @GenerateFieldPort(name = "colorBlue", hasDefault = true)
+    private float mColorBlue = 0.0f;
+
+    private final String mVertexShader =
+        "attribute vec4 aPosition;\n" +
+        "void main() {\n" +
+        "  gl_Position = aPosition;\n" +
+        "}\n";
+
+    private final String mFixedColorFragmentShader =
+        "precision mediump float;\n" +
+        "uniform vec4 color;\n" +
+        "void main() {\n" +
+        "  gl_FragColor = color;\n" +
+        "}\n";
+
+    private ShaderProgram mProgram;
+
+
+    public DrawRectFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
+                                                       FrameFormat.TARGET_GPU));
+        addMaskedInputPort("box", ObjectFormat.fromClass(Quad.class, FrameFormat.TARGET_SIMPLE));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    @Override
+    public void prepare(FilterContext context) {
+        mProgram = new ShaderProgram(context, mVertexShader, mFixedColorFragmentShader);
+    }
+
+    @Override
+    public void process(FilterContext env) {
+        // Get input frame
+        Frame imageFrame = pullInput("image");
+        Frame boxFrame = pullInput("box");
+
+        // Get the box
+        Quad box = (Quad)boxFrame.getObjectValue();
+        box = box.scaled(2.0f).translated(-1.0f, -1.0f);
+
+        // Create output frame with copy of input
+        GLFrame output = (GLFrame)env.getFrameManager().duplicateFrame(imageFrame);
+
+        // Draw onto output
+        output.focus();
+        renderBox(box);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    private void renderBox(Quad box) {
+        final int FLOAT_SIZE = 4;
+
+        // Get current values
+        float[] color = {mColorRed, mColorGreen, mColorBlue, 1f};
+        float[] vertexValues = { box.p0.x, box.p0.y,
+                                 box.p1.x, box.p1.y,
+                                 box.p3.x, box.p3.y,
+                                 box.p2.x, box.p2.y };
+
+        // Set the program variables
+        mProgram.setHostValue("color", color);
+        mProgram.setAttributeValues("aPosition", vertexValues, 2);
+        mProgram.setVertexCount(4);
+
+        // Draw
+        mProgram.beginDrawing();
+        GLES20.glLineWidth(1.0f);
+        GLES20.glDrawArrays(GLES20.GL_LINE_LOOP, 0, 4);
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/DuotoneFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/DuotoneFilter.java
new file mode 100644
index 0000000..d8c88ee
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/DuotoneFilter.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+import android.graphics.Color;
+
+public class DuotoneFilter extends Filter {
+
+    @GenerateFieldPort(name = "first_color", hasDefault = true)
+    private int mFirstColor = 0xFFFF0000;
+
+    @GenerateFieldPort(name = "second_color", hasDefault = true)
+    private int mSecondColor = 0xFFFFFF00;
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private final String mDuotoneShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform vec3 first;\n" +
+            "uniform vec3 second;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  float energy = (color.r + color.g + color.b) * 0.3333;\n" +
+            "  vec3 new_color = (1.0 - energy) * first + energy * second;\n" +
+            "  gl_FragColor = vec4(new_color.rgb, color.a);\n" +
+            "}\n";
+
+    public DuotoneFilter(String name) {
+      super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mDuotoneShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter Duotone does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+        }
+        updateParameters();
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    private void updateParameters() {
+        float first[] = { Color.red(mFirstColor)/255f,
+                Color.green(mFirstColor)/255f,
+                Color.blue(mFirstColor)/255f };
+        float second[] = { Color.red(mSecondColor)/255f,
+                Color.green(mSecondColor)/255f,
+                Color.blue(mSecondColor)/255f };
+
+        mProgram.setHostValue("first", first);
+        mProgram.setHostValue("second", second);
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/FillLightFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/FillLightFilter.java
new file mode 100644
index 0000000..fc917a1
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/FillLightFilter.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GenerateFinalPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import android.util.Log;
+
+public class FillLightFilter extends Filter {
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    @GenerateFieldPort(name = "strength", hasDefault = true)
+    private float mBacklight = 0f;
+
+    private Program mProgram;
+
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private final String mFillLightShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform float mult;\n" +
+            "uniform float igamma;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main()\n" +
+            "{\n" +
+            "  const vec3 color_weights = vec3(0.25, 0.5, 0.25);\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  float lightmask = dot(color.rgb, color_weights);\n" +
+            "  float backmask = (1.0 - lightmask);\n" +
+            "  vec3 ones = vec3(1.0, 1.0, 1.0);\n" +
+            "  vec3 diff = pow(mult * color.rgb, igamma * ones) - color.rgb;\n" +
+            "  diff = min(diff, 1.0);\n" +
+            "  vec3 new_color = min(color.rgb + diff * backmask, 1.0);\n" +
+            "  gl_FragColor = vec4(new_color, color.a);\n" +
+            "}\n";
+
+    public FillLightFilter(String name) {
+      super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mFillLightShader);
+                Log.e("FillLight", "tile size: " + mTileSize);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter FillLight does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+            updateParameters();
+        }
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mProgram != null) {
+            updateParameters();
+        }
+    }
+
+    private void updateParameters() {
+        float fade_gamma = 0.3f;
+        float amt = 1.0f - mBacklight;
+        float mult = 1.0f / (amt * 0.7f + 0.3f);
+        float faded = fade_gamma + (1.0f -fade_gamma) *mult;
+        float igamma = 1.0f / faded;
+
+        mProgram.setHostValue("mult", mult);
+        mProgram.setHostValue("igamma", igamma);
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/FisheyeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/FisheyeFilter.java
new file mode 100644
index 0000000..8d38f98
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/FisheyeFilter.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import android.util.Log;
+
+import java.lang.Math;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class FisheyeFilter extends Filter {
+    private static final String TAG = "FisheyeFilter";
+
+    // This parameter has range between 0 and 1. It controls the effect of radial distortion.
+    // The larger the value, the more prominent the distortion effect becomes (a straight line
+    // becomes a curve).
+    @GenerateFieldPort(name = "scale", hasDefault = true)
+    private float mScale = 0f;
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+
+    private int mWidth = 0;
+    private int mHeight = 0;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private static final String mFisheyeShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform vec2 center;\n" +
+            "uniform float alpha;\n" +
+            "uniform float bound;\n" +
+            "uniform float radius2;\n" +
+            "uniform float factor;\n" +
+            "uniform float inv_height;\n" +
+            "uniform float inv_width;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  const float m_pi_2 = 1.570963;\n" +
+            "  float dist = distance(gl_FragCoord.xy, center);\n" +
+            "  float radian = m_pi_2 - atan(alpha * sqrt(radius2 - dist * dist), dist);\n" +
+            "  float scale = radian * factor / dist;\n" +
+            "  vec2 new_coord = gl_FragCoord.xy * scale + (1.0 - scale) * center;\n" +
+            "  new_coord.x *= inv_width;\n" +
+            "  new_coord.y *= inv_height;\n" +
+            "  vec4 color = texture2D(tex_sampler_0, new_coord);\n" +
+            "  gl_FragColor = color;\n" +
+            "}\n";
+
+    public FisheyeFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mFisheyeShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter FisheyeFilter does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+        }
+
+        // Check if the frame size has changed
+        if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) {
+            updateFrameSize(inputFormat.getWidth(), inputFormat.getHeight());
+        }
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mProgram != null) {
+            updateProgramParams();
+        }
+    }
+
+    private void updateFrameSize(int width, int height) {
+        float center[] = {0.5f * width, 0.5f * height};
+
+        mProgram.setHostValue("center", center);
+        mProgram.setHostValue("inv_width", 1.0f / width);
+        mProgram.setHostValue("inv_height", 1.0f / height);
+
+        mWidth = width;
+        mHeight = height;
+
+        updateProgramParams();
+    }
+
+    private void updateProgramParams() {
+        final float pi = 3.14159265f;
+
+        float alpha = mScale * 2.0f + 0.75f;
+        float bound2 = 0.25f * (mWidth * mWidth  + mHeight * mHeight);
+        float bound = (float) Math.sqrt(bound2);
+        float radius = 1.15f * bound;
+        float radius2 = radius * radius;
+        float max_radian = 0.5f * pi -
+            (float) Math.atan(alpha / bound * (float) Math.sqrt(radius2 - bound2));
+        float factor = bound / max_radian;
+
+        mProgram.setHostValue("radius2",radius2);
+        mProgram.setHostValue("factor", factor);
+        mProgram.setHostValue("alpha", (float) (mScale * 2.0 + 0.75));
+    }
+
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/FixedRotationFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/FixedRotationFilter.java
new file mode 100644
index 0000000..3d319ea
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/FixedRotationFilter.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+import android.filterfw.geometry.Point;
+import android.filterfw.geometry.Quad;
+
+/**
+ * The FixedRotationFilter rotates the input image clockwise, it only accepts
+ * 4 rotation angles: 0, 90, 180, 270
+ * @hide
+ */
+public class FixedRotationFilter extends Filter {
+
+    @GenerateFieldPort(name = "rotation", hasDefault = true)
+    private int mRotation = 0;
+
+    private ShaderProgram mProgram = null;
+
+    public FixedRotationFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
+                                                       FrameFormat.TARGET_GPU));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        Frame input = pullInput("image");
+        if (mRotation == 0) {
+            pushOutput("image", input);
+            return;
+        }
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create program if not created already
+        if (mProgram == null) {
+            mProgram = ShaderProgram.createIdentity(context);
+        }
+        MutableFrameFormat outputFormat = inputFormat.mutableCopy();
+        int width = inputFormat.getWidth();
+        int height = inputFormat.getHeight();
+        Point p1 = new Point(0.0f, 0.0f);
+        Point p2 = new Point(1.0f, 0.0f);
+        Point p3 = new Point(0.0f, 1.0f);
+        Point p4 = new Point(1.0f, 1.0f);
+        Quad sourceRegion;
+        switch (((int)Math.round(mRotation / 90f)) % 4) {
+            case 1:
+                sourceRegion = new Quad(p3,p1,p4,p2);
+                outputFormat.setDimensions(height, width);
+                break;
+            case 2:
+                sourceRegion = new Quad(p4,p3,p2,p1);
+                break;
+            case 3:
+                sourceRegion = new Quad(p2,p4,p1,p3);
+                outputFormat.setDimensions(height, width);
+                break;
+            case 0:
+            default:
+                sourceRegion = new Quad(p1,p2,p3,p4);
+                break;
+        }
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(outputFormat);
+
+        // Set the source region
+        mProgram.setSourceRegion(sourceRegion);
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/FlipFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/FlipFilter.java
new file mode 100644
index 0000000..f8b857b
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/FlipFilter.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+/**
+ * @hide
+ */
+public class FlipFilter extends Filter {
+
+    @GenerateFieldPort(name = "vertical", hasDefault = true)
+    private boolean mVertical = false;
+
+    @GenerateFieldPort(name = "horizontal", hasDefault = true)
+    private boolean mHorizontal = false;
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    public FlipFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = ShaderProgram.createIdentity(context);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+        updateParameters();
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mProgram != null) {
+            updateParameters();
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+        }
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    private void updateParameters() {
+        float x_origin = (mHorizontal) ? 1.0f : 0.0f;
+        float y_origin = (mVertical) ? 1.0f : 0.0f;
+
+        float width  = (mHorizontal) ? -1.0f : 1.0f;
+        float height  = (mVertical) ? -1.0f : 1.0f;
+
+        ((ShaderProgram) mProgram).setSourceRect(x_origin, y_origin, width, height);
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/GrainFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/GrainFilter.java
new file mode 100644
index 0000000..168a9c6
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/GrainFilter.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+import android.filterfw.geometry.Quad;
+import android.filterfw.geometry.Point;
+
+import java.util.Random;
+
+public class GrainFilter extends Filter {
+
+    private static final int RAND_THRESHOLD = 128;
+
+    @GenerateFieldPort(name = "strength", hasDefault = true)
+    private float mScale = 0f;
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+
+    private int mWidth = 0;
+    private int mHeight = 0;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private Frame mNoiseFrame = null;
+    private Random mRandom;
+
+    private final String mGrainShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform sampler2D tex_sampler_1;\n" +
+            "uniform float scale;\n" +
+            "uniform float stepX;\n" +
+            "uniform float stepY;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  float noise = texture2D(tex_sampler_1, v_texcoord + vec2(-stepX, -stepY)).r * 0.224;\n" +
+            "  noise += texture2D(tex_sampler_1, v_texcoord + vec2(-stepX, stepY)).r * 0.224;\n" +
+            "  noise += texture2D(tex_sampler_1, v_texcoord + vec2(stepX, -stepY)).r * 0.224;\n" +
+            "  noise += texture2D(tex_sampler_1, v_texcoord + vec2(stepX, stepY)).r * 0.224;\n" +
+            "  noise += 0.4448;\n" +
+            "  noise *= scale;\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  float energy = 0.33333 * color.r + 0.33333 * color.g + 0.33333 * color.b;\n" +
+            "  float mask = (1.0 - sqrt(energy));\n" +
+            "  float weight = 1.0 - 1.333 * mask * noise;\n" +
+            "  gl_FragColor = vec4(color.rgb * weight, color.a);\n" +
+            "}\n";
+
+    public GrainFilter(String name) {
+        super(name);
+
+        mRandom = new Random();
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mGrainShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    private void updateParameters() {
+        mProgram.setHostValue("scale", mScale);
+    }
+
+    private void updateFrameSize(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+
+        if (mProgram != null) {
+            mProgram.setHostValue("stepX", 0.5f / mWidth);
+            mProgram.setHostValue("stepY", 0.5f / mHeight);
+            updateParameters();
+        }
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mProgram != null) {
+            updateParameters();
+        }
+    }
+
+    @Override
+    public void tearDown(FilterContext context) {
+        if (mNoiseFrame != null) {
+            mNoiseFrame.release();
+            mNoiseFrame = null;
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+            updateParameters();
+        }
+
+        // Check if the frame size has changed
+        if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) {
+            updateFrameSize(inputFormat.getWidth(), inputFormat.getHeight());
+
+            int[] buffer = new int[mWidth * mHeight];
+            for (int i = 0; i < mWidth * mHeight; ++i) {
+                buffer[i] = (mRandom.nextInt(256) < RAND_THRESHOLD) ?
+                    mRandom.nextInt(256) : 0;
+            }
+            FrameFormat format = ImageFormat.create(mWidth, mHeight,
+                                                    ImageFormat.COLORSPACE_RGBA,
+                                                    FrameFormat.TARGET_GPU);
+            if (mNoiseFrame != null) {
+                mNoiseFrame.release();
+            }
+            mNoiseFrame = context.getFrameManager().newFrame(format);
+            mNoiseFrame.setInts(buffer);
+        }
+
+        if (mNoiseFrame.getFormat().getWidth() != mWidth ||
+            mNoiseFrame.getFormat().getHeight() != mHeight) {
+            throw new RuntimeException("Random map and imput image size mismatch!");
+        }
+
+        // Process
+        Frame[] inputs = {input, mNoiseFrame};
+        mProgram.process(inputs, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageCombineFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageCombineFilter.java
new file mode 100644
index 0000000..858489b
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageCombineFilter.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import java.lang.reflect.Field;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public abstract class ImageCombineFilter extends Filter {
+
+    protected Program mProgram;
+    protected String[] mInputNames;
+    protected String mOutputName;
+    protected String mParameterName;
+    protected int mCurrentTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    public ImageCombineFilter(String name,
+                              String[] inputNames,
+                              String outputName,
+                              String parameterName) {
+        super(name);
+        mInputNames = inputNames;
+        mOutputName = outputName;
+        mParameterName = parameterName;
+    }
+
+    @Override
+    public void setupPorts() {
+        if (mParameterName != null) {
+            try {
+                Field programField = ImageCombineFilter.class.getDeclaredField("mProgram");
+                addProgramPort(mParameterName, mParameterName, programField, float.class, false);
+            } catch (NoSuchFieldException e) {
+                throw new RuntimeException("Internal Error: mProgram field not found!");
+            }
+        }
+        for (String inputName : mInputNames) {
+            addMaskedInputPort(inputName, ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        }
+        addOutputBasedOnInput(mOutputName, mInputNames[0]);
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    private void assertAllInputTargetsMatch() {
+        int target = getInputFormat(mInputNames[0]).getTarget();
+        for (String inputName : mInputNames) {
+            if (target != getInputFormat(inputName).getTarget()) {
+                throw new RuntimeException("Type mismatch of input formats in filter " + this
+                    + ". All input frames must have the same target!");
+            }
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Pull input frames
+        int i = 0;
+        Frame[] inputs = new Frame[mInputNames.length];
+        for (String inputName : mInputNames) {
+            inputs[i++] = pullInput(inputName);
+        }
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputs[0].getFormat());
+
+        // Make sure we have a program
+        updateProgramWithTarget(inputs[0].getFormat().getTarget(), context);
+
+        // Process
+        mProgram.process(inputs, output);
+
+        // Push output
+        pushOutput(mOutputName, output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    protected void updateProgramWithTarget(int target, FilterContext context) {
+        if (target != mCurrentTarget) {
+            switch (target) {
+                case FrameFormat.TARGET_NATIVE:
+                    mProgram = getNativeProgram(context);
+                    break;
+
+                case FrameFormat.TARGET_GPU:
+                    mProgram = getShaderProgram(context);
+                    break;
+
+                default:
+                    mProgram = null;
+                    break;
+            }
+            if (mProgram == null) {
+                throw new RuntimeException("Could not create a program for image filter "
+                    + this + "!");
+            }
+            initProgramInputs(mProgram, context);
+            mCurrentTarget = target;
+        }
+    }
+
+    protected abstract Program getNativeProgram(FilterContext context);
+
+    protected abstract Program getShaderProgram(FilterContext context);
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageEncoder.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageEncoder.java
new file mode 100644
index 0000000..a5405cb
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageEncoder.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.content.Context;
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.format.ImageFormat;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+
+import android.util.Log;
+
+import java.io.OutputStream;
+import java.io.IOException;
+
+/**
+ * @hide
+ */
+public class ImageEncoder extends Filter {
+
+    @GenerateFieldPort(name = "stream")
+    private OutputStream mOutputStream;
+
+    @GenerateFieldPort(name = "quality", hasDefault = true)
+    private int mQuality = 80;
+
+    public ImageEncoder(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
+                                                       FrameFormat.TARGET_UNSPECIFIED));
+    }
+
+    @Override
+    public void process(FilterContext env) {
+        Frame input = pullInput("image");
+        Bitmap bitmap = input.getBitmap();
+        bitmap.compress(CompressFormat.JPEG, mQuality, mOutputStream);
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageSlicer.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageSlicer.java
new file mode 100644
index 0000000..b996eb8
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageSlicer.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.FrameManager;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+public class ImageSlicer extends Filter {
+
+    @GenerateFieldPort(name = "xSlices")
+    private int mXSlices;
+
+    @GenerateFieldPort(name = "ySlices")
+    private int mYSlices;
+
+    @GenerateFieldPort(name = "padSize")
+    private int mPadSize;
+
+    // The current slice index from 0 to xSlices * ySlices
+    private int mSliceIndex;
+
+    private Frame mOriginalFrame;
+
+    private Program mProgram;
+
+    private int mInputWidth;
+    private int mInputHeight;
+
+    private int mSliceWidth;
+    private int mSliceHeight;
+
+    private int mOutputWidth;
+    private int mOutputHeight;
+
+    public ImageSlicer(String name) {
+        super(name);
+        mSliceIndex = 0;
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
+                                                       FrameFormat.TARGET_GPU));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    private void calcOutputFormatForInput(Frame frame) {
+
+        // calculate the output size based on input size, xSlices, and ySlices
+        mInputWidth = frame.getFormat().getWidth();
+        mInputHeight = frame.getFormat().getHeight();
+
+        mSliceWidth = (mInputWidth + mXSlices - 1) / mXSlices;
+        mSliceHeight = (mInputHeight + mYSlices - 1)/ mYSlices;
+
+        mOutputWidth = mSliceWidth + mPadSize * 2;
+        mOutputHeight = mSliceHeight + mPadSize * 2;
+    }
+
+
+    @Override
+    public void process(FilterContext context) {
+
+        // Get input frame
+        if (mSliceIndex == 0) {
+            mOriginalFrame = pullInput("image");
+            calcOutputFormatForInput(mOriginalFrame);
+        }
+
+        FrameFormat inputFormat = mOriginalFrame.getFormat();
+        MutableFrameFormat outputFormat = inputFormat.mutableCopy();
+        outputFormat.setDimensions(mOutputWidth, mOutputHeight);
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(outputFormat);
+
+        // Create the program if not created already
+        if (mProgram == null) {
+            mProgram = ShaderProgram.createIdentity(context);
+        }
+
+        // Calculate the four corner of the source region
+        int xSliceIndex = mSliceIndex % mXSlices;
+        int ySliceIndex = mSliceIndex / mXSlices;
+
+        // TODO(rslin) : not sure shifting by 0.5 is needed.
+        float x0 = (xSliceIndex * mSliceWidth - mPadSize) / ((float) mInputWidth);
+        float y0 = (ySliceIndex * mSliceHeight - mPadSize) / ((float) mInputHeight);
+
+        ((ShaderProgram) mProgram).setSourceRect(x0, y0,
+                                                 ((float) mOutputWidth) / mInputWidth,
+                                                 ((float) mOutputHeight) / mInputHeight);
+
+        // Process
+        mProgram.process(mOriginalFrame, output);
+        mSliceIndex++;
+
+        if (mSliceIndex == mXSlices * mYSlices) {
+            mSliceIndex = 0;
+            mOriginalFrame.release();
+            setWaitsOnInputPort("image", true);
+        } else {
+            // Retain the original frame so it can be used next time.
+            mOriginalFrame.retain();
+            setWaitsOnInputPort("image", false);
+        }
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageStitcher.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageStitcher.java
new file mode 100644
index 0000000..20aba91
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageStitcher.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.FrameManager;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import android.util.Log;
+
+public class ImageStitcher extends Filter {
+
+    @GenerateFieldPort(name = "xSlices")
+    private int mXSlices;
+
+    @GenerateFieldPort(name = "ySlices")
+    private int mYSlices;
+
+    @GenerateFieldPort(name = "padSize")
+    private int mPadSize;
+
+    private Program mProgram;
+    private Frame mOutputFrame;
+
+    private int mInputWidth;
+    private int mInputHeight;
+
+    private int mImageWidth;
+    private int mImageHeight;
+
+    private int mSliceWidth;
+    private int mSliceHeight;
+
+    private int mSliceIndex;
+
+    public ImageStitcher(String name) {
+        super(name);
+        mSliceIndex = 0;
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
+                                                      FrameFormat.TARGET_GPU));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    private FrameFormat calcOutputFormatForInput(FrameFormat format) {
+        MutableFrameFormat outputFormat = format.mutableCopy();
+
+        mInputWidth = format.getWidth();
+        mInputHeight = format.getHeight();
+
+        mSliceWidth = mInputWidth - 2 * mPadSize;
+        mSliceHeight = mInputHeight - 2 * mPadSize;
+
+        mImageWidth =  mSliceWidth * mXSlices;
+        mImageHeight = mSliceHeight * mYSlices;
+
+        outputFormat.setDimensions(mImageWidth, mImageHeight);
+        return outputFormat;
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat format = input.getFormat();
+
+        // Create output frame
+        if (mSliceIndex == 0) {
+            mOutputFrame = context.getFrameManager().newFrame(calcOutputFormatForInput(format));
+        } else {
+            if ((format.getWidth() != mInputWidth) ||
+                (format.getHeight() != mInputHeight)) {
+                // CHECK input format here
+                throw new RuntimeException("Image size should not change.");
+            }
+        }
+
+        // Create the program if not created already
+        if (mProgram == null) {
+            mProgram = ShaderProgram.createIdentity(context);
+        }
+
+        // TODO(rslin) : not sure shifting by 0.5 is needed.
+        float x0 = ((float) mPadSize) / mInputWidth;
+        float y0 = ((float) mPadSize) / mInputHeight;
+
+        int outputOffsetX = (mSliceIndex % mXSlices) * mSliceWidth;
+        int outputOffsetY = (mSliceIndex / mXSlices) * mSliceHeight;
+
+        float outputWidth = (float) Math.min(mSliceWidth, mImageWidth - outputOffsetX);
+        float outputHeight = (float) Math.min(mSliceHeight, mImageHeight - outputOffsetY);
+
+        // We need to set the source rect as well because the input are padded images.
+        ((ShaderProgram) mProgram).setSourceRect(x0, y0,
+                                                 outputWidth / mInputWidth,
+                                                 outputHeight / mInputHeight);
+
+        ((ShaderProgram) mProgram).setTargetRect(((float) outputOffsetX)/ mImageWidth,
+                                                 ((float) outputOffsetY) / mImageHeight,
+                                                 outputWidth / mImageWidth,
+                                                 outputHeight / mImageHeight);
+
+        // Process this tile
+        mProgram.process(input, mOutputFrame);
+        mSliceIndex++;
+
+        // Push output
+        if (mSliceIndex == mXSlices * mYSlices) {
+            pushOutput("image", mOutputFrame);
+            mOutputFrame.release();
+            mSliceIndex = 0;
+        }
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/Invert.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/Invert.java
new file mode 100644
index 0000000..400fd5d
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/Invert.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+
+/**
+ * @hide
+ */
+public class Invert extends SimpleImageFilter {
+
+    private static final String mInvertShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  gl_FragColor.r = 1.0 - color.r;\n" +
+            "  gl_FragColor.g = 1.0 - color.g;\n" +
+            "  gl_FragColor.b = 1.0 - color.b;\n" +
+            "  gl_FragColor.a = color.a;\n" +
+            "}\n";
+
+    public Invert(String name) {
+        super(name, null);
+    }
+
+    @Override
+    protected Program getNativeProgram(FilterContext context) {
+        return new NativeProgram("filterpack_imageproc", "invert");
+    }
+
+    @Override
+    protected Program getShaderProgram(FilterContext context) {
+        return new ShaderProgram(context, mInvertShader);
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/LomoishFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/LomoishFilter.java
new file mode 100644
index 0000000..452a833
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/LomoishFilter.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import java.util.Random;
+
+public class LomoishFilter extends Filter {
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+
+    private int mWidth = 0;
+    private int mHeight = 0;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private Frame mNoiseFrame;
+    private Random mRandom;
+
+    private final String mLomoishShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform sampler2D tex_sampler_1;\n" +
+            "uniform float stepsizeX;\n" +
+            "uniform float stepsizeY;\n" +
+            "uniform float stepsize;\n" +
+            "uniform vec2 center;\n" +
+            "uniform float inv_max_dist;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            // sharpen
+            "  vec3 nbr_color = vec3(0.0, 0.0, 0.0);\n" +
+            "  vec2 coord;\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  coord.x = v_texcoord.x - 0.5 * stepsizeX;\n" +
+            "  coord.y = v_texcoord.y - stepsizeY;\n" +
+            "  nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" +
+            "  coord.x = v_texcoord.x - stepsizeX;\n" +
+            "  coord.y = v_texcoord.y + 0.5 * stepsizeY;\n" +
+            "  nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" +
+            "  coord.x = v_texcoord.x + stepsizeX;\n" +
+            "  coord.y = v_texcoord.y - 0.5 * stepsizeY;\n" +
+            "  nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" +
+            "  coord.x = v_texcoord.x + stepsizeX;\n" +
+            "  coord.y = v_texcoord.y + 0.5 * stepsizeY;\n" +
+            "  nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" +
+            "  vec3 s_color = vec3(color.rgb + 0.3 * nbr_color);\n" +
+            // cross process
+            "  vec3 c_color = vec3(0.0, 0.0, 0.0);\n" +
+            "  float value;\n" +
+            "  if (s_color.r < 0.5) {\n" +
+            "    value = s_color.r;\n" +
+            "  } else {\n" +
+            "    value = 1.0 - s_color.r;\n" +
+            "  }\n" +
+            "  float red = 4.0 * value * value * value;\n" +
+            "  if (s_color.r < 0.5) {\n" +
+            "    c_color.r = red;\n" +
+            "  } else {\n" +
+            "    c_color.r = 1.0 - red;\n" +
+            "  }\n" +
+            "  if (s_color.g < 0.5) {\n" +
+            "    value = s_color.g;\n" +
+            "  } else {\n" +
+            "    value = 1.0 - s_color.g;\n" +
+            "  }\n" +
+            "  float green = 2.0 * value * value;\n" +
+            "  if (s_color.g < 0.5) {\n" +
+            "    c_color.g = green;\n" +
+            "  } else {\n" +
+            "    c_color.g = 1.0 - green;\n" +
+            "  }\n" +
+            "  c_color.b = s_color.b * 0.5 + 0.25;\n" +
+            // blackwhite
+            "  float dither = texture2D(tex_sampler_1, v_texcoord).r;\n" +
+            "  vec3 xform = clamp((c_color.rgb - 0.15) * 1.53846, 0.0, 1.0);\n" +
+            "  vec3 temp = clamp((color.rgb + stepsize - 0.15) * 1.53846, 0.0, 1.0);\n" +
+            "  vec3 bw_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n" +
+            // vignette
+            "  float dist = distance(gl_FragCoord.xy, center);\n" +
+            "  float lumen = 0.85 / (1.0 + exp((dist * inv_max_dist - 0.73) * 20.0)) + 0.15;\n" +
+            "  gl_FragColor = vec4(bw_color * lumen, color.a);\n" +
+            "}\n";
+
+    public LomoishFilter(String name) {
+        super(name);
+
+        mRandom = new Random();
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    @Override
+    public void tearDown(FilterContext context) {
+        if (mNoiseFrame != null) {
+            mNoiseFrame.release();
+            mNoiseFrame = null;
+        }
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mLomoishShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    private void initParameters() {
+        if (mProgram !=null) {
+            float centerX = (float) (0.5 * mWidth);
+            float centerY = (float) (0.5 * mHeight);
+            float center[] = {centerX, centerY};
+            float max_dist = (float) Math.sqrt(centerX * centerX + centerY * centerY);
+
+            mProgram.setHostValue("center", center);
+            mProgram.setHostValue("inv_max_dist", 1.0f / max_dist);
+
+            mProgram.setHostValue("stepsize", 1.0f / 255.0f);
+            mProgram.setHostValue("stepsizeX", 1.0f / mWidth);
+            mProgram.setHostValue("stepsizeY", 1.0f / mHeight);
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+        }
+
+        // Check if the frame size has changed
+        if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) {
+            mWidth = inputFormat.getWidth();
+            mHeight = inputFormat.getHeight();
+
+            int[] buffer = new int[mWidth * mHeight];
+            for (int i = 0; i < mWidth * mHeight; ++i) {
+              buffer[i] = mRandom.nextInt(255);
+            }
+            FrameFormat format = ImageFormat.create(mWidth, mHeight,
+                                                    ImageFormat.COLORSPACE_RGBA,
+                                                    FrameFormat.TARGET_GPU);
+            if (mNoiseFrame != null) {
+                mNoiseFrame.release();
+            }
+            mNoiseFrame = context.getFrameManager().newFrame(format);
+            mNoiseFrame.setInts(buffer);
+
+            initParameters();
+        }
+
+        if (mNoiseFrame != null && (mNoiseFrame.getFormat().getWidth() != mWidth ||
+                                    mNoiseFrame.getFormat().getHeight() != mHeight)) {
+            throw new RuntimeException("Random map and imput image size mismatch!");
+        }
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Process
+        Frame[] inputs = {input, mNoiseFrame};
+        mProgram.process(inputs, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/NegativeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/NegativeFilter.java
new file mode 100644
index 0000000..440d6a6
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/NegativeFilter.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+
+public class NegativeFilter extends Filter {
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private final String mNegativeShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  gl_FragColor = vec4(1.0 - color.rgb, color.a);\n" +
+            "}\n";
+
+    public NegativeFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mNegativeShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+        }
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/PosterizeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/PosterizeFilter.java
new file mode 100644
index 0000000..bc2e553
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/PosterizeFilter.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+public class PosterizeFilter extends Filter {
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private final String mPosterizeShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  vec3 pcolor;\n" +
+            "  pcolor.r = (color.r >= 0.5) ? 0.75 : 0.25;\n" +
+            "  pcolor.g = (color.g >= 0.5) ? 0.75 : 0.25;\n" +
+            "  pcolor.b = (color.b >= 0.5) ? 0.75 : 0.25;\n" +
+            "  gl_FragColor = vec4(pcolor, color.a);\n" +
+            "}\n";
+
+    public PosterizeFilter(String name) {
+      super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mPosterizeShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+        }
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java
new file mode 100644
index 0000000..5632a5e
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class RedEyeFilter extends Filter {
+
+    private static final float RADIUS_RATIO = 0.06f;
+    private static final float MIN_RADIUS = 10.0f;
+    private static final float DEFAULT_RED_INTENSITY = 1.30f;
+
+    @GenerateFieldPort(name = "centers")
+    private float[] mCenters;
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Frame mRedEyeFrame;
+    private Bitmap mRedEyeBitmap;
+
+    private final Canvas mCanvas = new Canvas();
+    private final Paint mPaint = new Paint();
+
+    private float mRadius;
+
+    private int mWidth = 0;
+    private int mHeight = 0;
+
+    private Program mProgram;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private final String mRedEyeShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform sampler2D tex_sampler_1;\n" +
+            "uniform float intensity;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  vec4 mask = texture2D(tex_sampler_1, v_texcoord);\n" +
+            "  gl_FragColor = vec4(mask.a, mask.a, mask.a, 1.0) * intensity + color * (1.0 - intensity);\n" +
+            "  if (mask.a > 0.0) {\n" +
+            "    gl_FragColor.r = 0.0;\n" +
+            "    float green_blue = color.g + color.b;\n" +
+            "    float red_intensity = color.r / green_blue;\n" +
+            "    if (red_intensity > intensity) {\n" +
+            "      color.r = 0.5 * green_blue;\n" +
+            "    }\n" +
+            "  }\n" +
+            "  gl_FragColor = color;\n" +
+            "}\n";
+
+    public RedEyeFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mRedEyeShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter RedEye does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    @Override
+    public void tearDown(FilterContext context) {
+        if (mRedEyeBitmap != null) {
+            mRedEyeBitmap.recycle();
+            mRedEyeBitmap = null;
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+        }
+
+        // Check if the frame size has changed
+        if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) {
+            mWidth = inputFormat.getWidth();
+            mHeight = inputFormat.getHeight();
+
+            createRedEyeBitmap();
+        }
+
+        createRedEyeFrame(context);
+
+        // Process
+        Frame[] inputs = {input, mRedEyeFrame};
+        mProgram.process(inputs, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+
+        // Release unused frame
+        mRedEyeFrame.release();
+        mRedEyeFrame = null;
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+         if (mProgram != null) {
+            updateProgramParams();
+        }
+    }
+
+    private void createRedEyeBitmap() {
+        if (mRedEyeBitmap != null) {
+            mRedEyeBitmap.recycle();
+        }
+
+        int bitmapWidth = mWidth / 2;
+        int bitmapHeight = mHeight / 2;
+
+        mRedEyeBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
+        mCanvas.setBitmap(mRedEyeBitmap);
+        mPaint.setColor(Color.WHITE);
+        mRadius = Math.max(MIN_RADIUS, RADIUS_RATIO * Math.min(bitmapWidth, bitmapHeight));
+
+        updateProgramParams();
+    }
+
+    private void createRedEyeFrame(FilterContext context) {
+        FrameFormat format = ImageFormat.create(mRedEyeBitmap.getWidth() ,
+                                                mRedEyeBitmap.getHeight(),
+                                                ImageFormat.COLORSPACE_RGBA,
+                                                FrameFormat.TARGET_GPU);
+        mRedEyeFrame = context.getFrameManager().newFrame(format);
+        mRedEyeFrame.setBitmap(mRedEyeBitmap);
+    }
+
+    private void updateProgramParams() {
+        mProgram.setHostValue("intensity", DEFAULT_RED_INTENSITY);
+
+        if ( mCenters.length % 2 == 1) {
+            throw new RuntimeException("The size of center array must be even.");
+        }
+
+        if (mRedEyeBitmap != null) {
+            for (int i = 0; i < mCenters.length; i += 2) {
+                mCanvas.drawCircle(mCenters[i] * mRedEyeBitmap.getWidth(),
+                                   mCenters[i + 1] * mRedEyeBitmap.getHeight(),
+                                   mRadius, mPaint);
+            }
+        }
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ResizeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ResizeFilter.java
new file mode 100644
index 0000000..411e061
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ResizeFilter.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import android.opengl.GLES20;
+
+/**
+ * @hide
+ */
+public class ResizeFilter extends Filter {
+
+    @GenerateFieldPort(name = "owidth")
+    private int mOWidth;
+    @GenerateFieldPort(name = "oheight")
+    private int mOHeight;
+    @GenerateFieldPort(name = "keepAspectRatio", hasDefault = true)
+    private boolean mKeepAspectRatio = false;
+    @GenerateFieldPort(name = "generateMipMap", hasDefault = true)
+    private boolean mGenerateMipMap = false;
+
+    private Program mProgram;
+    private FrameFormat mLastFormat = null;
+
+    private MutableFrameFormat mOutputFormat;
+    private int mInputChannels;
+
+    public ResizeFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    protected void createProgram(FilterContext context, FrameFormat format) {
+        if (mLastFormat != null && mLastFormat.getTarget() == format.getTarget()) return;
+        mLastFormat = format;
+        switch (format.getTarget()) {
+            case FrameFormat.TARGET_NATIVE:
+                throw new RuntimeException("Native ResizeFilter not implemented yet!");
+
+
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram prog = ShaderProgram.createIdentity(context);
+                mProgram = prog;
+                break;
+
+            default:
+                throw new RuntimeException("ResizeFilter could not create suitable program!");
+        }
+    }
+    @Override
+    public void process(FilterContext env) {
+        // Get input frame
+        Frame input = pullInput("image");
+        createProgram(env, input.getFormat());
+
+        // Create output frame
+        MutableFrameFormat outputFormat = input.getFormat().mutableCopy();
+        if (mKeepAspectRatio) {
+            FrameFormat inputFormat = input.getFormat();
+            mOHeight = mOWidth * inputFormat.getHeight() / inputFormat.getWidth();
+        }
+        outputFormat.setDimensions(mOWidth, mOHeight);
+        Frame output = env.getFrameManager().newFrame(outputFormat);
+
+        // Process
+        if (mGenerateMipMap) {
+            GLFrame mipmapped = (GLFrame)env.getFrameManager().newFrame(input.getFormat());
+            mipmapped.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER,
+                                          GLES20.GL_LINEAR_MIPMAP_NEAREST);
+            mipmapped.setDataFromFrame(input);
+            mipmapped.generateMipMap();
+            mProgram.process(mipmapped, output);
+            mipmapped.release();
+        } else {
+            mProgram.process(input, output);
+        }
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/RotateFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/RotateFilter.java
new file mode 100644
index 0000000..3da7939
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/RotateFilter.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+import android.filterfw.geometry.Quad;
+import android.filterfw.geometry.Point;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class RotateFilter extends Filter {
+
+    @GenerateFieldPort(name = "angle")
+    private int mAngle;
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+
+    private int mWidth = 0;
+    private int mHeight = 0;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private int mOutputWidth;
+    private int mOutputHeight;
+
+    public RotateFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = ShaderProgram.createIdentity(context);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                shaderProgram.setClearsOutput(true);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mProgram != null) {
+            updateParameters();
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+        }
+
+        if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) {
+            mWidth = inputFormat.getWidth();
+            mHeight = inputFormat.getHeight();
+            mOutputWidth = mWidth;
+            mOutputHeight = mHeight;
+
+            updateParameters();
+        }
+
+        // Create output frame
+        FrameFormat outputFormat = ImageFormat.create(mOutputWidth, mOutputHeight,
+                                                      ImageFormat.COLORSPACE_RGBA,
+                                                      FrameFormat.TARGET_GPU);
+
+        Frame output = context.getFrameManager().newFrame(outputFormat);
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    private void updateParameters() {
+        float sinTheta;
+        float cosTheta;
+
+        if (mAngle % 90 == 0) {
+            if (mAngle % 180 == 0) {
+                sinTheta = 0f;
+                cosTheta = (mAngle % 360 == 0) ? 1f:-1f;
+            } else {
+                cosTheta = 0f;
+                sinTheta = ((mAngle + 90) % 360 == 0) ? -1f:1f;
+
+                mOutputWidth = mHeight;
+                mOutputHeight = mWidth;
+            }
+        } else {
+            throw new RuntimeException("degree has to be multiply of 90.");
+        }
+
+        Point x0 = new Point(0.5f * (-cosTheta + sinTheta + 1f),
+                             0.5f * (-sinTheta - cosTheta + 1f));
+        Point x1 = new Point(0.5f * (cosTheta + sinTheta + 1f),
+                             0.5f * (sinTheta - cosTheta + 1f));
+        Point x2 = new Point(0.5f * (-cosTheta - sinTheta + 1f),
+                             0.5f * (-sinTheta + cosTheta + 1f));
+        Point x3 = new Point(0.5f * (cosTheta - sinTheta + 1f),
+                             0.5f * (sinTheta + cosTheta + 1f));
+        Quad quad = new Quad(x0, x1, x2, x3);
+        ((ShaderProgram) mProgram).setTargetRegion(quad);
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/SaturateFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/SaturateFilter.java
new file mode 100644
index 0000000..b83af39
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/SaturateFilter.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+public class SaturateFilter extends Filter {
+
+    @GenerateFieldPort(name = "scale", hasDefault = true)
+    private float mScale = 0f;
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mBenProgram;
+    private Program mHerfProgram;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private final String mBenSaturateShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform float scale;\n" +
+            "uniform float shift;\n" +
+            "uniform vec3 weights;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  float kv = dot(color.rgb, weights) + shift;\n" +
+            "  vec3 new_color = scale * color.rgb + (1.0 - scale) * kv;\n" +
+            "  gl_FragColor = vec4(new_color, color.a);\n" +
+            "}\n";
+
+    private final String mHerfSaturateShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform vec3 weights;\n" +
+            "uniform vec3 exponents;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  float de = dot(color.rgb, weights);\n" +
+            "  float inv_de = 1.0 / de;\n" +
+            "  vec3 new_color = de * pow(color.rgb * inv_de, exponents);\n" +
+            "  float max_color = max(max(max(new_color.r, new_color.g), new_color.b), 1.0);\n" +
+            "  gl_FragColor = vec4(new_color / max_color, color.a);\n" +
+            "}\n";
+
+
+    public SaturateFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mBenSaturateShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mBenProgram = shaderProgram;
+
+                shaderProgram = new ShaderProgram(context, mHerfSaturateShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mHerfProgram = shaderProgram;
+
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mBenProgram != null && mHerfProgram != null) {
+            updateParameters();
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create program if not created already
+        if (mBenProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+            initParameters();
+        }
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Process
+        if (mScale > 0.0f) {
+            mHerfProgram.process(input, output);
+        } else {
+            mBenProgram.process(input, output);
+        }
+         // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    private void initParameters() {
+        float shift = 1.0f / 255.0f;
+        float weights[] = { 2f/8f, 5f/8f, 1f/8f};
+
+        mBenProgram.setHostValue("weights", weights);
+        mBenProgram.setHostValue("shift", shift);
+
+        mHerfProgram.setHostValue("weights", weights);
+
+        updateParameters();
+    }
+
+    private void updateParameters() {
+
+        if (mScale > 0.0f) {
+            float exponents[] = new float[3];
+
+            exponents[0] = (0.9f * mScale) + 1.0f;
+            exponents[1] = (2.1f * mScale) + 1.0f;
+            exponents[2] = (2.7f * mScale) + 1.0f;
+
+            mHerfProgram.setHostValue("exponents", exponents);
+        } else {
+            mBenProgram.setHostValue("scale", 1.0f + mScale);
+        }
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/SepiaFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/SepiaFilter.java
new file mode 100644
index 0000000..7a83fdf
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/SepiaFilter.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+public class SepiaFilter extends Filter {
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private final String mSepiaShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform mat3 matrix;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  vec3 new_color = min(matrix * color.rgb, 1.0);\n" +
+            "  gl_FragColor = vec4(new_color.rgb, color.a);\n" +
+            "}\n";
+
+    public SepiaFilter(String name) {
+      super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mSepiaShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+   @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+            initParameters();
+        }
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    private void initParameters() {
+        float weights[] = { 805.0f / 2048.0f, 715.0f / 2048.0f, 557.0f / 2048.0f,
+                            1575.0f / 2048.0f, 1405.0f / 2048.0f, 1097.0f / 2048.0f,
+                            387.0f / 2048.0f, 344.0f / 2048.0f, 268.0f / 2048.0f };
+        mProgram.setHostValue("matrix", weights);
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/SharpenFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/SharpenFilter.java
new file mode 100644
index 0000000..256b769
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/SharpenFilter.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import java.util.Set;
+
+public class SharpenFilter extends Filter {
+
+    @GenerateFieldPort(name = "scale", hasDefault = true)
+    private float mScale = 0f;
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+
+    private int mWidth = 0;
+    private int mHeight = 0;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private final String mSharpenShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform float scale;\n" +
+            "uniform float stepsizeX;\n" +
+            "uniform float stepsizeY;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec3 nbr_color = vec3(0.0, 0.0, 0.0);\n" +
+            "  vec2 coord;\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  coord.x = v_texcoord.x - 0.5 * stepsizeX;\n" +
+            "  coord.y = v_texcoord.y - stepsizeY;\n" +
+            "  nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" +
+            "  coord.x = v_texcoord.x - stepsizeX;\n" +
+            "  coord.y = v_texcoord.y + 0.5 * stepsizeY;\n" +
+            "  nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" +
+            "  coord.x = v_texcoord.x + stepsizeX;\n" +
+            "  coord.y = v_texcoord.y - 0.5 * stepsizeY;\n" +
+            "  nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" +
+            "  coord.x = v_texcoord.x + stepsizeX;\n" +
+            "  coord.y = v_texcoord.y + 0.5 * stepsizeY;\n" +
+            "  nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" +
+            "  gl_FragColor = vec4(color.rgb - 2.0 * scale * nbr_color, color.a);\n" +
+            "}\n";
+
+    public SharpenFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mSharpenShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+        }
+
+        // Check if the frame size has changed
+        if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) {
+            updateFrameSize(inputFormat.getWidth(), inputFormat.getHeight());
+        }
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    private void updateFrameSize(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+
+        if (mProgram != null) {
+            mProgram.setHostValue("stepsizeX", 1.0f / mWidth);
+            mProgram.setHostValue("stepsizeY", 1.0f / mHeight);
+            updateParameters();
+        }
+    }
+
+    private void updateParameters() {
+        mProgram.setHostValue("scale", mScale);
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mProgram != null) {
+            updateParameters();
+        }
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/SimpleImageFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/SimpleImageFilter.java
new file mode 100644
index 0000000..f4fc271e
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/SimpleImageFilter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import java.lang.reflect.Field;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public abstract class SimpleImageFilter extends Filter {
+
+    protected int mCurrentTarget = FrameFormat.TARGET_UNSPECIFIED;
+    protected Program mProgram;
+    protected String mParameterName;
+
+    public SimpleImageFilter(String name, String parameterName) {
+        super(name);
+        mParameterName = parameterName;
+    }
+
+    @Override
+    public void setupPorts() {
+        if (mParameterName != null) {
+            try {
+                Field programField = SimpleImageFilter.class.getDeclaredField("mProgram");
+                addProgramPort(mParameterName, mParameterName, programField, float.class, false);
+            } catch (NoSuchFieldException e) {
+                throw new RuntimeException("Internal Error: mProgram field not found!");
+            }
+        }
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Create program if not created already
+        updateProgramWithTarget(inputFormat.getTarget(), context);
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    protected void updateProgramWithTarget(int target, FilterContext context) {
+        if (target != mCurrentTarget) {
+            switch (target) {
+                case FrameFormat.TARGET_NATIVE:
+                    mProgram = getNativeProgram(context);
+                    break;
+
+                case FrameFormat.TARGET_GPU:
+                    mProgram = getShaderProgram(context);
+                    break;
+
+                default:
+                    mProgram = null;
+                    break;
+            }
+            if (mProgram == null) {
+                throw new RuntimeException("Could not create a program for image filter " + this + "!");
+            }
+            initProgramInputs(mProgram, context);
+            mCurrentTarget = target;
+        }
+    }
+
+    protected abstract Program getNativeProgram(FilterContext context);
+
+    protected abstract Program getShaderProgram(FilterContext context);
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/StraightenFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/StraightenFilter.java
new file mode 100644
index 0000000..c9f097d
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/StraightenFilter.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+import android.filterfw.geometry.Quad;
+import android.filterfw.geometry.Point;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class StraightenFilter extends Filter {
+
+    @GenerateFieldPort(name = "angle", hasDefault = true)
+    private float mAngle = 0f;
+
+    @GenerateFieldPort(name = "maxAngle", hasDefault = true)
+    private float mMaxAngle = 45f;
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+
+    private int mWidth = 0;
+    private int mHeight = 0;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private static final float DEGREE_TO_RADIAN = (float) Math.PI / 180.0f;
+
+    public StraightenFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = ShaderProgram.createIdentity(context);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mProgram != null) {
+            updateParameters();
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+        }
+
+        // Create output frame
+        if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) {
+            mWidth = inputFormat.getWidth();
+            mHeight = inputFormat.getHeight();
+            updateParameters();
+        }
+
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    private void updateParameters() {
+        float cosTheta = (float) Math.cos(mAngle * DEGREE_TO_RADIAN);
+        float sinTheta = (float) Math.sin(mAngle * DEGREE_TO_RADIAN);
+
+        if (mMaxAngle <= 0)
+            throw new RuntimeException("Max angle is out of range (0-180).");
+        mMaxAngle = (mMaxAngle > 90) ? 90 : mMaxAngle;
+
+        Point p0 = new Point(-cosTheta * mWidth + sinTheta * mHeight,
+                             -sinTheta * mWidth - cosTheta * mHeight);
+
+        Point p1 = new Point(cosTheta * mWidth + sinTheta * mHeight,
+                             sinTheta * mWidth - cosTheta * mHeight);
+
+        Point p2 = new Point(-cosTheta * mWidth - sinTheta * mHeight,
+                             -sinTheta * mWidth + cosTheta * mHeight);
+
+        Point p3 = new Point(cosTheta * mWidth - sinTheta * mHeight,
+                             sinTheta * mWidth + cosTheta * mHeight);
+
+        float maxWidth = (float) Math.max(Math.abs(p0.x), Math.abs(p1.x));
+        float maxHeight = (float) Math.max(Math.abs(p0.y), Math.abs(p1.y));
+
+        float scale = 0.5f * Math.min( mWidth / maxWidth,
+                                       mHeight / maxHeight);
+
+        p0.set(scale * p0.x / mWidth + 0.5f, scale * p0.y / mHeight + 0.5f);
+        p1.set(scale * p1.x / mWidth + 0.5f, scale * p1.y / mHeight + 0.5f);
+        p2.set(scale * p2.x / mWidth + 0.5f, scale * p2.y / mHeight + 0.5f);
+        p3.set(scale * p3.x / mWidth + 0.5f, scale * p3.y / mHeight + 0.5f);
+
+        Quad quad = new Quad(p0, p1, p2, p3);
+        ((ShaderProgram) mProgram).setSourceRegion(quad);
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/TintFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/TintFilter.java
new file mode 100644
index 0000000..0da54a5
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/TintFilter.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+import android.graphics.Color;
+
+public class TintFilter extends Filter {
+
+    @GenerateFieldPort(name = "tint", hasDefault = true)
+    private int mTint = 0xFF0000FF;
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private final String mTintShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform vec3 tint;\n" +
+            "uniform vec3 color_ratio;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  float avg_color = dot(color_ratio, color.rgb);\n" +
+            "  vec3 new_color = min(0.8 * avg_color + 0.2 * tint, 1.0);\n" +
+            "  gl_FragColor = vec4(new_color.rgb, color.a);\n" +
+            "}\n";
+
+    public TintFilter(String name) {
+      super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mTintShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mProgram != null) {
+            updateParameters();
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+            initParameters();
+        }
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+    private void initParameters() {
+        float color_ratio[] = {0.21f, 0.71f, 0.07f};
+        mProgram.setHostValue("color_ratio", color_ratio);
+
+        updateParameters();
+    }
+
+    private void updateParameters() {
+        float tint_color[] = {Color.red(mTint) / 255f,
+                              Color.green(mTint) / 255f,
+                              Color.blue(mTint) / 255f };
+
+        mProgram.setHostValue("tint", tint_color);
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToGrayFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToGrayFilter.java
new file mode 100644
index 0000000..00e7bf4
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToGrayFilter.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import android.util.Log;
+
+import java.lang.reflect.Field;
+
+/**
+ * @hide
+ */
+public class ToGrayFilter extends SimpleImageFilter {
+
+    @GenerateFieldPort(name = "invertSource", hasDefault = true)
+    private boolean mInvertSource = false;
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private MutableFrameFormat mOutputFormat;
+
+    private static final String mColorToGray4Shader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  float y = dot(color, vec4(0.299, 0.587, 0.114, 0));\n" +
+            "  gl_FragColor = vec4(y, y, y, color.a);\n" +
+            "}\n";
+
+    public ToGrayFilter(String name) {
+        super(name, null);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
+                                                       FrameFormat.TARGET_GPU));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    protected Program getNativeProgram(FilterContext context) {
+        throw new RuntimeException("Native toGray not implemented yet!");
+    }
+
+    @Override
+    protected Program getShaderProgram(FilterContext context) {
+        int inputChannels = getInputFormat("image").getBytesPerSample();
+        if (inputChannels != 4) {
+            throw new RuntimeException("Unsupported GL input channels: " +
+                                       inputChannels + "! Channels must be 4!");
+        }
+        ShaderProgram program = new ShaderProgram(context, mColorToGray4Shader);
+        program.setMaximumTileSize(mTileSize);
+        if (mInvertSource)
+            program.setSourceRect(0.0f, 1.0f, 1.0f, -1.0f);
+        return program;
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToPackedGrayFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToPackedGrayFilter.java
new file mode 100644
index 0000000..bc4a65e
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToPackedGrayFilter.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import android.util.Log;
+
+import java.lang.Math;
+/**
+ * @hide
+ */
+public class ToPackedGrayFilter extends Filter {
+
+    @GenerateFieldPort(name = "owidth", hasDefault = true)
+    private int mOWidth = FrameFormat.SIZE_UNSPECIFIED;
+    @GenerateFieldPort(name = "oheight", hasDefault = true)
+    private int mOHeight = FrameFormat.SIZE_UNSPECIFIED;
+    @GenerateFieldPort(name = "keepAspectRatio", hasDefault = true)
+    private boolean mKeepAspectRatio = false;
+
+    private Program mProgram;
+
+    private final String mColorToPackedGrayShader =
+        "precision mediump float;\n" +
+        "const vec4 coeff_y = vec4(0.299, 0.587, 0.114, 0);\n" +
+        "uniform sampler2D tex_sampler_0;\n" +
+        "uniform float pix_stride;\n" +
+        "varying vec2 v_texcoord;\n" +
+        "void main() {\n" +
+        "  for (int i = 0; i < 4; ++i) {\n" +
+        "    vec4 p = texture2D(tex_sampler_0,\n" +
+        "                       v_texcoord + vec2(pix_stride * float(i), 0.0));\n" +
+        "    gl_FragColor[i] = dot(p, coeff_y);\n" +
+        "  }\n" +
+        "}\n";
+
+    public ToPackedGrayFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
+                                                       FrameFormat.TARGET_GPU));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return convertInputFormat(inputFormat);
+    }
+
+    private void checkOutputDimensions(int outputWidth, int outputHeight) {
+        if (outputWidth <= 0 || outputHeight <= 0) {
+            throw new RuntimeException("Invalid output dimensions: " +
+                                       outputWidth + " " + outputHeight);
+        }
+    }
+
+    private FrameFormat convertInputFormat(FrameFormat inputFormat) {
+        int ow = mOWidth;
+        int oh = mOHeight;
+        int w = inputFormat.getWidth();
+        int h = inputFormat.getHeight();
+        if (mOWidth == FrameFormat.SIZE_UNSPECIFIED) {
+            ow = w;
+        }
+        if (mOHeight == FrameFormat.SIZE_UNSPECIFIED) {
+            oh = h;
+        }
+        if (mKeepAspectRatio) {
+            // if keep aspect ratio, use the bigger dimension to determine the
+            // final output size
+            if (w > h) {
+                ow = Math.max(ow, oh);
+                oh = ow * h / w;
+            } else {
+                oh = Math.max(ow, oh);
+                ow = oh * w / h;
+            }
+        }
+        ow = (ow > 0 && ow < 4) ? 4 : (ow / 4) * 4; // ensure width is multiple of 4
+        return ImageFormat.create(ow, oh,
+                                  ImageFormat.COLORSPACE_GRAY,
+                                  FrameFormat.TARGET_NATIVE);
+    }
+
+    @Override
+    public void prepare(FilterContext context) {
+        mProgram = new ShaderProgram(context, mColorToPackedGrayShader);
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+        FrameFormat outputFormat = convertInputFormat(inputFormat);
+        int ow = outputFormat.getWidth();
+        int oh = outputFormat.getHeight();
+        checkOutputDimensions(ow, oh);
+        mProgram.setHostValue("pix_stride", 1.0f / ow);
+
+        // Do the RGBA to luminance conversion.
+        MutableFrameFormat tempFrameFormat = inputFormat.mutableCopy();
+        tempFrameFormat.setDimensions(ow / 4, oh);
+        Frame temp = context.getFrameManager().newFrame(tempFrameFormat);
+        mProgram.process(input, temp);
+
+        // Read frame from GPU to CPU.
+        Frame output = context.getFrameManager().newFrame(outputFormat);
+        output.setDataFromFrame(temp);
+        temp.release();
+
+        // Push output and yield ownership.
+        pushOutput("image", output);
+        output.release();
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBAFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBAFilter.java
new file mode 100644
index 0000000..ab4814f
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBAFilter.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class ToRGBAFilter extends Filter {
+
+    private int mInputBPP;
+    private Program mProgram;
+    private FrameFormat mLastFormat = null;
+
+    public ToRGBAFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        MutableFrameFormat mask = new MutableFrameFormat(FrameFormat.TYPE_BYTE,
+                                                         FrameFormat.TARGET_NATIVE);
+        mask.setDimensionCount(2);
+        addMaskedInputPort("image", mask);
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return getConvertedFormat(inputFormat);
+    }
+
+    public FrameFormat getConvertedFormat(FrameFormat format) {
+        MutableFrameFormat result = format.mutableCopy();
+        result.setMetaValue(ImageFormat.COLORSPACE_KEY, ImageFormat.COLORSPACE_RGBA);
+        result.setBytesPerSample(4);
+        return result;
+    }
+
+    public void createProgram(FilterContext context, FrameFormat format) {
+        mInputBPP = format.getBytesPerSample();
+        if (mLastFormat != null && mLastFormat.getBytesPerSample() == mInputBPP) return;
+        mLastFormat = format;
+        switch (mInputBPP) {
+            case 1:
+                mProgram = new NativeProgram("filterpack_imageproc", "gray_to_rgba");
+                break;
+            case 3:
+                mProgram = new NativeProgram("filterpack_imageproc", "rgb_to_rgba");
+                break;
+            default:
+                throw new RuntimeException("Unsupported BytesPerPixel: " + mInputBPP + "!");
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        createProgram(context, input.getFormat());
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(getConvertedFormat(input.getFormat()));
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBFilter.java
new file mode 100644
index 0000000..9258502
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBFilter.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class ToRGBFilter extends Filter {
+
+    private int mInputBPP;
+    private Program mProgram;
+    private FrameFormat mLastFormat = null;
+
+    public ToRGBFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        MutableFrameFormat mask = new MutableFrameFormat(FrameFormat.TYPE_BYTE,
+                                                         FrameFormat.TARGET_NATIVE);
+        mask.setDimensionCount(2);
+        addMaskedInputPort("image", mask);
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return getConvertedFormat(inputFormat);
+    }
+
+    public FrameFormat getConvertedFormat(FrameFormat format) {
+        MutableFrameFormat result = format.mutableCopy();
+        result.setMetaValue(ImageFormat.COLORSPACE_KEY, ImageFormat.COLORSPACE_RGB);
+        result.setBytesPerSample(3);
+        return result;
+    }
+
+    public void createProgram(FilterContext context, FrameFormat format) {
+        mInputBPP = format.getBytesPerSample();
+        if (mLastFormat != null && mLastFormat.getBytesPerSample() == mInputBPP) return;
+        mLastFormat = format;
+        switch (mInputBPP) {
+            case 1:
+                mProgram = new NativeProgram("filterpack_imageproc", "gray_to_rgb");
+                break;
+            case 4:
+                mProgram = new NativeProgram("filterpack_imageproc", "rgba_to_rgb");
+                break;
+            default:
+                throw new RuntimeException("Unsupported BytesPerPixel: " + mInputBPP + "!");
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        createProgram(context, input.getFormat());
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(getConvertedFormat(input.getFormat()));
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/VignetteFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/VignetteFilter.java
new file mode 100644
index 0000000..2d78fff
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/VignetteFilter.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.imageproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+public class VignetteFilter extends Filter {
+
+    @GenerateFieldPort(name = "scale", hasDefault = true)
+    private float mScale = 0f;
+
+    @GenerateFieldPort(name = "tile_size", hasDefault = true)
+    private int mTileSize = 640;
+
+    private Program mProgram;
+
+    private int mWidth = 0;
+    private int mHeight = 0;
+    private int mTarget = FrameFormat.TARGET_UNSPECIFIED;
+
+    private final float mSlope = 20.0f;
+    private final float mShade = 0.85f;
+
+    private final String mVignetteShader =
+            "precision mediump float;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform float range;\n" +
+            "uniform float inv_max_dist;\n" +
+            "uniform float shade;\n" +
+            "uniform vec2 center;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  const float slope = 20.0;\n" +
+            "  float dist = distance(gl_FragCoord.xy, center);\n" +
+            "  float lumen = shade / (1.0 + exp((dist * inv_max_dist - range) * slope)) + (1.0 - shade);\n" +
+            "  vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  gl_FragColor = vec4(color.rgb * lumen, color.a);\n" +
+            "}\n";
+
+    public VignetteFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+        addOutputBasedOnInput("image", "image");
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    public void initProgram(FilterContext context, int target) {
+        switch (target) {
+            case FrameFormat.TARGET_GPU:
+                ShaderProgram shaderProgram = new ShaderProgram(context, mVignetteShader);
+                shaderProgram.setMaximumTileSize(mTileSize);
+                mProgram = shaderProgram;
+                break;
+
+            default:
+                throw new RuntimeException("Filter Sharpen does not support frames of " +
+                    "target " + target + "!");
+        }
+        mTarget = target;
+    }
+
+    private void initParameters() {
+        if (mProgram != null) {
+            float centerX = (float) (0.5 * mWidth);
+            float centerY = (float) (0.5 * mHeight);
+            float center[] = {centerX, centerY};
+            float max_dist = (float) Math.sqrt(centerX * centerX + centerY * centerY);
+
+            mProgram.setHostValue("center", center);
+            mProgram.setHostValue("inv_max_dist", 1.0f / max_dist);
+            mProgram.setHostValue("shade", mShade);
+
+            updateParameters();
+        }
+    }
+
+    private void updateParameters() {
+        // The 'range' is between 1.3 to 0.6. When scale is zero then range is 1.3
+        // which means no vignette at all because the luminousity difference is
+        // less than 1/256 and will cause nothing.
+        mProgram.setHostValue("range", 1.30f - (float) Math.sqrt(mScale) * 0.7f);
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mProgram != null) {
+            updateParameters();
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Get input frame
+        Frame input = pullInput("image");
+        FrameFormat inputFormat = input.getFormat();
+
+        // Create program if not created already
+        if (mProgram == null || inputFormat.getTarget() != mTarget) {
+            initProgram(context, inputFormat.getTarget());
+        }
+
+        // Check if the frame size has changed
+        if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) {
+            mWidth = inputFormat.getWidth();
+            mHeight = inputFormat.getHeight();
+            initParameters();
+        }
+
+        // Create output frame
+        Frame output = context.getFrameManager().newFrame(inputFormat);
+
+        // Process
+        mProgram.process(input, output);
+
+        // Push output
+        pushOutput("image", output);
+
+        // Release pushed frame
+        output.release();
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/package-info.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/package-info.java
new file mode 100644
index 0000000..1cf48b0
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * @hide
+ */
+package android.filterpacks.imageproc;
diff --git a/media/mca/filterpacks/java/android/filterpacks/numeric/SinWaveFilter.java b/media/mca/filterpacks/java/android/filterpacks/numeric/SinWaveFilter.java
new file mode 100644
index 0000000..7e2b4cf
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/numeric/SinWaveFilter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.numeric;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.format.ObjectFormat;
+
+import java.lang.Math;
+
+/**
+ * @hide
+ */
+public class SinWaveFilter extends Filter {
+
+    @GenerateFieldPort(name = "stepSize", hasDefault = true)
+    private float mStepSize = 0.05f;
+
+    private float mValue = 0.0f;
+
+    private FrameFormat mOutputFormat;
+
+    public SinWaveFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        mOutputFormat = ObjectFormat.fromClass(Float.class, FrameFormat.TARGET_SIMPLE);
+        addOutputPort("value", mOutputFormat);
+    }
+
+    @Override
+    public void open(FilterContext env) {
+        mValue = 0.0f;
+    }
+
+    @Override
+    public void process(FilterContext env) {
+        Frame output = env.getFrameManager().newFrame(mOutputFormat);
+        output.setObjectValue(((float)Math.sin(mValue) + 1.0f) / 2.0f);
+        pushOutput("value", output);
+        mValue += mStepSize;
+        output.release();
+    }
+
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/numeric/package-info.java b/media/mca/filterpacks/java/android/filterpacks/numeric/package-info.java
new file mode 100644
index 0000000..55088eb
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/numeric/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * @hide
+ */
+package android.filterpacks.numeric;
diff --git a/media/mca/filterpacks/java/android/filterpacks/performance/Throughput.java b/media/mca/filterpacks/java/android/filterpacks/performance/Throughput.java
new file mode 100644
index 0000000..51f29f3
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/performance/Throughput.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.performance;
+
+/**
+ * @hide
+ */
+public class Throughput {
+
+    private final int mTotalFrames;
+    private final int mPeriodFrames;
+    private final int mPeriodTime;
+    private final int mPixels;
+
+    public Throughput(int totalFrames, int periodFrames, int periodTime, int pixels) {
+        mTotalFrames = totalFrames;
+        mPeriodFrames = periodFrames;
+        mPeriodTime = periodTime;
+        mPixels = pixels;
+    }
+
+    public int getTotalFrameCount() {
+        return mTotalFrames;
+    }
+
+    public int getPeriodFrameCount() {
+        return mPeriodFrames;
+    }
+
+    public int getPeriodTime() {
+        return mPeriodTime;
+    }
+
+    public float getFramesPerSecond() {
+        return mPeriodFrames / (float)mPeriodTime;
+    }
+
+    public float getNanosPerPixel() {
+        double frameTimeInNanos = (mPeriodTime / (double)mPeriodFrames) * 1000000.0;
+        return (float)(frameTimeInNanos / mPixels);
+    }
+
+    public String toString() {
+        return getFramesPerSecond() + " FPS";
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/performance/ThroughputFilter.java b/media/mca/filterpacks/java/android/filterpacks/performance/ThroughputFilter.java
new file mode 100644
index 0000000..ac837ed
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/performance/ThroughputFilter.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.performance;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.format.ObjectFormat;
+import android.os.SystemClock;
+
+/**
+ * @hide
+ */
+public class ThroughputFilter extends Filter {
+
+    @GenerateFieldPort(name = "period", hasDefault = true)
+    private int mPeriod = 5;
+
+    private long mLastTime = 0;
+
+    private int mTotalFrameCount = 0;
+    private int mPeriodFrameCount = 0;
+
+    private FrameFormat mOutputFormat;
+
+    public ThroughputFilter(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        // Add input ports
+        addInputPort("frame");
+
+        // Add output ports
+        mOutputFormat = ObjectFormat.fromClass(Throughput.class, FrameFormat.TARGET_SIMPLE);
+        addOutputBasedOnInput("frame", "frame");
+        addOutputPort("throughput", mOutputFormat);
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        return inputFormat;
+    }
+
+    @Override
+    public void open(FilterContext env) {
+        mTotalFrameCount = 0;
+        mPeriodFrameCount = 0;
+        mLastTime = 0;
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Pass through input frame
+        Frame input = pullInput("frame");
+        pushOutput("frame", input);
+
+        // Update stats
+        ++mTotalFrameCount;
+        ++mPeriodFrameCount;
+
+        // Check clock
+        if (mLastTime == 0) {
+            mLastTime = SystemClock.elapsedRealtime();
+        }
+        long curTime = SystemClock.elapsedRealtime();
+
+        // Output throughput info if time period is up
+        if ((curTime - mLastTime) >= (mPeriod * 1000)) {
+            FrameFormat inputFormat = input.getFormat();
+            int pixelCount = inputFormat.getWidth() * inputFormat.getHeight();
+            Throughput throughput = new Throughput(mTotalFrameCount,
+                                                   mPeriodFrameCount,
+                                                   mPeriod,
+                                                   pixelCount);
+            Frame throughputFrame = context.getFrameManager().newFrame(mOutputFormat);
+            throughputFrame.setObjectValue(throughput);
+            pushOutput("throughput", throughputFrame);
+            mLastTime = curTime;
+            mPeriodFrameCount = 0;
+        }
+    }
+
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/performance/package-info.java b/media/mca/filterpacks/java/android/filterpacks/performance/package-info.java
new file mode 100644
index 0000000..8b77bbb
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/performance/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * @hide
+ */
+package android.filterpacks.performance;
diff --git a/media/mca/filterpacks/java/android/filterpacks/text/StringLogger.java b/media/mca/filterpacks/java/android/filterpacks/text/StringLogger.java
new file mode 100644
index 0000000..8c7cd69
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/text/StringLogger.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.text;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.format.ObjectFormat;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class StringLogger extends Filter {
+
+    public StringLogger(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        addMaskedInputPort("string", ObjectFormat.fromClass(Object.class,
+                                                            FrameFormat.TARGET_SIMPLE));
+    }
+
+    @Override
+    public void process(FilterContext env) {
+        Frame input = pullInput("string");
+        String inputString = input.getObjectValue().toString();
+        Log.i("StringLogger", inputString);
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/text/StringSource.java b/media/mca/filterpacks/java/android/filterpacks/text/StringSource.java
new file mode 100644
index 0000000..cc33b89
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/text/StringSource.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.text;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.format.ObjectFormat;
+
+/**
+ * @hide
+ */
+public class StringSource extends Filter {
+
+    @GenerateFieldPort(name = "stringValue")
+    private String mString;
+
+    private FrameFormat mOutputFormat;
+
+    public StringSource(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        mOutputFormat = ObjectFormat.fromClass(String.class, FrameFormat.TARGET_SIMPLE);
+        addOutputPort("string", mOutputFormat);
+    }
+
+    @Override
+    public void process(FilterContext env) {
+        Frame output = env.getFrameManager().newFrame(mOutputFormat);
+        output.setObjectValue(mString);
+        output.setTimestamp(Frame.TIMESTAMP_UNKNOWN);
+        pushOutput("string", output);
+        closeOutputPort("string");
+    }
+
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/text/ToUpperCase.java b/media/mca/filterpacks/java/android/filterpacks/text/ToUpperCase.java
new file mode 100644
index 0000000..0cf3477
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/text/ToUpperCase.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.text;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.format.ObjectFormat;
+
+/**
+ * @hide
+ */
+public class ToUpperCase extends Filter {
+
+    private FrameFormat mOutputFormat;
+
+    public ToUpperCase(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setupPorts() {
+        mOutputFormat = ObjectFormat.fromClass(String.class, FrameFormat.TARGET_SIMPLE);
+        addMaskedInputPort("mixedcase", mOutputFormat);
+        addOutputPort("uppercase", mOutputFormat);
+    }
+
+    @Override
+    public void process(FilterContext env) {
+        Frame input = pullInput("mixedcase");
+        String inputString = (String)input.getObjectValue();
+
+        Frame output = env.getFrameManager().newFrame(mOutputFormat);
+        output.setObjectValue(inputString.toUpperCase());
+
+        pushOutput("uppercase", output);
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/text/package-info.java b/media/mca/filterpacks/java/android/filterpacks/text/package-info.java
new file mode 100644
index 0000000..371d3c1
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/text/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * @hide
+ */
+package android.filterpacks.text;
diff --git a/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceRenderFilter.java b/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceRenderFilter.java
new file mode 100644
index 0000000..a5c1ccb
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceRenderFilter.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.ui;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.FilterSurfaceView;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GenerateFinalPort;
+import android.filterfw.core.GLEnvironment;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import android.graphics.Rect;
+
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class SurfaceRenderFilter extends Filter implements SurfaceHolder.Callback {
+
+    private final int RENDERMODE_STRETCH   = 0;
+    private final int RENDERMODE_FIT       = 1;
+    private final int RENDERMODE_FILL_CROP = 2;
+
+    /** Required. Sets the destination filter surface view for this
+     * node.
+     */
+    @GenerateFinalPort(name = "surfaceView")
+    private FilterSurfaceView mSurfaceView;
+
+    /** Optional. Control how the incoming frames are rendered onto the
+     * output. Default is FIT.
+     * RENDERMODE_STRETCH: Just fill the output surfaceView.
+     * RENDERMODE_FIT: Keep aspect ratio and fit without cropping. May
+     * have black bars.
+     * RENDERMODE_FILL_CROP: Keep aspect ratio and fit without black
+     * bars. May crop.
+     */
+    @GenerateFieldPort(name = "renderMode", hasDefault = true)
+    private String mRenderModeString;
+
+    private boolean mIsBound = false;
+
+    private ShaderProgram mProgram;
+    private GLFrame mScreen;
+    private int mRenderMode = RENDERMODE_FIT;
+    private float mAspectRatio = 1.f;
+
+    private int mScreenWidth;
+    private int mScreenHeight;
+
+    private boolean mLogVerbose;
+    private static final String TAG = "SurfaceRenderFilter";
+
+    public SurfaceRenderFilter(String name) {
+        super(name);
+
+        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    }
+
+    @Override
+    public void setupPorts() {
+        // Make sure we have a SurfaceView
+        if (mSurfaceView == null) {
+            throw new RuntimeException("NULL SurfaceView passed to SurfaceRenderFilter");
+        }
+
+        // Add input port
+        addMaskedInputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+    }
+
+    public void updateRenderMode() {
+        if (mRenderModeString != null) {
+            if (mRenderModeString.equals("stretch")) {
+                mRenderMode = RENDERMODE_STRETCH;
+            } else if (mRenderModeString.equals("fit")) {
+                mRenderMode = RENDERMODE_FIT;
+            } else if (mRenderModeString.equals("fill_crop")) {
+                mRenderMode = RENDERMODE_FILL_CROP;
+            } else {
+                throw new RuntimeException("Unknown render mode '" + mRenderModeString + "'!");
+            }
+        }
+        updateTargetRect();
+    }
+
+    @Override
+    public void prepare(FilterContext context) {
+        // Create identity shader to render, and make sure to render upside-down, as textures
+        // are stored internally bottom-to-top.
+        mProgram = ShaderProgram.createIdentity(context);
+        mProgram.setSourceRect(0, 1, 1, -1);
+        mProgram.setClearsOutput(true);
+        mProgram.setClearColor(0.0f, 0.0f, 0.0f);
+
+        updateRenderMode();
+
+        // Create a frame representing the screen
+        MutableFrameFormat screenFormat = ImageFormat.create(mSurfaceView.getWidth(),
+                                                             mSurfaceView.getHeight(),
+                                                             ImageFormat.COLORSPACE_RGBA,
+                                                             FrameFormat.TARGET_GPU);
+        mScreen = (GLFrame)context.getFrameManager().newBoundFrame(screenFormat,
+                                                                   GLFrame.EXISTING_FBO_BINDING,
+                                                                   0);
+    }
+
+    @Override
+    public void open(FilterContext context) {
+        // Bind surface view to us. This will emit a surfaceCreated and surfaceChanged call that
+        // will update our screen width and height.
+        mSurfaceView.unbind();
+        mSurfaceView.bindToListener(this, context.getGLEnvironment());
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Make sure we are bound to a surface before rendering
+        if (!mIsBound) {
+            Log.w("SurfaceRenderFilter",
+                  this + ": Ignoring frame as there is no surface to render to!");
+            return;
+        }
+
+        if (mLogVerbose) Log.v(TAG, "Starting frame processing");
+
+        GLEnvironment glEnv = mSurfaceView.getGLEnv();
+        if (glEnv != context.getGLEnvironment()) {
+            throw new RuntimeException("Surface created under different GLEnvironment!");
+        }
+
+
+        // Get input frame
+        Frame input = pullInput("frame");
+        boolean createdFrame = false;
+
+        float currentAspectRatio = (float)input.getFormat().getWidth() / input.getFormat().getHeight();
+        if (currentAspectRatio != mAspectRatio) {
+            if (mLogVerbose) Log.v(TAG, "New aspect ratio: " + currentAspectRatio +", previously: " + mAspectRatio);
+            mAspectRatio = currentAspectRatio;
+            updateTargetRect();
+        }
+
+        // See if we need to copy to GPU
+        Frame gpuFrame = null;
+        if (mLogVerbose) Log.v("SurfaceRenderFilter", "Got input format: " + input.getFormat());
+        int target = input.getFormat().getTarget();
+        if (target != FrameFormat.TARGET_GPU) {
+            gpuFrame = context.getFrameManager().duplicateFrameToTarget(input,
+                                                                        FrameFormat.TARGET_GPU);
+            createdFrame = true;
+        } else {
+            gpuFrame = input;
+        }
+
+        // Activate our surface
+        glEnv.activateSurfaceWithId(mSurfaceView.getSurfaceId());
+
+        // Process
+        mProgram.process(gpuFrame, mScreen);
+
+        // And swap buffers
+        glEnv.swapBuffers();
+
+        if (createdFrame) {
+            gpuFrame.release();
+        }
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        updateTargetRect();
+    }
+
+    @Override
+    public void close(FilterContext context) {
+        mSurfaceView.unbind();
+    }
+
+    @Override
+    public void tearDown(FilterContext context) {
+        if (mScreen != null) {
+            mScreen.release();
+        }
+    }
+
+    @Override
+    public synchronized void surfaceCreated(SurfaceHolder holder) {
+        mIsBound = true;
+    }
+
+    @Override
+    public synchronized void surfaceChanged(SurfaceHolder holder,
+                                            int format,
+                                            int width,
+                                            int height) {
+        // If the screen is null, we do not care about surface changes (yet). Once we have a
+        // screen object, we need to keep track of these changes.
+        if (mScreen != null) {
+            mScreenWidth = width;
+            mScreenHeight = height;
+            mScreen.setViewport(0, 0, mScreenWidth, mScreenHeight);
+            updateTargetRect();
+        }
+    }
+
+    @Override
+    public synchronized void surfaceDestroyed(SurfaceHolder holder) {
+        mIsBound = false;
+    }
+
+    private void updateTargetRect() {
+        if (mScreenWidth > 0 && mScreenHeight > 0 && mProgram != null) {
+            float screenAspectRatio = (float)mScreenWidth / mScreenHeight;
+            float relativeAspectRatio = screenAspectRatio / mAspectRatio;
+
+            switch (mRenderMode) {
+                case RENDERMODE_STRETCH:
+                    mProgram.setTargetRect(0, 0, 1, 1);
+                    break;
+                case RENDERMODE_FIT:
+                    if (relativeAspectRatio > 1.0f) {
+                        // Screen is wider than the camera, scale down X
+                        mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f,
+                                               1.0f / relativeAspectRatio, 1.0f);
+                    } else {
+                        // Screen is taller than the camera, scale down Y
+                        mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio,
+                                               1.0f, relativeAspectRatio);
+                    }
+                    break;
+                case RENDERMODE_FILL_CROP:
+                    if (relativeAspectRatio > 1) {
+                        // Screen is wider than the camera, crop in Y
+                        mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio,
+                                               1.0f, relativeAspectRatio);
+                    } else {
+                        // Screen is taller than the camera, crop in X
+                        mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f,
+                                               1.0f / relativeAspectRatio, 1.0f);
+                    }
+                    break;
+            }
+        }
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceTargetFilter.java b/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceTargetFilter.java
new file mode 100644
index 0000000..308d168
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceTargetFilter.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.ui;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GenerateFinalPort;
+import android.filterfw.core.GLEnvironment;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import android.graphics.Rect;
+
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class SurfaceTargetFilter extends Filter {
+
+    private final int RENDERMODE_STRETCH   = 0;
+    private final int RENDERMODE_FIT       = 1;
+    private final int RENDERMODE_FILL_CROP = 2;
+
+    /** Required. Sets the destination surface for this node. This assumes that
+     * higher-level code is ensuring that the surface is valid, and properly
+     * updates Surface parameters if they change.
+     */
+    @GenerateFinalPort(name = "surface")
+    private Surface mSurface;
+
+    /** Required. Width of the output surface */
+    @GenerateFieldPort(name = "owidth")
+    private int mScreenWidth;
+
+    /** Required. Height of the output surface */
+    @GenerateFieldPort(name = "oheight")
+    private int mScreenHeight;
+
+    /** Optional. Control how the incoming frames are rendered onto the
+     * output. Default is FIT.
+     * RENDERMODE_STRETCH: Just fill the output surfaceView.
+     * RENDERMODE_FIT: Keep aspect ratio and fit without cropping. May
+     * have black bars.
+     * RENDERMODE_FILL_CROP: Keep aspect ratio and fit without black
+     * bars. May crop.
+     */
+    @GenerateFieldPort(name = "renderMode", hasDefault = true)
+    private String mRenderModeString;
+
+    private ShaderProgram mProgram;
+    private GLEnvironment mGlEnv;
+    private GLFrame mScreen;
+    private int mRenderMode = RENDERMODE_FIT;
+    private float mAspectRatio = 1.f;
+
+    private int mSurfaceId = -1;
+
+    private boolean mLogVerbose;
+    private static final String TAG = "SurfaceRenderFilter";
+
+    public SurfaceTargetFilter(String name) {
+        super(name);
+
+        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    }
+
+    @Override
+    public void setupPorts() {
+        // Make sure we have a Surface
+        if (mSurface == null) {
+            throw new RuntimeException("NULL Surface passed to SurfaceTargetFilter");
+        }
+
+        // Add input port
+        addMaskedInputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+    }
+
+    public void updateRenderMode() {
+        if (mRenderModeString != null) {
+            if (mRenderModeString.equals("stretch")) {
+                mRenderMode = RENDERMODE_STRETCH;
+            } else if (mRenderModeString.equals("fit")) {
+                mRenderMode = RENDERMODE_FIT;
+            } else if (mRenderModeString.equals("fill_crop")) {
+                mRenderMode = RENDERMODE_FILL_CROP;
+            } else {
+                throw new RuntimeException("Unknown render mode '" + mRenderModeString + "'!");
+            }
+        }
+        updateTargetRect();
+    }
+
+    @Override
+    public void prepare(FilterContext context) {
+        mGlEnv = context.getGLEnvironment();
+
+        // Create identity shader to render, and make sure to render upside-down, as textures
+        // are stored internally bottom-to-top.
+        mProgram = ShaderProgram.createIdentity(context);
+        mProgram.setSourceRect(0, 1, 1, -1);
+        mProgram.setClearsOutput(true);
+        mProgram.setClearColor(0.0f, 0.0f, 0.0f);
+
+        MutableFrameFormat screenFormat = ImageFormat.create(mScreenWidth,
+                                                             mScreenHeight,
+                                                             ImageFormat.COLORSPACE_RGBA,
+                                                             FrameFormat.TARGET_GPU);
+        mScreen = (GLFrame)context.getFrameManager().newBoundFrame(screenFormat,
+                                                                   GLFrame.EXISTING_FBO_BINDING,
+                                                                   0);
+
+        // Set up cropping
+        updateRenderMode();
+    }
+
+    @Override
+    public void open(FilterContext context) {
+        registerSurface();
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Starting frame processing");
+
+        // Get input frame
+        Frame input = pullInput("frame");
+        boolean createdFrame = false;
+
+        float currentAspectRatio = (float)input.getFormat().getWidth() / input.getFormat().getHeight();
+        if (currentAspectRatio != mAspectRatio) {
+            if (mLogVerbose) Log.v(TAG, "New aspect ratio: " + currentAspectRatio +", previously: " + mAspectRatio);
+            mAspectRatio = currentAspectRatio;
+            updateTargetRect();
+        }
+
+        // See if we need to copy to GPU
+        Frame gpuFrame = null;
+        if (mLogVerbose) Log.v("SurfaceRenderFilter", "Got input format: " + input.getFormat());
+        int target = input.getFormat().getTarget();
+        if (target != FrameFormat.TARGET_GPU) {
+            gpuFrame = context.getFrameManager().duplicateFrameToTarget(input,
+                                                                        FrameFormat.TARGET_GPU);
+            createdFrame = true;
+        } else {
+            gpuFrame = input;
+        }
+
+        // Activate our surface
+        mGlEnv.activateSurfaceWithId(mSurfaceId);
+
+        // Process
+        mProgram.process(gpuFrame, mScreen);
+
+        // And swap buffers
+        mGlEnv.swapBuffers();
+
+        if (createdFrame) {
+            gpuFrame.release();
+        }
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        mScreen.setViewport(0, 0, mScreenWidth, mScreenHeight);
+        updateTargetRect();
+    }
+
+    @Override
+    public void close(FilterContext context) {
+        unregisterSurface();
+    }
+
+    @Override
+    public void tearDown(FilterContext context) {
+        if (mScreen != null) {
+            mScreen.release();
+        }
+    }
+
+    private void updateTargetRect() {
+        if (mScreenWidth > 0 && mScreenHeight > 0 && mProgram != null) {
+            float screenAspectRatio = (float)mScreenWidth / mScreenHeight;
+            float relativeAspectRatio = screenAspectRatio / mAspectRatio;
+
+            switch (mRenderMode) {
+                case RENDERMODE_STRETCH:
+                    mProgram.setTargetRect(0, 0, 1, 1);
+                    break;
+                case RENDERMODE_FIT:
+                    if (relativeAspectRatio > 1.0f) {
+                        // Screen is wider than the camera, scale down X
+                        mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f,
+                                               1.0f / relativeAspectRatio, 1.0f);
+                    } else {
+                        // Screen is taller than the camera, scale down Y
+                        mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio,
+                                               1.0f, relativeAspectRatio);
+                    }
+                    break;
+                case RENDERMODE_FILL_CROP:
+                    if (relativeAspectRatio > 1) {
+                        // Screen is wider than the camera, crop in Y
+                        mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio,
+                                               1.0f, relativeAspectRatio);
+                    } else {
+                        // Screen is taller than the camera, crop in X
+                        mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f,
+                                               1.0f / relativeAspectRatio, 1.0f);
+                    }
+                    break;
+            }
+        }
+    }
+
+    private void registerSurface() {
+        mSurfaceId = mGlEnv.registerSurface(mSurface);
+        if (mSurfaceId < 0) {
+            throw new RuntimeException("Could not register Surface: " + mSurface);
+        }
+    }
+
+    private void unregisterSurface() {
+        if (mSurfaceId > 0) {
+            mGlEnv.unregisterSurfaceId(mSurfaceId);
+        }
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/ui/package-info.java b/media/mca/filterpacks/java/android/filterpacks/ui/package-info.java
new file mode 100644
index 0000000..9ed7d51
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/ui/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * @hide
+ */
+package android.filterpacks.ui;
diff --git a/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java b/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java
new file mode 100644
index 0000000..52c9fda
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java
@@ -0,0 +1,976 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.videoproc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GenerateFinalPort;
+import android.filterfw.core.Frame;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+import android.opengl.GLES20;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.lang.ArrayIndexOutOfBoundsException;
+import java.lang.Math;
+import java.util.Arrays;
+import java.nio.ByteBuffer;
+
+/**
+ * @hide
+ */
+public class BackDropperFilter extends Filter {
+    /** User-visible parameters */
+
+    private final int BACKGROUND_STRETCH   = 0;
+    private final int BACKGROUND_FIT       = 1;
+    private final int BACKGROUND_FILL_CROP = 2;
+
+    @GenerateFieldPort(name = "backgroundFitMode", hasDefault = true)
+    private int mBackgroundFitMode = BACKGROUND_FILL_CROP;
+    @GenerateFieldPort(name = "learningDuration", hasDefault = true)
+    private int mLearningDuration = DEFAULT_LEARNING_DURATION;
+    @GenerateFieldPort(name = "learningVerifyDuration", hasDefault = true)
+    private int mLearningVerifyDuration = DEFAULT_LEARNING_VERIFY_DURATION;
+    @GenerateFieldPort(name = "acceptStddev", hasDefault = true)
+    private float mAcceptStddev = DEFAULT_ACCEPT_STDDEV;
+    @GenerateFieldPort(name = "hierLrgScale", hasDefault = true)
+    private float mHierarchyLrgScale = DEFAULT_HIER_LRG_SCALE;
+    @GenerateFieldPort(name = "hierMidScale", hasDefault = true)
+    private float mHierarchyMidScale = DEFAULT_HIER_MID_SCALE;
+    @GenerateFieldPort(name = "hierSmlScale", hasDefault = true)
+    private float mHierarchySmlScale = DEFAULT_HIER_SML_SCALE;
+
+    // Dimensions of foreground / background mask. Optimum value should take into account only
+    // image contents, NOT dimensions of input video stream.
+    @GenerateFieldPort(name = "maskWidthExp", hasDefault = true)
+    private int mMaskWidthExp = DEFAULT_MASK_WIDTH_EXPONENT;
+    @GenerateFieldPort(name = "maskHeightExp", hasDefault = true)
+    private int mMaskHeightExp = DEFAULT_MASK_HEIGHT_EXPONENT;
+
+    // Levels at which to compute foreground / background decision. Think of them as are deltas
+    // SUBTRACTED from maskWidthExp and maskHeightExp.
+    @GenerateFieldPort(name = "hierLrgExp", hasDefault = true)
+    private int mHierarchyLrgExp = DEFAULT_HIER_LRG_EXPONENT;
+    @GenerateFieldPort(name = "hierMidExp", hasDefault = true)
+    private int mHierarchyMidExp = DEFAULT_HIER_MID_EXPONENT;
+    @GenerateFieldPort(name = "hierSmlExp", hasDefault = true)
+    private int mHierarchySmlExp = DEFAULT_HIER_SML_EXPONENT;
+
+    @GenerateFieldPort(name = "lumScale", hasDefault = true)
+    private float mLumScale = DEFAULT_Y_SCALE_FACTOR;
+    @GenerateFieldPort(name = "chromaScale", hasDefault = true)
+    private float mChromaScale = DEFAULT_UV_SCALE_FACTOR;
+    @GenerateFieldPort(name = "maskBg", hasDefault = true)
+    private float mMaskBg = DEFAULT_MASK_BLEND_BG;
+    @GenerateFieldPort(name = "maskFg", hasDefault = true)
+    private float mMaskFg = DEFAULT_MASK_BLEND_FG;
+    @GenerateFieldPort(name = "exposureChange", hasDefault = true)
+    private float mExposureChange = DEFAULT_EXPOSURE_CHANGE;
+    @GenerateFieldPort(name = "whitebalanceredChange", hasDefault = true)
+    private float mWhiteBalanceRedChange = DEFAULT_WHITE_BALANCE_RED_CHANGE;
+    @GenerateFieldPort(name = "whitebalanceblueChange", hasDefault = true)
+    private float mWhiteBalanceBlueChange = DEFAULT_WHITE_BALANCE_BLUE_CHANGE;
+    @GenerateFieldPort(name = "autowbToggle", hasDefault = true)
+    private int mAutoWBToggle = DEFAULT_WHITE_BALANCE_TOGGLE;
+
+    // TODO: These are not updatable:
+    @GenerateFieldPort(name = "learningAdaptRate", hasDefault = true)
+    private float mAdaptRateLearning = DEFAULT_LEARNING_ADAPT_RATE;
+    @GenerateFieldPort(name = "adaptRateBg", hasDefault = true)
+    private float mAdaptRateBg = DEFAULT_ADAPT_RATE_BG;
+    @GenerateFieldPort(name = "adaptRateFg", hasDefault = true)
+    private float mAdaptRateFg = DEFAULT_ADAPT_RATE_FG;
+    @GenerateFieldPort(name = "maskVerifyRate", hasDefault = true)
+    private float mVerifyRate = DEFAULT_MASK_VERIFY_RATE;
+    @GenerateFieldPort(name = "learningDoneListener", hasDefault = true)
+    private LearningDoneListener mLearningDoneListener = null;
+
+    @GenerateFieldPort(name = "useTheForce", hasDefault = true)
+    private boolean mUseTheForce = false;
+
+    @GenerateFinalPort(name = "provideDebugOutputs", hasDefault = true)
+    private boolean mProvideDebugOutputs = false;
+
+    // Whether to mirror the background or not. For ex, the Camera app
+    // would mirror the preview for the front camera
+    @GenerateFieldPort(name = "mirrorBg", hasDefault = true)
+    private boolean mMirrorBg = false;
+
+    // The orientation of the display. This will change the flipping
+    // coordinates, if we were to mirror the background
+    @GenerateFieldPort(name = "orientation", hasDefault = true)
+    private int mOrientation = 0;
+
+    /** Default algorithm parameter values, for non-shader use */
+
+    // Frame count for learning bg model
+    private static final int DEFAULT_LEARNING_DURATION = 40;
+    // Frame count for learning verification
+    private static final int DEFAULT_LEARNING_VERIFY_DURATION = 10;
+    // Maximum distance (in standard deviations) for considering a pixel as background
+    private static final float DEFAULT_ACCEPT_STDDEV = 0.85f;
+    // Variance threshold scale factor for large scale of hierarchy
+    private static final float DEFAULT_HIER_LRG_SCALE = 0.7f;
+    // Variance threshold scale factor for medium scale of hierarchy
+    private static final float DEFAULT_HIER_MID_SCALE = 0.6f;
+    // Variance threshold scale factor for small scale of hierarchy
+    private static final float DEFAULT_HIER_SML_SCALE = 0.5f;
+    // Width of foreground / background mask.
+    private static final int DEFAULT_MASK_WIDTH_EXPONENT = 8;
+    // Height of foreground / background mask.
+    private static final int DEFAULT_MASK_HEIGHT_EXPONENT = 8;
+    // Area over which to average for large scale (length in pixels = 2^HIERARCHY_*_EXPONENT)
+    private static final int DEFAULT_HIER_LRG_EXPONENT = 3;
+    // Area over which to average for medium scale
+    private static final int DEFAULT_HIER_MID_EXPONENT = 2;
+    // Area over which to average for small scale
+    private static final int DEFAULT_HIER_SML_EXPONENT = 0;
+    // Scale factor for luminance channel in distance calculations (larger = more significant)
+    private static final float DEFAULT_Y_SCALE_FACTOR = 0.40f;
+    // Scale factor for chroma channels in distance calculations
+    private static final float DEFAULT_UV_SCALE_FACTOR = 1.35f;
+    // Mask value to start blending away from background
+    private static final float DEFAULT_MASK_BLEND_BG = 0.65f;
+    // Mask value to start blending away from foreground
+    private static final float DEFAULT_MASK_BLEND_FG = 0.95f;
+    // Exposure stop number to change the brightness of foreground
+    private static final float DEFAULT_EXPOSURE_CHANGE = 1.0f;
+    // White balance change in Red channel for foreground
+    private static final float DEFAULT_WHITE_BALANCE_RED_CHANGE = 0.0f;
+    // White balance change in Blue channel for foreground
+    private static final float DEFAULT_WHITE_BALANCE_BLUE_CHANGE = 0.0f;
+    // Variable to control automatic white balance effect
+    // 0.f -> Auto WB is off; 1.f-> Auto WB is on
+    private static final int DEFAULT_WHITE_BALANCE_TOGGLE = 0;
+
+    // Default rate at which to learn bg model during learning period
+    private static final float DEFAULT_LEARNING_ADAPT_RATE = 0.2f;
+    // Default rate at which to learn bg model from new background pixels
+    private static final float DEFAULT_ADAPT_RATE_BG = 0.0f;
+    // Default rate at which to learn bg model from new foreground pixels
+    private static final float DEFAULT_ADAPT_RATE_FG = 0.0f;
+    // Default rate at which to verify whether background is stable
+    private static final float DEFAULT_MASK_VERIFY_RATE = 0.25f;
+    // Default rate at which to verify whether background is stable
+    private static final int   DEFAULT_LEARNING_DONE_THRESHOLD = 20;
+
+    // Default 3x3 matrix, column major, for fitting background 1:1
+    private static final float[] DEFAULT_BG_FIT_TRANSFORM = new float[] {
+        1.0f, 0.0f, 0.0f,
+        0.0f, 1.0f, 0.0f,
+        0.0f, 0.0f, 1.0f
+    };
+
+    /** Default algorithm parameter values, for shader use */
+
+    // Area over which to blur binary mask values (length in pixels = 2^MASK_SMOOTH_EXPONENT)
+    private static final String MASK_SMOOTH_EXPONENT = "2.0";
+    // Scale value for mapping variance distance to fit nicely to 0-1, 8-bit
+    private static final String DISTANCE_STORAGE_SCALE = "0.6";
+    // Scale value for mapping variance to fit nicely to 0-1, 8-bit
+    private static final String VARIANCE_STORAGE_SCALE = "5.0";
+    // Default scale of auto white balance parameters
+    private static final String DEFAULT_AUTO_WB_SCALE = "0.25";
+    // Minimum variance (0-255 scale)
+    private static final String MIN_VARIANCE = "3.0";
+    // Column-major array for 4x4 matrix converting RGB to YCbCr, JPEG definition (no pedestal)
+    private static final String RGB_TO_YUV_MATRIX = "0.299, -0.168736,  0.5,      0.000, " +
+                                                    "0.587, -0.331264, -0.418688, 0.000, " +
+                                                    "0.114,  0.5,      -0.081312, 0.000, " +
+                                                    "0.000,  0.5,       0.5,      1.000 ";
+    /** Stream names */
+
+    private static final String[] mInputNames = {"video",
+                                                 "background"};
+
+    private static final String[] mOutputNames = {"video"};
+
+    private static final String[] mDebugOutputNames = {"debug1",
+                                                       "debug2"};
+
+    /** Other private variables */
+
+    private FrameFormat mOutputFormat;
+    private MutableFrameFormat mMemoryFormat;
+    private MutableFrameFormat mMaskFormat;
+    private MutableFrameFormat mAverageFormat;
+
+    private final boolean mLogVerbose;
+    private static final String TAG = "BackDropperFilter";
+
+    /** Shader source code */
+
+    // Shared uniforms and utility functions
+    private static String mSharedUtilShader =
+            "precision mediump float;\n" +
+            "uniform float fg_adapt_rate;\n" +
+            "uniform float bg_adapt_rate;\n" +
+            "const mat4 coeff_yuv = mat4(" + RGB_TO_YUV_MATRIX + ");\n" +
+            "const float dist_scale = " + DISTANCE_STORAGE_SCALE + ";\n" +
+            "const float inv_dist_scale = 1. / dist_scale;\n" +
+            "const float var_scale=" + VARIANCE_STORAGE_SCALE + ";\n" +
+            "const float inv_var_scale = 1. / var_scale;\n" +
+            "const float min_variance = inv_var_scale *" + MIN_VARIANCE + "/ 256.;\n" +
+            "const float auto_wb_scale = " + DEFAULT_AUTO_WB_SCALE + ";\n" +
+            "\n" +
+            // Variance distance in luminance between current pixel and background model
+            "float gauss_dist_y(float y, float mean, float variance) {\n" +
+            "  float dist = (y - mean) * (y - mean) / variance;\n" +
+            "  return dist;\n" +
+            "}\n" +
+            // Sum of variance distances in chroma between current pixel and background
+            // model
+            "float gauss_dist_uv(vec2 uv, vec2 mean, vec2 variance) {\n" +
+            "  vec2 dist = (uv - mean) * (uv - mean) / variance;\n" +
+            "  return dist.r + dist.g;\n" +
+            "}\n" +
+            // Select learning rate for pixel based on smoothed decision mask alpha
+            "float local_adapt_rate(float alpha) {\n" +
+            "  return mix(bg_adapt_rate, fg_adapt_rate, alpha);\n" +
+            "}\n" +
+            "\n";
+
+    // Distance calculation shader. Calculates a distance metric between the foreground and the
+    //   current background model, in both luminance and in chroma (yuv space).  Distance is
+    //   measured in variances from the mean background value. For chroma, the distance is the sum
+    //   of the two individual color channel distances. The distances are output on the b and alpha
+    //   channels, r and g are for debug information.
+    // Inputs:
+    //   tex_sampler_0: Mip-map for foreground (live) video frame.
+    //   tex_sampler_1: Background mean mask.
+    //   tex_sampler_2: Background variance mask.
+    //   subsample_level: Level on foreground frame's mip-map.
+    private static final String mBgDistanceShader =
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform sampler2D tex_sampler_1;\n" +
+            "uniform sampler2D tex_sampler_2;\n" +
+            "uniform float subsample_level;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 fg_rgb = texture2D(tex_sampler_0, v_texcoord, subsample_level);\n" +
+            "  vec4 fg = coeff_yuv * vec4(fg_rgb.rgb, 1.);\n" +
+            "  vec4 mean = texture2D(tex_sampler_1, v_texcoord);\n" +
+            "  vec4 variance = inv_var_scale * texture2D(tex_sampler_2, v_texcoord);\n" +
+            "\n" +
+            "  float dist_y = gauss_dist_y(fg.r, mean.r, variance.r);\n" +
+            "  float dist_uv = gauss_dist_uv(fg.gb, mean.gb, variance.gb);\n" +
+            "  gl_FragColor = vec4(0.5*fg.rg, dist_scale*dist_y, dist_scale*dist_uv);\n" +
+            "}\n";
+
+    // Foreground/background mask decision shader. Decides whether a frame is in the foreground or
+    //   the background using a hierarchical threshold on the distance. Binary foreground/background
+    //   mask is placed in the alpha channel. The RGB channels contain debug information.
+    private static final String mBgMaskShader =
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform float accept_variance;\n" +
+            "uniform vec2 yuv_weights;\n" +
+            "uniform float scale_lrg;\n" +
+            "uniform float scale_mid;\n" +
+            "uniform float scale_sml;\n" +
+            "uniform float exp_lrg;\n" +
+            "uniform float exp_mid;\n" +
+            "uniform float exp_sml;\n" +
+            "varying vec2 v_texcoord;\n" +
+            // Decide whether pixel is foreground or background based on Y and UV
+            //   distance and maximum acceptable variance.
+            // yuv_weights.x is smaller than yuv_weights.y to discount the influence of shadow
+            "bool is_fg(vec2 dist_yc, float accept_variance) {\n" +
+            "  return ( dot(yuv_weights, dist_yc) >= accept_variance );\n" +
+            "}\n" +
+            "void main() {\n" +
+            "  vec4 dist_lrg_sc = texture2D(tex_sampler_0, v_texcoord, exp_lrg);\n" +
+            "  vec4 dist_mid_sc = texture2D(tex_sampler_0, v_texcoord, exp_mid);\n" +
+            "  vec4 dist_sml_sc = texture2D(tex_sampler_0, v_texcoord, exp_sml);\n" +
+            "  vec2 dist_lrg = inv_dist_scale * dist_lrg_sc.ba;\n" +
+            "  vec2 dist_mid = inv_dist_scale * dist_mid_sc.ba;\n" +
+            "  vec2 dist_sml = inv_dist_scale * dist_sml_sc.ba;\n" +
+            "  vec2 norm_dist = 0.75 * dist_sml / accept_variance;\n" + // For debug viz
+            "  bool is_fg_lrg = is_fg(dist_lrg, accept_variance * scale_lrg);\n" +
+            "  bool is_fg_mid = is_fg_lrg || is_fg(dist_mid, accept_variance * scale_mid);\n" +
+            "  float is_fg_sml =\n" +
+            "      float(is_fg_mid || is_fg(dist_sml, accept_variance * scale_sml));\n" +
+            "  float alpha = 0.5 * is_fg_sml + 0.3 * float(is_fg_mid) + 0.2 * float(is_fg_lrg);\n" +
+            "  gl_FragColor = vec4(alpha, norm_dist, is_fg_sml);\n" +
+            "}\n";
+
+    // Automatic White Balance parameter decision shader
+    // Use the Gray World assumption that in a white balance corrected image, the average of R, G, B
+    //   channel will be a common gray value.
+    // To match the white balance of foreground and background, the average of R, G, B channel of
+    //   two videos should match.
+    // Inputs:
+    //   tex_sampler_0: Mip-map for foreground (live) video frame.
+    //   tex_sampler_1: Mip-map for background (playback) video frame.
+    //   pyramid_depth: Depth of input frames' mip-maps.
+    private static final String mAutomaticWhiteBalance =
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform sampler2D tex_sampler_1;\n" +
+            "uniform float pyramid_depth;\n" +
+            "uniform bool autowb_toggle;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "   vec4 mean_video = texture2D(tex_sampler_0, v_texcoord, pyramid_depth);\n"+
+            "   vec4 mean_bg = texture2D(tex_sampler_1, v_texcoord, pyramid_depth);\n" +
+            // If Auto WB is toggled off, the return texture will be a unicolor texture of value 1
+            // If Auto WB is toggled on, the return texture will be a unicolor texture with
+            //   adjustment parameters for R and B channels stored in the corresponding channel
+            "   float green_normalizer = mean_video.g / mean_bg.g;\n"+
+            "   vec4 adjusted_value = vec4(mean_bg.r / mean_video.r * green_normalizer, 1., \n" +
+            "                         mean_bg.b / mean_video.b * green_normalizer, 1.) * auto_wb_scale; \n" +
+            "   gl_FragColor = autowb_toggle ? adjusted_value : vec4(auto_wb_scale);\n" +
+            "}\n";
+
+
+    // Background subtraction shader. Uses a mipmap of the binary mask map to blend smoothly between
+    //   foreground and background
+    // Inputs:
+    //   tex_sampler_0: Foreground (live) video frame.
+    //   tex_sampler_1: Background (playback) video frame.
+    //   tex_sampler_2: Foreground/background mask.
+    //   tex_sampler_3: Auto white-balance factors.
+    private static final String mBgSubtractShader =
+            "uniform mat3 bg_fit_transform;\n" +
+            "uniform float mask_blend_bg;\n" +
+            "uniform float mask_blend_fg;\n" +
+            "uniform float exposure_change;\n" +
+            "uniform float whitebalancered_change;\n" +
+            "uniform float whitebalanceblue_change;\n" +
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform sampler2D tex_sampler_1;\n" +
+            "uniform sampler2D tex_sampler_2;\n" +
+            "uniform sampler2D tex_sampler_3;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec2 bg_texcoord = (bg_fit_transform * vec3(v_texcoord, 1.)).xy;\n" +
+            "  vec4 bg_rgb = texture2D(tex_sampler_1, bg_texcoord);\n" +
+            // The foreground texture is modified by multiplying both manual and auto white balance changes in R and B
+            //   channel and multiplying exposure change in all R, G, B channels.
+            "  vec4 wb_auto_scale = texture2D(tex_sampler_3, v_texcoord) * exposure_change / auto_wb_scale;\n" +
+            "  vec4 wb_manual_scale = vec4(1. + whitebalancered_change, 1., 1. + whitebalanceblue_change, 1.);\n" +
+            "  vec4 fg_rgb = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  vec4 fg_adjusted = fg_rgb * wb_manual_scale * wb_auto_scale;\n"+
+            "  vec4 mask = texture2D(tex_sampler_2, v_texcoord, \n" +
+            "                      " + MASK_SMOOTH_EXPONENT + ");\n" +
+            "  float alpha = smoothstep(mask_blend_bg, mask_blend_fg, mask.a);\n" +
+            "  gl_FragColor = mix(bg_rgb, fg_adjusted, alpha);\n";
+
+    // May the Force... Makes the foreground object translucent blue, with a bright
+    // blue-white outline
+    private static final String mBgSubtractForceShader =
+            "  vec4 ghost_rgb = (fg_adjusted * 0.7 + vec4(0.3,0.3,0.4,0.))*0.65 + \n" +
+            "                   0.35*bg_rgb;\n" +
+            "  float glow_start = 0.75 * mask_blend_bg; \n"+
+            "  float glow_max   = mask_blend_bg; \n"+
+            "  gl_FragColor = mask.a < glow_start ? bg_rgb : \n" +
+            "                 mask.a < glow_max ? mix(bg_rgb, vec4(0.9,0.9,1.0,1.0), \n" +
+            "                                     (mask.a - glow_start) / (glow_max - glow_start) ) : \n" +
+            "                 mask.a < mask_blend_fg ? mix(vec4(0.9,0.9,1.0,1.0), ghost_rgb, \n" +
+            "                                    (mask.a - glow_max) / (mask_blend_fg - glow_max) ) : \n" +
+            "                 ghost_rgb;\n" +
+            "}\n";
+
+    // Background model mean update shader. Skews the current model mean toward the most recent pixel
+    //   value for a pixel, weighted by the learning rate and by whether the pixel is classified as
+    //   foreground or background.
+    // Inputs:
+    //   tex_sampler_0: Mip-map for foreground (live) video frame.
+    //   tex_sampler_1: Background mean mask.
+    //   tex_sampler_2: Foreground/background mask.
+    //   subsample_level: Level on foreground frame's mip-map.
+    private static final String mUpdateBgModelMeanShader =
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform sampler2D tex_sampler_1;\n" +
+            "uniform sampler2D tex_sampler_2;\n" +
+            "uniform float subsample_level;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 fg_rgb = texture2D(tex_sampler_0, v_texcoord, subsample_level);\n" +
+            "  vec4 fg = coeff_yuv * vec4(fg_rgb.rgb, 1.);\n" +
+            "  vec4 mean = texture2D(tex_sampler_1, v_texcoord);\n" +
+            "  vec4 mask = texture2D(tex_sampler_2, v_texcoord, \n" +
+            "                      " + MASK_SMOOTH_EXPONENT + ");\n" +
+            "\n" +
+            "  float alpha = local_adapt_rate(mask.a);\n" +
+            "  vec4 new_mean = mix(mean, fg, alpha);\n" +
+            "  gl_FragColor = new_mean;\n" +
+            "}\n";
+
+    // Background model variance update shader. Skews the current model variance toward the most
+    //   recent variance for the pixel, weighted by the learning rate and by whether the pixel is
+    //   classified as foreground or background.
+    // Inputs:
+    //   tex_sampler_0: Mip-map for foreground (live) video frame.
+    //   tex_sampler_1: Background mean mask.
+    //   tex_sampler_2: Background variance mask.
+    //   tex_sampler_3: Foreground/background mask.
+    //   subsample_level: Level on foreground frame's mip-map.
+    // TODO: to improve efficiency, use single mark for mean + variance, then merge this into
+    // mUpdateBgModelMeanShader.
+    private static final String mUpdateBgModelVarianceShader =
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform sampler2D tex_sampler_1;\n" +
+            "uniform sampler2D tex_sampler_2;\n" +
+            "uniform sampler2D tex_sampler_3;\n" +
+            "uniform float subsample_level;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 fg_rgb = texture2D(tex_sampler_0, v_texcoord, subsample_level);\n" +
+            "  vec4 fg = coeff_yuv * vec4(fg_rgb.rgb, 1.);\n" +
+            "  vec4 mean = texture2D(tex_sampler_1, v_texcoord);\n" +
+            "  vec4 variance = inv_var_scale * texture2D(tex_sampler_2, v_texcoord);\n" +
+            "  vec4 mask = texture2D(tex_sampler_3, v_texcoord, \n" +
+            "                      " + MASK_SMOOTH_EXPONENT + ");\n" +
+            "\n" +
+            "  float alpha = local_adapt_rate(mask.a);\n" +
+            "  vec4 cur_variance = (fg-mean)*(fg-mean);\n" +
+            "  vec4 new_variance = mix(variance, cur_variance, alpha);\n" +
+            "  new_variance = max(new_variance, vec4(min_variance));\n" +
+            "  gl_FragColor = var_scale * new_variance;\n" +
+            "}\n";
+
+    // Background verification shader. Skews the current background verification mask towards the
+    //   most recent frame, weighted by the learning rate.
+    private static final String mMaskVerifyShader =
+            "uniform sampler2D tex_sampler_0;\n" +
+            "uniform sampler2D tex_sampler_1;\n" +
+            "uniform float verify_rate;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  vec4 lastmask = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "  vec4 mask = texture2D(tex_sampler_1, v_texcoord);\n" +
+            "  float newmask = mix(lastmask.a, mask.a, verify_rate);\n" +
+            "  gl_FragColor = vec4(0., 0., 0., newmask);\n" +
+            "}\n";
+
+    /** Shader program objects */
+
+    private ShaderProgram mBgDistProgram;
+    private ShaderProgram mBgMaskProgram;
+    private ShaderProgram mBgSubtractProgram;
+    private ShaderProgram mBgUpdateMeanProgram;
+    private ShaderProgram mBgUpdateVarianceProgram;
+    private ShaderProgram mCopyOutProgram;
+    private ShaderProgram mAutomaticWhiteBalanceProgram;
+    private ShaderProgram mMaskVerifyProgram;
+    private ShaderProgram copyShaderProgram;
+
+    /** Background model storage */
+
+    private boolean mPingPong;
+    private GLFrame mBgMean[];
+    private GLFrame mBgVariance[];
+    private GLFrame mMaskVerify[];
+    private GLFrame mDistance;
+    private GLFrame mAutoWB;
+    private GLFrame mMask;
+    private GLFrame mVideoInput;
+    private GLFrame mBgInput;
+    private GLFrame mMaskAverage;
+
+    /** Overall filter state */
+
+    private boolean isOpen;
+    private int mFrameCount;
+    private boolean mStartLearning;
+    private boolean mBackgroundFitModeChanged;
+    private float mRelativeAspect;
+    private int mPyramidDepth;
+    private int mSubsampleLevel;
+
+    /** Learning listener object */
+
+    public interface LearningDoneListener {
+        public void onLearningDone(BackDropperFilter filter);
+    }
+
+    /** Public Filter methods */
+
+    public BackDropperFilter(String name) {
+        super(name);
+
+        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    }
+
+    @Override
+    public void setupPorts() {
+        // Inputs.
+        // TODO: Target should be GPU, but relaxed for now.
+        FrameFormat imageFormat = ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
+                                                     FrameFormat.TARGET_UNSPECIFIED);
+        for (String inputName : mInputNames) {
+            addMaskedInputPort(inputName, imageFormat);
+        }
+        // Normal outputs
+        for (String outputName : mOutputNames) {
+            addOutputBasedOnInput(outputName, "video");
+        }
+
+        // Debug outputs
+        if (mProvideDebugOutputs) {
+            for (String outputName : mDebugOutputNames) {
+                addOutputBasedOnInput(outputName, "video");
+            }
+        }
+    }
+
+    @Override
+    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
+        // Create memory format based on video input.
+        MutableFrameFormat format = inputFormat.mutableCopy();
+        // Is this a debug output port? If so, leave dimensions unspecified.
+        if (!Arrays.asList(mOutputNames).contains(portName)) {
+            format.setDimensions(FrameFormat.SIZE_UNSPECIFIED, FrameFormat.SIZE_UNSPECIFIED);
+        }
+        return format;
+    }
+
+    private boolean createMemoryFormat(FrameFormat inputFormat) {
+        // We can't resize because that would require re-learning.
+        if (mMemoryFormat != null) {
+            return false;
+        }
+
+        if (inputFormat.getWidth() == FrameFormat.SIZE_UNSPECIFIED ||
+            inputFormat.getHeight() == FrameFormat.SIZE_UNSPECIFIED) {
+            throw new RuntimeException("Attempting to process input frame with unknown size");
+        }
+
+        mMaskFormat = inputFormat.mutableCopy();
+        int maskWidth = (int)Math.pow(2, mMaskWidthExp);
+        int maskHeight = (int)Math.pow(2, mMaskHeightExp);
+        mMaskFormat.setDimensions(maskWidth, maskHeight);
+
+        mPyramidDepth = Math.max(mMaskWidthExp, mMaskHeightExp);
+        mMemoryFormat = mMaskFormat.mutableCopy();
+        int widthExp = Math.max(mMaskWidthExp, pyramidLevel(inputFormat.getWidth()));
+        int heightExp = Math.max(mMaskHeightExp, pyramidLevel(inputFormat.getHeight()));
+        mPyramidDepth = Math.max(widthExp, heightExp);
+        int memWidth = Math.max(maskWidth, (int)Math.pow(2, widthExp));
+        int memHeight = Math.max(maskHeight, (int)Math.pow(2, heightExp));
+        mMemoryFormat.setDimensions(memWidth, memHeight);
+        mSubsampleLevel = mPyramidDepth - Math.max(mMaskWidthExp, mMaskHeightExp);
+
+        if (mLogVerbose) {
+            Log.v(TAG, "Mask frames size " + maskWidth + " x " + maskHeight);
+            Log.v(TAG, "Pyramid levels " + widthExp + " x " + heightExp);
+            Log.v(TAG, "Memory frames size " + memWidth + " x " + memHeight);
+        }
+
+        mAverageFormat = inputFormat.mutableCopy();
+        mAverageFormat.setDimensions(1,1);
+        return true;
+    }
+
+    public void prepare(FilterContext context){
+        if (mLogVerbose) Log.v(TAG, "Preparing BackDropperFilter!");
+
+        mBgMean = new GLFrame[2];
+        mBgVariance = new GLFrame[2];
+        mMaskVerify = new GLFrame[2];
+        copyShaderProgram = ShaderProgram.createIdentity(context);
+    }
+
+    private void allocateFrames(FrameFormat inputFormat, FilterContext context) {
+        if (!createMemoryFormat(inputFormat)) {
+            return;  // All set.
+        }
+        if (mLogVerbose) Log.v(TAG, "Allocating BackDropperFilter frames");
+
+        // Create initial background model values
+        int numBytes = mMaskFormat.getSize();
+        byte[] initialBgMean = new byte[numBytes];
+        byte[] initialBgVariance = new byte[numBytes];
+        byte[] initialMaskVerify = new byte[numBytes];
+        for (int i = 0; i < numBytes; i++) {
+            initialBgMean[i] = (byte)128;
+            initialBgVariance[i] = (byte)10;
+            initialMaskVerify[i] = (byte)0;
+        }
+
+        // Get frames to store background model in
+        for (int i = 0; i < 2; i++) {
+            mBgMean[i] = (GLFrame)context.getFrameManager().newFrame(mMaskFormat);
+            mBgMean[i].setData(initialBgMean, 0, numBytes);
+
+            mBgVariance[i] = (GLFrame)context.getFrameManager().newFrame(mMaskFormat);
+            mBgVariance[i].setData(initialBgVariance, 0, numBytes);
+
+            mMaskVerify[i] = (GLFrame)context.getFrameManager().newFrame(mMaskFormat);
+            mMaskVerify[i].setData(initialMaskVerify, 0, numBytes);
+        }
+
+        // Get frames to store other textures in
+        if (mLogVerbose) Log.v(TAG, "Done allocating texture for Mean and Variance objects!");
+
+        mDistance = (GLFrame)context.getFrameManager().newFrame(mMaskFormat);
+        mMask = (GLFrame)context.getFrameManager().newFrame(mMaskFormat);
+        mAutoWB = (GLFrame)context.getFrameManager().newFrame(mAverageFormat);
+        mVideoInput = (GLFrame)context.getFrameManager().newFrame(mMemoryFormat);
+        mBgInput = (GLFrame)context.getFrameManager().newFrame(mMemoryFormat);
+        mMaskAverage = (GLFrame)context.getFrameManager().newFrame(mAverageFormat);
+
+        // Create shader programs
+        mBgDistProgram = new ShaderProgram(context, mSharedUtilShader + mBgDistanceShader);
+        mBgDistProgram.setHostValue("subsample_level", (float)mSubsampleLevel);
+
+        mBgMaskProgram = new ShaderProgram(context, mSharedUtilShader + mBgMaskShader);
+        mBgMaskProgram.setHostValue("accept_variance", mAcceptStddev * mAcceptStddev);
+        float[] yuvWeights = { mLumScale, mChromaScale };
+        mBgMaskProgram.setHostValue("yuv_weights", yuvWeights );
+        mBgMaskProgram.setHostValue("scale_lrg", mHierarchyLrgScale);
+        mBgMaskProgram.setHostValue("scale_mid", mHierarchyMidScale);
+        mBgMaskProgram.setHostValue("scale_sml", mHierarchySmlScale);
+        mBgMaskProgram.setHostValue("exp_lrg", (float)(mSubsampleLevel + mHierarchyLrgExp));
+        mBgMaskProgram.setHostValue("exp_mid", (float)(mSubsampleLevel + mHierarchyMidExp));
+        mBgMaskProgram.setHostValue("exp_sml", (float)(mSubsampleLevel + mHierarchySmlExp));
+
+        if (mUseTheForce) {
+            mBgSubtractProgram = new ShaderProgram(context, mSharedUtilShader + mBgSubtractShader + mBgSubtractForceShader);
+        } else {
+            mBgSubtractProgram = new ShaderProgram(context, mSharedUtilShader + mBgSubtractShader + "}\n");
+        }
+        mBgSubtractProgram.setHostValue("bg_fit_transform", DEFAULT_BG_FIT_TRANSFORM);
+        mBgSubtractProgram.setHostValue("mask_blend_bg", mMaskBg);
+        mBgSubtractProgram.setHostValue("mask_blend_fg", mMaskFg);
+        mBgSubtractProgram.setHostValue("exposure_change", mExposureChange);
+        mBgSubtractProgram.setHostValue("whitebalanceblue_change", mWhiteBalanceBlueChange);
+        mBgSubtractProgram.setHostValue("whitebalancered_change", mWhiteBalanceRedChange);
+
+
+        mBgUpdateMeanProgram = new ShaderProgram(context, mSharedUtilShader + mUpdateBgModelMeanShader);
+        mBgUpdateMeanProgram.setHostValue("subsample_level", (float)mSubsampleLevel);
+
+        mBgUpdateVarianceProgram = new ShaderProgram(context, mSharedUtilShader + mUpdateBgModelVarianceShader);
+        mBgUpdateVarianceProgram.setHostValue("subsample_level", (float)mSubsampleLevel);
+
+        mCopyOutProgram = ShaderProgram.createIdentity(context);
+
+        mAutomaticWhiteBalanceProgram = new ShaderProgram(context, mSharedUtilShader + mAutomaticWhiteBalance);
+        mAutomaticWhiteBalanceProgram.setHostValue("pyramid_depth", (float)mPyramidDepth);
+        mAutomaticWhiteBalanceProgram.setHostValue("autowb_toggle", mAutoWBToggle);
+
+        mMaskVerifyProgram = new ShaderProgram(context, mSharedUtilShader + mMaskVerifyShader);
+        mMaskVerifyProgram.setHostValue("verify_rate", mVerifyRate);
+
+        if (mLogVerbose) Log.v(TAG, "Shader width set to " + mMemoryFormat.getWidth());
+
+        mRelativeAspect = 1.f;
+
+        mFrameCount = 0;
+        mStartLearning = true;
+    }
+
+    public void process(FilterContext context) {
+        // Grab inputs and ready intermediate frames and outputs.
+        Frame video = pullInput("video");
+        Frame background = pullInput("background");
+        allocateFrames(video.getFormat(), context);
+
+        // Update learning rate after initial learning period
+        if (mStartLearning) {
+            if (mLogVerbose) Log.v(TAG, "Starting learning");
+            mBgUpdateMeanProgram.setHostValue("bg_adapt_rate", mAdaptRateLearning);
+            mBgUpdateMeanProgram.setHostValue("fg_adapt_rate", mAdaptRateLearning);
+            mBgUpdateVarianceProgram.setHostValue("bg_adapt_rate", mAdaptRateLearning);
+            mBgUpdateVarianceProgram.setHostValue("fg_adapt_rate", mAdaptRateLearning);
+            mFrameCount = 0;
+            mStartLearning = false;
+        }
+
+        // Select correct pingpong buffers
+        int inputIndex = mPingPong ? 0 : 1;
+        int outputIndex = mPingPong ? 1 : 0;
+        mPingPong = !mPingPong;
+
+        // Check relative aspect ratios
+        updateBgScaling(video, background, mBackgroundFitModeChanged);
+        mBackgroundFitModeChanged = false;
+
+        // Make copies for input frames to GLFrames
+
+        copyShaderProgram.process(video, mVideoInput);
+        copyShaderProgram.process(background, mBgInput);
+
+        mVideoInput.generateMipMap();
+        mVideoInput.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER,
+                                        GLES20.GL_LINEAR_MIPMAP_NEAREST);
+
+        mBgInput.generateMipMap();
+        mBgInput.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER,
+                                     GLES20.GL_LINEAR_MIPMAP_NEAREST);
+
+        // Process shaders
+        Frame[] distInputs = { mVideoInput, mBgMean[inputIndex], mBgVariance[inputIndex] };
+        mBgDistProgram.process(distInputs, mDistance);
+        mDistance.generateMipMap();
+        mDistance.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER,
+                                      GLES20.GL_LINEAR_MIPMAP_NEAREST);
+
+        mBgMaskProgram.process(mDistance, mMask);
+        mMask.generateMipMap();
+        mMask.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER,
+                                  GLES20.GL_LINEAR_MIPMAP_NEAREST);
+
+        Frame[] autoWBInputs = { mVideoInput, mBgInput };
+        mAutomaticWhiteBalanceProgram.process(autoWBInputs, mAutoWB);
+
+        if (mFrameCount <= mLearningDuration) {
+            // During learning
+            pushOutput("video", video);
+
+            if (mFrameCount == mLearningDuration - mLearningVerifyDuration) {
+                copyShaderProgram.process(mMask, mMaskVerify[outputIndex]);
+
+                mBgUpdateMeanProgram.setHostValue("bg_adapt_rate", mAdaptRateBg);
+                mBgUpdateMeanProgram.setHostValue("fg_adapt_rate", mAdaptRateFg);
+                mBgUpdateVarianceProgram.setHostValue("bg_adapt_rate", mAdaptRateBg);
+                mBgUpdateVarianceProgram.setHostValue("fg_adapt_rate", mAdaptRateFg);
+
+
+            } else if (mFrameCount > mLearningDuration - mLearningVerifyDuration) {
+                // In the learning verification stage, compute background masks and a weighted average
+                //   with weights grow exponentially with time
+                Frame[] maskVerifyInputs = {mMaskVerify[inputIndex], mMask};
+                mMaskVerifyProgram.process(maskVerifyInputs, mMaskVerify[outputIndex]);
+                mMaskVerify[outputIndex].generateMipMap();
+                mMaskVerify[outputIndex].setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER,
+                                                             GLES20.GL_LINEAR_MIPMAP_NEAREST);
+            }
+
+            if (mFrameCount == mLearningDuration) {
+                // In the last verification frame, verify if the verification mask is almost blank
+                // If not, restart learning
+                copyShaderProgram.process(mMaskVerify[outputIndex], mMaskAverage);
+                ByteBuffer mMaskAverageByteBuffer = mMaskAverage.getData();
+                byte[] mask_average = mMaskAverageByteBuffer.array();
+                int bi = (int)(mask_average[3] & 0xFF);
+                if (mLogVerbose) Log.v(TAG, String.format("Mask_average is %d", bi));
+
+                if (bi >= DEFAULT_LEARNING_DONE_THRESHOLD) {
+                    mStartLearning = true;                                      // Restart learning
+                } else {
+                  if (mLogVerbose) Log.v(TAG, "Learning done");
+                  if (mLearningDoneListener != null) {
+                      mLearningDoneListener.onLearningDone(this);
+                   }
+                }
+            }
+        } else {
+            Frame output = context.getFrameManager().newFrame(video.getFormat());
+            Frame[] subtractInputs = { video, background, mMask, mAutoWB };
+            mBgSubtractProgram.process(subtractInputs, output);
+            pushOutput("video", output);
+            output.release();
+        }
+
+        // Compute mean and variance of the background
+        if (mFrameCount < mLearningDuration - mLearningVerifyDuration ||
+            mAdaptRateBg > 0.0 || mAdaptRateFg > 0.0) {
+            Frame[] meanUpdateInputs = { mVideoInput, mBgMean[inputIndex], mMask };
+            mBgUpdateMeanProgram.process(meanUpdateInputs, mBgMean[outputIndex]);
+            mBgMean[outputIndex].generateMipMap();
+            mBgMean[outputIndex].setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER,
+                                                     GLES20.GL_LINEAR_MIPMAP_NEAREST);
+
+            Frame[] varianceUpdateInputs = {
+              mVideoInput, mBgMean[inputIndex], mBgVariance[inputIndex], mMask
+            };
+            mBgUpdateVarianceProgram.process(varianceUpdateInputs, mBgVariance[outputIndex]);
+            mBgVariance[outputIndex].generateMipMap();
+            mBgVariance[outputIndex].setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER,
+                                                         GLES20.GL_LINEAR_MIPMAP_NEAREST);
+        }
+
+        // Provide debug output to two smaller viewers
+        if (mProvideDebugOutputs) {
+            Frame dbg1 = context.getFrameManager().newFrame(video.getFormat());
+            mCopyOutProgram.process(video, dbg1);
+            pushOutput("debug1", dbg1);
+            dbg1.release();
+
+            Frame dbg2 = context.getFrameManager().newFrame(mMemoryFormat);
+            mCopyOutProgram.process(mMask, dbg2);
+            pushOutput("debug2", dbg2);
+            dbg2.release();
+        }
+
+        mFrameCount++;
+
+        if (mLogVerbose) {
+            if (mFrameCount % 30 == 0) {
+                if (startTime == -1) {
+                    context.getGLEnvironment().activate();
+                    GLES20.glFinish();
+                    startTime = SystemClock.elapsedRealtime();
+                } else {
+                    context.getGLEnvironment().activate();
+                    GLES20.glFinish();
+                    long endTime = SystemClock.elapsedRealtime();
+                    Log.v(TAG, "Avg. frame duration: " + String.format("%.2f",(endTime-startTime)/30.) +
+                          " ms. Avg. fps: " + String.format("%.2f", 1000./((endTime-startTime)/30.)) );
+                    startTime = endTime;
+                }
+            }
+        }
+    }
+
+    private long startTime = -1;
+
+    public void close(FilterContext context) {
+        if (mMemoryFormat == null) {
+            return;
+        }
+
+        if (mLogVerbose) Log.v(TAG, "Filter Closing!");
+        for (int i = 0; i < 2; i++) {
+            mBgMean[i].release();
+            mBgVariance[i].release();
+            mMaskVerify[i].release();
+        }
+        mDistance.release();
+        mMask.release();
+        mAutoWB.release();
+        mVideoInput.release();
+        mBgInput.release();
+        mMaskAverage.release();
+
+        mMemoryFormat = null;
+    }
+
+    // Relearn background model
+    synchronized public void relearn() {
+        // Let the processing thread know about learning restart
+        mStartLearning = true;
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        // TODO: Many of these can be made ProgramPorts!
+        if (name.equals("backgroundFitMode")) {
+            mBackgroundFitModeChanged = true;
+        } else if (name.equals("acceptStddev")) {
+            mBgMaskProgram.setHostValue("accept_variance", mAcceptStddev * mAcceptStddev);
+        } else if (name.equals("hierLrgScale")) {
+            mBgMaskProgram.setHostValue("scale_lrg", mHierarchyLrgScale);
+        } else if (name.equals("hierMidScale")) {
+            mBgMaskProgram.setHostValue("scale_mid", mHierarchyMidScale);
+        } else if (name.equals("hierSmlScale")) {
+            mBgMaskProgram.setHostValue("scale_sml", mHierarchySmlScale);
+        } else if (name.equals("hierLrgExp")) {
+            mBgMaskProgram.setHostValue("exp_lrg", (float)(mSubsampleLevel + mHierarchyLrgExp));
+        } else if (name.equals("hierMidExp")) {
+            mBgMaskProgram.setHostValue("exp_mid", (float)(mSubsampleLevel + mHierarchyMidExp));
+        } else if (name.equals("hierSmlExp")) {
+            mBgMaskProgram.setHostValue("exp_sml", (float)(mSubsampleLevel + mHierarchySmlExp));
+        } else if (name.equals("lumScale") || name.equals("chromaScale")) {
+            float[] yuvWeights = { mLumScale, mChromaScale };
+            mBgMaskProgram.setHostValue("yuv_weights", yuvWeights );
+        } else if (name.equals("maskBg")) {
+            mBgSubtractProgram.setHostValue("mask_blend_bg", mMaskBg);
+        } else if (name.equals("maskFg")) {
+            mBgSubtractProgram.setHostValue("mask_blend_fg", mMaskFg);
+        } else if (name.equals("exposureChange")) {
+            mBgSubtractProgram.setHostValue("exposure_change", mExposureChange);
+        } else if (name.equals("whitebalanceredChange")) {
+            mBgSubtractProgram.setHostValue("whitebalancered_change", mWhiteBalanceRedChange);
+        } else if (name.equals("whitebalanceblueChange")) {
+            mBgSubtractProgram.setHostValue("whitebalanceblue_change", mWhiteBalanceBlueChange);
+        } else if (name.equals("autowbToggle")){
+            mAutomaticWhiteBalanceProgram.setHostValue("autowb_toggle", mAutoWBToggle);
+        }
+    }
+
+    private void updateBgScaling(Frame video, Frame background, boolean fitModeChanged) {
+        float foregroundAspect = (float)video.getFormat().getWidth() / video.getFormat().getHeight();
+        float backgroundAspect = (float)background.getFormat().getWidth() / background.getFormat().getHeight();
+        float currentRelativeAspect = foregroundAspect/backgroundAspect;
+        if (currentRelativeAspect != mRelativeAspect || fitModeChanged) {
+            mRelativeAspect = currentRelativeAspect;
+            float xMin = 0.f, xWidth = 1.f, yMin = 0.f, yWidth = 1.f;
+            switch (mBackgroundFitMode) {
+                case BACKGROUND_STRETCH:
+                    // Just map 1:1
+                    break;
+                case BACKGROUND_FIT:
+                    if (mRelativeAspect > 1.0f) {
+                        // Foreground is wider than background, scale down
+                        // background in X
+                        xMin = 0.5f - 0.5f * mRelativeAspect;
+                        xWidth = 1.f * mRelativeAspect;
+                    } else {
+                        // Foreground is taller than background, scale down
+                        // background in Y
+                        yMin = 0.5f - 0.5f / mRelativeAspect;
+                        yWidth = 1 / mRelativeAspect;
+                    }
+                    break;
+                case BACKGROUND_FILL_CROP:
+                    if (mRelativeAspect > 1.0f) {
+                        // Foreground is wider than background, crop
+                        // background in Y
+                        yMin = 0.5f - 0.5f / mRelativeAspect;
+                        yWidth = 1.f / mRelativeAspect;
+                    } else {
+                        // Foreground is taller than background, crop
+                        // background in X
+                        xMin = 0.5f - 0.5f * mRelativeAspect;
+                        xWidth = mRelativeAspect;
+                    }
+                    break;
+            }
+            // If mirroring is required (for ex. the camera mirrors the preview
+            // in the front camera)
+            // TODO: Backdropper does not attempt to apply any other transformation
+            // than just flipping. However, in the current state, it's "x-axis" is always aligned
+            // with the Camera's width. Hence, we need to define the mirroring based on the camera
+            // orientation. In the future, a cleaner design would be to cast away all the rotation
+            // in a separate place.
+            if (mMirrorBg) {
+                if (mLogVerbose) Log.v(TAG, "Mirroring the background!");
+                // Mirroring in portrait
+                if (mOrientation == 0 || mOrientation == 180) {
+                    xWidth = -xWidth;
+                    xMin = 1.0f - xMin;
+                } else {
+                    // Mirroring in landscape
+                    yWidth = -yWidth;
+                    yMin = 1.0f - yMin;
+                }
+            }
+            if (mLogVerbose) Log.v(TAG, "bgTransform: xMin, yMin, xWidth, yWidth : " +
+                    xMin + ", " + yMin + ", " + xWidth + ", " + yWidth +
+                    ", mRelAspRatio = " + mRelativeAspect);
+            // The following matrix is the transpose of the actual matrix
+            float[] bgTransform = {xWidth, 0.f, 0.f,
+                                   0.f, yWidth, 0.f,
+                                   xMin, yMin,  1.f};
+            mBgSubtractProgram.setHostValue("bg_fit_transform", bgTransform);
+        }
+    }
+
+    private int pyramidLevel(int size) {
+        return (int)Math.floor(Math.log10(size) / Math.log10(2)) - 1;
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java
new file mode 100644
index 0000000..3657d8a
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java
@@ -0,0 +1,469 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.videosink;
+
+import android.content.Context;
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.FrameManager;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GenerateFinalPort;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+import android.filterfw.geometry.Point;
+import android.filterfw.geometry.Quad;
+import android.os.ConditionVariable;
+import android.media.MediaRecorder;
+import android.media.CamcorderProfile;
+import android.filterfw.core.GLEnvironment;
+
+import java.io.IOException;
+import java.io.FileDescriptor;
+import java.util.List;
+import java.util.Set;
+
+import android.util.Log;
+
+/** @hide */
+public class MediaEncoderFilter extends Filter {
+
+    /** User-visible parameters */
+
+    /** Recording state. When set to false, recording will stop, or will not
+     * start if not yet running the graph. Instead, frames are simply ignored.
+     * When switched back to true, recording will restart. This allows a single
+     * graph to both provide preview and to record video. If this is false,
+     * recording settings can be updated while the graph is running.
+     */
+    @GenerateFieldPort(name = "recording", hasDefault = true)
+    private boolean mRecording = true;
+
+    /** Filename to save the output. */
+    @GenerateFieldPort(name = "outputFile", hasDefault = true)
+    private String mOutputFile = new String("/sdcard/MediaEncoderOut.mp4");
+
+    /** File Descriptor to save the output. */
+    @GenerateFieldPort(name = "outputFileDescriptor", hasDefault = true)
+    private FileDescriptor mFd = null;
+
+    /** Input audio source. If not set, no audio will be recorded.
+     * Select from the values in MediaRecorder.AudioSource
+     */
+    @GenerateFieldPort(name = "audioSource", hasDefault = true)
+    private int mAudioSource = NO_AUDIO_SOURCE;
+
+    /** Media recorder info listener, which needs to implement
+     * MediaRecorder.OnInfoListener. Set this to receive notifications about
+     * recording events.
+     */
+    @GenerateFieldPort(name = "infoListener", hasDefault = true)
+    private MediaRecorder.OnInfoListener mInfoListener = null;
+
+    /** Media recorder error listener, which needs to implement
+     * MediaRecorder.OnErrorListener. Set this to receive notifications about
+     * recording errors.
+     */
+    @GenerateFieldPort(name = "errorListener", hasDefault = true)
+    private MediaRecorder.OnErrorListener mErrorListener = null;
+
+    /** Media recording done callback, which needs to implement OnRecordingDoneListener.
+     * Set this to finalize media upon completion of media recording.
+     */
+    @GenerateFieldPort(name = "recordingDoneListener", hasDefault = true)
+    private OnRecordingDoneListener mRecordingDoneListener = null;
+
+    /** Orientation hint. Used for indicating proper video playback orientation.
+     * Units are in degrees of clockwise rotation, valid values are (0, 90, 180,
+     * 270).
+     */
+    @GenerateFieldPort(name = "orientationHint", hasDefault = true)
+    private int mOrientationHint = 0;
+
+    /** Camcorder profile to use. Select from the profiles available in
+     * android.media.CamcorderProfile. If this field is set, it overrides
+     * settings to width, height, framerate, outputFormat, and videoEncoder.
+     */
+    @GenerateFieldPort(name = "recordingProfile", hasDefault = true)
+    private CamcorderProfile mProfile = null;
+
+    /** Frame width to be encoded, defaults to 320.
+     * Actual received frame size has to match this */
+    @GenerateFieldPort(name = "width", hasDefault = true)
+    private int mWidth = 320;
+
+    /** Frame height to to be encoded, defaults to 240.
+     * Actual received frame size has to match */
+    @GenerateFieldPort(name = "height", hasDefault = true)
+    private int mHeight = 240;
+
+    /** Stream framerate to encode the frames at.
+     * By default, frames are encoded at 30 FPS*/
+    @GenerateFieldPort(name = "framerate", hasDefault = true)
+    private int mFps = 30;
+
+    /** The output format to encode the frames in.
+     * Choose an output format from the options in
+     * android.media.MediaRecorder.OutputFormat */
+    @GenerateFieldPort(name = "outputFormat", hasDefault = true)
+    private int mOutputFormat = MediaRecorder.OutputFormat.MPEG_4;
+
+    /** The videoencoder to encode the frames with.
+     * Choose a videoencoder from the options in
+     * android.media.MediaRecorder.VideoEncoder */
+    @GenerateFieldPort(name = "videoEncoder", hasDefault = true)
+    private int mVideoEncoder = MediaRecorder.VideoEncoder.H264;
+
+    /** The input region to read from the frame. The corners of this quad are
+     * mapped to the output rectangle. The input frame ranges from (0,0)-(1,1),
+     * top-left to bottom-right. The corners of the quad are specified in the
+     * order bottom-left, bottom-right, top-left, top-right.
+     */
+    @GenerateFieldPort(name = "inputRegion", hasDefault = true)
+    private Quad mSourceRegion;
+
+    /** The maximum filesize (in bytes) of the recording session.
+     * By default, it will be 0 and will be passed on to the MediaRecorder.
+     * If the limit is zero or negative, MediaRecorder will disable the limit*/
+    @GenerateFieldPort(name = "maxFileSize", hasDefault = true)
+    private long mMaxFileSize = 0;
+
+    /** The maximum duration (in milliseconds) of the recording session.
+     * By default, it will be 0 and will be passed on to the MediaRecorder.
+     * If the limit is zero or negative, MediaRecorder will record indefinitely*/
+    @GenerateFieldPort(name = "maxDurationMs", hasDefault = true)
+    private int mMaxDurationMs = 0;
+
+    /** TimeLapse Interval between frames.
+     * By default, it will be 0. Whether the recording is timelapsed
+     * is inferred based on its value being greater than 0 */
+    @GenerateFieldPort(name = "timelapseRecordingIntervalUs", hasDefault = true)
+    private long mTimeBetweenTimeLapseFrameCaptureUs = 0;
+
+    // End of user visible parameters
+
+    private static final int NO_AUDIO_SOURCE = -1;
+
+    private int mSurfaceId;
+    private ShaderProgram mProgram;
+    private GLFrame mScreen;
+
+    private boolean mRecordingActive = false;
+    private long mTimestampNs = 0;
+    private long mLastTimeLapseFrameRealTimestampNs = 0;
+    private int mNumFramesEncoded = 0;
+    // Used to indicate whether recording is timelapsed.
+    // Inferred based on (mTimeBetweenTimeLapseFrameCaptureUs > 0)
+    private boolean mCaptureTimeLapse = false;
+
+    private boolean mLogVerbose;
+    private static final String TAG = "MediaEncoderFilter";
+
+    // Our hook to the encoder
+    private MediaRecorder mMediaRecorder;
+
+    /** Callback to be called when media recording completes. */
+
+    public interface OnRecordingDoneListener {
+        public void onRecordingDone();
+    }
+
+    public MediaEncoderFilter(String name) {
+        super(name);
+        Point bl = new Point(0, 0);
+        Point br = new Point(1, 0);
+        Point tl = new Point(0, 1);
+        Point tr = new Point(1, 1);
+        mSourceRegion = new Quad(bl, br, tl, tr);
+        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    }
+
+    @Override
+    public void setupPorts() {
+        // Add input port- will accept RGBA GLFrames
+        addMaskedInputPort("videoframe", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
+                                                      FrameFormat.TARGET_GPU));
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Port " + name + " has been updated");
+        if (name.equals("recording")) return;
+        if (name.equals("inputRegion")) {
+            if (isOpen()) updateSourceRegion();
+            return;
+        }
+        // TODO: Not sure if it is possible to update the maxFileSize
+        // when the recording is going on. For now, not doing that.
+        if (isOpen() && mRecordingActive) {
+            throw new RuntimeException("Cannot change recording parameters"
+                                       + " when the filter is recording!");
+        }
+    }
+
+    private void updateSourceRegion() {
+        // Flip source quad to map to OpenGL origin
+        Quad flippedRegion = new Quad();
+        flippedRegion.p0 = mSourceRegion.p2;
+        flippedRegion.p1 = mSourceRegion.p3;
+        flippedRegion.p2 = mSourceRegion.p0;
+        flippedRegion.p3 = mSourceRegion.p1;
+        mProgram.setSourceRegion(flippedRegion);
+    }
+
+    // update the MediaRecorderParams based on the variables.
+    // These have to be in certain order as per the MediaRecorder
+    // documentation
+    private void updateMediaRecorderParams() {
+        mCaptureTimeLapse = mTimeBetweenTimeLapseFrameCaptureUs > 0;
+        final int GRALLOC_BUFFER = 2;
+        mMediaRecorder.setVideoSource(GRALLOC_BUFFER);
+        if (!mCaptureTimeLapse && (mAudioSource != NO_AUDIO_SOURCE)) {
+            mMediaRecorder.setAudioSource(mAudioSource);
+        }
+        if (mProfile != null) {
+            mMediaRecorder.setProfile(mProfile);
+            mFps = mProfile.videoFrameRate;
+        } else {
+            mMediaRecorder.setOutputFormat(mOutputFormat);
+            mMediaRecorder.setVideoEncoder(mVideoEncoder);
+            mMediaRecorder.setVideoSize(mWidth, mHeight);
+            mMediaRecorder.setVideoFrameRate(mFps);
+        }
+        mMediaRecorder.setOrientationHint(mOrientationHint);
+        mMediaRecorder.setOnInfoListener(mInfoListener);
+        mMediaRecorder.setOnErrorListener(mErrorListener);
+        if (mFd != null) {
+            mMediaRecorder.setOutputFile(mFd);
+        } else {
+            mMediaRecorder.setOutputFile(mOutputFile);
+        }
+        try {
+            mMediaRecorder.setMaxFileSize(mMaxFileSize);
+        } catch (Exception e) {
+            // Following the logic in  VideoCamera.java (in Camera app)
+            // We are going to ignore failure of setMaxFileSize here, as
+            // a) The composer selected may simply not support it, or
+            // b) The underlying media framework may not handle 64-bit range
+            // on the size restriction.
+            Log.w(TAG, "Setting maxFileSize on MediaRecorder unsuccessful! "
+                    + e.getMessage());
+        }
+        mMediaRecorder.setMaxDuration(mMaxDurationMs);
+    }
+
+    @Override
+    public void prepare(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Preparing");
+
+        mProgram = ShaderProgram.createIdentity(context);
+
+        mRecordingActive = false;
+    }
+
+    @Override
+    public void open(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Opening");
+        updateSourceRegion();
+        if (mRecording) startRecording(context);
+    }
+
+    private void startRecording(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Starting recording");
+
+        // Create a frame representing the screen
+        MutableFrameFormat screenFormat = new MutableFrameFormat(
+                              FrameFormat.TYPE_BYTE, FrameFormat.TARGET_GPU);
+        screenFormat.setBytesPerSample(4);
+
+        int width, height;
+        if (mProfile != null) {
+            width = mProfile.videoFrameWidth;
+            height = mProfile.videoFrameHeight;
+        } else {
+            width = mWidth;
+            height = mHeight;
+        }
+        screenFormat.setDimensions(width, height);
+        mScreen = (GLFrame)context.getFrameManager().newBoundFrame(
+                           screenFormat, GLFrame.EXISTING_FBO_BINDING, 0);
+
+        // Initialize the media recorder
+
+        mMediaRecorder = new MediaRecorder();
+        updateMediaRecorderParams();
+
+        try {
+            mMediaRecorder.prepare();
+        } catch (IllegalStateException e) {
+            throw e;
+        } catch (IOException e) {
+            throw new RuntimeException("IOException in"
+                    + "MediaRecorder.prepare()!", e);
+        } catch (Exception e) {
+            throw new RuntimeException("Unknown Exception in"
+                    + "MediaRecorder.prepare()!", e);
+        }
+        // Make sure start() is called before trying to
+        // register the surface. The native window handle needed to create
+        // the surface is initiated in start()
+        mMediaRecorder.start();
+        if (mLogVerbose) Log.v(TAG, "Open: registering surface from Mediarecorder");
+        mSurfaceId = context.getGLEnvironment().
+                registerSurfaceFromMediaRecorder(mMediaRecorder);
+        mNumFramesEncoded = 0;
+        mRecordingActive = true;
+    }
+
+    public boolean skipFrameAndModifyTimestamp(long timestampNs) {
+        // first frame- encode. Don't skip
+        if (mNumFramesEncoded == 0) {
+            mLastTimeLapseFrameRealTimestampNs = timestampNs;
+            mTimestampNs = timestampNs;
+            if (mLogVerbose) Log.v(TAG, "timelapse: FIRST frame, last real t= "
+                    + mLastTimeLapseFrameRealTimestampNs +
+                    ", setting t = " + mTimestampNs );
+            return false;
+        }
+
+        // Workaround to bypass the first 2 input frames for skipping.
+        // The first 2 output frames from the encoder are: decoder specific info and
+        // the compressed video frame data for the first input video frame.
+        if (mNumFramesEncoded >= 2 && timestampNs <
+            (mLastTimeLapseFrameRealTimestampNs +  1000L * mTimeBetweenTimeLapseFrameCaptureUs)) {
+            // If 2 frames have been already encoded,
+            // Skip all frames from last encoded frame until
+            // sufficient time (mTimeBetweenTimeLapseFrameCaptureUs) has passed.
+            if (mLogVerbose) Log.v(TAG, "timelapse: skipping intermediate frame");
+            return true;
+        } else {
+            // Desired frame has arrived after mTimeBetweenTimeLapseFrameCaptureUs time:
+            // - Reset mLastTimeLapseFrameRealTimestampNs to current time.
+            // - Artificially modify timestampNs to be one frame time (1/framerate) ahead
+            // of the last encoded frame's time stamp.
+            if (mLogVerbose) Log.v(TAG, "timelapse: encoding frame, Timestamp t = " + timestampNs +
+                    ", last real t= " + mLastTimeLapseFrameRealTimestampNs +
+                    ", interval = " + mTimeBetweenTimeLapseFrameCaptureUs);
+            mLastTimeLapseFrameRealTimestampNs = timestampNs;
+            mTimestampNs = mTimestampNs + (1000000000L / (long)mFps);
+            if (mLogVerbose) Log.v(TAG, "timelapse: encoding frame, setting t = "
+                    + mTimestampNs + ", delta t = " + (1000000000L / (long)mFps) +
+                    ", fps = " + mFps );
+            return false;
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Starting frame processing");
+
+        GLEnvironment glEnv = context.getGLEnvironment();
+        // Get input frame
+        Frame input = pullInput("videoframe");
+
+        // Check if recording needs to start
+        if (!mRecordingActive && mRecording) {
+            startRecording(context);
+        }
+        // Check if recording needs to stop
+        if (mRecordingActive && !mRecording) {
+            stopRecording(context);
+        }
+
+        if (!mRecordingActive) return;
+
+        if (mCaptureTimeLapse) {
+            if (skipFrameAndModifyTimestamp(input.getTimestamp())) {
+                return;
+            }
+        } else {
+            mTimestampNs = input.getTimestamp();
+        }
+
+        // Activate our surface
+        glEnv.activateSurfaceWithId(mSurfaceId);
+
+        // Process
+        mProgram.process(input, mScreen);
+
+        // Set timestamp from input
+        glEnv.setSurfaceTimestamp(mTimestampNs);
+        // And swap buffers
+        glEnv.swapBuffers();
+        mNumFramesEncoded++;
+        if (mLogVerbose) Log.v(TAG, "numFramesEncoded = " + mNumFramesEncoded);
+    }
+
+    private void stopRecording(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Stopping recording");
+
+        mRecordingActive = false;
+        mNumFramesEncoded = 0;
+        GLEnvironment glEnv = context.getGLEnvironment();
+        // The following call will switch the surface_id to 0
+        // (thus, calling eglMakeCurrent on surface with id 0) and
+        // then call eglDestroy on the surface. Hence, this will
+        // call disconnect the SurfaceMediaSource, which is needed to
+        // be called before calling Stop on the mediarecorder
+        if (mLogVerbose) Log.v(TAG, String.format("Unregistering surface %d", mSurfaceId));
+        glEnv.unregisterSurfaceId(mSurfaceId);
+        try {
+            mMediaRecorder.stop();
+        } catch (RuntimeException e) {
+            throw new MediaRecorderStopException("MediaRecorder.stop() failed!", e);
+        }
+        mMediaRecorder.release();
+        mMediaRecorder = null;
+
+        mScreen.release();
+        mScreen = null;
+
+        // Use an EffectsRecorder callback to forward a media finalization
+        // call so that it creates the video thumbnail, and whatever else needs
+        // to be done to finalize media.
+        if (mRecordingDoneListener != null) {
+            mRecordingDoneListener.onRecordingDone();
+        }
+    }
+
+    @Override
+    public void close(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Closing");
+        if (mRecordingActive) stopRecording(context);
+    }
+
+    @Override
+    public void tearDown(FilterContext context) {
+        // Release all the resources associated with the MediaRecorder
+        // and GLFrame members
+        if (mMediaRecorder != null) {
+            mMediaRecorder.release();
+        }
+        if (mScreen != null) {
+            mScreen.release();
+        }
+
+    }
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosink/MediaRecorderStopException.java b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaRecorderStopException.java
new file mode 100644
index 0000000..dbf9768
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaRecorderStopException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.videosink;
+
+import java.lang.RuntimeException;
+import android.util.Log;
+
+/** @hide **/
+public class MediaRecorderStopException extends RuntimeException {
+
+    private static final String TAG = "MediaRecorderStopException";
+
+    public MediaRecorderStopException(String msg) {
+        super(msg);
+    }
+
+    public MediaRecorderStopException() {
+        super();
+    }
+
+    public MediaRecorderStopException(String msg, Throwable t) {
+        super(msg, t);
+    }
+
+    public MediaRecorderStopException(Throwable t) {
+        super(t);
+    }
+}
+
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/CameraSource.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/CameraSource.java
new file mode 100644
index 0000000..2c474ab
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/CameraSource.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.videosrc;
+
+import android.content.Context;
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.FrameManager;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GenerateFinalPort;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.os.ConditionVariable;
+import android.opengl.Matrix;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class CameraSource extends Filter {
+
+    /** User-visible parameters */
+
+    /** Camera ID to use for input. Defaults to 0. */
+    @GenerateFieldPort(name = "id", hasDefault = true)
+    private int mCameraId = 0;
+
+    /** Frame width to request from camera. Actual size may not match requested. */
+    @GenerateFieldPort(name = "width", hasDefault = true)
+    private int mWidth = 320;
+
+    /** Frame height to request from camera. Actual size may not match requested. */
+    @GenerateFieldPort(name = "height", hasDefault = true)
+    private int mHeight = 240;
+
+    /** Stream framerate to request from camera. Actual frame rate may not match requested. */
+    @GenerateFieldPort(name = "framerate", hasDefault = true)
+    private int mFps = 30;
+
+    /** Whether the filter should always wait for a new frame from the camera
+     * before providing output.  If set to false, the filter will keep
+     * outputting the last frame it received from the camera if multiple process
+     * calls are received before the next update from the Camera. Defaults to true.
+     */
+    @GenerateFinalPort(name = "waitForNewFrame", hasDefault = true)
+    private boolean mWaitForNewFrame = true;
+
+    private Camera mCamera;
+    private GLFrame mCameraFrame;
+    private SurfaceTexture mSurfaceTexture;
+    private ShaderProgram mFrameExtractor;
+    private MutableFrameFormat mOutputFormat;
+
+    private float[] mCameraTransform;
+    private float[] mMappedCoords;
+    // These default source coordinates perform the necessary flip
+    // for converting from OpenGL origin to MFF/Bitmap origin.
+    private static final float[] mSourceCoords = { 0, 1, 0, 1,
+                                                   1, 1, 0, 1,
+                                                   0, 0, 0, 1,
+                                                   1, 0, 0, 1 };
+
+    private static final int NEWFRAME_TIMEOUT = 100; //ms
+    private static final int NEWFRAME_TIMEOUT_REPEAT = 10;
+
+    private boolean mNewFrameAvailable;
+
+    private Camera.Parameters mCameraParameters;
+
+    private static final String mFrameShader =
+            "#extension GL_OES_EGL_image_external : require\n" +
+            "precision mediump float;\n" +
+            "uniform samplerExternalOES tex_sampler_0;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "}\n";
+
+    private final boolean mLogVerbose;
+    private static final String TAG = "CameraSource";
+
+    public CameraSource(String name) {
+        super(name);
+        mCameraTransform = new float[16];
+        mMappedCoords = new float[16];
+
+        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    }
+
+    @Override
+    public void setupPorts() {
+        // Add input port
+        addOutputPort("video", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
+                                                  FrameFormat.TARGET_GPU));
+    }
+
+    private void createFormats() {
+        mOutputFormat = ImageFormat.create(mWidth, mHeight,
+                                           ImageFormat.COLORSPACE_RGBA,
+                                           FrameFormat.TARGET_GPU);
+    }
+
+    @Override
+    public void prepare(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Preparing");
+        // Compile shader TODO: Move to onGLEnvSomething?
+        mFrameExtractor = new ShaderProgram(context, mFrameShader);
+    }
+
+    @Override
+    public void open(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Opening");
+        // Open camera
+        mCamera = Camera.open(mCameraId);
+
+        // Set parameters
+        getCameraParameters();
+        mCamera.setParameters(mCameraParameters);
+
+        // Create frame formats
+        createFormats();
+
+        // Bind it to our camera frame
+        mCameraFrame = (GLFrame)context.getFrameManager().newBoundFrame(mOutputFormat,
+                                                                        GLFrame.EXTERNAL_TEXTURE,
+                                                                        0);
+        mSurfaceTexture = new SurfaceTexture(mCameraFrame.getTextureId());
+        try {
+            mCamera.setPreviewTexture(mSurfaceTexture);
+        } catch (IOException e) {
+            throw new RuntimeException("Could not bind camera surface texture: " +
+                                       e.getMessage() + "!");
+        }
+
+        // Connect SurfaceTexture to callback
+        mSurfaceTexture.setOnFrameAvailableListener(onCameraFrameAvailableListener);
+        // Start the preview
+        mNewFrameAvailable = false;
+        mCamera.startPreview();
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Processing new frame");
+
+        if (mWaitForNewFrame) {
+            int waitCount = 0;
+            while (!mNewFrameAvailable) {
+                if (waitCount == NEWFRAME_TIMEOUT_REPEAT) {
+                    throw new RuntimeException("Timeout waiting for new frame");
+                }
+                try {
+                    this.wait(NEWFRAME_TIMEOUT);
+                } catch (InterruptedException e) {
+                    if (mLogVerbose) Log.v(TAG, "Interrupted while waiting for new frame");
+                }
+            }
+            mNewFrameAvailable = false;
+            if (mLogVerbose) Log.v(TAG, "Got new frame");
+        }
+
+        mSurfaceTexture.updateTexImage();
+
+        if (mLogVerbose) Log.v(TAG, "Using frame extractor in thread: " + Thread.currentThread());
+        mSurfaceTexture.getTransformMatrix(mCameraTransform);
+        Matrix.multiplyMM(mMappedCoords, 0,
+                          mCameraTransform, 0,
+                          mSourceCoords, 0);
+        mFrameExtractor.setSourceRegion(mMappedCoords[0], mMappedCoords[1],
+                                        mMappedCoords[4], mMappedCoords[5],
+                                        mMappedCoords[8], mMappedCoords[9],
+                                        mMappedCoords[12], mMappedCoords[13]);
+
+        Frame output = context.getFrameManager().newFrame(mOutputFormat);
+        mFrameExtractor.process(mCameraFrame, output);
+
+        long timestamp = mSurfaceTexture.getTimestamp();
+        if (mLogVerbose) Log.v(TAG, "Timestamp: " + (timestamp / 1000000000.0) + " s");
+        output.setTimestamp(timestamp);
+
+        pushOutput("video", output);
+
+        // Release pushed frame
+        output.release();
+
+        if (mLogVerbose) Log.v(TAG, "Done processing new frame");
+    }
+
+    @Override
+    public void close(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Closing");
+
+        mCamera.release();
+        mCamera = null;
+        mSurfaceTexture.release();
+        mSurfaceTexture = null;
+    }
+
+    @Override
+    public void tearDown(FilterContext context) {
+        if (mCameraFrame != null) {
+            mCameraFrame.release();
+        }
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (name.equals("framerate")) {
+            getCameraParameters();
+            int closestRange[] = findClosestFpsRange(mFps, mCameraParameters);
+            mCameraParameters.setPreviewFpsRange(closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+                                                 closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+            mCamera.setParameters(mCameraParameters);
+        }
+    }
+
+    synchronized public Camera.Parameters getCameraParameters() {
+        boolean closeCamera = false;
+        if (mCameraParameters == null) {
+            if (mCamera == null) {
+                mCamera = Camera.open(mCameraId);
+                closeCamera = true;
+            }
+            mCameraParameters = mCamera.getParameters();
+
+            if (closeCamera) {
+                mCamera.release();
+                mCamera = null;
+            }
+        }
+
+        int closestSize[] = findClosestSize(mWidth, mHeight, mCameraParameters);
+        mWidth = closestSize[0];
+        mHeight = closestSize[1];
+        mCameraParameters.setPreviewSize(mWidth, mHeight);
+
+        int closestRange[] = findClosestFpsRange(mFps, mCameraParameters);
+
+        mCameraParameters.setPreviewFpsRange(closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
+                                             closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
+
+        return mCameraParameters;
+    }
+
+    /** Update camera parameters. Image resolution cannot be changed. */
+    synchronized public void setCameraParameters(Camera.Parameters params) {
+        params.setPreviewSize(mWidth, mHeight);
+        mCameraParameters = params;
+        if (isOpen()) {
+            mCamera.setParameters(mCameraParameters);
+        }
+    }
+
+    private int[] findClosestSize(int width, int height, Camera.Parameters parameters) {
+        List<Camera.Size> previewSizes = parameters.getSupportedPreviewSizes();
+        int closestWidth = -1;
+        int closestHeight = -1;
+        int smallestWidth = previewSizes.get(0).width;
+        int smallestHeight =  previewSizes.get(0).height;
+        for (Camera.Size size : previewSizes) {
+            // Best match defined as not being larger in either dimension than
+            // the requested size, but as close as possible. The below isn't a
+            // stable selection (reording the size list can give different
+            // results), but since this is a fallback nicety, that's acceptable.
+            if ( size.width <= width &&
+                 size.height <= height &&
+                 size.width >= closestWidth &&
+                 size.height >= closestHeight) {
+                closestWidth = size.width;
+                closestHeight = size.height;
+            }
+            if ( size.width < smallestWidth &&
+                 size.height < smallestHeight) {
+                smallestWidth = size.width;
+                smallestHeight = size.height;
+            }
+        }
+        if (closestWidth == -1) {
+            // Requested size is smaller than any listed size; match with smallest possible
+            closestWidth = smallestWidth;
+            closestHeight = smallestHeight;
+        }
+
+        if (mLogVerbose) {
+            Log.v(TAG,
+                  "Requested resolution: (" + width + ", " + height
+                  + "). Closest match: (" + closestWidth + ", "
+                  + closestHeight + ").");
+        }
+        int[] closestSize = {closestWidth, closestHeight};
+        return closestSize;
+    }
+
+    private int[] findClosestFpsRange(int fps, Camera.Parameters params) {
+        List<int[]> supportedFpsRanges = params.getSupportedPreviewFpsRange();
+        int[] closestRange = supportedFpsRanges.get(0);
+        for (int[] range : supportedFpsRanges) {
+            if (range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] < fps*1000 &&
+                range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] > fps*1000 &&
+                range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] >
+                closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] &&
+                range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] <
+                closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]) {
+                closestRange = range;
+            }
+        }
+        if (mLogVerbose) Log.v(TAG, "Requested fps: " + fps
+                               + ".Closest frame rate range: ["
+                               + closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] / 1000.
+                               + ","
+                               + closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] / 1000.
+                               + "]");
+
+        return closestRange;
+    }
+
+    private SurfaceTexture.OnFrameAvailableListener onCameraFrameAvailableListener =
+            new SurfaceTexture.OnFrameAvailableListener() {
+        @Override
+        public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+            if (mLogVerbose) Log.v(TAG, "New frame from camera");
+            synchronized(CameraSource.this) {
+                mNewFrameAvailable = true;
+                CameraSource.this.notify();
+            }
+        }
+    };
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java
new file mode 100644
index 0000000..9c40cec
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java
@@ -0,0 +1,567 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.videosrc;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.FrameManager;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GenerateFinalPort;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.media.MediaPlayer;
+import android.os.ConditionVariable;
+import android.opengl.Matrix;
+import android.view.Surface;
+
+import java.io.IOException;
+import java.io.FileDescriptor;
+import java.lang.IllegalArgumentException;
+import java.util.List;
+import java.util.Set;
+
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class MediaSource extends Filter {
+
+    /** User-visible parameters */
+
+    /** The source URL for the media source. Can be an http: link to a remote
+     * resource, or a file: link to a local media file
+     */
+    @GenerateFieldPort(name = "sourceUrl", hasDefault = true)
+    private String mSourceUrl = "";
+
+    /** An open asset file descriptor to a local media source. Default is null */
+    @GenerateFieldPort(name = "sourceAsset", hasDefault = true)
+    private AssetFileDescriptor mSourceAsset = null;
+
+    /** Whether the media source is a URL or an asset file descriptor. Defaults
+     * to false.
+     */
+    @GenerateFieldPort(name = "sourceIsUrl", hasDefault = true)
+    private boolean mSelectedIsUrl = false;
+
+    /** Whether the filter will always wait for a new video frame, or whether it
+     * will output an old frame again if a new frame isn't available. Defaults
+     * to true.
+     */
+    @GenerateFinalPort(name = "waitForNewFrame", hasDefault = true)
+    private boolean mWaitForNewFrame = true;
+
+    /** Whether the media source should loop automatically or not. Defaults to
+     * true.
+     */
+    @GenerateFieldPort(name = "loop", hasDefault = true)
+    private boolean mLooping = true;
+
+    /** Volume control. Currently sound is piped directly to the speakers, so
+     * this defaults to mute.
+     */
+    @GenerateFieldPort(name = "volume", hasDefault = true)
+    private float mVolume = 0.f;
+
+    /** Orientation. This controls the output orientation of the video. Valid
+     * values are 0, 90, 180, 270
+     */
+    @GenerateFieldPort(name = "orientation", hasDefault = true)
+    private int mOrientation = 0;
+
+    private MediaPlayer mMediaPlayer;
+    private GLFrame mMediaFrame;
+    private SurfaceTexture mSurfaceTexture;
+    private ShaderProgram mFrameExtractor;
+    private MutableFrameFormat mOutputFormat;
+    private int mWidth, mHeight;
+
+    // Total timeouts will be PREP_TIMEOUT*PREP_TIMEOUT_REPEAT
+    private static final int PREP_TIMEOUT = 100; // ms
+    private static final int PREP_TIMEOUT_REPEAT = 100;
+    private static final int NEWFRAME_TIMEOUT = 100; //ms
+    private static final int NEWFRAME_TIMEOUT_REPEAT = 10;
+
+    // This is an identity shader; not using the default identity
+    // shader because reading from a SurfaceTexture requires the
+    // GL_OES_EGL_image_external extension.
+    private final String mFrameShader =
+            "#extension GL_OES_EGL_image_external : require\n" +
+            "precision mediump float;\n" +
+            "uniform samplerExternalOES tex_sampler_0;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "}\n";
+
+    // The following transforms enable rotation of the decoded source.
+    // These are multiplied with the transform obtained from the
+    // SurfaceTexture to get the final transform to be set on the media source.
+    // Currently, given a device orientation, the MediaSource rotates in such a way
+    // that the source is displayed upright. A particular use case
+    // is "Background Replacement" feature in the Camera app
+    // where the MediaSource rotates the source to align with the camera feed and pass it
+    // on to the backdropper filter. The backdropper only does the blending
+    // and does not have to do any rotation
+    // (except for mirroring in case of front camera).
+    // TODO: Currently the rotations are spread over a bunch of stages in the
+    // pipeline. A cleaner design
+    // could be to cast away all the rotation in a separate filter or attach a transform
+    // to the frame so that MediaSource itself need not know about any rotation.
+    private static final float[] mSourceCoords_0 = { 1, 1, 0, 1,
+                                                     0, 1, 0, 1,
+                                                     1, 0, 0, 1,
+                                                     0, 0, 0, 1 };
+    private static final float[] mSourceCoords_270 = { 0, 1, 0, 1,
+                                                      0, 0, 0, 1,
+                                                      1, 1, 0, 1,
+                                                      1, 0, 0, 1 };
+    private static final float[] mSourceCoords_180 = { 0, 0, 0, 1,
+                                                       1, 0, 0, 1,
+                                                       0, 1, 0, 1,
+                                                       1, 1, 0, 1 };
+    private static final float[] mSourceCoords_90 = { 1, 0, 0, 1,
+                                                       1, 1, 0, 1,
+                                                       0, 0, 0, 1,
+                                                       0, 1, 0, 1 };
+
+    private boolean mGotSize;
+    private boolean mPrepared;
+    private boolean mPlaying;
+    private boolean mNewFrameAvailable;
+    private boolean mOrientationUpdated;
+    private boolean mPaused;
+    private boolean mCompleted;
+
+    private final boolean mLogVerbose;
+    private static final String TAG = "MediaSource";
+
+    public MediaSource(String name) {
+        super(name);
+        mNewFrameAvailable = false;
+
+        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    }
+
+    @Override
+    public void setupPorts() {
+        // Add input port
+        addOutputPort("video", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
+                                                  FrameFormat.TARGET_GPU));
+    }
+
+    private void createFormats() {
+        mOutputFormat = ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
+                                           FrameFormat.TARGET_GPU);
+    }
+
+    @Override
+    protected void prepare(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Preparing MediaSource");
+
+        mFrameExtractor = new ShaderProgram(context, mFrameShader);
+        // SurfaceTexture defines (0,0) to be bottom-left. The filter framework
+        // defines (0,0) as top-left, so do the flip here.
+        mFrameExtractor.setSourceRect(0, 1, 1, -1);
+
+        createFormats();
+    }
+
+    @Override
+    public void open(FilterContext context) {
+        if (mLogVerbose) {
+            Log.v(TAG, "Opening MediaSource");
+            if (mSelectedIsUrl) {
+                Log.v(TAG, "Current URL is " + mSourceUrl);
+            } else {
+                Log.v(TAG, "Current source is Asset!");
+            }
+        }
+
+        mMediaFrame = (GLFrame)context.getFrameManager().newBoundFrame(
+                mOutputFormat,
+                GLFrame.EXTERNAL_TEXTURE,
+                0);
+
+        mSurfaceTexture = new SurfaceTexture(mMediaFrame.getTextureId());
+
+        if (!setupMediaPlayer(mSelectedIsUrl)) {
+          throw new RuntimeException("Error setting up MediaPlayer!");
+        }
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        // Note: process is synchronized by its caller in the Filter base class
+        if (mLogVerbose) Log.v(TAG, "Processing new frame");
+
+        if (mMediaPlayer == null) {
+            // Something went wrong in initialization or parameter updates
+            throw new NullPointerException("Unexpected null media player!");
+        }
+
+        if (mCompleted) {
+            // Video playback is done, so close us down
+            closeOutputPort("video");
+            return;
+        }
+
+        if (!mPlaying) {
+            int waitCount = 0;
+            if (mLogVerbose) Log.v(TAG, "Waiting for preparation to complete");
+            while (!mGotSize || !mPrepared) {
+                try {
+                    this.wait(PREP_TIMEOUT);
+                } catch (InterruptedException e) {
+                    // ignoring
+                }
+                if (mCompleted) {
+                    // Video playback is done, so close us down
+                    closeOutputPort("video");
+                    return;
+                }
+                waitCount++;
+                if (waitCount == PREP_TIMEOUT_REPEAT) {
+                    mMediaPlayer.release();
+                    throw new RuntimeException("MediaPlayer timed out while preparing!");
+                }
+            }
+            if (mLogVerbose) Log.v(TAG, "Starting playback");
+            mMediaPlayer.start();
+        }
+
+        // Use last frame if paused, unless just starting playback, in which case
+        // we want at least one valid frame before pausing
+        if (!mPaused || !mPlaying) {
+            if (mWaitForNewFrame) {
+                if (mLogVerbose) Log.v(TAG, "Waiting for new frame");
+
+                int waitCount = 0;
+                while (!mNewFrameAvailable) {
+                    if (waitCount == NEWFRAME_TIMEOUT_REPEAT) {
+                        if (mCompleted) {
+                            // Video playback is done, so close us down
+                            closeOutputPort("video");
+                            return;
+                        } else {
+                            throw new RuntimeException("Timeout waiting for new frame!");
+                        }
+                    }
+                    try {
+                        this.wait(NEWFRAME_TIMEOUT);
+                    } catch (InterruptedException e) {
+                        if (mLogVerbose) Log.v(TAG, "interrupted");
+                        // ignoring
+                    }
+                    waitCount++;
+                }
+                mNewFrameAvailable = false;
+                if (mLogVerbose) Log.v(TAG, "Got new frame");
+            }
+
+            mSurfaceTexture.updateTexImage();
+            mOrientationUpdated = true;
+        }
+        if (mOrientationUpdated) {
+            float[] surfaceTransform = new float[16];
+            mSurfaceTexture.getTransformMatrix(surfaceTransform);
+
+            float[] sourceCoords = new float[16];
+            switch (mOrientation) {
+                default:
+                case 0:
+                    Matrix.multiplyMM(sourceCoords, 0,
+                                      surfaceTransform, 0,
+                                      mSourceCoords_0, 0);
+                    break;
+                case 90:
+                    Matrix.multiplyMM(sourceCoords, 0,
+                                      surfaceTransform, 0,
+                                      mSourceCoords_90, 0);
+                    break;
+                case 180:
+                    Matrix.multiplyMM(sourceCoords, 0,
+                                      surfaceTransform, 0,
+                                      mSourceCoords_180, 0);
+                    break;
+                case 270:
+                    Matrix.multiplyMM(sourceCoords, 0,
+                                      surfaceTransform, 0,
+                                      mSourceCoords_270, 0);
+                    break;
+            }
+            if (mLogVerbose) {
+                Log.v(TAG, "OrientationHint = " + mOrientation);
+                String temp = String.format("SetSourceRegion: %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f",
+                        sourceCoords[4], sourceCoords[5],sourceCoords[0], sourceCoords[1],
+                        sourceCoords[12], sourceCoords[13],sourceCoords[8], sourceCoords[9]);
+                Log.v(TAG, temp);
+            }
+            mFrameExtractor.setSourceRegion(sourceCoords[4], sourceCoords[5],
+                    sourceCoords[0], sourceCoords[1],
+                    sourceCoords[12], sourceCoords[13],
+                    sourceCoords[8], sourceCoords[9]);
+            mOrientationUpdated = false;
+        }
+
+        Frame output = context.getFrameManager().newFrame(mOutputFormat);
+        mFrameExtractor.process(mMediaFrame, output);
+
+        long timestamp = mSurfaceTexture.getTimestamp();
+        if (mLogVerbose) Log.v(TAG, "Timestamp: " + (timestamp / 1000000000.0) + " s");
+        output.setTimestamp(timestamp);
+
+        pushOutput("video", output);
+        output.release();
+
+        mPlaying = true;
+    }
+
+    @Override
+    public void close(FilterContext context) {
+        if (mMediaPlayer.isPlaying()) {
+            mMediaPlayer.stop();
+        }
+        mPrepared = false;
+        mGotSize = false;
+        mPlaying = false;
+        mPaused = false;
+        mCompleted = false;
+        mNewFrameAvailable = false;
+
+        mMediaPlayer.release();
+        mMediaPlayer = null;
+        mSurfaceTexture.release();
+        mSurfaceTexture = null;
+        if (mLogVerbose) Log.v(TAG, "MediaSource closed");
+    }
+
+    @Override
+    public void tearDown(FilterContext context) {
+        if (mMediaFrame != null) {
+            mMediaFrame.release();
+        }
+    }
+
+    // When updating the port values of the filter, users can update sourceIsUrl to switch
+    //   between using URL objects or Assets.
+    // If updating only sourceUrl/sourceAsset, MediaPlayer gets reset if the current player
+    //   uses Url objects/Asset.
+    // Otherwise the new sourceUrl/sourceAsset is stored and will be used when users switch
+    //   sourceIsUrl next time.
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Parameter update");
+        if (name.equals("sourceUrl")) {
+           if (isOpen()) {
+                if (mLogVerbose) Log.v(TAG, "Opening new source URL");
+                if (mSelectedIsUrl) {
+                    setupMediaPlayer(mSelectedIsUrl);
+                }
+            }
+        } else if (name.equals("sourceAsset") ) {
+            if (isOpen()) {
+                if (mLogVerbose) Log.v(TAG, "Opening new source FD");
+                if (!mSelectedIsUrl) {
+                    setupMediaPlayer(mSelectedIsUrl);
+                }
+            }
+        } else if (name.equals("loop")) {
+            if (isOpen()) {
+                mMediaPlayer.setLooping(mLooping);
+            }
+        } else if (name.equals("sourceIsUrl")) {
+            if (isOpen()){
+                if (mSelectedIsUrl){
+                    if (mLogVerbose) Log.v(TAG, "Opening new source URL");
+                } else {
+                    if (mLogVerbose) Log.v(TAG, "Opening new source Asset");
+                }
+                setupMediaPlayer(mSelectedIsUrl);
+            }
+        } else if (name.equals("volume")) {
+            if (isOpen()) {
+                mMediaPlayer.setVolume(mVolume, mVolume);
+            }
+        } else if (name.equals("orientation") && mGotSize) {
+            if (mOrientation == 0 || mOrientation == 180) {
+                mOutputFormat.setDimensions(mWidth, mHeight);
+            } else {
+                mOutputFormat.setDimensions(mHeight, mWidth);
+            }
+            mOrientationUpdated = true;
+        }
+    }
+
+    synchronized public void pauseVideo(boolean pauseState) {
+        if (isOpen()) {
+            if (pauseState && !mPaused) {
+                mMediaPlayer.pause();
+            } else if (!pauseState && mPaused) {
+                mMediaPlayer.start();
+            }
+        }
+        mPaused = pauseState;
+    }
+
+    /** Creates a media player, sets it up, and calls prepare */
+    synchronized private boolean setupMediaPlayer(boolean useUrl) {
+        mPrepared = false;
+        mGotSize = false;
+        mPlaying = false;
+        mPaused = false;
+        mCompleted = false;
+        mNewFrameAvailable = false;
+
+        if (mLogVerbose) Log.v(TAG, "Setting up playback.");
+
+        if (mMediaPlayer != null) {
+            // Clean up existing media players
+            if (mLogVerbose) Log.v(TAG, "Resetting existing MediaPlayer.");
+            mMediaPlayer.reset();
+        } else {
+            // Create new media player
+            if (mLogVerbose) Log.v(TAG, "Creating new MediaPlayer.");
+            mMediaPlayer = new MediaPlayer();
+        }
+
+        if (mMediaPlayer == null) {
+            throw new RuntimeException("Unable to create a MediaPlayer!");
+        }
+
+        // Set up data sources, etc
+        try {
+            if (useUrl) {
+                if (mLogVerbose) Log.v(TAG, "Setting MediaPlayer source to URI " + mSourceUrl);
+                mMediaPlayer.setDataSource(mSourceUrl);
+            } else {
+                if (mLogVerbose) Log.v(TAG, "Setting MediaPlayer source to asset " + mSourceAsset);
+                mMediaPlayer.setDataSource(mSourceAsset.getFileDescriptor(), mSourceAsset.getStartOffset(), mSourceAsset.getLength());
+            }
+        } catch(IOException e) {
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+            if (useUrl) {
+                throw new RuntimeException(String.format("Unable to set MediaPlayer to URL %s!", mSourceUrl), e);
+            } else {
+                throw new RuntimeException(String.format("Unable to set MediaPlayer to asset %s!", mSourceAsset), e);
+            }
+        } catch(IllegalArgumentException e) {
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+            if (useUrl) {
+                throw new RuntimeException(String.format("Unable to set MediaPlayer to URL %s!", mSourceUrl), e);
+            } else {
+                throw new RuntimeException(String.format("Unable to set MediaPlayer to asset %s!", mSourceAsset), e);
+            }
+        }
+
+        mMediaPlayer.setLooping(mLooping);
+        mMediaPlayer.setVolume(mVolume, mVolume);
+
+        // Bind it to our media frame
+        Surface surface = new Surface(mSurfaceTexture);
+        mMediaPlayer.setSurface(surface);
+        surface.release();
+
+        // Connect Media Player to callbacks
+
+        mMediaPlayer.setOnVideoSizeChangedListener(onVideoSizeChangedListener);
+        mMediaPlayer.setOnPreparedListener(onPreparedListener);
+        mMediaPlayer.setOnCompletionListener(onCompletionListener);
+
+        // Connect SurfaceTexture to callback
+        mSurfaceTexture.setOnFrameAvailableListener(onMediaFrameAvailableListener);
+
+        if (mLogVerbose) Log.v(TAG, "Preparing MediaPlayer.");
+        mMediaPlayer.prepareAsync();
+
+        return true;
+    }
+
+    private MediaPlayer.OnVideoSizeChangedListener onVideoSizeChangedListener =
+            new MediaPlayer.OnVideoSizeChangedListener() {
+        public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+            if (mLogVerbose) Log.v(TAG, "MediaPlayer sent dimensions: " + width + " x " + height);
+            if (!mGotSize) {
+                if (mOrientation == 0 || mOrientation == 180) {
+                    mOutputFormat.setDimensions(width, height);
+                } else {
+                    mOutputFormat.setDimensions(height, width);
+                }
+                mWidth = width;
+                mHeight = height;
+            } else {
+                if (mOutputFormat.getWidth() != width ||
+                    mOutputFormat.getHeight() != height) {
+                    Log.e(TAG, "Multiple video size change events received!");
+                }
+            }
+            synchronized(MediaSource.this) {
+                mGotSize = true;
+                MediaSource.this.notify();
+            }
+        }
+    };
+
+    private MediaPlayer.OnPreparedListener onPreparedListener =
+            new MediaPlayer.OnPreparedListener() {
+        public void onPrepared(MediaPlayer mp) {
+            if (mLogVerbose) Log.v(TAG, "MediaPlayer is prepared");
+            synchronized(MediaSource.this) {
+                mPrepared = true;
+                MediaSource.this.notify();
+            }
+        }
+    };
+
+    private MediaPlayer.OnCompletionListener onCompletionListener =
+            new MediaPlayer.OnCompletionListener() {
+        public void onCompletion(MediaPlayer mp) {
+            if (mLogVerbose) Log.v(TAG, "MediaPlayer has completed playback");
+            synchronized(MediaSource.this) {
+                mCompleted = true;
+            }
+        }
+    };
+
+    private SurfaceTexture.OnFrameAvailableListener onMediaFrameAvailableListener =
+            new SurfaceTexture.OnFrameAvailableListener() {
+        public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+            if (mLogVerbose) Log.v(TAG, "New frame from media player");
+            synchronized(MediaSource.this) {
+                if (mLogVerbose) Log.v(TAG, "New frame: notify");
+                mNewFrameAvailable = true;
+                MediaSource.this.notify();
+                if (mLogVerbose) Log.v(TAG, "New frame: notify done");
+            }
+        }
+    };
+
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java
new file mode 100644
index 0000000..37fa242
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.filterpacks.videosrc;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.FrameManager;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GenerateFinalPort;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.media.MediaPlayer;
+import android.os.ConditionVariable;
+import android.opengl.Matrix;
+
+import java.io.IOException;
+import java.io.FileDescriptor;
+import java.lang.IllegalArgumentException;
+import java.util.List;
+import java.util.Set;
+
+import android.util.Log;
+
+/** <p>A filter that converts textures from a SurfaceTexture object into frames for
+ * processing in the filter framework.</p>
+ *
+ * <p>To use, connect up the sourceListener callback, and then when executing
+ * the graph, use the SurfaceTexture object passed to the callback to feed
+ * frames into the filter graph. For example, pass the SurfaceTexture into
+ * {#link
+ * android.hardware.Camera.setPreviewTexture(android.graphics.SurfaceTexture)}.
+ * This filter is intended for applications that need for flexibility than the
+ * CameraSource and MediaSource provide. Note that the application needs to
+ * provide width and height information for the SurfaceTextureSource, which it
+ * should obtain from wherever the SurfaceTexture data is coming from to avoid
+ * unnecessary resampling.</p>
+ *
+ * @hide
+ */
+public class SurfaceTextureSource extends Filter {
+
+    /** User-visible parameters */
+
+    /** The callback interface for the sourceListener parameter */
+    public interface SurfaceTextureSourceListener {
+        public void onSurfaceTextureSourceReady(SurfaceTexture source);
+    }
+    /** A callback to send the internal SurfaceTexture object to, once it is
+     * created. This callback will be called when the the filter graph is
+     * preparing to execute, but before any processing has actually taken
+     * place. The SurfaceTexture object passed to this callback is the only way
+     * to feed this filter. When the filter graph is shutting down, this
+     * callback will be called again with null as the source.
+     *
+     * This callback may be called from an arbitrary thread, so it should not
+     * assume it is running in the UI thread in particular.
+     */
+    @GenerateFinalPort(name = "sourceListener")
+    private SurfaceTextureSourceListener mSourceListener;
+
+    /** The width of the output image frame. If the texture width for the
+     * SurfaceTexture source is known, use it here to minimize resampling. */
+    @GenerateFieldPort(name = "width")
+    private int mWidth;
+
+    /** The height of the output image frame. If the texture height for the
+     * SurfaceTexture source is known, use it here to minimize resampling. */
+    @GenerateFieldPort(name = "height")
+    private int mHeight;
+
+    /** Whether the filter will always wait for a new frame from its
+     * SurfaceTexture, or whether it will output an old frame again if a new
+     * frame isn't available. The filter will always wait for the first frame,
+     * to avoid outputting a blank frame. Defaults to true.
+     */
+    @GenerateFieldPort(name = "waitForNewFrame", hasDefault = true)
+    private boolean mWaitForNewFrame = true;
+
+    /** Maximum timeout before signaling error when waiting for a new frame. Set
+     * this to zero to disable the timeout and wait indefinitely. In milliseconds.
+     */
+    @GenerateFieldPort(name = "waitTimeout", hasDefault = true)
+    private int mWaitTimeout = 1000;
+
+    /** Whether a timeout is an exception-causing failure, or just causes the
+     * filter to close.
+     */
+    @GenerateFieldPort(name = "closeOnTimeout", hasDefault = true)
+    private boolean mCloseOnTimeout = false;
+
+    // Variables for input->output conversion
+    private GLFrame mMediaFrame;
+    private ShaderProgram mFrameExtractor;
+    private SurfaceTexture mSurfaceTexture;
+    private MutableFrameFormat mOutputFormat;
+    private ConditionVariable mNewFrameAvailable;
+    private boolean mFirstFrame;
+
+    private float[] mFrameTransform;
+    private float[] mMappedCoords;
+    // These default source coordinates perform the necessary flip
+    // for converting from MFF/Bitmap origin to OpenGL origin.
+    private static final float[] mSourceCoords = { 0, 1, 0, 1,
+                                                   1, 1, 0, 1,
+                                                   0, 0, 0, 1,
+                                                   1, 0, 0, 1 };
+    // Shader for output
+    private final String mRenderShader =
+            "#extension GL_OES_EGL_image_external : require\n" +
+            "precision mediump float;\n" +
+            "uniform samplerExternalOES tex_sampler_0;\n" +
+            "varying vec2 v_texcoord;\n" +
+            "void main() {\n" +
+            "  gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" +
+            "}\n";
+
+    // Variables for logging
+
+    private static final String TAG = "SurfaceTextureSource";
+    private static final boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+
+    public SurfaceTextureSource(String name) {
+        super(name);
+        mNewFrameAvailable = new ConditionVariable();
+        mFrameTransform = new float[16];
+        mMappedCoords = new float[16];
+    }
+
+    @Override
+    public void setupPorts() {
+        // Add input port
+        addOutputPort("video", ImageFormat.create(ImageFormat.COLORSPACE_RGBA,
+                                                  FrameFormat.TARGET_GPU));
+    }
+
+    private void createFormats() {
+        mOutputFormat = ImageFormat.create(mWidth, mHeight,
+                                           ImageFormat.COLORSPACE_RGBA,
+                                           FrameFormat.TARGET_GPU);
+    }
+
+    @Override
+    protected void prepare(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Preparing SurfaceTextureSource");
+
+        createFormats();
+
+        // Prepare input
+        mMediaFrame = (GLFrame)context.getFrameManager().newBoundFrame(mOutputFormat,
+                                                                       GLFrame.EXTERNAL_TEXTURE,
+                                                                       0);
+
+        // Prepare output
+        mFrameExtractor = new ShaderProgram(context, mRenderShader);
+    }
+
+    @Override
+    public void open(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Opening SurfaceTextureSource");
+        // Create SurfaceTexture anew each time - it can use substantial memory.
+        mSurfaceTexture = new SurfaceTexture(mMediaFrame.getTextureId());
+        // Connect SurfaceTexture to callback
+        mSurfaceTexture.setOnFrameAvailableListener(onFrameAvailableListener);
+        // Connect SurfaceTexture to source
+        mSourceListener.onSurfaceTextureSourceReady(mSurfaceTexture);
+        mFirstFrame = true;
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Processing new frame");
+
+        // First, get new frame if available
+        if (mWaitForNewFrame || mFirstFrame) {
+            boolean gotNewFrame;
+            if (mWaitTimeout != 0) {
+                gotNewFrame = mNewFrameAvailable.block(mWaitTimeout);
+                if (!gotNewFrame) {
+                    if (!mCloseOnTimeout) {
+                        throw new RuntimeException("Timeout waiting for new frame");
+                    } else {
+                        if (mLogVerbose) Log.v(TAG, "Timeout waiting for a new frame. Closing.");
+                        closeOutputPort("video");
+                        return;
+                    }
+                }
+            } else {
+                mNewFrameAvailable.block();
+            }
+            mNewFrameAvailable.close();
+            mFirstFrame = false;
+        }
+
+        mSurfaceTexture.updateTexImage();
+
+        mSurfaceTexture.getTransformMatrix(mFrameTransform);
+        Matrix.multiplyMM(mMappedCoords, 0,
+                          mFrameTransform, 0,
+                          mSourceCoords, 0);
+        mFrameExtractor.setSourceRegion(mMappedCoords[0], mMappedCoords[1],
+                                        mMappedCoords[4], mMappedCoords[5],
+                                        mMappedCoords[8], mMappedCoords[9],
+                                        mMappedCoords[12], mMappedCoords[13]);
+        // Next, render to output
+        Frame output = context.getFrameManager().newFrame(mOutputFormat);
+        mFrameExtractor.process(mMediaFrame, output);
+
+        output.setTimestamp(mSurfaceTexture.getTimestamp());
+
+        pushOutput("video", output);
+        output.release();
+    }
+
+    @Override
+    public void close(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "SurfaceTextureSource closed");
+        mSourceListener.onSurfaceTextureSourceReady(null);
+        mSurfaceTexture.release();
+        mSurfaceTexture = null;
+    }
+
+    @Override
+    public void tearDown(FilterContext context) {
+        if (mMediaFrame != null) {
+            mMediaFrame.release();
+        }
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        if (name.equals("width") || name.equals("height") ) {
+            mOutputFormat.setDimensions(mWidth, mHeight);
+        }
+    }
+
+    private SurfaceTexture.OnFrameAvailableListener onFrameAvailableListener =
+            new SurfaceTexture.OnFrameAvailableListener() {
+        public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+            if (mLogVerbose) Log.v(TAG, "New frame from SurfaceTexture");
+            mNewFrameAvailable.open();
+        }
+    };
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java
new file mode 100644
index 0000000..436caab
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.filterpacks.videosrc;
+
+import android.filterfw.core.Filter;
+import android.filterfw.core.FilterContext;
+import android.filterfw.core.FilterSurfaceView;
+import android.filterfw.core.Frame;
+import android.filterfw.core.FrameFormat;
+import android.filterfw.core.GenerateFieldPort;
+import android.filterfw.core.GenerateFinalPort;
+import android.filterfw.core.GLEnvironment;
+import android.filterfw.core.GLFrame;
+import android.filterfw.core.KeyValueMap;
+import android.filterfw.core.MutableFrameFormat;
+import android.filterfw.core.NativeProgram;
+import android.filterfw.core.NativeFrame;
+import android.filterfw.core.Program;
+import android.filterfw.core.ShaderProgram;
+import android.filterfw.format.ImageFormat;
+
+import android.filterfw.geometry.Quad;
+import android.filterfw.geometry.Point;
+
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class SurfaceTextureTarget extends Filter {
+
+    private final int RENDERMODE_STRETCH   = 0;
+    private final int RENDERMODE_FIT       = 1;
+    private final int RENDERMODE_FILL_CROP = 2;
+    private final int RENDERMODE_CUSTOMIZE = 3;
+
+    /** Required. Sets the destination surfaceTexture.
+     */
+    @GenerateFinalPort(name = "surfaceTexture")
+    private SurfaceTexture mSurfaceTexture;
+
+    /** Required. Sets the width of the output surfaceTexture images */
+    @GenerateFinalPort(name = "width")
+    private int mScreenWidth;
+
+    /** Required. Sets the height of the output surfaceTexture images */
+    @GenerateFinalPort(name = "height")
+    private int mScreenHeight;
+
+
+    /** Optional. Control how the incoming frames are rendered onto the
+     * output. Default is FIT.
+     * RENDERMODE_STRETCH: Just fill the output surfaceView.
+     * RENDERMODE_FIT: Keep aspect ratio and fit without cropping. May
+     * have black bars.
+     * RENDERMODE_FILL_CROP: Keep aspect ratio and fit without black
+     * bars. May crop.
+     */
+    @GenerateFieldPort(name = "renderMode", hasDefault = true)
+    private String mRenderModeString;
+
+    @GenerateFieldPort(name = "sourceQuad", hasDefault = true)
+    private Quad mSourceQuad = new Quad(new Point(0.0f, 1.0f),
+                                        new Point(1.0f, 1.0f),
+                                        new Point(0.0f, 0.0f),
+                                        new Point(1.0f, 0.0f));
+
+    @GenerateFieldPort(name = "targetQuad", hasDefault = true)
+    private Quad mTargetQuad = new Quad(new Point(0.0f, 0.0f),
+                                        new Point(1.0f, 0.0f),
+                                        new Point(0.0f, 1.0f),
+                                        new Point(1.0f, 1.0f));
+
+    private int mSurfaceId;
+
+    private ShaderProgram mProgram;
+    private GLFrame mScreen;
+    private int mRenderMode = RENDERMODE_FIT;
+    private float mAspectRatio = 1.f;
+
+    private boolean mLogVerbose;
+    private static final String TAG = "SurfaceTextureTarget";
+
+    public SurfaceTextureTarget(String name) {
+        super(name);
+
+        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    }
+
+    @Override
+    public void setupPorts() {
+        // Make sure we have a SurfaceView
+        if (mSurfaceTexture == null) {
+            throw new RuntimeException("Null SurfaceTexture passed to SurfaceTextureTarget");
+        }
+
+        // Add input port - will accept anything that's 4-channel.
+        addMaskedInputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA));
+    }
+
+    public void updateRenderMode() {
+        if (mRenderModeString != null) {
+            if (mRenderModeString.equals("stretch")) {
+                mRenderMode = RENDERMODE_STRETCH;
+            } else if (mRenderModeString.equals("fit")) {
+                mRenderMode = RENDERMODE_FIT;
+            } else if (mRenderModeString.equals("fill_crop")) {
+                mRenderMode = RENDERMODE_FILL_CROP;
+            } else if (mRenderModeString.equals("customize")) {
+                mRenderMode = RENDERMODE_CUSTOMIZE;
+            } else {
+                throw new RuntimeException("Unknown render mode '" + mRenderModeString + "'!");
+            }
+        }
+        updateTargetRect();
+    }
+
+    @Override
+    public void prepare(FilterContext context) {
+        // Create identity shader to render, and make sure to render upside-down, as textures
+        // are stored internally bottom-to-top.
+        mProgram = ShaderProgram.createIdentity(context);
+        mProgram.setSourceRect(0, 1, 1, -1);
+        mProgram.setClearColor(0.0f, 0.0f, 0.0f);
+
+        updateRenderMode();
+
+        // Create a frame representing the screen
+        MutableFrameFormat screenFormat = new MutableFrameFormat(FrameFormat.TYPE_BYTE,
+                                                                 FrameFormat.TARGET_GPU);
+        screenFormat.setBytesPerSample(4);
+        screenFormat.setDimensions(mScreenWidth, mScreenHeight);
+        mScreen = (GLFrame)context.getFrameManager().newBoundFrame(screenFormat,
+                                                                   GLFrame.EXISTING_FBO_BINDING,
+                                                                   0);
+    }
+
+    @Override
+    public void open(FilterContext context) {
+        // Set up SurfaceTexture internals
+        mSurfaceId = context.getGLEnvironment().registerSurfaceTexture(mSurfaceTexture, mScreenWidth, mScreenHeight);
+    }
+
+    @Override
+    public void process(FilterContext context) {
+        if (mLogVerbose) Log.v(TAG, "Starting frame processing");
+
+        GLEnvironment glEnv = context.getGLEnvironment();
+
+        // Get input frame
+        Frame input = pullInput("frame");
+        boolean createdFrame = false;
+
+        float currentAspectRatio = (float)input.getFormat().getWidth() / input.getFormat().getHeight();
+        if (currentAspectRatio != mAspectRatio) {
+            if (mLogVerbose) Log.v(TAG, "New aspect ratio: " + currentAspectRatio +", previously: " + mAspectRatio);
+            mAspectRatio = currentAspectRatio;
+            updateTargetRect();
+        }
+
+        // See if we need to copy to GPU
+        Frame gpuFrame = null;
+        if (mLogVerbose) Log.v("SurfaceTextureTarget", "Got input format: " + input.getFormat());
+
+        int target = input.getFormat().getTarget();
+        if (target != FrameFormat.TARGET_GPU) {
+            gpuFrame = context.getFrameManager().duplicateFrameToTarget(input,
+                                                                        FrameFormat.TARGET_GPU);
+            createdFrame = true;
+        } else {
+            gpuFrame = input;
+        }
+
+        // Activate our surface
+        glEnv.activateSurfaceWithId(mSurfaceId);
+
+        // Process
+        mProgram.process(gpuFrame, mScreen);
+
+        glEnv.setSurfaceTimestamp(input.getTimestamp());
+
+        // And swap buffers
+        glEnv.swapBuffers();
+
+        if (createdFrame) {
+            gpuFrame.release();
+        }
+    }
+
+    @Override
+    public void fieldPortValueUpdated(String name, FilterContext context) {
+        updateRenderMode();
+    }
+
+    @Override
+    public void tearDown(FilterContext context) {
+        if (mScreen != null) {
+            mScreen.release();
+        }
+    }
+
+    private void updateTargetRect() {
+        if (mScreenWidth > 0 && mScreenHeight > 0 && mProgram != null) {
+            float screenAspectRatio = (float)mScreenWidth / mScreenHeight;
+            float relativeAspectRatio = screenAspectRatio / mAspectRatio;
+
+            if (relativeAspectRatio == 1.0f && mRenderMode != RENDERMODE_CUSTOMIZE) {
+                mProgram.setClearsOutput(false);
+            } else {
+                switch (mRenderMode) {
+                    case RENDERMODE_STRETCH:
+                        mProgram.setTargetRect(0, 0, 1, 1);
+                        mTargetQuad.p0.set(0f, 0.0f);
+                        mTargetQuad.p1.set(1f, 0.0f);
+                        mTargetQuad.p2.set(0f, 1.0f);
+                        mTargetQuad.p3.set(1f, 1.0f);
+                        mProgram.setClearsOutput(false);
+                        break;
+                    case RENDERMODE_FIT:
+                        if (relativeAspectRatio > 1.0f) {
+                            // Screen is wider than the camera, scale down X
+                            mTargetQuad.p0.set(0.5f - 0.5f / relativeAspectRatio, 0.0f);
+                            mTargetQuad.p1.set(0.5f + 0.5f / relativeAspectRatio, 0.0f);
+                            mTargetQuad.p2.set(0.5f - 0.5f / relativeAspectRatio, 1.0f);
+                            mTargetQuad.p3.set(0.5f + 0.5f / relativeAspectRatio, 1.0f);
+
+                        } else {
+                            // Screen is taller than the camera, scale down Y
+                            mTargetQuad.p0.set(0.0f, 0.5f - 0.5f * relativeAspectRatio);
+                            mTargetQuad.p1.set(1.0f, 0.5f - 0.5f * relativeAspectRatio);
+                            mTargetQuad.p2.set(0.0f, 0.5f + 0.5f * relativeAspectRatio);
+                            mTargetQuad.p3.set(1.0f, 0.5f + 0.5f * relativeAspectRatio);
+                        }
+                        mProgram.setClearsOutput(true);
+                        break;
+                    case RENDERMODE_FILL_CROP:
+                        if (relativeAspectRatio > 1) {
+                            // Screen is wider than the camera, crop in Y
+                            mTargetQuad.p0.set(0.0f, 0.5f - 0.5f * relativeAspectRatio);
+                            mTargetQuad.p1.set(1.0f, 0.5f - 0.5f * relativeAspectRatio);
+                            mTargetQuad.p2.set(0.0f, 0.5f + 0.5f * relativeAspectRatio);
+                            mTargetQuad.p3.set(1.0f, 0.5f + 0.5f * relativeAspectRatio);
+                        } else {
+                            // Screen is taller than the camera, crop in X
+                            mTargetQuad.p0.set(0.5f - 0.5f / relativeAspectRatio, 0.0f);
+                            mTargetQuad.p1.set(0.5f + 0.5f / relativeAspectRatio, 0.0f);
+                            mTargetQuad.p2.set(0.5f - 0.5f / relativeAspectRatio, 1.0f);
+                            mTargetQuad.p3.set(0.5f + 0.5f / relativeAspectRatio, 1.0f);
+                        }
+                        mProgram.setClearsOutput(true);
+                        break;
+                    case RENDERMODE_CUSTOMIZE:
+                        ((ShaderProgram) mProgram).setSourceRegion(mSourceQuad);
+                        break;
+                }
+                ((ShaderProgram) mProgram).setTargetRegion(mTargetQuad);
+            }
+        }
+    }
+}
diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/package-info.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/package-info.java
new file mode 100644
index 0000000..d8fd0bd
--- /dev/null
+++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * @hide
+ */
+package android.filterpacks.videosrc;
diff --git a/media/mca/filterpacks/native/base/geometry.cpp b/media/mca/filterpacks/native/base/geometry.cpp
new file mode 100644
index 0000000..7812d502
--- /dev/null
+++ b/media/mca/filterpacks/native/base/geometry.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cutils/log.h>
+#include <cmath>
+
+#include "geometry.h"
+
+namespace android {
+namespace filterfw {
+
+float Point::Length() const {
+  return std::sqrt(x_ * x_ + y_ * y_);
+}
+
+bool Point::ScaleTo(float new_length) {
+  float length = Length();
+  if (length == 0.0f) {
+    return false;
+  }
+  x_ *= new_length / length;
+  y_ *= new_length / length;
+  return true;
+}
+
+float Point::Distance(const Point& p0, const Point& p1) {
+  Point diff = p1 - p0;
+  return diff.Length();
+}
+
+Point Point::operator+(const Point& other) const {
+  Point out;
+  out.x_ = x_ + other.x_;
+  out.y_ = y_ + other.y_;
+  return out;
+}
+
+Point Point::operator-(const Point& other) const {
+  Point out;
+  out.x_ = x_ - other.x_;
+  out.y_ = y_ - other.y_;
+  return out;
+}
+
+Point Point::operator*(float factor) const {
+  Point out;
+  out.x_ = factor * x_;
+  out.y_ = factor * y_;
+  return out;
+}
+
+void Point::Rotate90Clockwise() {
+  const float x = x_;
+  x_ = y_;
+  y_ = -x;
+}
+
+bool Rect::ExpandToAspectRatio(float ratio) {
+  if (width <= 0.0f || height <= 0.0f || ratio <= 0.0f) {
+    return false;
+  }
+
+  const float current_ratio = width / height;
+  if (current_ratio < ratio) {
+    const float dx = width * (ratio / current_ratio - 1.0f);
+    x -= dx / 2.0f;
+    width += dx;
+  } else {
+    const float dy = height * (current_ratio / ratio - 1.0f);
+    y -= dy / 2.0f;
+    height += dy;
+  }
+  return true;
+}
+
+bool Rect::ExpandToMinLength(float length) {
+  if (width <= 0.0f || height <= 0.0f || length <= 0.0f) {
+    return false;
+  }
+
+  const float current_length = width > height ? width : height;
+  if (length > current_length) {
+    const float dx = width * (length / current_length - 1.0f);
+    x -= dx / 2.0f;
+    width += dx;
+    const float dy = height * (length / current_length - 1.0f);
+    y -= dy / 2.0f;
+    height += dy;
+  }
+  return true;
+}
+
+bool Rect::ScaleWithLengthLimit(float factor, float max_length) {
+  if (width <= 0.0f || height <= 0.0f || factor <= 0.0f) {
+    return false;
+  }
+
+  const float current_length = width > height ? width : height;
+  if (current_length >= max_length) {
+    return true;
+  }
+
+  float f = factor;
+  if (current_length * f > max_length) {
+    f *= max_length / (current_length * f);
+  }
+
+  const float dx = width * (f - 1.0f);
+  x -= dx / 2.0f;
+  width += dx;
+  const float dy = height * (f - 1.0f);
+  y -= dy / 2.0f;
+  height += dy;
+  return true;
+}
+
+const Point& Quad::point(int ix) const {
+  ALOG_ASSERT(ix < static_cast<int>(points_.size()), "Access out of bounds");
+  return points_[ix];
+}
+
+bool SlantedRect::FromCenterAxisAndLengths(const Point& center,
+                                           const Point& vert_axis,
+                                           const Point& lengths) {
+  Point dy = vert_axis;
+  if (!dy.ScaleTo(lengths.y() / 2.0f)) {
+    ALOGE("Illegal axis: %f %f", vert_axis.x(), vert_axis.y());
+    return false;
+  }
+
+  Point dx = dy;
+  dx.Rotate90Clockwise();
+  dx.ScaleTo(lengths.x() / 2.0f);
+
+  points_[0] = center - dx - dy;
+  points_[1] = center + dx - dy;
+  points_[2] = center - dx + dy;
+  points_[3] = center + dx + dy;
+
+  width_ = lengths.x();
+  height_ = lengths.y();
+
+  return true;
+}
+
+} // namespace filterfw
+} // namespace android
diff --git a/media/mca/filterpacks/native/base/geometry.h b/media/mca/filterpacks/native/base/geometry.h
new file mode 100644
index 0000000..40a9343
--- /dev/null
+++ b/media/mca/filterpacks/native/base/geometry.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_FILTERPACKS_BASE_GEOMETRY_H
+#define ANDROID_FILTERFW_FILTERPACKS_BASE_GEOMETRY_H
+
+#include <vector>
+
+namespace android {
+namespace filterfw {
+
+// This is an initial implementation of some geometrical structures. This is
+// likely to grow and become more sophisticated in the future.
+
+class Point {
+  public:
+    Point() : x_(0.0f), y_(0.0f) {}
+    Point(float x, float y) : x_(x), y_(y) {}
+
+    float x() const { return x_; }
+    float y() const { return y_; }
+
+    float Length() const;
+    bool ScaleTo(float new_length);
+    static float Distance(const Point& p0, const Point& p1);
+
+    // Add more of these as needed:
+    Point operator+(const Point& other) const;
+    Point operator-(const Point& other) const;
+    Point operator*(float factor) const;
+
+    void Rotate90Clockwise();
+
+  private:
+    float x_, y_;
+};
+
+class Quad {
+  public:
+    Quad() : points_(4) {}
+    virtual ~Quad() {}
+
+    Quad(const Point& p0, const Point& p1, const Point& p2, const Point& p3)
+        : points_(4) {
+      points_[0] = p0;
+      points_[1] = p1;
+      points_[2] = p2;
+      points_[3] = p3;
+    }
+
+    const std::vector<Point>& points() const { return points_; }
+    const Point& point(int ix) const;
+
+  protected:
+    std::vector<Point> points_;
+};
+
+class SlantedRect : public Quad {
+  public:
+    SlantedRect() : width_(0.0f), height_(0.0f) {}
+    virtual ~SlantedRect() {}
+
+    bool FromCenterAxisAndLengths(const Point& center,
+                                  const Point& vert_axis,
+                                  const Point& lenghts);
+
+    float width() const { return width_; }
+    float height() const { return height_; }
+
+  private:
+    float width_;
+    float height_;
+};
+
+struct Rect {
+  float x, y, width, height;
+
+  Rect() {
+    x = y = 0.0f;
+    width = height = 1.0f;
+  }
+
+  Rect(float x, float y, float width, float height) {
+    this->x = x;
+    this->y = y;
+    this->width = width;
+    this->height = height;
+  }
+
+  bool ExpandToAspectRatio(float ratio);
+  bool ExpandToMinLength(float length);
+  bool ScaleWithLengthLimit(float factor, float max_length);
+};
+
+} // namespace filterfw
+} // namespace android
+
+#endif // ANDROID_FILTERFW_FILTERPACKS_BASE_GEOMETRY_H
diff --git a/media/mca/filterpacks/native/base/time_util.cpp b/media/mca/filterpacks/native/base/time_util.cpp
new file mode 100644
index 0000000..1a78a95
--- /dev/null
+++ b/media/mca/filterpacks/native/base/time_util.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "time_util.h"
+#include "utilities.h"
+
+#include <cutils/log.h>
+#include <sys/time.h>
+#include <map>
+
+namespace android {
+namespace filterfw {
+
+uint64_t getTimeUs() {
+    static long basesec;
+    struct timeval tv;
+    uint64_t nowtime;
+    gettimeofday(&tv, 0);
+    if (basesec == 0) {
+        basesec = tv.tv_sec;
+    }
+    nowtime = (uint64_t)(tv.tv_sec - basesec) * (uint64_t)1000000 +
+              (uint64_t)tv.tv_usec;
+    return nowtime;
+}
+
+const uint64_t NamedStopWatch::kDefaultLoggingPeriodInFrames = 100;
+
+NamedStopWatch::NamedStopWatch(const std::string& name)
+      : mName(name),
+        mLoggingPeriodInFrames(kDefaultLoggingPeriodInFrames),
+        mStartUSec(0),
+        mNumCalls(0),
+        mTotalUSec(0) {
+}
+
+void NamedStopWatch::Start() {
+    mStartUSec = getTimeUs();
+}
+
+void NamedStopWatch::Stop() {
+    if (!mStartUSec) {
+        return;
+    }
+    uint64_t stopUSec = getTimeUs();
+    if (stopUSec > mStartUSec) {
+        ++mNumCalls;
+        mTotalUSec += stopUSec - mStartUSec;
+        if (mNumCalls % mLoggingPeriodInFrames == 0) {
+            const float mSec = TotalUSec() * 1.0E-3f / NumCalls();
+            ALOGE("%s: %f ms", Name().c_str(), mSec);
+        }
+    }
+    mStartUSec = 0;
+}
+
+namespace {
+static NamedStopWatch* GetWatchForName(const std::string& watch_name) {
+    // TODO: this leaks the NamedStopWatch objects. Replace it with a
+    // singleton to avoid that and make it thread safe.
+    static std::map<std::string, NamedStopWatch*> watches;
+    NamedStopWatch* watch = FindPtrOrNull(watches, watch_name);
+    if (!watch) {
+        watch = new NamedStopWatch(watch_name);
+        watches[watch_name] = watch;
+    }
+    return watch;
+};
+}  // namespace
+
+ScopedTimer::ScopedTimer(const std::string& stop_watch_name) {
+    mWatch = GetWatchForName(stop_watch_name);
+    mWatch->Start();
+}
+
+} // namespace filterfw
+} // namespace android
diff --git a/media/mca/filterpacks/native/base/time_util.h b/media/mca/filterpacks/native/base/time_util.h
new file mode 100644
index 0000000..60d76c6
--- /dev/null
+++ b/media/mca/filterpacks/native/base/time_util.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_FILTERPACKS_BASE_TIME_UTIL_H
+#define ANDROID_FILTERFW_FILTERPACKS_BASE_TIME_UTIL_H
+
+#include <string>
+#include <utils/RefBase.h>
+
+#define LOG_MFF_RUNNING_TIMES 0
+
+namespace android {
+namespace filterfw {
+
+uint64_t getTimeUs();
+
+class NamedStopWatch : public RefBase {
+  public:
+    static const uint64_t kDefaultLoggingPeriodInFrames;
+
+    explicit NamedStopWatch(const std::string& name);
+    void Start();
+    void Stop();
+
+    void SetName(const std::string& name) { mName = name; }
+    void SetLoggingPeriodInFrames(uint64_t numFrames) {
+        mLoggingPeriodInFrames = numFrames;
+    }
+
+    const std::string& Name() const { return mName; }
+    uint64_t NumCalls() const { return mNumCalls; }
+    uint64_t TotalUSec() const { return mTotalUSec; }
+
+  private:
+    std::string mName;
+    uint64_t mLoggingPeriodInFrames;
+    uint64_t mStartUSec;
+    uint64_t mNumCalls;
+    uint64_t mTotalUSec;
+};
+
+class ScopedTimer {
+  public:
+    explicit ScopedTimer(const std::string& stop_watch_name);
+    explicit ScopedTimer(NamedStopWatch* watch)
+        : mWatch(watch) { mWatch->Start(); }
+    ~ScopedTimer() { mWatch->Stop(); }
+
+  private:
+    NamedStopWatch* mWatch;
+};
+
+} // namespace filterfw
+} // namespace android
+
+#endif  // ANDROID_FILTERFW_FILTERPACKS_BASE_TIME_UTIL_H
diff --git a/media/mca/filterpacks/native/base/utilities.h b/media/mca/filterpacks/native/base/utilities.h
new file mode 100644
index 0000000..302e177
--- /dev/null
+++ b/media/mca/filterpacks/native/base/utilities.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_FILTERPACKS_BASE_UTILITIES_H
+#define ANDROID_FILTERFW_FILTERPACKS_BASE_UTILITIES_H
+
+#include <set>
+#include <utility>
+
+namespace android {
+namespace filterfw {
+
+// Convenience Macro to make copy constructor and assignment operator private
+// (thereby disallowing copying and assigning).
+#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
+  TypeName(const TypeName&);               \
+  void operator=(const TypeName&)
+
+// A macro to disallow all the implicit constructors, namely the
+// default constructor, copy constructor and operator= functions.
+#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
+  TypeName();                                    \
+  DISALLOW_COPY_AND_ASSIGN(TypeName)
+
+// STLDeleteContainerPointers()
+//  For a range within a container of pointers, calls delete
+//  (non-array version) on these pointers.
+// NOTE: for these three functions, we could just implement a DeleteObject
+// functor and then call for_each() on the range and functor, but this
+// requires us to pull in all of algorithm.h, which seems expensive.
+// For hash_[multi]set, it is important that this deletes behind the iterator
+// because the hash_set may call the hash function on the iterator when it is
+// advanced, which could result in the hash function trying to deference a
+// stale pointer.
+template <class ForwardIterator>
+void STLDeleteContainerPointers(ForwardIterator begin,
+                                ForwardIterator end) {
+  while (begin != end) {
+    ForwardIterator temp = begin;
+    ++begin;
+    delete *temp;
+  }
+}
+
+// Given an STL container consisting of (key, value) pairs, STLDeleteValues
+// deletes all the "value" components and clears the container.  Does nothing
+// in the case it's given a NULL pointer.
+template <class T>
+void STLDeleteValues(T *v) {
+  if (!v) return;
+  for (typename T::iterator i = v->begin(); i != v->end(); ++i) {
+    delete i->second;
+  }
+  v->clear();
+}
+
+// Perform a lookup in a map or hash_map.
+// If the key is present a const pointer to the associated value is returned,
+// otherwise a NULL pointer is returned.
+template <class Collection>
+const typename Collection::value_type::second_type*
+FindOrNull(const Collection& collection,
+           const typename Collection::value_type::first_type& key) {
+  typename Collection::const_iterator it = collection.find(key);
+  if (it == collection.end()) {
+    return 0;
+  }
+  return &it->second;
+}
+
+// A simple class that gives checklist functionality: There are essemtially two
+// operations defined on a CheckList:
+//  - Adding a new (unchecked) item.
+//  - Checking off an item.
+// When checking off the last remaining item CheckItem() returns true.
+template<typename T>
+class CheckList {
+  public:
+    // Add a new unchecked item. Does nothing if item is already in checklist.
+    void AddItem(const T& item);
+
+    // Check off an item in the checklist. Returns true if all items have been
+    // checked.
+    bool CheckItem(const T& item);
+
+    // Clear the checklist.
+    void Clear() {
+      items_.clear();
+    }
+
+  private:
+    std::set<T> items_;
+};
+
+template<typename T>
+void CheckList<T>::AddItem(const T& item) {
+  if (!ContainsKey(items_, item))
+    items_.insert(item);
+}
+
+template<typename T>
+bool CheckList<T>::CheckItem(const T& item) {
+  typename std::set<T>::iterator iter = items_.find(item);
+  if (iter != items_.end())
+    items_.erase(iter);
+  return items_.empty();
+}
+
+// Perform a lookup in a map or hash_map whose values are pointers.
+// If the key is present a const pointer to the associated value is returned,
+// otherwise a NULL pointer is returned.
+// This function does not distinguish between a missing key and a key mapped
+// to a NULL value.
+template <class Collection>
+const typename Collection::value_type::second_type
+FindPtrOrNull(const Collection& collection,
+              const typename Collection::value_type::first_type& key) {
+  typename Collection::const_iterator it = collection.find(key);
+  if (it == collection.end()) {
+    return 0;
+  }
+  return it->second;
+}
+
+// Test to see if a set, map, hash_set or hash_map contains a particular key.
+// Returns true if the key is in the collection.
+template <typename Collection, typename Key>
+bool ContainsKey(const Collection& collection, const Key& key) {
+  return collection.find(key) != collection.end();
+}
+
+// Insert a new key and value into a map or hash_map.
+// If the key is not present in the map the key and value are
+// inserted, otherwise nothing happens. True indicates that an insert
+// took place, false indicates the key was already present.
+template <class Collection, class Key, class Value>
+bool InsertIfNotPresent(Collection * const collection,
+                        const Key& key, const Value& value) {
+  std::pair<typename Collection::iterator, bool> ret =
+    collection->insert(typename Collection::value_type(key, value));
+  return ret.second;
+}
+
+} // namespace filterfw
+} // namespace android
+
+#endif // ANDROID_FILTERFW_FILTERPACKS_BASE_UTILITIES_H
diff --git a/media/mca/filterpacks/native/base/vec_types.h b/media/mca/filterpacks/native/base/vec_types.h
new file mode 100644
index 0000000..65967c9
--- /dev/null
+++ b/media/mca/filterpacks/native/base/vec_types.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILTERFW_FILTERPACKS_BASE_VEC_TYPES_H
+#define ANDROID_FILTERFW_FILTERPACKS_BASE_VEC_TYPES_H
+
+namespace android {
+namespace filterfw {
+
+template < class T, int dim>
+class VecBase {
+ public:
+  T data[dim];
+  VecBase() {}
+  VecBase<T,dim>& operator = (const VecBase<T, dim> &x) {
+    memcpy(data, x.data, sizeof(T)*dim);
+    return *this;
+  }
+  T & operator [] (int i) {
+    // out of boundary not checked
+    return data[i];
+  }
+  const T & operator [] (int i) const {
+    // out of boundary not checked
+    return data[i];
+  }
+  T Length() {
+    double sum = 0;
+    for (int i = 0; i < dim; ++i)
+      sum += static_cast<double> (data[i] * data[i]);
+    return static_cast<T>(sqrt(sum));
+  }
+};
+
+template < class T, int dim>
+class Vec : public VecBase<T,dim> {
+ public:
+  Vec() {}
+  Vec<T,dim>& operator = (const Vec<T, dim> &x) {
+    memcpy(this->data, x.data, sizeof(T)*dim);
+    return *this;
+  }
+};
+
+template <class T, int dim>
+Vec<T, dim> operator + (const Vec<T,dim> &x, const Vec<T,dim> &y) {
+  Vec<T, dim> out;
+  for (int i = 0; i < dim; i++)
+    out.data[i] = x.data[i] + y.data[i];
+  return out;
+}
+
+template <class T, int dim>
+Vec<T, dim> operator - (const Vec<T,dim> &x, const Vec<T,dim> &y) {
+  Vec<T, dim> out;
+  for (int i = 0; i < dim; i++)
+    out.data[i] = x.data[i] - y.data[i];
+  return out;
+}
+
+template <class T, int dim>
+Vec<T, dim> operator * (const Vec<T,dim> &x, const Vec<T,dim> &y) {
+  Vec<T, dim> out;
+  for (int i = 0; i < dim; i++)
+    out.data[i] = x.data[i] * y.data[i];
+  return out;
+}
+
+template <class T, int dim>
+Vec<T, dim> operator / (const Vec<T,dim> &x, const Vec<T,dim> &y) {
+  Vec<T, dim> out;
+  for (int i = 0; i < dim; i++)
+    out.data[i] = x.data[i] / y.data[i];
+  return out;
+}
+
+template <class T, int dim>
+T dot(const Vec<T,dim> &x, const Vec<T,dim> &y) {
+  T out = 0;
+  for (int i = 0; i < dim; i++)
+    out += x.data[i] * y.data[i];
+  return out;
+}
+
+template <class T, int dim>
+Vec<T, dim> operator * (const Vec<T,dim> &x, T scale) {
+  Vec<T, dim> out;
+  for (int i = 0; i < dim; i++)
+    out.data[i] = x.data[i] * scale;
+  return out;
+}
+
+template <class T, int dim>
+Vec<T, dim> operator / (const Vec<T,dim> &x, T scale) {
+  Vec<T, dim> out;
+  for (int i = 0; i < dim; i++)
+    out.data[i] = x.data[i] / scale;
+  return out;
+}
+
+template <class T, int dim>
+Vec<T, dim> operator + (const Vec<T,dim> &x, T val) {
+  Vec<T, dim> out;
+  for (int i = 0; i < dim; i++)
+    out.data[i] = x.data[i] + val;
+  return out;
+}
+
+// specialization for vec2, vec3, vec4 float
+template<>
+class Vec<float, 2> : public VecBase<float, 2> {
+public:
+  Vec() {}
+  Vec(float x, float y) {
+    data[0] = x;
+    data[1] = y;
+  }
+  Vec<float, 2>& operator = (const Vec<float, 2> &x) {
+    memcpy(data, x.data, sizeof(float)*2);
+    return *this;
+  }
+};
+
+template<>
+class Vec<float, 3> {
+public:
+  float data[3];
+  Vec() {}
+  Vec(float x, float y, float z) {
+    data[0] = x;
+    data[1] = y;
+    data[2] = z;
+  }
+  Vec<float, 3>& operator = (const Vec<float, 3> &x) {
+    memcpy(data, x.data, sizeof(float)*3);
+    return *this;
+  }
+};
+
+template<>
+class Vec<float, 4> {
+public:
+  float data[4];
+  Vec() {}
+  Vec(float x, float y, float z, float w) {
+    data[0] = x;
+    data[1] = y;
+    data[2] = z;
+    data[3] = w;
+  }
+  Vec<float, 4>& operator = (const Vec<float, 4> &x) {
+    memcpy(data, x.data, sizeof(float)*4);
+    return *this;
+  }
+};
+
+typedef Vec<float,2> Vec2f;
+typedef Vec<float,3> Vec3f;
+typedef Vec<float,4> Vec4f;
+
+} // namespace filterfw
+} // namespace android
+
+#endif // ANDROID_FILTERFW_FILTERPACKS_BASE_VEC_TYPES_H
diff --git a/media/mca/filterpacks/native/imageproc/brightness.c b/media/mca/filterpacks/native/imageproc/brightness.c
new file mode 100644
index 0000000..f4addf1
--- /dev/null
+++ b/media/mca/filterpacks/native/imageproc/brightness.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/log.h>
+#include <stdlib.h>
+
+#define  LOGI(...) __android_log_print(ANDROID_LOG_INFO, "MCA", __VA_ARGS__)
+#define  LOGW(...) __android_log_print(ANDROID_LOG_WARN, "MCA", __VA_ARGS__)
+#define  LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "MCA", __VA_ARGS__)
+
+typedef struct {
+  float brightness;
+} BrightnessParameters;
+
+typedef union {
+    int value;
+    char rgba[4];
+} Pixel;
+
+void brightness_init(void** user_data) {
+  (*user_data) = malloc(sizeof(BrightnessParameters));
+}
+
+void brightness_teardown(void* user_data) {
+  free(user_data);
+}
+
+void brightness_setvalue(const char* key, const char* value, void* user_data) {
+  if (strcmp(key, "brightness") == 0)
+    ((BrightnessParameters*)user_data)->brightness = atof(value);
+  else
+    LOGE("Unknown parameter: %s!", key);
+}
+
+int brightness_process(const char** inputs,
+                       const int* input_sizes,
+                       int input_count,
+                       char* output,
+                       int output_size,
+                       void* user_data) {
+  // Make sure we have exactly one input
+  if (input_count != 1) {
+    LOGE("Brightness: Incorrect input count! Expected 1 but got %d!", input_count);
+    return 0;
+  }
+
+  // Make sure sizes match up
+  if (input_sizes[0] != output_size) {
+    LOGE("Brightness: Input-output sizes do not match up. %d vs. %d!", input_sizes[0], output_size);
+    return 0;
+  }
+
+  // Get the input and output pointers
+  const int* input_ptr = (int*)inputs[0];
+  int* output_ptr = (int*)output;
+  const int* end_ptr = input_ptr + (output_size / 4);
+  if (!input_ptr || !output_ptr) {
+    LOGE("Brightness: No input or output pointer found!");
+    return 0;
+  }
+
+  // Get the parameters
+  BrightnessParameters* params = (BrightnessParameters*)user_data;
+  const float brightness = params->brightness;
+
+  // Run the brightness adjustment
+  const int factor = (int)(brightness * 255.0f);
+  Pixel pixel;
+  while (input_ptr < end_ptr) {
+    pixel.value = *(input_ptr++);
+
+    const short r = (pixel.rgba[0] * factor) / 255;
+    const short g = (pixel.rgba[1] * factor) / 255;
+    const short b = (pixel.rgba[2] * factor) / 255;
+
+    *(output_ptr++) = (r > 255 ? 255 : r)
+                    | ((g > 255 ? 255 : g) << 8)
+                    | ((b > 255 ? 255 : b) << 16)
+                    | (pixel.rgba[3] << 24);
+  }
+
+  return 1;
+}
+
diff --git a/media/mca/filterpacks/native/imageproc/contrast.c b/media/mca/filterpacks/native/imageproc/contrast.c
new file mode 100644
index 0000000..ea8c8d2
--- /dev/null
+++ b/media/mca/filterpacks/native/imageproc/contrast.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/log.h>
+#include <stdlib.h>
+
+#define  LOGI(...) __android_log_print(ANDROID_LOG_INFO, "MCA", __VA_ARGS__)
+#define  LOGW(...) __android_log_print(ANDROID_LOG_WARN, "MCA", __VA_ARGS__)
+#define  LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "MCA", __VA_ARGS__)
+
+typedef struct {
+  float contrast;
+} ContrastParameters;
+
+void contrast_init(void** user_data) {
+  (*user_data) = malloc(sizeof(ContrastParameters));
+}
+
+void contrast_teardown(void* user_data) {
+  free(user_data);
+}
+
+void contrast_setvalue(const char* key, const char* value, void* user_data) {
+  if (strcmp(key, "contrast") == 0)
+    ((ContrastParameters*)user_data)->contrast = atof(value);
+  else
+    LOGE("Unknown parameter: %s!", key);
+}
+
+int contrast_process(const char** inputs,
+                     const int* input_sizes,
+                     int input_count,
+                     char* output,
+                     int output_size,
+                     void* user_data) {
+  // Make sure we have exactly one input
+  if (input_count != 1) {
+    LOGE("Contrast: Incorrect input count! Expected 1 but got %d!", input_count);
+    return 0;
+  }
+
+  // Make sure sizes match up
+  if (input_sizes[0] != output_size) {
+    LOGE("Contrast: Input-output sizes do not match up. %d vs. %d!", input_sizes[0], output_size);
+    return 0;
+  }
+
+  // Get the input and output pointers
+  const char* input_ptr = inputs[0];
+  char* output_ptr = output;
+  if (!input_ptr || !output_ptr) {
+    LOGE("Contrast: No input or output pointer found!");
+    return 0;
+  }
+
+  // Get the parameters
+  ContrastParameters* params = (ContrastParameters*)user_data;
+  const float contrast = params->contrast;
+
+  // Run the contrast adjustment
+  int i;
+  for (i = 0; i < output_size; ++i) {
+    float px = *(input_ptr++) / 255.0;
+    px -= 0.5;
+    px *= contrast;
+    px += 0.5;
+    *(output_ptr++) = (char)(px > 1.0 ? 255.0 : (px < 0.0 ? 0.0 : px * 255.0));
+  }
+
+  return 1;
+}
+
diff --git a/media/mca/filterpacks/native/imageproc/invert.c b/media/mca/filterpacks/native/imageproc/invert.c
new file mode 100644
index 0000000..5938aac
--- /dev/null
+++ b/media/mca/filterpacks/native/imageproc/invert.c
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/log.h>
+
+int invert_process(const char** inputs,
+                   const int* input_sizes,
+                   int input_count,
+                   char* output,
+                   int output_size,
+                   void* user_data) {
+  // Make sure we have exactly one input
+  if (input_count != 1)
+    return 0;
+
+  // Make sure sizes match up
+  if (input_sizes[0] != output_size)
+    return 0;
+
+  // Get the input and output pointers
+  const char* input_ptr = inputs[0];
+  char* output_ptr = output;
+  if (!input_ptr || !output_ptr)
+    return 0;
+
+  // Run the inversion
+  int i;
+  for (i = 0; i < output_size; ++i)
+    *(output_ptr++) = 255 - *(input_ptr++);
+
+  return 1;
+}
+
diff --git a/media/mca/filterpacks/native/imageproc/to_rgba.c b/media/mca/filterpacks/native/imageproc/to_rgba.c
new file mode 100644
index 0000000..bf4db2a
--- /dev/null
+++ b/media/mca/filterpacks/native/imageproc/to_rgba.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+
+int gray_to_rgb_process(const char** inputs,
+                        const int* input_sizes,
+                        int input_count,
+                        char* output,
+                        int output_size,
+                        void* user_data) {
+  // Make sure we have exactly one input
+  if (input_count != 1)
+    return 0;
+
+  // Make sure sizes match up
+  if (input_sizes[0] != output_size/3)
+    return 0;
+
+  // Get the input and output pointers
+  const char* input_ptr = inputs[0];
+  char* output_ptr = output;
+  if (!input_ptr || !output_ptr)
+    return 0;
+
+  // Run the conversion
+  int i;
+  for (i = 0; i < input_sizes[0]; ++i) {
+    *(output_ptr++) = *(input_ptr);
+    *(output_ptr++) = *(input_ptr);
+    *(output_ptr++) = *(input_ptr++);
+  }
+
+  return 1;
+}
+
+int rgba_to_rgb_process(const char** inputs,
+                        const int* input_sizes,
+                        int input_count,
+                        char* output,
+                        int output_size,
+                        void* user_data) {
+  // Make sure we have exactly one input
+  if (input_count != 1)
+    return 0;
+
+  // Make sure sizes match up
+  if (input_sizes[0]/4 != output_size/3)
+    return 0;
+
+  // Get the input and output pointers
+  const char* input_ptr = inputs[0];
+  char* output_ptr = output;
+  if (!input_ptr || !output_ptr)
+    return 0;
+
+  // Run the conversion
+  int i;
+  for (i = 0; i < input_sizes[0] / 4; ++i) {
+    *(output_ptr++) = *(input_ptr++);
+    *(output_ptr++) = *(input_ptr++);
+    *(output_ptr++) = *(input_ptr++);
+    ++input_ptr;
+  }
+
+  return 1;
+}
+
+int gray_to_rgba_process(const char** inputs,
+                         const int* input_sizes,
+                         int input_count,
+                         char* output,
+                         int output_size,
+                         void* user_data) {
+  // Make sure we have exactly one input
+  if (input_count != 1)
+    return 0;
+
+  // Make sure sizes match up
+  if (input_sizes[0] != output_size/4)
+    return 0;
+
+  // Get the input and output pointers
+  const char* input_ptr = inputs[0];
+  char* output_ptr = output;
+  if (!input_ptr || !output_ptr)
+    return 0;
+
+  // Run the conversion
+  int i;
+  for (i = 0; i < input_sizes[0]; ++i) {
+    *(output_ptr++) = *(input_ptr);
+    *(output_ptr++) = *(input_ptr);
+    *(output_ptr++) = *(input_ptr++);
+    *(output_ptr++) = 255;
+  }
+
+  return 1;
+}
+
+int rgb_to_rgba_process(const char** inputs,
+                        const int* input_sizes,
+                        int input_count,
+                        char* output,
+                        int output_size,
+                        void* user_data) {
+  // Make sure we have exactly one input
+  if (input_count != 1)
+    return 0;
+
+  // Make sure sizes match up
+  if (input_sizes[0]/3 != output_size/4)
+    return 0;
+
+  // Get the input and output pointers
+  const char* input_ptr = inputs[0];
+  char* output_ptr = output;
+  if (!input_ptr || !output_ptr)
+    return 0;
+
+  // Run the conversion
+  int i;
+  for (i = 0; i < output_size / 4; ++i) {
+    *(output_ptr++) = *(input_ptr++);
+    *(output_ptr++) = *(input_ptr++);
+    *(output_ptr++) = *(input_ptr++);
+    *(output_ptr++) = 255;
+  }
+
+  return 1;
+}
+
diff --git a/build/phone-xhdpi-1024-dalvik-heap.mk b/media/mca/samples/Android.mk
similarity index 73%
copy from build/phone-xhdpi-1024-dalvik-heap.mk
copy to media/mca/samples/Android.mk
index f76535a..b1ce91e 100644
--- a/build/phone-xhdpi-1024-dalvik-heap.mk
+++ b/media/mca/samples/Android.mk
@@ -1,4 +1,3 @@
-#
 # Copyright (C) 2011 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,9 +13,9 @@
 # limitations under the License.
 #
 
-# Provides overrides to configure the Dalvik heap for a standard tablet device.
+#
+# Build all native libraries
+#
+include $(call all-subdir-makefiles)
 
-PRODUCT_PROPERTY_OVERRIDES += \
-    dalvik.vm.heapstartsize=8m \
-    dalvik.vm.heapgrowthlimit=64m \
-    dalvik.vm.heapsize=256m
+
diff --git a/media/mca/samples/CameraEffectsRecordingSample/Android.mk b/media/mca/samples/CameraEffectsRecordingSample/Android.mk
new file mode 100644
index 0000000..d3c4336
--- /dev/null
+++ b/media/mca/samples/CameraEffectsRecordingSample/Android.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Build activity
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := CameraEffectsRecordingSample
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
+
+# ============================================================
+
+# Also build all of the sub-targets under this one: the shared library.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/media/mca/samples/CameraEffectsRecordingSample/AndroidManifest.xml b/media/mca/samples/CameraEffectsRecordingSample/AndroidManifest.xml
new file mode 100644
index 0000000..a65129d
--- /dev/null
+++ b/media/mca/samples/CameraEffectsRecordingSample/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      android:versionCode="1"
+      android:versionName="1.0" package="android.media.filterfw.samples">
+    <uses-sdk android:minSdkVersion="3" />
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-feature android:name="android.hardware.camera" />
+    <uses-feature android:name="android.hardware.camera.autofocus" />
+    <application android:label="@string/app_name"
+                 android:debuggable="true">
+        <activity android:name=".CameraEffectsRecordingSample"
+                  android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/media/mca/samples/CameraEffectsRecordingSample/java/android/media/filterfw/samples/CameraEffectsRecordingSample.java b/media/mca/samples/CameraEffectsRecordingSample/java/android/media/filterfw/samples/CameraEffectsRecordingSample.java
new file mode 100644
index 0000000..c0c3034
--- /dev/null
+++ b/media/mca/samples/CameraEffectsRecordingSample/java/android/media/filterfw/samples/CameraEffectsRecordingSample.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.media.filterfw.samples;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Environment;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.SurfaceView;
+import android.view.KeyEvent;
+import android.widget.Button;
+import android.filterfw.GraphEnvironment;
+import android.filterfw.core.GraphRunner;
+import android.filterpacks.videosink.MediaEncoderFilter;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.content.Intent;
+
+public class CameraEffectsRecordingSample extends Activity {
+
+    private Button mRunButton;
+    private SurfaceView mCameraView;
+
+    private GraphRunner mRunner;
+    private int mCameraId = 0;
+    private String mOutFileName =  Environment.getExternalStorageDirectory().toString() +
+        "/CameraEffectsRecordingSample.mp4";
+
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+        mRunButton = (Button) findViewById(R.id.runbutton);
+        mCameraView = (SurfaceView) findViewById(R.id.cameraview);
+        mRunButton.setOnClickListener(mRunButtonClick);
+
+        Intent intent = getIntent();
+        if (intent.hasExtra("OUTPUT_FILENAME")) {
+            mOutFileName = intent.getStringExtra("OUTPUT_FILENAME");
+        }
+        // Set up the references and load the filter graph
+        createGraph();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_CAMERA:
+                mRunButton.performClick();
+                return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    private void createGraph() {
+        Bitmap sourceBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.android);
+        GraphEnvironment graphEnvironment = new GraphEnvironment();
+        graphEnvironment.createGLEnvironment();
+        graphEnvironment.addReferences("cameraView", mCameraView);
+        graphEnvironment.addReferences("cameraId", mCameraId);
+        graphEnvironment.addReferences("outputFileName", mOutFileName);
+        int graphId = graphEnvironment.loadGraph(this, R.raw.cameraeffectsrecordingsample);
+        mRunner = graphEnvironment.getRunner(graphId, GraphEnvironment.MODE_ASYNCHRONOUS);
+    }
+
+    protected void onPause() {
+        super.onPause();
+        if (mRunner.isRunning()) {
+            mRunner.stop();
+            mRunButton.setText("Record");
+        }
+    }
+
+    private OnClickListener mRunButtonClick = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (mRunner.isRunning()) {
+                mRunner.stop();
+                mRunButton.setText("Record");
+            } else {
+                mRunner.run();
+                mRunButton.setText("Stop");
+            }
+        }
+    };
+}
diff --git a/media/mca/samples/CameraEffectsRecordingSample/res/drawable/android.jpg b/media/mca/samples/CameraEffectsRecordingSample/res/drawable/android.jpg
new file mode 100644
index 0000000..7c26a8d
--- /dev/null
+++ b/media/mca/samples/CameraEffectsRecordingSample/res/drawable/android.jpg
Binary files differ
diff --git a/media/mca/samples/CameraEffectsRecordingSample/res/layout/main.xml b/media/mca/samples/CameraEffectsRecordingSample/res/layout/main.xml
new file mode 100644
index 0000000..0cc07ce
--- /dev/null
+++ b/media/mca/samples/CameraEffectsRecordingSample/res/layout/main.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+      android:orientation="vertical"
+      android:layout_width="fill_parent"
+      android:layout_height="fill_parent">
+      <Button
+          android:id="@+id/runbutton"
+          android:layout_width="fill_parent"
+          android:layout_height="wrap_content"
+          android:text="Record" />
+      <android.filterfw.core.FilterSurfaceView
+          android:id="@+id/cameraview"
+          android:layout_height="fill_parent"
+          android:layout_width="fill_parent"
+          android:layout_weight="1" />
+</LinearLayout>
diff --git a/media/mca/samples/CameraEffectsRecordingSample/res/raw/cameraeffectsrecordingsample.graph b/media/mca/samples/CameraEffectsRecordingSample/res/raw/cameraeffectsrecordingsample.graph
new file mode 100644
index 0000000..f3ed667
--- /dev/null
+++ b/media/mca/samples/CameraEffectsRecordingSample/res/raw/cameraeffectsrecordingsample.graph
@@ -0,0 +1,57 @@
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// A simple graph that displays the raw camera stream.
+
+@setting autoBranch = "synced";
+
+// Imports ---------------------------------------------------
+@import android.filterpacks.videosrc;
+@import android.filterpacks.ui;
+@import android.filterpacks.videosink;
+@import android.filterpacks.base;
+
+// Externals -------------------------------------------------
+@external cameraView;
+@external outputFileName;
+@external cameraId;
+
+@set widthLoc = 320;
+@set heightLoc = 240;
+
+// Filters ---------------------------------------------------
+@filter CameraSource camera {
+    id = $cameraId;
+    width = $widthLoc;
+    height = $heightLoc;
+    waitForNewFrame = false;
+}
+
+@filter MediaEncoderFilter mediaEncoder {
+    width = $widthLoc;
+    height = $heightLoc;
+    outputFile = $outputFileName;
+}
+
+@filter SurfaceRenderFilter renderer {
+  surfaceView = $cameraView;
+}
+
+
+// Connections -----------------------------------------------
+// Camera->Preview, Camera->Encoder
+// Render camera output on to a surface
+@connect camera[video] => renderer[frame];
+// Also pass it to an encoder
+@connect camera[video] => mediaEncoder[videoframe];
diff --git a/media/mca/samples/CameraEffectsRecordingSample/res/values/strings.xml b/media/mca/samples/CameraEffectsRecordingSample/res/values/strings.xml
new file mode 100644
index 0000000..6491043
--- /dev/null
+++ b/media/mca/samples/CameraEffectsRecordingSample/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ -->
+<resources>
+    <string name="app_name">CameraEffectsRecordingSample</string>
+</resources>
diff --git a/media/mca/structgen.py b/media/mca/structgen.py
new file mode 100644
index 0000000..437326c
--- /dev/null
+++ b/media/mca/structgen.py
@@ -0,0 +1,367 @@
+#!/usr/bin/env python
+
+#
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import os
+import sys
+
+hFileTemplate = """/**
+ * This file is auto-generated by platform/system/media/mca/structgen.py! Do NOT modify!
+ **/
+
+#ifndef %s
+#define %s
+
+%s
+
+#endif // %s
+"""
+
+jniFileTemplate = """/**
+ * This file is auto-generated by platform/system/media/mca/structgen.py! Do NOT modify!
+ **/
+
+#include <stdint.h>
+#include "native/%s.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "jni.h"
+
+// Helper functions ////////////////////////////////////////////////////////////////////////////////
+%s* Get%sAtIndex(JNIEnv* env, jobject buffer, int index) {
+  jclass base_class = (*env)->FindClass(env, "android/filterfw/core/NativeBuffer");
+  jfieldID ptr_field = (*env)->GetFieldID(env, base_class, "mDataPointer", "J");
+  uintptr_t data_ptr = (*env)->GetLongField(env, buffer, ptr_field);
+  %s* array = (%s*)data_ptr;
+  (*env)->DeleteLocalRef(env, base_class);
+  return &array[index];
+}
+
+// Declarations ////////////////////////////////////////////////////////////////////////////////////
+JNIEXPORT jint JNICALL
+Java_%s_getElementSize(JNIEnv* env, jobject thiz);
+
+%s
+
+#ifdef __cplusplus
+}
+#endif
+
+// Implementation //////////////////////////////////////////////////////////////////////////////////
+jint Java_%s_getElementSize(JNIEnv* env, jobject thiz) {
+  return sizeof(%s);
+}
+
+%s
+"""
+
+javaFileTemplate = """/**
+ * This file is auto-generated by platform/system/media/mca/structgen.py! Do NOT modify!
+ **/
+
+package %s;
+
+import android.filterfw.core.NativeBuffer;
+
+%s
+"""
+
+
+def ToJavaName(cname, start_upper_at = 1):
+  lower = cname.split("_")
+  upper = [c.title() for c in lower]
+  return "".join(lower[:start_upper_at] + upper[start_upper_at:])
+
+def ToJNIPackage(package, jclassname):
+  return "%s_%s" % (package.replace(".", "_"), jclassname)
+
+def ToMacroDefName(cname, pname):
+  return "%s_%s" % (pname.replace(".", "_").upper(), cname.upper())
+
+class ParseError:
+  def __init__(self, lineno, message):
+    self.lineno = lineno
+    self.message = message
+
+  def __str__(self):
+    return "On line %d: %s" % (self.lineno, self.message)
+
+class FieldType_BasePOD:
+  def __init__(self, name, structname, jclassname, package, javatype, ctype, jtype, defval):
+    self.name = name
+    self.structname = structname
+    self.jclassname = jclassname
+    self.package = package
+    self.javatype = javatype
+    self.ctype = ctype
+    self.jtype = jtype
+    self.defval = defval
+
+  def cString(self):
+    return "  %s %s;" % (self.ctype, self.name)
+
+  def javaGetter(self):
+    return "    public %s get%s(int index) {\n"\
+           "        assertReadable();\n"\
+           "        return nativeGet%s(index);\n"\
+           "    }" % (self.javatype, ToJavaName(self.name, 0), ToJavaName(self.name, 0))
+
+  def javaSetter(self):
+    return "    public void set%s(int index, %s value) {\n"\
+           "        assertWritable();\n"\
+           "        nativeSet%s(index, value);\n"\
+           "    }" % (ToJavaName(self.name, 0), self.javatype, ToJavaName(self.name, 0))
+
+  def javaNativeGetter(self):
+    return "    private native %s nativeGet%s(int index);"\
+           % (self.javatype, ToJavaName(self.name, 0))
+
+  def javaNativeSetter(self):
+    return "    private native boolean nativeSet%s(int index, %s value);"\
+           % (ToJavaName(self.name, 0), self.javatype)
+
+  def jniGetterDefString(self):
+    return "JNIEXPORT %s JNICALL\n" \
+           "Java_%s_nativeGet%s(JNIEnv* env, jobject thiz, jint index);" \
+           % (self.jtype, ToJNIPackage(self.package, self.jclassname), ToJavaName(self.name, 0))
+
+  def jniGetterImplString(self):
+    return \
+      "%s Java_%s_nativeGet%s(JNIEnv* env, jobject thiz, jint index) {\n"\
+      "  %s* instance = Get%sAtIndex(env, thiz, index);\n"\
+      "  return instance ? instance->%s : %s;\n"\
+      "}\n" % (self.jtype, ToJNIPackage(self.package, self.jclassname), ToJavaName(self.name, 0),\
+               self.structname, self.structname, self.name, self.defval)
+
+  def jniSetterDefString(self):
+    return "JNIEXPORT jboolean JNICALL\n" \
+           "Java_%s_nativeSet%s(JNIEnv* env, jobject thiz, jint index, %s value);" \
+           % (ToJNIPackage(self.package, self.jclassname), ToJavaName(self.name, 0), self.jtype)
+
+  def jniSetterImplString(self):
+    return \
+      "jboolean Java_%s_nativeSet%s(JNIEnv* env, jobject thiz, jint index, %s value) {\n"\
+      "  %s* instance = Get%sAtIndex(env, thiz, index);\n"\
+      "  if (instance) {\n"\
+      "    instance->%s = value;\n"\
+      "    return JNI_TRUE;\n"\
+      "  }\n"\
+      "  return JNI_FALSE;\n"\
+      "}\n" % (ToJNIPackage(self.package, self.jclassname), ToJavaName(self.name, 0),\
+               self.jtype, self.structname, self.structname, self.name)
+
+class FieldType_Float(FieldType_BasePOD):
+  def __init__(self, name, structname, jclassname, package):
+    FieldType_BasePOD.__init__(self, name, structname, jclassname, package, "float", "float", "jfloat", "0.0")
+
+class FieldType_Int(FieldType_BasePOD):
+  def __init__(self, name, structname, jclassname, package):
+    FieldType_BasePOD.__init__(self, name, structname, jclassname, package, "int", "int", "jint", "0")
+
+class FieldType_Long(FieldType_BasePOD):
+  def __init__(self, name, structname, jclassname, package):
+    FieldType_BasePOD.__init__(self, name, structname, jclassname, package, "long", "long long", "jlong", "0")
+
+class StructSpec:
+
+  def parseTextFile(self, filepath):
+    # Init
+    self.name = None
+    self.package = None
+    self.fields = []
+    self.structname = None
+    self.jclassname = None
+    self.libname = None
+
+    # Open the file
+    txtfile = open(filepath)
+
+    # Parse it line by line
+    lineno = 0
+    for line in txtfile:
+      # Split line into components
+      linecomps = line.split()
+      if len(linecomps) == 0:
+        continue
+
+      # Execute command
+      cmd = linecomps[0]
+      if cmd == "@name":
+        self.commandArgAssert(linecomps, 1, lineno)
+        self.name = linecomps[1]
+        if not self.structname:
+          self.structname = self.name
+        if not self.jclassname:
+          self.jclassname = self.name
+      elif cmd == "@package":
+        self.commandArgAssert(linecomps, 1, lineno)
+        self.package = linecomps[1]
+      elif cmd == "@libname":
+        self.commandArgAssert(linecomps, 1, lineno)
+        self.libname = linecomps[1]
+      elif cmd == "@structname":
+        self.commandArgAssert(linecomps, 1, lineno)
+        self.structname = linecomps[1]
+      elif cmd == "@javaclassname":
+        self.commandArgAssert(linecomps, 1, lineno)
+        self.jclassname = linecomps[1]
+      elif cmd == "@field":
+        self.commandArgAssert(linecomps, 2, lineno)
+        typestr = linecomps[1]
+        if typestr == "int":
+          fieldtype = FieldType_Int(linecomps[2], self.structname, self.jclassname, self.package)
+        elif typestr == "long":
+          fieldtype = FieldType_Long(linecomps[2], self.structname, self.jclassname, self.package)
+        elif typestr == "float":
+          fieldtype = FieldType_Float(linecomps[2], self.structname, self.jclassname, self.package)
+        else:
+          raise ParseError(lineno, "Unknown field type '%s'!" % typestr)
+        self.fields.append(fieldtype)
+      else:
+        raise ParseError(lineno, "Unknown command: '%s'!" % cmd)
+
+      lineno = lineno + 1
+
+    # Make sure we have all required info
+    if not self.name:
+      raise ParseError(lineno, "Required field '@name' missing!")
+    elif not self.package:
+      raise ParseError(lineno, "Required field '@package' missing!")
+    elif not self.libname:
+      raise ParseError(lineno, "Required field '@libname' missing!")
+
+    # Normalize values
+    if self.libname[:3] == "lib":
+      self.libname = self.libname[3:]
+
+  def commandArgAssert(self, linecomps, expectedcount, lineno):
+    foundcount = len(linecomps) - 1
+    if foundcount < expectedcount:
+      raise ParseError(lineno, "Not enough arguments specifed for command '%s'! Expected %d, " \
+                       "but got only %d!" % (linecomps[0], expectedcount, foundcount))
+    elif foundcount > expectedcount + 1:
+      raise ParseError(lineno, "Too many arguments specifed for command '%s'! Expected %d, " \
+                       "but got %d!" % (linecomps[0], expectedcount, foundcount))
+
+
+  def cStructString(self):
+    cfields = [f.cString() for f in self.fields]
+    return "typedef struct Struct%s {\n%s\n} %s;\n" % (self.structname,\
+                                                       "\n".join(cfields),\
+                                                       self.structname)
+
+  def javaClassString(self):
+    jgetters = [f.javaGetter() for f in self.fields]
+    jsetters = [f.javaSetter() for f in self.fields]
+    jnativesetters = [f.javaNativeSetter() for f in self.fields]
+    jnativegetters = [f.javaNativeGetter() for f in self.fields]
+    return "public class %s extends NativeBuffer {\n\n"\
+           "    public %s() {\n"\
+           "        super();\n"\
+           "    }\n"\
+           "\n"\
+           "    public %s(int count) {\n"\
+           "        super(count);\n"\
+           "    }\n"\
+           "\n"\
+           "    public native int getElementSize();\n"\
+           "\n"\
+           "%s\n\n"\
+           "%s\n\n"\
+           "%s\n\n"\
+           "%s\n\n"\
+           "    static {\n"\
+           "        System.loadLibrary(\"%s\");\n"\
+           "    }\n"\
+           "\n"\
+           "};\n" % (self.jclassname,\
+                     self.jclassname,\
+                     self.jclassname,\
+                     "\n\n".join(jgetters),\
+                     "\n\n".join(jsetters),\
+                     "\n\n".join(jnativegetters),\
+                     "\n\n".join(jnativesetters),\
+                     self.libname)
+
+  def jniDeclString(self):
+    jnigetters = [f.jniGetterDefString() for f in self.fields]
+    jnisetters = [f.jniSetterDefString() for f in self.fields]
+    return "\n\n".join(jnigetters + jnisetters)
+
+  def jniImplString(self):
+    jnigetters = [f.jniGetterImplString() for f in self.fields]
+    jnisetters = [f.jniSetterImplString() for f in self.fields]
+    return "\n\n".join(jnigetters + jnisetters)
+
+  def hFileString(self):
+    defname = ToMacroDefName(self.structname, self.package)
+    return hFileTemplate % (defname, defname, self.cStructString(), defname)
+
+  def javaFileString(self):
+    return javaFileTemplate % (self.package, self.javaClassString())
+
+  def jniFileString(self):
+    return jniFileTemplate % (self.structname.lower(),\
+                              self.structname,\
+                              self.structname,\
+                              self.structname,\
+                              self.structname,\
+                              ToJNIPackage(self.package, self.jclassname),\
+                              self.jniDeclString(),\
+                              ToJNIPackage(self.package, self.jclassname),\
+                              self.structname,
+                              self.jniImplString())
+
+def main(argv):
+  if len(argv) != 2:
+    print("Usage: %s <file.struct>" % argv[0])
+    return -1
+
+  filepath = argv[1]
+
+  structspec = StructSpec()
+  structspec.parseTextFile(filepath)
+
+  hfilename = "%s.h" % structspec.structname.lower()
+  javafilename = "%s.java" % structspec.jclassname
+  jnifilename = "jni_%s.c" % structspec.structname.lower()
+
+  javapackagepath = structspec.package.replace('.','/')
+
+  rootdir = os.path.dirname(filepath)
+  hfilepath = "%s/../native/%s" % (rootdir, hfilename)
+  javafilepath = "%s/../java/%s/%s" % (rootdir, javapackagepath, javafilename)
+  jnifilepath = "%s/../jni/%s" % (rootdir, jnifilename)
+
+  hfile = open(hfilepath, 'w')
+  hfile.write(structspec.hFileString())
+  hfile.close()
+
+  javafile = open(javafilepath, 'w')
+  javafile.write(structspec.javaFileString())
+  javafile.close()
+
+  jnifile = open(jnifilepath, 'w')
+  jnifile.write(structspec.jniFileString())
+  jnifile.close()
+
+
+if __name__ == "__main__":
+  sys.exit(main(sys.argv))
diff --git a/media/mca/tests/Android.mk b/media/mca/tests/Android.mk
new file mode 100644
index 0000000..2abd7f6
--- /dev/null
+++ b/media/mca/tests/Android.mk
@@ -0,0 +1,18 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CameraEffectsTests
+
+LOCAL_INSTRUMENTATION_FOR := CameraEffectsRecordingSample
+
+include $(BUILD_PACKAGE)
+
+
diff --git a/media/mca/tests/AndroidManifest.xml b/media/mca/tests/AndroidManifest.xml
new file mode 100644
index 0000000..5133640
--- /dev/null
+++ b/media/mca/tests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.camera.mediaeffects.tests">
+
+    <uses-permission android:name="android.permission.INJECT_EVENTS" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+             android:targetPackage="android.media.filterfw.samples"
+             android:label="Tests for Camera Effects Recording."/>
+</manifest>
diff --git a/media/mca/tests/src/android/camera/mediaeffects/tests/functional/EffectsVideoCapture.java b/media/mca/tests/src/android/camera/mediaeffects/tests/functional/EffectsVideoCapture.java
new file mode 100644
index 0000000..474b00f
--- /dev/null
+++ b/media/mca/tests/src/android/camera/mediaeffects/tests/functional/EffectsVideoCapture.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.camera.mediaeffects.tests.functional;
+
+import android.media.filterfw.samples.CameraEffectsRecordingSample;
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.KeyEvent;
+import android.util.Log;
+import android.content.Intent;
+import android.os.Environment;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import java.io.File;
+
+public class EffectsVideoCapture extends ActivityInstrumentationTestCase2
+                                               <CameraEffectsRecordingSample> {
+    private static final String TAG = "EffectsVideoCaptureTest";
+    private static final long WAIT_FOR_PREVIEW = 4 * 1000; // 4 seconds
+
+    public EffectsVideoCapture() {
+        super(CameraEffectsRecordingSample.class);
+    }
+
+    private void captureVideos(String reportTag, Instrumentation inst) throws Exception{
+        int total_num_of_videos = 1;
+        int video_duration = 4 * 1000; // 4 seconds
+
+        Log.v(TAG, reportTag);
+        for (int i = 0; i < total_num_of_videos; i++) {
+            Thread.sleep(WAIT_FOR_PREVIEW);
+            // record a video
+            inst.sendCharacterSync(KeyEvent.KEYCODE_CAMERA);
+            Thread.sleep(video_duration);
+            inst.sendCharacterSync(KeyEvent.KEYCODE_CAMERA);
+        }
+    }
+
+    @LargeTest
+    public void testBackEffectsVideoCapture() throws Exception {
+        Instrumentation inst = getInstrumentation();
+
+        Intent intent = new Intent();
+        intent.setClass(getInstrumentation().getTargetContext(),
+                CameraEffectsRecordingSample.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra("OUTPUT_FILENAME", Environment.getExternalStorageDirectory().toString()
+                + "/CameraEffectsRecordingTest.mp4");
+        Activity act = inst.startActivitySync(intent);
+        captureVideos("Back Camera Video Capture\n", inst);
+        act.finish();
+
+        // Verification
+        File file = new File(Environment.getExternalStorageDirectory(),
+                "CameraEffectsRecordingTest.mp4");
+        Uri uri = Uri.fromFile(file);
+        verify(getActivity(), uri);
+    }
+
+    // Verify result code, result data, and the duration.
+    private void verify(CameraEffectsRecordingSample activity, Uri uri) throws Exception {
+        assertNotNull(uri);
+        // Verify the video file
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        retriever.setDataSource(activity, uri);
+        String duration = retriever.extractMetadata(
+                MediaMetadataRetriever.METADATA_KEY_DURATION);
+        assertNotNull(duration);
+        int durationValue = Integer.parseInt(duration);
+        Log.v(TAG, "Video duration is " + durationValue);
+        assertTrue(durationValue > 0);
+    }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
index 4f6e7d2..db64b94 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
@@ -101,6 +101,9 @@
 
     protected void setUp() throws Exception {
         super.setUp();
+        //Insert a 2 second before launching the test activity. This is
+        //the workaround for the race condition of requesting the updated surface.
+        Thread.sleep(2000);
         getActivity();
         if (MediaFrameworkPerfTestRunner.mGetNativeHeapDump)
             MediaTestUtil.getNativeHeapDump(this.getName() + "_before");
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java
index b2086d6..25b6e7f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java
@@ -43,13 +43,18 @@
 /**
  * Junit / Instrumentation test case for the media player
  */
-public class MediaPlayerStressTest extends InstrumentationTestCase {
+public class MediaPlayerStressTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
     private String TAG = "MediaPlayerStressTest";
 
     public MediaPlayerStressTest() {
+        super("com.android.mediaframeworktest", MediaFrameworkTest.class);
     }
 
     protected void setUp() throws Exception {
+        //Insert a 2 second before launching the test activity. This is
+        //the workaround for the race condition of requesting the updated surface.
+        Thread.sleep(2000);
+        getActivity();
         super.setUp();
     }
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java
index 5e649e0..e9bc6f0 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java
@@ -88,7 +88,9 @@
         if (! sem.tryAcquire(WAIT_TIMEOUT, TimeUnit.MILLISECONDS)) {
             fail("Failed to start the looper.");
         }
-
+        //Insert a 2 second before launching the test activity. This is
+        //the workaround for the race condition of requesting the updated surface.
+        Thread.sleep(2000);
         getActivity();
         super.setUp();
     }
diff --git a/media/tests/omxjpegdecoder/Android.mk b/media/tests/omxjpegdecoder/Android.mk
index 3e09a5f..9dcc7ba 100644
--- a/media/tests/omxjpegdecoder/Android.mk
+++ b/media/tests/omxjpegdecoder/Android.mk
@@ -32,7 +32,6 @@
     libjpeg
 
 LOCAL_C_INCLUDES := \
-    $(JNI_H_INCLUDE) \
     $(TOP)/external/jpeg \
     $(TOP)/external/skia/include/config \
     $(TOP)/external/skia/include/core \
diff --git a/native/copy-to-ndk.sh b/native/copy-to-ndk.sh
index 6470892..90ff556 100644
--- a/native/copy-to-ndk.sh
+++ b/native/copy-to-ndk.sh
@@ -5,7 +5,7 @@
     local CURR_PLATFORM=android-9
     local ALL_PLATFORMS="$CURR_PLATFORM android-8 android-5 android-4 android-3"
 
-    local SRC_HEADERS=$ANDROID_BUILD_TOP/frameworks/base/native/include/android
+    local SRC_HEADERS=$ANDROID_BUILD_TOP/frameworks/native/include/android
     local NDK_PLATFORMS=$ANDROID_BUILD_TOP/development/ndk/platforms
     local DST_HEADERS=$NDK_PLATFORMS/$CURR_PLATFORM
 
diff --git a/native/include/android/asset_manager.h b/native/include/android/asset_manager.h
deleted file mode 100644
index f5df46b..0000000
--- a/native/include/android/asset_manager.h
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#ifndef ANDROID_ASSET_MANAGER_H
-#define ANDROID_ASSET_MANAGER_H
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct AAssetManager;
-typedef struct AAssetManager AAssetManager;
-
-struct AAssetDir;
-typedef struct AAssetDir AAssetDir;
-
-struct AAsset;
-typedef struct AAsset AAsset;
-
-/* Available modes for opening assets */
-enum {
-    AASSET_MODE_UNKNOWN      = 0,
-    AASSET_MODE_RANDOM       = 1,
-    AASSET_MODE_STREAMING    = 2,
-    AASSET_MODE_BUFFER       = 3
-};
-
-
-/**
- * Open the named directory within the asset hierarchy.  The directory can then
- * be inspected with the AAssetDir functions.  To open the top-level directory,
- * pass in "" as the dirName.
- *
- * The object returned here should be freed by calling AAssetDir_close().
- */
-AAssetDir* AAssetManager_openDir(AAssetManager* mgr, const char* dirName);
-
-/**
- * Open an asset.
- *
- * The object returned here should be freed by calling AAsset_close().
- */
-AAsset* AAssetManager_open(AAssetManager* mgr, const char* filename, int mode);
-
-/**
- * Iterate over the files in an asset directory.  A NULL string is returned
- * when all the file names have been returned.
- *
- * The returned file name is suitable for passing to AAssetManager_open().
- *
- * The string returned here is owned by the AssetDir implementation and is not
- * guaranteed to remain valid if any other calls are made on this AAssetDir
- * instance.
- */
-const char* AAssetDir_getNextFileName(AAssetDir* assetDir);
-
-/**
- * Reset the iteration state of AAssetDir_getNextFileName() to the beginning.
- */
-void AAssetDir_rewind(AAssetDir* assetDir);
-
-/**
- * Close an opened AAssetDir, freeing any related resources.
- */
-void AAssetDir_close(AAssetDir* assetDir);
-
-/**
- * Attempt to read 'count' bytes of data from the current offset.
- *
- * Returns the number of bytes read, zero on EOF, or < 0 on error.
- */
-int AAsset_read(AAsset* asset, void* buf, size_t count);
-
-/**
- * Seek to the specified offset within the asset data.  'whence' uses the
- * same constants as lseek()/fseek().
- *
- * Returns the new position on success, or (off_t) -1 on error.
- */
-off_t AAsset_seek(AAsset* asset, off_t offset, int whence);
-
-/**
- * Seek to the specified offset within the asset data.  'whence' uses the
- * same constants as lseek()/fseek().
- *
- * Uses 64-bit data type for large files as opposed to the 32-bit type used
- * by AAsset_seek.
- *
- * Returns the new position on success, or (off64_t) -1 on error.
- */
-off64_t AAsset_seek64(AAsset* asset, off64_t offset, int whence);
-
-/**
- * Close the asset, freeing all associated resources.
- */
-void AAsset_close(AAsset* asset);
-
-/**
- * Get a pointer to a buffer holding the entire contents of the assset.
- *
- * Returns NULL on failure.
- */
-const void* AAsset_getBuffer(AAsset* asset);
-
-/**
- * Report the total size of the asset data.
- */
-off_t AAsset_getLength(AAsset* asset);
-
-/**
- * Report the total size of the asset data. Reports the size using a 64-bit
- * number insted of 32-bit as AAsset_getLength.
- */
-off64_t AAsset_getLength64(AAsset* asset);
-
-/**
- * Report the total amount of asset data that can be read from the current position.
- */
-off_t AAsset_getRemainingLength(AAsset* asset);
-
-/**
- * Report the total amount of asset data that can be read from the current position.
- *
- * Uses a 64-bit number instead of a 32-bit number as AAsset_getRemainingLength does.
- */
-off64_t AAsset_getRemainingLength64(AAsset* asset);
-
-/**
- * Open a new file descriptor that can be used to read the asset data. If the
- * start or length cannot be represented by a 32-bit number, it will be
- * truncated. If the file is large, use AAsset_openFileDescriptor64 instead.
- *
- * Returns < 0 if direct fd access is not possible (for example, if the asset is
- * compressed).
- */
-int AAsset_openFileDescriptor(AAsset* asset, off_t* outStart, off_t* outLength);
-
-/**
- * Open a new file descriptor that can be used to read the asset data.
- *
- * Uses a 64-bit number for the offset and length instead of 32-bit instead of
- * as AAsset_openFileDescriptor does.
- *
- * Returns < 0 if direct fd access is not possible (for example, if the asset is
- * compressed).
- */
-int AAsset_openFileDescriptor64(AAsset* asset, off64_t* outStart, off64_t* outLength);
-
-/**
- * Returns whether this asset's internal buffer is allocated in ordinary RAM (i.e. not
- * mmapped).
- */
-int AAsset_isAllocated(AAsset* asset);
-
-
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif      // ANDROID_ASSET_MANAGER_H
diff --git a/native/include/android/asset_manager_jni.h b/native/include/android/asset_manager_jni.h
deleted file mode 100644
index aec2d3c..0000000
--- a/native/include/android/asset_manager_jni.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#ifndef ANDROID_ASSET_MANAGER_JNI_H
-#define ANDROID_ASSET_MANAGER_JNI_H
-
-#include <android/asset_manager.h>
-#include <jni.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * Given a Dalvik AssetManager object, obtain the corresponding native AAssetManager
- * object.  Note that the caller is responsible for obtaining and holding a VM reference
- * to the jobject to prevent its being garbage collected while the native object is
- * in use.
- */
-AAssetManager* AAssetManager_fromJava(JNIEnv* env, jobject assetManager);
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif      // ANDROID_ASSET_MANAGER_JNI_H
diff --git a/native/include/android/bitmap.h b/native/include/android/bitmap.h
deleted file mode 100644
index 5078277..0000000
--- a/native/include/android/bitmap.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_BITMAP_H
-#define ANDROID_BITMAP_H
-
-#include <stdint.h>
-#include <jni.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define ANDROID_BITMAP_RESUT_SUCCESS            0
-#define ANDROID_BITMAP_RESULT_BAD_PARAMETER     -1
-#define ANDROID_BITMAP_RESULT_JNI_EXCEPTION     -2
-#define ANDROID_BITMAP_RESULT_ALLOCATION_FAILED -3
-
-enum AndroidBitmapFormat {
-    ANDROID_BITMAP_FORMAT_NONE      = 0,
-    ANDROID_BITMAP_FORMAT_RGBA_8888 = 1,
-    ANDROID_BITMAP_FORMAT_RGB_565   = 4,
-    ANDROID_BITMAP_FORMAT_RGBA_4444 = 7,
-    ANDROID_BITMAP_FORMAT_A_8       = 8,
-};
-
-typedef struct {
-    uint32_t    width;
-    uint32_t    height;
-    uint32_t    stride;
-    int32_t     format;
-    uint32_t    flags;      // 0 for now
-} AndroidBitmapInfo;
-
-/**
- * Given a java bitmap object, fill out the AndroidBitmap struct for it.
- * If the call fails, the info parameter will be ignored
- */
-int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,
-                          AndroidBitmapInfo* info);
-
-/**
- * Given a java bitmap object, attempt to lock the pixel address.
- * Locking will ensure that the memory for the pixels will not move
- * until the unlockPixels call, and ensure that, if the pixels had been
- * previously purged, they will have been restored.
- *
- * If this call succeeds, it must be balanced by a call to
- * AndroidBitmap_unlockPixels, after which time the address of the pixels should
- * no longer be used.
- *
- * If this succeeds, *addrPtr will be set to the pixel address. If the call
- * fails, addrPtr will be ignored.
- */
-int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);
-
-/**
- * Call this to balanace a successful call to AndroidBitmap_lockPixels
- */
-int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/native/include/android/configuration.h b/native/include/android/configuration.h
deleted file mode 100644
index 06cd3da..0000000
--- a/native/include/android/configuration.h
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_CONFIGURATION_H
-#define ANDROID_CONFIGURATION_H
-
-#include <android/asset_manager.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct AConfiguration;
-typedef struct AConfiguration AConfiguration;
-
-enum {
-    ACONFIGURATION_ORIENTATION_ANY  = 0x0000,
-    ACONFIGURATION_ORIENTATION_PORT = 0x0001,
-    ACONFIGURATION_ORIENTATION_LAND = 0x0002,
-    ACONFIGURATION_ORIENTATION_SQUARE = 0x0003,
-
-    ACONFIGURATION_TOUCHSCREEN_ANY  = 0x0000,
-    ACONFIGURATION_TOUCHSCREEN_NOTOUCH  = 0x0001,
-    ACONFIGURATION_TOUCHSCREEN_STYLUS  = 0x0002,
-    ACONFIGURATION_TOUCHSCREEN_FINGER  = 0x0003,
-
-    ACONFIGURATION_DENSITY_DEFAULT = 0,
-    ACONFIGURATION_DENSITY_LOW = 120,
-    ACONFIGURATION_DENSITY_MEDIUM = 160,
-    ACONFIGURATION_DENSITY_TV = 213,
-    ACONFIGURATION_DENSITY_HIGH = 240,
-    ACONFIGURATION_DENSITY_XHIGH = 320,
-    ACONFIGURATION_DENSITY_XXHIGH = 480,
-    ACONFIGURATION_DENSITY_NONE = 0xffff,
-
-    ACONFIGURATION_KEYBOARD_ANY  = 0x0000,
-    ACONFIGURATION_KEYBOARD_NOKEYS  = 0x0001,
-    ACONFIGURATION_KEYBOARD_QWERTY  = 0x0002,
-    ACONFIGURATION_KEYBOARD_12KEY  = 0x0003,
-
-    ACONFIGURATION_NAVIGATION_ANY  = 0x0000,
-    ACONFIGURATION_NAVIGATION_NONAV  = 0x0001,
-    ACONFIGURATION_NAVIGATION_DPAD  = 0x0002,
-    ACONFIGURATION_NAVIGATION_TRACKBALL  = 0x0003,
-    ACONFIGURATION_NAVIGATION_WHEEL  = 0x0004,
-
-    ACONFIGURATION_KEYSHIDDEN_ANY = 0x0000,
-    ACONFIGURATION_KEYSHIDDEN_NO = 0x0001,
-    ACONFIGURATION_KEYSHIDDEN_YES = 0x0002,
-    ACONFIGURATION_KEYSHIDDEN_SOFT = 0x0003,
-
-    ACONFIGURATION_NAVHIDDEN_ANY = 0x0000,
-    ACONFIGURATION_NAVHIDDEN_NO = 0x0001,
-    ACONFIGURATION_NAVHIDDEN_YES = 0x0002,
-
-    ACONFIGURATION_SCREENSIZE_ANY  = 0x00,
-    ACONFIGURATION_SCREENSIZE_SMALL = 0x01,
-    ACONFIGURATION_SCREENSIZE_NORMAL = 0x02,
-    ACONFIGURATION_SCREENSIZE_LARGE = 0x03,
-    ACONFIGURATION_SCREENSIZE_XLARGE = 0x04,
-
-    ACONFIGURATION_SCREENLONG_ANY = 0x00,
-    ACONFIGURATION_SCREENLONG_NO = 0x1,
-    ACONFIGURATION_SCREENLONG_YES = 0x2,
-
-    ACONFIGURATION_UI_MODE_TYPE_ANY = 0x00,
-    ACONFIGURATION_UI_MODE_TYPE_NORMAL = 0x01,
-    ACONFIGURATION_UI_MODE_TYPE_DESK = 0x02,
-    ACONFIGURATION_UI_MODE_TYPE_CAR = 0x03,
-    ACONFIGURATION_UI_MODE_TYPE_TELEVISION = 0x04,
-    ACONFIGURATION_UI_MODE_TYPE_APPLIANCE = 0x05,
-
-    ACONFIGURATION_UI_MODE_NIGHT_ANY = 0x00,
-    ACONFIGURATION_UI_MODE_NIGHT_NO = 0x1,
-    ACONFIGURATION_UI_MODE_NIGHT_YES = 0x2,
-
-    ACONFIGURATION_SCREEN_WIDTH_DP_ANY = 0x0000,
-
-    ACONFIGURATION_SCREEN_HEIGHT_DP_ANY = 0x0000,
-
-    ACONFIGURATION_SMALLEST_SCREEN_WIDTH_DP_ANY = 0x0000,
-
-    ACONFIGURATION_MCC = 0x0001,
-    ACONFIGURATION_MNC = 0x0002,
-    ACONFIGURATION_LOCALE = 0x0004,
-    ACONFIGURATION_TOUCHSCREEN = 0x0008,
-    ACONFIGURATION_KEYBOARD = 0x0010,
-    ACONFIGURATION_KEYBOARD_HIDDEN = 0x0020,
-    ACONFIGURATION_NAVIGATION = 0x0040,
-    ACONFIGURATION_ORIENTATION = 0x0080,
-    ACONFIGURATION_DENSITY = 0x0100,
-    ACONFIGURATION_SCREEN_SIZE = 0x0200,
-    ACONFIGURATION_VERSION = 0x0400,
-    ACONFIGURATION_SCREEN_LAYOUT = 0x0800,
-    ACONFIGURATION_UI_MODE = 0x1000,
-    ACONFIGURATION_SMALLEST_SCREEN_SIZE = 0x2000,
-};
-
-/**
- * Create a new AConfiguration, initialized with no values set.
- */
-AConfiguration* AConfiguration_new();
-
-/**
- * Free an AConfiguration that was previously created with
- * AConfiguration_new().
- */
-void AConfiguration_delete(AConfiguration* config);
-
-/**
- * Create and return a new AConfiguration based on the current configuration in
- * use in the given AssetManager.
- */
-void AConfiguration_fromAssetManager(AConfiguration* out, AAssetManager* am);
-
-/**
- * Copy the contents of 'src' to 'dest'.
- */
-void AConfiguration_copy(AConfiguration* dest, AConfiguration* src);
-
-/**
- * Return the current MCC set in the configuration.  0 if not set.
- */
-int32_t AConfiguration_getMcc(AConfiguration* config);
-
-/**
- * Set the current MCC in the configuration.  0 to clear.
- */
-void AConfiguration_setMcc(AConfiguration* config, int32_t mcc);
-
-/**
- * Return the current MNC set in the configuration.  0 if not set.
- */
-int32_t AConfiguration_getMnc(AConfiguration* config);
-
-/**
- * Set the current MNC in the configuration.  0 to clear.
- */
-void AConfiguration_setMnc(AConfiguration* config, int32_t mnc);
-
-/**
- * Return the current language code set in the configuration.  The output will
- * be filled with an array of two characters.  They are not 0-terminated.  If
- * a language is not set, they will be 0.
- */
-void AConfiguration_getLanguage(AConfiguration* config, char* outLanguage);
-
-/**
- * Set the current language code in the configuration, from the first two
- * characters in the string.
- */
-void AConfiguration_setLanguage(AConfiguration* config, const char* language);
-
-/**
- * Return the current country code set in the configuration.  The output will
- * be filled with an array of two characters.  They are not 0-terminated.  If
- * a country is not set, they will be 0.
- */
-void AConfiguration_getCountry(AConfiguration* config, char* outCountry);
-
-/**
- * Set the current country code in the configuration, from the first two
- * characters in the string.
- */
-void AConfiguration_setCountry(AConfiguration* config, const char* country);
-
-/**
- * Return the current ACONFIGURATION_ORIENTATION_* set in the configuration.
- */
-int32_t AConfiguration_getOrientation(AConfiguration* config);
-
-/**
- * Set the current orientation in the configuration.
- */
-void AConfiguration_setOrientation(AConfiguration* config, int32_t orientation);
-
-/**
- * Return the current ACONFIGURATION_TOUCHSCREEN_* set in the configuration.
- */
-int32_t AConfiguration_getTouchscreen(AConfiguration* config);
-
-/**
- * Set the current touchscreen in the configuration.
- */
-void AConfiguration_setTouchscreen(AConfiguration* config, int32_t touchscreen);
-
-/**
- * Return the current ACONFIGURATION_DENSITY_* set in the configuration.
- */
-int32_t AConfiguration_getDensity(AConfiguration* config);
-
-/**
- * Set the current density in the configuration.
- */
-void AConfiguration_setDensity(AConfiguration* config, int32_t density);
-
-/**
- * Return the current ACONFIGURATION_KEYBOARD_* set in the configuration.
- */
-int32_t AConfiguration_getKeyboard(AConfiguration* config);
-
-/**
- * Set the current keyboard in the configuration.
- */
-void AConfiguration_setKeyboard(AConfiguration* config, int32_t keyboard);
-
-/**
- * Return the current ACONFIGURATION_NAVIGATION_* set in the configuration.
- */
-int32_t AConfiguration_getNavigation(AConfiguration* config);
-
-/**
- * Set the current navigation in the configuration.
- */
-void AConfiguration_setNavigation(AConfiguration* config, int32_t navigation);
-
-/**
- * Return the current ACONFIGURATION_KEYSHIDDEN_* set in the configuration.
- */
-int32_t AConfiguration_getKeysHidden(AConfiguration* config);
-
-/**
- * Set the current keys hidden in the configuration.
- */
-void AConfiguration_setKeysHidden(AConfiguration* config, int32_t keysHidden);
-
-/**
- * Return the current ACONFIGURATION_NAVHIDDEN_* set in the configuration.
- */
-int32_t AConfiguration_getNavHidden(AConfiguration* config);
-
-/**
- * Set the current nav hidden in the configuration.
- */
-void AConfiguration_setNavHidden(AConfiguration* config, int32_t navHidden);
-
-/**
- * Return the current SDK (API) version set in the configuration.
- */
-int32_t AConfiguration_getSdkVersion(AConfiguration* config);
-
-/**
- * Set the current SDK version in the configuration.
- */
-void AConfiguration_setSdkVersion(AConfiguration* config, int32_t sdkVersion);
-
-/**
- * Return the current ACONFIGURATION_SCREENSIZE_* set in the configuration.
- */
-int32_t AConfiguration_getScreenSize(AConfiguration* config);
-
-/**
- * Set the current screen size in the configuration.
- */
-void AConfiguration_setScreenSize(AConfiguration* config, int32_t screenSize);
-
-/**
- * Return the current ACONFIGURATION_SCREENLONG_* set in the configuration.
- */
-int32_t AConfiguration_getScreenLong(AConfiguration* config);
-
-/**
- * Set the current screen long in the configuration.
- */
-void AConfiguration_setScreenLong(AConfiguration* config, int32_t screenLong);
-
-/**
- * Return the current ACONFIGURATION_UI_MODE_TYPE_* set in the configuration.
- */
-int32_t AConfiguration_getUiModeType(AConfiguration* config);
-
-/**
- * Set the current UI mode type in the configuration.
- */
-void AConfiguration_setUiModeType(AConfiguration* config, int32_t uiModeType);
-
-/**
- * Return the current ACONFIGURATION_UI_MODE_NIGHT_* set in the configuration.
- */
-int32_t AConfiguration_getUiModeNight(AConfiguration* config);
-
-/**
- * Set the current UI mode night in the configuration.
- */
-void AConfiguration_setUiModeNight(AConfiguration* config, int32_t uiModeNight);
-
-/**
- * Return the current configuration screen width in dp units, or
- * ACONFIGURATION_SCREEN_WIDTH_DP_ANY if not set.
- */
-int32_t AConfiguration_getScreenWidthDp(AConfiguration* config);
-
-/**
- * Set the configuration's current screen width in dp units.
- */
-void AConfiguration_setScreenWidthDp(AConfiguration* config, int32_t value);
-
-/**
- * Return the current configuration screen height in dp units, or
- * ACONFIGURATION_SCREEN_HEIGHT_DP_ANY if not set.
- */
-int32_t AConfiguration_getScreenHeightDp(AConfiguration* config);
-
-/**
- * Set the configuration's current screen width in dp units.
- */
-void AConfiguration_setScreenHeightDp(AConfiguration* config, int32_t value);
-
-/**
- * Return the configuration's smallest screen width in dp units, or
- * ACONFIGURATION_SMALLEST_SCREEN_WIDTH_DP_ANY if not set.
- */
-int32_t AConfiguration_getSmallestScreenWidthDp(AConfiguration* config);
-
-/**
- * Set the configuration's smallest screen width in dp units.
- */
-void AConfiguration_setSmallestScreenWidthDp(AConfiguration* config, int32_t value);
-
-/**
- * Perform a diff between two configurations.  Returns a bit mask of
- * ACONFIGURATION_* constants, each bit set meaning that configuration element
- * is different between them.
- */
-int32_t AConfiguration_diff(AConfiguration* config1, AConfiguration* config2);
-
-/**
- * Determine whether 'base' is a valid configuration for use within the
- * environment 'requested'.  Returns 0 if there are any values in 'base'
- * that conflict with 'requested'.  Returns 1 if it does not conflict.
- */
-int32_t AConfiguration_match(AConfiguration* base, AConfiguration* requested);
-
-/**
- * Determine whether the configuration in 'test' is better than the existing
- * configuration in 'base'.  If 'requested' is non-NULL, this decision is based
- * on the overall configuration given there.  If it is NULL, this decision is
- * simply based on which configuration is more specific.  Returns non-0 if
- * 'test' is better than 'base'.
- *
- * This assumes you have already filtered the configurations with
- * AConfiguration_match().
- */
-int32_t AConfiguration_isBetterThan(AConfiguration* base, AConfiguration* test,
-        AConfiguration* requested);
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif // ANDROID_CONFIGURATION_H
diff --git a/native/include/android/input.h b/native/include/android/input.h
deleted file mode 100644
index f2befa9e..0000000
--- a/native/include/android/input.h
+++ /dev/null
@@ -1,848 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _ANDROID_INPUT_H
-#define _ANDROID_INPUT_H
-
-/******************************************************************
- *
- * IMPORTANT NOTICE:
- *
- *   This file is part of Android's set of stable system headers
- *   exposed by the Android NDK (Native Development Kit).
- *
- *   Third-party source AND binary code relies on the definitions
- *   here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES.
- *
- *   - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES)
- *   - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS
- *   - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY
- *   - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
- */
-
-/*
- * Structures and functions to receive and process input events in
- * native code.
- *
- * NOTE: These functions MUST be implemented by /system/lib/libui.so
- */
-
-#include <stdint.h>
-#include <sys/types.h>
-#include <android/keycodes.h>
-#include <android/looper.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/*
- * Key states (may be returned by queries about the current state of a
- * particular key code, scan code or switch).
- */
-enum {
-    /* The key state is unknown or the requested key itself is not supported. */
-    AKEY_STATE_UNKNOWN = -1,
-
-    /* The key is up. */
-    AKEY_STATE_UP = 0,
-
-    /* The key is down. */
-    AKEY_STATE_DOWN = 1,
-
-    /* The key is down but is a virtual key press that is being emulated by the system. */
-    AKEY_STATE_VIRTUAL = 2
-};
-
-/*
- * Meta key / modifer state.
- */
-enum {
-    /* No meta keys are pressed. */
-    AMETA_NONE = 0,
-
-    /* This mask is used to check whether one of the ALT meta keys is pressed. */
-    AMETA_ALT_ON = 0x02,
-
-    /* This mask is used to check whether the left ALT meta key is pressed. */
-    AMETA_ALT_LEFT_ON = 0x10,
-
-    /* This mask is used to check whether the right ALT meta key is pressed. */
-    AMETA_ALT_RIGHT_ON = 0x20,
-
-    /* This mask is used to check whether one of the SHIFT meta keys is pressed. */
-    AMETA_SHIFT_ON = 0x01,
-
-    /* This mask is used to check whether the left SHIFT meta key is pressed. */
-    AMETA_SHIFT_LEFT_ON = 0x40,
-
-    /* This mask is used to check whether the right SHIFT meta key is pressed. */
-    AMETA_SHIFT_RIGHT_ON = 0x80,
-
-    /* This mask is used to check whether the SYM meta key is pressed. */
-    AMETA_SYM_ON = 0x04,
-
-    /* This mask is used to check whether the FUNCTION meta key is pressed. */
-    AMETA_FUNCTION_ON = 0x08,
-
-    /* This mask is used to check whether one of the CTRL meta keys is pressed. */
-    AMETA_CTRL_ON = 0x1000,
-
-    /* This mask is used to check whether the left CTRL meta key is pressed. */
-    AMETA_CTRL_LEFT_ON = 0x2000,
-
-    /* This mask is used to check whether the right CTRL meta key is pressed. */
-    AMETA_CTRL_RIGHT_ON = 0x4000,
-
-    /* This mask is used to check whether one of the META meta keys is pressed. */
-    AMETA_META_ON = 0x10000,
-
-    /* This mask is used to check whether the left META meta key is pressed. */
-    AMETA_META_LEFT_ON = 0x20000,
-
-    /* This mask is used to check whether the right META meta key is pressed. */
-    AMETA_META_RIGHT_ON = 0x40000,
-
-    /* This mask is used to check whether the CAPS LOCK meta key is on. */
-    AMETA_CAPS_LOCK_ON = 0x100000,
-
-    /* This mask is used to check whether the NUM LOCK meta key is on. */
-    AMETA_NUM_LOCK_ON = 0x200000,
-
-    /* This mask is used to check whether the SCROLL LOCK meta key is on. */
-    AMETA_SCROLL_LOCK_ON = 0x400000,
-};
-
-/*
- * Input events.
- *
- * Input events are opaque structures.  Use the provided accessors functions to
- * read their properties.
- */
-struct AInputEvent;
-typedef struct AInputEvent AInputEvent;
-
-/*
- * Input event types.
- */
-enum {
-    /* Indicates that the input event is a key event. */
-    AINPUT_EVENT_TYPE_KEY = 1,
-
-    /* Indicates that the input event is a motion event. */
-    AINPUT_EVENT_TYPE_MOTION = 2
-};
-
-/*
- * Key event actions.
- */
-enum {
-    /* The key has been pressed down. */
-    AKEY_EVENT_ACTION_DOWN = 0,
-
-    /* The key has been released. */
-    AKEY_EVENT_ACTION_UP = 1,
-
-    /* Multiple duplicate key events have occurred in a row, or a complex string is
-     * being delivered.  The repeat_count property of the key event contains the number
-     * of times the given key code should be executed.
-     */
-    AKEY_EVENT_ACTION_MULTIPLE = 2
-};
-
-/*
- * Key event flags.
- */
-enum {
-    /* This mask is set if the device woke because of this key event. */
-    AKEY_EVENT_FLAG_WOKE_HERE = 0x1,
-
-    /* This mask is set if the key event was generated by a software keyboard. */
-    AKEY_EVENT_FLAG_SOFT_KEYBOARD = 0x2,
-
-    /* This mask is set if we don't want the key event to cause us to leave touch mode. */
-    AKEY_EVENT_FLAG_KEEP_TOUCH_MODE = 0x4,
-
-    /* This mask is set if an event was known to come from a trusted part
-     * of the system.  That is, the event is known to come from the user,
-     * and could not have been spoofed by a third party component. */
-    AKEY_EVENT_FLAG_FROM_SYSTEM = 0x8,
-
-    /* This mask is used for compatibility, to identify enter keys that are
-     * coming from an IME whose enter key has been auto-labelled "next" or
-     * "done".  This allows TextView to dispatch these as normal enter keys
-     * for old applications, but still do the appropriate action when
-     * receiving them. */
-    AKEY_EVENT_FLAG_EDITOR_ACTION = 0x10,
-
-    /* When associated with up key events, this indicates that the key press
-     * has been canceled.  Typically this is used with virtual touch screen
-     * keys, where the user can slide from the virtual key area on to the
-     * display: in that case, the application will receive a canceled up
-     * event and should not perform the action normally associated with the
-     * key.  Note that for this to work, the application can not perform an
-     * action for a key until it receives an up or the long press timeout has
-     * expired. */
-    AKEY_EVENT_FLAG_CANCELED = 0x20,
-
-    /* This key event was generated by a virtual (on-screen) hard key area.
-     * Typically this is an area of the touchscreen, outside of the regular
-     * display, dedicated to "hardware" buttons. */
-    AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY = 0x40,
-
-    /* This flag is set for the first key repeat that occurs after the
-     * long press timeout. */
-    AKEY_EVENT_FLAG_LONG_PRESS = 0x80,
-
-    /* Set when a key event has AKEY_EVENT_FLAG_CANCELED set because a long
-     * press action was executed while it was down. */
-    AKEY_EVENT_FLAG_CANCELED_LONG_PRESS = 0x100,
-
-    /* Set for AKEY_EVENT_ACTION_UP when this event's key code is still being
-     * tracked from its initial down.  That is, somebody requested that tracking
-     * started on the key down and a long press has not caused
-     * the tracking to be canceled. */
-    AKEY_EVENT_FLAG_TRACKING = 0x200,
-
-    /* Set when a key event has been synthesized to implement default behavior
-     * for an event that the application did not handle.
-     * Fallback key events are generated by unhandled trackball motions
-     * (to emulate a directional keypad) and by certain unhandled key presses
-     * that are declared in the key map (such as special function numeric keypad
-     * keys when numlock is off). */
-    AKEY_EVENT_FLAG_FALLBACK = 0x400,
-};
-
-/*
- * Motion event actions.
- */
-
-/* Bit shift for the action bits holding the pointer index as
- * defined by AMOTION_EVENT_ACTION_POINTER_INDEX_MASK.
- */
-#define AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT 8
-
-enum {
-    /* Bit mask of the parts of the action code that are the action itself.
-     */
-    AMOTION_EVENT_ACTION_MASK = 0xff,
-
-    /* Bits in the action code that represent a pointer index, used with
-     * AMOTION_EVENT_ACTION_POINTER_DOWN and AMOTION_EVENT_ACTION_POINTER_UP.  Shifting
-     * down by AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT provides the actual pointer
-     * index where the data for the pointer going up or down can be found.
-     */
-    AMOTION_EVENT_ACTION_POINTER_INDEX_MASK  = 0xff00,
-
-    /* A pressed gesture has started, the motion contains the initial starting location.
-     */
-    AMOTION_EVENT_ACTION_DOWN = 0,
-
-    /* A pressed gesture has finished, the motion contains the final release location
-     * as well as any intermediate points since the last down or move event.
-     */
-    AMOTION_EVENT_ACTION_UP = 1,
-
-    /* A change has happened during a press gesture (between AMOTION_EVENT_ACTION_DOWN and
-     * AMOTION_EVENT_ACTION_UP).  The motion contains the most recent point, as well as
-     * any intermediate points since the last down or move event.
-     */
-    AMOTION_EVENT_ACTION_MOVE = 2,
-
-    /* The current gesture has been aborted.
-     * You will not receive any more points in it.  You should treat this as
-     * an up event, but not perform any action that you normally would.
-     */
-    AMOTION_EVENT_ACTION_CANCEL = 3,
-
-    /* A movement has happened outside of the normal bounds of the UI element.
-     * This does not provide a full gesture, but only the initial location of the movement/touch.
-     */
-    AMOTION_EVENT_ACTION_OUTSIDE = 4,
-
-    /* A non-primary pointer has gone down.
-     * The bits in AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
-     */
-    AMOTION_EVENT_ACTION_POINTER_DOWN = 5,
-
-    /* A non-primary pointer has gone up.
-     * The bits in AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
-     */
-    AMOTION_EVENT_ACTION_POINTER_UP = 6,
-
-    /* A change happened but the pointer is not down (unlike AMOTION_EVENT_ACTION_MOVE).
-     * The motion contains the most recent point, as well as any intermediate points since
-     * the last hover move event.
-     */
-    AMOTION_EVENT_ACTION_HOVER_MOVE = 7,
-
-    /* The motion event contains relative vertical and/or horizontal scroll offsets.
-     * Use getAxisValue to retrieve the information from AMOTION_EVENT_AXIS_VSCROLL
-     * and AMOTION_EVENT_AXIS_HSCROLL.
-     * The pointer may or may not be down when this event is dispatched.
-     * This action is always delivered to the winder under the pointer, which
-     * may not be the window currently touched.
-     */
-    AMOTION_EVENT_ACTION_SCROLL = 8,
-
-    /* The pointer is not down but has entered the boundaries of a window or view.
-     */
-    AMOTION_EVENT_ACTION_HOVER_ENTER = 9,
-
-    /* The pointer is not down but has exited the boundaries of a window or view.
-     */
-    AMOTION_EVENT_ACTION_HOVER_EXIT = 10,
-};
-
-/*
- * Motion event flags.
- */
-enum {
-    /* This flag indicates that the window that received this motion event is partly
-     * or wholly obscured by another visible window above it.  This flag is set to true
-     * even if the event did not directly pass through the obscured area.
-     * A security sensitive application can check this flag to identify situations in which
-     * a malicious application may have covered up part of its content for the purpose
-     * of misleading the user or hijacking touches.  An appropriate response might be
-     * to drop the suspect touches or to take additional precautions to confirm the user's
-     * actual intent.
-     */
-    AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED = 0x1,
-};
-
-/*
- * Motion event edge touch flags.
- */
-enum {
-    /* No edges intersected */
-    AMOTION_EVENT_EDGE_FLAG_NONE = 0,
-
-    /* Flag indicating the motion event intersected the top edge of the screen. */
-    AMOTION_EVENT_EDGE_FLAG_TOP = 0x01,
-
-    /* Flag indicating the motion event intersected the bottom edge of the screen. */
-    AMOTION_EVENT_EDGE_FLAG_BOTTOM = 0x02,
-
-    /* Flag indicating the motion event intersected the left edge of the screen. */
-    AMOTION_EVENT_EDGE_FLAG_LEFT = 0x04,
-
-    /* Flag indicating the motion event intersected the right edge of the screen. */
-    AMOTION_EVENT_EDGE_FLAG_RIGHT = 0x08
-};
-
-/*
- * Constants that identify each individual axis of a motion event.
- * Refer to the documentation on the MotionEvent class for descriptions of each axis.
- */
-enum {
-    AMOTION_EVENT_AXIS_X = 0,
-    AMOTION_EVENT_AXIS_Y = 1,
-    AMOTION_EVENT_AXIS_PRESSURE = 2,
-    AMOTION_EVENT_AXIS_SIZE = 3,
-    AMOTION_EVENT_AXIS_TOUCH_MAJOR = 4,
-    AMOTION_EVENT_AXIS_TOUCH_MINOR = 5,
-    AMOTION_EVENT_AXIS_TOOL_MAJOR = 6,
-    AMOTION_EVENT_AXIS_TOOL_MINOR = 7,
-    AMOTION_EVENT_AXIS_ORIENTATION = 8,
-    AMOTION_EVENT_AXIS_VSCROLL = 9,
-    AMOTION_EVENT_AXIS_HSCROLL = 10,
-    AMOTION_EVENT_AXIS_Z = 11,
-    AMOTION_EVENT_AXIS_RX = 12,
-    AMOTION_EVENT_AXIS_RY = 13,
-    AMOTION_EVENT_AXIS_RZ = 14,
-    AMOTION_EVENT_AXIS_HAT_X = 15,
-    AMOTION_EVENT_AXIS_HAT_Y = 16,
-    AMOTION_EVENT_AXIS_LTRIGGER = 17,
-    AMOTION_EVENT_AXIS_RTRIGGER = 18,
-    AMOTION_EVENT_AXIS_THROTTLE = 19,
-    AMOTION_EVENT_AXIS_RUDDER = 20,
-    AMOTION_EVENT_AXIS_WHEEL = 21,
-    AMOTION_EVENT_AXIS_GAS = 22,
-    AMOTION_EVENT_AXIS_BRAKE = 23,
-    AMOTION_EVENT_AXIS_DISTANCE = 24,
-    AMOTION_EVENT_AXIS_TILT = 25,
-    AMOTION_EVENT_AXIS_GENERIC_1 = 32,
-    AMOTION_EVENT_AXIS_GENERIC_2 = 33,
-    AMOTION_EVENT_AXIS_GENERIC_3 = 34,
-    AMOTION_EVENT_AXIS_GENERIC_4 = 35,
-    AMOTION_EVENT_AXIS_GENERIC_5 = 36,
-    AMOTION_EVENT_AXIS_GENERIC_6 = 37,
-    AMOTION_EVENT_AXIS_GENERIC_7 = 38,
-    AMOTION_EVENT_AXIS_GENERIC_8 = 39,
-    AMOTION_EVENT_AXIS_GENERIC_9 = 40,
-    AMOTION_EVENT_AXIS_GENERIC_10 = 41,
-    AMOTION_EVENT_AXIS_GENERIC_11 = 42,
-    AMOTION_EVENT_AXIS_GENERIC_12 = 43,
-    AMOTION_EVENT_AXIS_GENERIC_13 = 44,
-    AMOTION_EVENT_AXIS_GENERIC_14 = 45,
-    AMOTION_EVENT_AXIS_GENERIC_15 = 46,
-    AMOTION_EVENT_AXIS_GENERIC_16 = 47,
-
-    // NOTE: If you add a new axis here you must also add it to several other files.
-    //       Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
-};
-
-/*
- * Constants that identify buttons that are associated with motion events.
- * Refer to the documentation on the MotionEvent class for descriptions of each button.
- */
-enum {
-    AMOTION_EVENT_BUTTON_PRIMARY = 1 << 0,
-    AMOTION_EVENT_BUTTON_SECONDARY = 1 << 1,
-    AMOTION_EVENT_BUTTON_TERTIARY = 1 << 2,
-    AMOTION_EVENT_BUTTON_BACK = 1 << 3,
-    AMOTION_EVENT_BUTTON_FORWARD = 1 << 4,
-};
-
-/*
- * Constants that identify tool types.
- * Refer to the documentation on the MotionEvent class for descriptions of each tool type.
- */
-enum {
-    AMOTION_EVENT_TOOL_TYPE_UNKNOWN = 0,
-    AMOTION_EVENT_TOOL_TYPE_FINGER = 1,
-    AMOTION_EVENT_TOOL_TYPE_STYLUS = 2,
-    AMOTION_EVENT_TOOL_TYPE_MOUSE = 3,
-    AMOTION_EVENT_TOOL_TYPE_ERASER = 4,
-};
-
-/*
- * Input sources.
- *
- * Refer to the documentation on android.view.InputDevice for more details about input sources
- * and their correct interpretation.
- */
-enum {
-    AINPUT_SOURCE_CLASS_MASK = 0x000000ff,
-
-    AINPUT_SOURCE_CLASS_BUTTON = 0x00000001,
-    AINPUT_SOURCE_CLASS_POINTER = 0x00000002,
-    AINPUT_SOURCE_CLASS_NAVIGATION = 0x00000004,
-    AINPUT_SOURCE_CLASS_POSITION = 0x00000008,
-    AINPUT_SOURCE_CLASS_JOYSTICK = 0x00000010,
-};
-
-enum {
-    AINPUT_SOURCE_UNKNOWN = 0x00000000,
-
-    AINPUT_SOURCE_KEYBOARD = 0x00000100 | AINPUT_SOURCE_CLASS_BUTTON,
-    AINPUT_SOURCE_DPAD = 0x00000200 | AINPUT_SOURCE_CLASS_BUTTON,
-    AINPUT_SOURCE_GAMEPAD = 0x00000400 | AINPUT_SOURCE_CLASS_BUTTON,
-    AINPUT_SOURCE_TOUCHSCREEN = 0x00001000 | AINPUT_SOURCE_CLASS_POINTER,
-    AINPUT_SOURCE_MOUSE = 0x00002000 | AINPUT_SOURCE_CLASS_POINTER,
-    AINPUT_SOURCE_STYLUS = 0x00004000 | AINPUT_SOURCE_CLASS_POINTER,
-    AINPUT_SOURCE_TRACKBALL = 0x00010000 | AINPUT_SOURCE_CLASS_NAVIGATION,
-    AINPUT_SOURCE_TOUCHPAD = 0x00100000 | AINPUT_SOURCE_CLASS_POSITION,
-    AINPUT_SOURCE_JOYSTICK = 0x01000000 | AINPUT_SOURCE_CLASS_JOYSTICK,
-
-    AINPUT_SOURCE_ANY = 0xffffff00,
-};
-
-/*
- * Keyboard types.
- *
- * Refer to the documentation on android.view.InputDevice for more details.
- */
-enum {
-    AINPUT_KEYBOARD_TYPE_NONE = 0,
-    AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC = 1,
-    AINPUT_KEYBOARD_TYPE_ALPHABETIC = 2,
-};
-
-/*
- * Constants used to retrieve information about the range of motion for a particular
- * coordinate of a motion event.
- *
- * Refer to the documentation on android.view.InputDevice for more details about input sources
- * and their correct interpretation.
- *
- * DEPRECATION NOTICE: These constants are deprecated.  Use AMOTION_EVENT_AXIS_* constants instead.
- */
-enum {
-    AINPUT_MOTION_RANGE_X = AMOTION_EVENT_AXIS_X,
-    AINPUT_MOTION_RANGE_Y = AMOTION_EVENT_AXIS_Y,
-    AINPUT_MOTION_RANGE_PRESSURE = AMOTION_EVENT_AXIS_PRESSURE,
-    AINPUT_MOTION_RANGE_SIZE = AMOTION_EVENT_AXIS_SIZE,
-    AINPUT_MOTION_RANGE_TOUCH_MAJOR = AMOTION_EVENT_AXIS_TOUCH_MAJOR,
-    AINPUT_MOTION_RANGE_TOUCH_MINOR = AMOTION_EVENT_AXIS_TOUCH_MINOR,
-    AINPUT_MOTION_RANGE_TOOL_MAJOR = AMOTION_EVENT_AXIS_TOOL_MAJOR,
-    AINPUT_MOTION_RANGE_TOOL_MINOR = AMOTION_EVENT_AXIS_TOOL_MINOR,
-    AINPUT_MOTION_RANGE_ORIENTATION = AMOTION_EVENT_AXIS_ORIENTATION,
-} __attribute__ ((deprecated));
-
-
-/*
- * Input event accessors.
- *
- * Note that most functions can only be used on input events that are of a given type.
- * Calling these functions on input events of other types will yield undefined behavior.
- */
-
-/*** Accessors for all input events. ***/
-
-/* Get the input event type. */
-int32_t AInputEvent_getType(const AInputEvent* event);
-
-/* Get the id for the device that an input event came from.
- *
- * Input events can be generated by multiple different input devices.
- * Use the input device id to obtain information about the input
- * device that was responsible for generating a particular event.
- *
- * An input device id of 0 indicates that the event didn't come from a physical device;
- * other numbers are arbitrary and you shouldn't depend on the values.
- * Use the provided input device query API to obtain information about input devices.
- */
-int32_t AInputEvent_getDeviceId(const AInputEvent* event);
-
-/* Get the input event source. */
-int32_t AInputEvent_getSource(const AInputEvent* event);
-
-/*** Accessors for key events only. ***/
-
-/* Get the key event action. */
-int32_t AKeyEvent_getAction(const AInputEvent* key_event);
-
-/* Get the key event flags. */
-int32_t AKeyEvent_getFlags(const AInputEvent* key_event);
-
-/* Get the key code of the key event.
- * This is the physical key that was pressed, not the Unicode character. */
-int32_t AKeyEvent_getKeyCode(const AInputEvent* key_event);
-
-/* Get the hardware key id of this key event.
- * These values are not reliable and vary from device to device. */
-int32_t AKeyEvent_getScanCode(const AInputEvent* key_event);
-
-/* Get the meta key state. */
-int32_t AKeyEvent_getMetaState(const AInputEvent* key_event);
-
-/* Get the repeat count of the event.
- * For both key up an key down events, this is the number of times the key has
- * repeated with the first down starting at 0 and counting up from there.  For
- * multiple key events, this is the number of down/up pairs that have occurred. */
-int32_t AKeyEvent_getRepeatCount(const AInputEvent* key_event);
-
-/* Get the time of the most recent key down event, in the
- * java.lang.System.nanoTime() time base.  If this is a down event,
- * this will be the same as eventTime.
- * Note that when chording keys, this value is the down time of the most recently
- * pressed key, which may not be the same physical key of this event. */
-int64_t AKeyEvent_getDownTime(const AInputEvent* key_event);
-
-/* Get the time this event occurred, in the
- * java.lang.System.nanoTime() time base. */
-int64_t AKeyEvent_getEventTime(const AInputEvent* key_event);
-
-/*** Accessors for motion events only. ***/
-
-/* Get the combined motion event action code and pointer index. */
-int32_t AMotionEvent_getAction(const AInputEvent* motion_event);
-
-/* Get the motion event flags. */
-int32_t AMotionEvent_getFlags(const AInputEvent* motion_event);
-
-/* Get the state of any meta / modifier keys that were in effect when the
- * event was generated. */
-int32_t AMotionEvent_getMetaState(const AInputEvent* motion_event);
-
-/* Get the button state of all buttons that are pressed. */
-int32_t AMotionEvent_getButtonState(const AInputEvent* motion_event);
-
-/* Get a bitfield indicating which edges, if any, were touched by this motion event.
- * For touch events, clients can use this to determine if the user's finger was
- * touching the edge of the display. */
-int32_t AMotionEvent_getEdgeFlags(const AInputEvent* motion_event);
-
-/* Get the time when the user originally pressed down to start a stream of
- * position events, in the java.lang.System.nanoTime() time base. */
-int64_t AMotionEvent_getDownTime(const AInputEvent* motion_event);
-
-/* Get the time when this specific event was generated,
- * in the java.lang.System.nanoTime() time base. */
-int64_t AMotionEvent_getEventTime(const AInputEvent* motion_event);
-
-/* Get the X coordinate offset.
- * For touch events on the screen, this is the delta that was added to the raw
- * screen coordinates to adjust for the absolute position of the containing windows
- * and views. */
-float AMotionEvent_getXOffset(const AInputEvent* motion_event);
-
-/* Get the precision of the Y coordinates being reported.
- * For touch events on the screen, this is the delta that was added to the raw
- * screen coordinates to adjust for the absolute position of the containing windows
- * and views. */
-float AMotionEvent_getYOffset(const AInputEvent* motion_event);
-
-/* Get the precision of the X coordinates being reported.
- * You can multiply this number with an X coordinate sample to find the
- * actual hardware value of the X coordinate. */
-float AMotionEvent_getXPrecision(const AInputEvent* motion_event);
-
-/* Get the precision of the Y coordinates being reported.
- * You can multiply this number with a Y coordinate sample to find the
- * actual hardware value of the Y coordinate. */
-float AMotionEvent_getYPrecision(const AInputEvent* motion_event);
-
-/* Get the number of pointers of data contained in this event.
- * Always >= 1. */
-size_t AMotionEvent_getPointerCount(const AInputEvent* motion_event);
-
-/* Get the pointer identifier associated with a particular pointer
- * data index in this event.  The identifier tells you the actual pointer
- * number associated with the data, accounting for individual pointers
- * going up and down since the start of the current gesture. */
-int32_t AMotionEvent_getPointerId(const AInputEvent* motion_event, size_t pointer_index);
-
-/* Get the tool type of a pointer for the given pointer index.
- * The tool type indicates the type of tool used to make contact such as a
- * finger or stylus, if known. */
-int32_t AMotionEvent_getToolType(const AInputEvent* motion_event, size_t pointer_index);
-
-/* Get the original raw X coordinate of this event.
- * For touch events on the screen, this is the original location of the event
- * on the screen, before it had been adjusted for the containing window
- * and views. */
-float AMotionEvent_getRawX(const AInputEvent* motion_event, size_t pointer_index);
-
-/* Get the original raw X coordinate of this event.
- * For touch events on the screen, this is the original location of the event
- * on the screen, before it had been adjusted for the containing window
- * and views. */
-float AMotionEvent_getRawY(const AInputEvent* motion_event, size_t pointer_index);
-
-/* Get the current X coordinate of this event for the given pointer index.
- * Whole numbers are pixels; the value may have a fraction for input devices
- * that are sub-pixel precise. */
-float AMotionEvent_getX(const AInputEvent* motion_event, size_t pointer_index);
-
-/* Get the current Y coordinate of this event for the given pointer index.
- * Whole numbers are pixels; the value may have a fraction for input devices
- * that are sub-pixel precise. */
-float AMotionEvent_getY(const AInputEvent* motion_event, size_t pointer_index);
-
-/* Get the current pressure of this event for the given pointer index.
- * The pressure generally ranges from 0 (no pressure at all) to 1 (normal pressure),
- * although values higher than 1 may be generated depending on the calibration of
- * the input device. */
-float AMotionEvent_getPressure(const AInputEvent* motion_event, size_t pointer_index);
-
-/* Get the current scaled value of the approximate size for the given pointer index.
- * This represents some approximation of the area of the screen being
- * pressed; the actual value in pixels corresponding to the
- * touch is normalized with the device specific range of values
- * and scaled to a value between 0 and 1.  The value of size can be used to
- * determine fat touch events. */
-float AMotionEvent_getSize(const AInputEvent* motion_event, size_t pointer_index);
-
-/* Get the current length of the major axis of an ellipse that describes the touch area
- * at the point of contact for the given pointer index. */
-float AMotionEvent_getTouchMajor(const AInputEvent* motion_event, size_t pointer_index);
-
-/* Get the current length of the minor axis of an ellipse that describes the touch area
- * at the point of contact for the given pointer index. */
-float AMotionEvent_getTouchMinor(const AInputEvent* motion_event, size_t pointer_index);
-
-/* Get the current length of the major axis of an ellipse that describes the size
- * of the approaching tool for the given pointer index.
- * The tool area represents the estimated size of the finger or pen that is
- * touching the device independent of its actual touch area at the point of contact. */
-float AMotionEvent_getToolMajor(const AInputEvent* motion_event, size_t pointer_index);
-
-/* Get the current length of the minor axis of an ellipse that describes the size
- * of the approaching tool for the given pointer index.
- * The tool area represents the estimated size of the finger or pen that is
- * touching the device independent of its actual touch area at the point of contact. */
-float AMotionEvent_getToolMinor(const AInputEvent* motion_event, size_t pointer_index);
-
-/* Get the current orientation of the touch area and tool area in radians clockwise from
- * vertical for the given pointer index.
- * An angle of 0 degrees indicates that the major axis of contact is oriented
- * upwards, is perfectly circular or is of unknown orientation.  A positive angle
- * indicates that the major axis of contact is oriented to the right.  A negative angle
- * indicates that the major axis of contact is oriented to the left.
- * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
- * (finger pointing fully right). */
-float AMotionEvent_getOrientation(const AInputEvent* motion_event, size_t pointer_index);
-
-/* Get the value of the request axis for the given pointer index. */
-float AMotionEvent_getAxisValue(const AInputEvent* motion_event,
-        int32_t axis, size_t pointer_index);
-
-/* Get the number of historical points in this event.  These are movements that
- * have occurred between this event and the previous event.  This only applies
- * to AMOTION_EVENT_ACTION_MOVE events -- all other actions will have a size of 0.
- * Historical samples are indexed from oldest to newest. */
-size_t AMotionEvent_getHistorySize(const AInputEvent* motion_event);
-
-/* Get the time that a historical movement occurred between this event and
- * the previous event, in the java.lang.System.nanoTime() time base. */
-int64_t AMotionEvent_getHistoricalEventTime(AInputEvent* motion_event,
-        size_t history_index);
-
-/* Get the historical raw X coordinate of this event for the given pointer index that
- * occurred between this event and the previous motion event.
- * For touch events on the screen, this is the original location of the event
- * on the screen, before it had been adjusted for the containing window
- * and views.
- * Whole numbers are pixels; the value may have a fraction for input devices
- * that are sub-pixel precise. */
-float AMotionEvent_getHistoricalRawX(const AInputEvent* motion_event, size_t pointer_index,
-        size_t history_index);
-
-/* Get the historical raw Y coordinate of this event for the given pointer index that
- * occurred between this event and the previous motion event.
- * For touch events on the screen, this is the original location of the event
- * on the screen, before it had been adjusted for the containing window
- * and views.
- * Whole numbers are pixels; the value may have a fraction for input devices
- * that are sub-pixel precise. */
-float AMotionEvent_getHistoricalRawY(const AInputEvent* motion_event, size_t pointer_index,
-        size_t history_index);
-
-/* Get the historical X coordinate of this event for the given pointer index that
- * occurred between this event and the previous motion event.
- * Whole numbers are pixels; the value may have a fraction for input devices
- * that are sub-pixel precise. */
-float AMotionEvent_getHistoricalX(AInputEvent* motion_event, size_t pointer_index,
-        size_t history_index);
-
-/* Get the historical Y coordinate of this event for the given pointer index that
- * occurred between this event and the previous motion event.
- * Whole numbers are pixels; the value may have a fraction for input devices
- * that are sub-pixel precise. */
-float AMotionEvent_getHistoricalY(AInputEvent* motion_event, size_t pointer_index,
-        size_t history_index);
-
-/* Get the historical pressure of this event for the given pointer index that
- * occurred between this event and the previous motion event.
- * The pressure generally ranges from 0 (no pressure at all) to 1 (normal pressure),
- * although values higher than 1 may be generated depending on the calibration of
- * the input device. */
-float AMotionEvent_getHistoricalPressure(AInputEvent* motion_event, size_t pointer_index,
-        size_t history_index);
-
-/* Get the current scaled value of the approximate size for the given pointer index that
- * occurred between this event and the previous motion event.
- * This represents some approximation of the area of the screen being
- * pressed; the actual value in pixels corresponding to the
- * touch is normalized with the device specific range of values
- * and scaled to a value between 0 and 1.  The value of size can be used to
- * determine fat touch events. */
-float AMotionEvent_getHistoricalSize(AInputEvent* motion_event, size_t pointer_index,
-        size_t history_index);
-
-/* Get the historical length of the major axis of an ellipse that describes the touch area
- * at the point of contact for the given pointer index that
- * occurred between this event and the previous motion event. */
-float AMotionEvent_getHistoricalTouchMajor(const AInputEvent* motion_event, size_t pointer_index,
-        size_t history_index);
-
-/* Get the historical length of the minor axis of an ellipse that describes the touch area
- * at the point of contact for the given pointer index that
- * occurred between this event and the previous motion event. */
-float AMotionEvent_getHistoricalTouchMinor(const AInputEvent* motion_event, size_t pointer_index,
-        size_t history_index);
-
-/* Get the historical length of the major axis of an ellipse that describes the size
- * of the approaching tool for the given pointer index that
- * occurred between this event and the previous motion event.
- * The tool area represents the estimated size of the finger or pen that is
- * touching the device independent of its actual touch area at the point of contact. */
-float AMotionEvent_getHistoricalToolMajor(const AInputEvent* motion_event, size_t pointer_index,
-        size_t history_index);
-
-/* Get the historical length of the minor axis of an ellipse that describes the size
- * of the approaching tool for the given pointer index that
- * occurred between this event and the previous motion event.
- * The tool area represents the estimated size of the finger or pen that is
- * touching the device independent of its actual touch area at the point of contact. */
-float AMotionEvent_getHistoricalToolMinor(const AInputEvent* motion_event, size_t pointer_index,
-        size_t history_index);
-
-/* Get the historical orientation of the touch area and tool area in radians clockwise from
- * vertical for the given pointer index that
- * occurred between this event and the previous motion event.
- * An angle of 0 degrees indicates that the major axis of contact is oriented
- * upwards, is perfectly circular or is of unknown orientation.  A positive angle
- * indicates that the major axis of contact is oriented to the right.  A negative angle
- * indicates that the major axis of contact is oriented to the left.
- * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
- * (finger pointing fully right). */
-float AMotionEvent_getHistoricalOrientation(const AInputEvent* motion_event, size_t pointer_index,
-        size_t history_index);
-
-/* Get the historical value of the request axis for the given pointer index
- * that occurred between this event and the previous motion event. */
-float AMotionEvent_getHistoricalAxisValue(const AInputEvent* motion_event,
-        int32_t axis, size_t pointer_index, size_t history_index);
-
-
-/*
- * Input queue
- *
- * An input queue is the facility through which you retrieve input
- * events.
- */
-struct AInputQueue;
-typedef struct AInputQueue AInputQueue;
-
-/*
- * Add this input queue to a looper for processing.  See
- * ALooper_addFd() for information on the ident, callback, and data params.
- */
-void AInputQueue_attachLooper(AInputQueue* queue, ALooper* looper,
-        int ident, ALooper_callbackFunc callback, void* data);
-
-/*
- * Remove the input queue from the looper it is currently attached to.
- */
-void AInputQueue_detachLooper(AInputQueue* queue);
-
-/*
- * Returns true if there are one or more events available in the
- * input queue.  Returns 1 if the queue has events; 0 if
- * it does not have events; and a negative value if there is an error.
- */
-int32_t AInputQueue_hasEvents(AInputQueue* queue);
-
-/*
- * Returns the next available event from the queue.  Returns a negative
- * value if no events are available or an error has occurred.
- */
-int32_t AInputQueue_getEvent(AInputQueue* queue, AInputEvent** outEvent);
-
-/*
- * Sends the key for standard pre-dispatching -- that is, possibly deliver
- * it to the current IME to be consumed before the app.  Returns 0 if it
- * was not pre-dispatched, meaning you can process it right now.  If non-zero
- * is returned, you must abandon the current event processing and allow the
- * event to appear again in the event queue (if it does not get consumed during
- * pre-dispatching).
- */
-int32_t AInputQueue_preDispatchEvent(AInputQueue* queue, AInputEvent* event);
-
-/*
- * Report that dispatching has finished with the given event.
- * This must be called after receiving an event with AInputQueue_get_event().
- */
-void AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // _ANDROID_INPUT_H
diff --git a/native/include/android/keycodes.h b/native/include/android/keycodes.h
deleted file mode 100644
index 8414ff6..0000000
--- a/native/include/android/keycodes.h
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _ANDROID_KEYCODES_H
-#define _ANDROID_KEYCODES_H
-
-/******************************************************************
- *
- * IMPORTANT NOTICE:
- *
- *   This file is part of Android's set of stable system headers
- *   exposed by the Android NDK (Native Development Kit).
- *
- *   Third-party source AND binary code relies on the definitions
- *   here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES.
- *
- *   - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES)
- *   - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS
- *   - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY
- *   - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
- */
-
-#include <sys/types.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/*
- * Key codes.
- */
-enum {
-    AKEYCODE_UNKNOWN         = 0,
-    AKEYCODE_SOFT_LEFT       = 1,
-    AKEYCODE_SOFT_RIGHT      = 2,
-    AKEYCODE_HOME            = 3,
-    AKEYCODE_BACK            = 4,
-    AKEYCODE_CALL            = 5,
-    AKEYCODE_ENDCALL         = 6,
-    AKEYCODE_0               = 7,
-    AKEYCODE_1               = 8,
-    AKEYCODE_2               = 9,
-    AKEYCODE_3               = 10,
-    AKEYCODE_4               = 11,
-    AKEYCODE_5               = 12,
-    AKEYCODE_6               = 13,
-    AKEYCODE_7               = 14,
-    AKEYCODE_8               = 15,
-    AKEYCODE_9               = 16,
-    AKEYCODE_STAR            = 17,
-    AKEYCODE_POUND           = 18,
-    AKEYCODE_DPAD_UP         = 19,
-    AKEYCODE_DPAD_DOWN       = 20,
-    AKEYCODE_DPAD_LEFT       = 21,
-    AKEYCODE_DPAD_RIGHT      = 22,
-    AKEYCODE_DPAD_CENTER     = 23,
-    AKEYCODE_VOLUME_UP       = 24,
-    AKEYCODE_VOLUME_DOWN     = 25,
-    AKEYCODE_POWER           = 26,
-    AKEYCODE_CAMERA          = 27,
-    AKEYCODE_CLEAR           = 28,
-    AKEYCODE_A               = 29,
-    AKEYCODE_B               = 30,
-    AKEYCODE_C               = 31,
-    AKEYCODE_D               = 32,
-    AKEYCODE_E               = 33,
-    AKEYCODE_F               = 34,
-    AKEYCODE_G               = 35,
-    AKEYCODE_H               = 36,
-    AKEYCODE_I               = 37,
-    AKEYCODE_J               = 38,
-    AKEYCODE_K               = 39,
-    AKEYCODE_L               = 40,
-    AKEYCODE_M               = 41,
-    AKEYCODE_N               = 42,
-    AKEYCODE_O               = 43,
-    AKEYCODE_P               = 44,
-    AKEYCODE_Q               = 45,
-    AKEYCODE_R               = 46,
-    AKEYCODE_S               = 47,
-    AKEYCODE_T               = 48,
-    AKEYCODE_U               = 49,
-    AKEYCODE_V               = 50,
-    AKEYCODE_W               = 51,
-    AKEYCODE_X               = 52,
-    AKEYCODE_Y               = 53,
-    AKEYCODE_Z               = 54,
-    AKEYCODE_COMMA           = 55,
-    AKEYCODE_PERIOD          = 56,
-    AKEYCODE_ALT_LEFT        = 57,
-    AKEYCODE_ALT_RIGHT       = 58,
-    AKEYCODE_SHIFT_LEFT      = 59,
-    AKEYCODE_SHIFT_RIGHT     = 60,
-    AKEYCODE_TAB             = 61,
-    AKEYCODE_SPACE           = 62,
-    AKEYCODE_SYM             = 63,
-    AKEYCODE_EXPLORER        = 64,
-    AKEYCODE_ENVELOPE        = 65,
-    AKEYCODE_ENTER           = 66,
-    AKEYCODE_DEL             = 67,
-    AKEYCODE_GRAVE           = 68,
-    AKEYCODE_MINUS           = 69,
-    AKEYCODE_EQUALS          = 70,
-    AKEYCODE_LEFT_BRACKET    = 71,
-    AKEYCODE_RIGHT_BRACKET   = 72,
-    AKEYCODE_BACKSLASH       = 73,
-    AKEYCODE_SEMICOLON       = 74,
-    AKEYCODE_APOSTROPHE      = 75,
-    AKEYCODE_SLASH           = 76,
-    AKEYCODE_AT              = 77,
-    AKEYCODE_NUM             = 78,
-    AKEYCODE_HEADSETHOOK     = 79,
-    AKEYCODE_FOCUS           = 80,   // *Camera* focus
-    AKEYCODE_PLUS            = 81,
-    AKEYCODE_MENU            = 82,
-    AKEYCODE_NOTIFICATION    = 83,
-    AKEYCODE_SEARCH          = 84,
-    AKEYCODE_MEDIA_PLAY_PAUSE= 85,
-    AKEYCODE_MEDIA_STOP      = 86,
-    AKEYCODE_MEDIA_NEXT      = 87,
-    AKEYCODE_MEDIA_PREVIOUS  = 88,
-    AKEYCODE_MEDIA_REWIND    = 89,
-    AKEYCODE_MEDIA_FAST_FORWARD = 90,
-    AKEYCODE_MUTE            = 91,
-    AKEYCODE_PAGE_UP         = 92,
-    AKEYCODE_PAGE_DOWN       = 93,
-    AKEYCODE_PICTSYMBOLS     = 94,
-    AKEYCODE_SWITCH_CHARSET  = 95,
-    AKEYCODE_BUTTON_A        = 96,
-    AKEYCODE_BUTTON_B        = 97,
-    AKEYCODE_BUTTON_C        = 98,
-    AKEYCODE_BUTTON_X        = 99,
-    AKEYCODE_BUTTON_Y        = 100,
-    AKEYCODE_BUTTON_Z        = 101,
-    AKEYCODE_BUTTON_L1       = 102,
-    AKEYCODE_BUTTON_R1       = 103,
-    AKEYCODE_BUTTON_L2       = 104,
-    AKEYCODE_BUTTON_R2       = 105,
-    AKEYCODE_BUTTON_THUMBL   = 106,
-    AKEYCODE_BUTTON_THUMBR   = 107,
-    AKEYCODE_BUTTON_START    = 108,
-    AKEYCODE_BUTTON_SELECT   = 109,
-    AKEYCODE_BUTTON_MODE     = 110,
-    AKEYCODE_ESCAPE          = 111,
-    AKEYCODE_FORWARD_DEL     = 112,
-    AKEYCODE_CTRL_LEFT       = 113,
-    AKEYCODE_CTRL_RIGHT      = 114,
-    AKEYCODE_CAPS_LOCK       = 115,
-    AKEYCODE_SCROLL_LOCK     = 116,
-    AKEYCODE_META_LEFT       = 117,
-    AKEYCODE_META_RIGHT      = 118,
-    AKEYCODE_FUNCTION        = 119,
-    AKEYCODE_SYSRQ           = 120,
-    AKEYCODE_BREAK           = 121,
-    AKEYCODE_MOVE_HOME       = 122,
-    AKEYCODE_MOVE_END        = 123,
-    AKEYCODE_INSERT          = 124,
-    AKEYCODE_FORWARD         = 125,
-    AKEYCODE_MEDIA_PLAY      = 126,
-    AKEYCODE_MEDIA_PAUSE     = 127,
-    AKEYCODE_MEDIA_CLOSE     = 128,
-    AKEYCODE_MEDIA_EJECT     = 129,
-    AKEYCODE_MEDIA_RECORD    = 130,
-    AKEYCODE_F1              = 131,
-    AKEYCODE_F2              = 132,
-    AKEYCODE_F3              = 133,
-    AKEYCODE_F4              = 134,
-    AKEYCODE_F5              = 135,
-    AKEYCODE_F6              = 136,
-    AKEYCODE_F7              = 137,
-    AKEYCODE_F8              = 138,
-    AKEYCODE_F9              = 139,
-    AKEYCODE_F10             = 140,
-    AKEYCODE_F11             = 141,
-    AKEYCODE_F12             = 142,
-    AKEYCODE_NUM_LOCK        = 143,
-    AKEYCODE_NUMPAD_0        = 144,
-    AKEYCODE_NUMPAD_1        = 145,
-    AKEYCODE_NUMPAD_2        = 146,
-    AKEYCODE_NUMPAD_3        = 147,
-    AKEYCODE_NUMPAD_4        = 148,
-    AKEYCODE_NUMPAD_5        = 149,
-    AKEYCODE_NUMPAD_6        = 150,
-    AKEYCODE_NUMPAD_7        = 151,
-    AKEYCODE_NUMPAD_8        = 152,
-    AKEYCODE_NUMPAD_9        = 153,
-    AKEYCODE_NUMPAD_DIVIDE   = 154,
-    AKEYCODE_NUMPAD_MULTIPLY = 155,
-    AKEYCODE_NUMPAD_SUBTRACT = 156,
-    AKEYCODE_NUMPAD_ADD      = 157,
-    AKEYCODE_NUMPAD_DOT      = 158,
-    AKEYCODE_NUMPAD_COMMA    = 159,
-    AKEYCODE_NUMPAD_ENTER    = 160,
-    AKEYCODE_NUMPAD_EQUALS   = 161,
-    AKEYCODE_NUMPAD_LEFT_PAREN = 162,
-    AKEYCODE_NUMPAD_RIGHT_PAREN = 163,
-    AKEYCODE_VOLUME_MUTE     = 164,
-    AKEYCODE_INFO            = 165,
-    AKEYCODE_CHANNEL_UP      = 166,
-    AKEYCODE_CHANNEL_DOWN    = 167,
-    AKEYCODE_ZOOM_IN         = 168,
-    AKEYCODE_ZOOM_OUT        = 169,
-    AKEYCODE_TV              = 170,
-    AKEYCODE_WINDOW          = 171,
-    AKEYCODE_GUIDE           = 172,
-    AKEYCODE_DVR             = 173,
-    AKEYCODE_BOOKMARK        = 174,
-    AKEYCODE_CAPTIONS        = 175,
-    AKEYCODE_SETTINGS        = 176,
-    AKEYCODE_TV_POWER        = 177,
-    AKEYCODE_TV_INPUT        = 178,
-    AKEYCODE_STB_POWER       = 179,
-    AKEYCODE_STB_INPUT       = 180,
-    AKEYCODE_AVR_POWER       = 181,
-    AKEYCODE_AVR_INPUT       = 182,
-    AKEYCODE_PROG_RED        = 183,
-    AKEYCODE_PROG_GREEN      = 184,
-    AKEYCODE_PROG_YELLOW     = 185,
-    AKEYCODE_PROG_BLUE       = 186,
-    AKEYCODE_APP_SWITCH      = 187,
-    AKEYCODE_BUTTON_1        = 188,
-    AKEYCODE_BUTTON_2        = 189,
-    AKEYCODE_BUTTON_3        = 190,
-    AKEYCODE_BUTTON_4        = 191,
-    AKEYCODE_BUTTON_5        = 192,
-    AKEYCODE_BUTTON_6        = 193,
-    AKEYCODE_BUTTON_7        = 194,
-    AKEYCODE_BUTTON_8        = 195,
-    AKEYCODE_BUTTON_9        = 196,
-    AKEYCODE_BUTTON_10       = 197,
-    AKEYCODE_BUTTON_11       = 198,
-    AKEYCODE_BUTTON_12       = 199,
-    AKEYCODE_BUTTON_13       = 200,
-    AKEYCODE_BUTTON_14       = 201,
-    AKEYCODE_BUTTON_15       = 202,
-    AKEYCODE_BUTTON_16       = 203,
-    AKEYCODE_LANGUAGE_SWITCH = 204,
-    AKEYCODE_MANNER_MODE     = 205,
-    AKEYCODE_3D_MODE         = 206,
-    AKEYCODE_CONTACTS        = 207,
-    AKEYCODE_CALENDAR        = 208,
-    AKEYCODE_MUSIC           = 209,
-    AKEYCODE_CALCULATOR      = 210,
-
-    // NOTE: If you add a new keycode here you must also add it to several other files.
-    //       Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
-};
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // _ANDROID_KEYCODES_H
diff --git a/native/include/android/looper.h b/native/include/android/looper.h
deleted file mode 100644
index 24e3967..0000000
--- a/native/include/android/looper.h
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#ifndef ANDROID_LOOPER_H
-#define ANDROID_LOOPER_H
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * ALooper
- *
- * A looper is the state tracking an event loop for a thread.
- * Loopers do not define event structures or other such things; rather
- * they are a lower-level facility to attach one or more discrete objects
- * listening for an event.  An "event" here is simply data available on
- * a file descriptor: each attached object has an associated file descriptor,
- * and waiting for "events" means (internally) polling on all of these file
- * descriptors until one or more of them have data available.
- *
- * A thread can have only one ALooper associated with it.
- */
-struct ALooper;
-typedef struct ALooper ALooper;
-
-/**
- * Returns the looper associated with the calling thread, or NULL if
- * there is not one.
- */
-ALooper* ALooper_forThread();
-
-enum {
-    /**
-     * Option for ALooper_prepare: this looper will accept calls to
-     * ALooper_addFd() that do not have a callback (that is provide NULL
-     * for the callback).  In this case the caller of ALooper_pollOnce()
-     * or ALooper_pollAll() MUST check the return from these functions to
-     * discover when data is available on such fds and process it.
-     */
-    ALOOPER_PREPARE_ALLOW_NON_CALLBACKS = 1<<0
-};
-
-/**
- * Prepares a looper associated with the calling thread, and returns it.
- * If the thread already has a looper, it is returned.  Otherwise, a new
- * one is created, associated with the thread, and returned.
- *
- * The opts may be ALOOPER_PREPARE_ALLOW_NON_CALLBACKS or 0.
- */
-ALooper* ALooper_prepare(int opts);
-
-enum {
-    /**
-     * Result from ALooper_pollOnce() and ALooper_pollAll():
-     * The poll was awoken using wake() before the timeout expired
-     * and no callbacks were executed and no other file descriptors were ready.
-     */
-    ALOOPER_POLL_WAKE = -1,
-
-    /**
-     * Result from ALooper_pollOnce() and ALooper_pollAll():
-     * One or more callbacks were executed.
-     */
-    ALOOPER_POLL_CALLBACK = -2,
-
-    /**
-     * Result from ALooper_pollOnce() and ALooper_pollAll():
-     * The timeout expired.
-     */
-    ALOOPER_POLL_TIMEOUT = -3,
-
-    /**
-     * Result from ALooper_pollOnce() and ALooper_pollAll():
-     * An error occurred.
-     */
-    ALOOPER_POLL_ERROR = -4,
-};
-
-/**
- * Acquire a reference on the given ALooper object.  This prevents the object
- * from being deleted until the reference is removed.  This is only needed
- * to safely hand an ALooper from one thread to another.
- */
-void ALooper_acquire(ALooper* looper);
-
-/**
- * Remove a reference that was previously acquired with ALooper_acquire().
- */
-void ALooper_release(ALooper* looper);
-
-/**
- * Flags for file descriptor events that a looper can monitor.
- *
- * These flag bits can be combined to monitor multiple events at once.
- */
-enum {
-    /**
-     * The file descriptor is available for read operations.
-     */
-    ALOOPER_EVENT_INPUT = 1 << 0,
-
-    /**
-     * The file descriptor is available for write operations.
-     */
-    ALOOPER_EVENT_OUTPUT = 1 << 1,
-
-    /**
-     * The file descriptor has encountered an error condition.
-     *
-     * The looper always sends notifications about errors; it is not necessary
-     * to specify this event flag in the requested event set.
-     */
-    ALOOPER_EVENT_ERROR = 1 << 2,
-
-    /**
-     * The file descriptor was hung up.
-     * For example, indicates that the remote end of a pipe or socket was closed.
-     *
-     * The looper always sends notifications about hangups; it is not necessary
-     * to specify this event flag in the requested event set.
-     */
-    ALOOPER_EVENT_HANGUP = 1 << 3,
-
-    /**
-     * The file descriptor is invalid.
-     * For example, the file descriptor was closed prematurely.
-     *
-     * The looper always sends notifications about invalid file descriptors; it is not necessary
-     * to specify this event flag in the requested event set.
-     */
-    ALOOPER_EVENT_INVALID = 1 << 4,
-};
-
-/**
- * For callback-based event loops, this is the prototype of the function
- * that is called when a file descriptor event occurs.
- * It is given the file descriptor it is associated with,
- * a bitmask of the poll events that were triggered (typically ALOOPER_EVENT_INPUT),
- * and the data pointer that was originally supplied.
- *
- * Implementations should return 1 to continue receiving callbacks, or 0
- * to have this file descriptor and callback unregistered from the looper.
- */
-typedef int (*ALooper_callbackFunc)(int fd, int events, void* data);
-
-/**
- * Waits for events to be available, with optional timeout in milliseconds.
- * Invokes callbacks for all file descriptors on which an event occurred.
- *
- * If the timeout is zero, returns immediately without blocking.
- * If the timeout is negative, waits indefinitely until an event appears.
- *
- * Returns ALOOPER_POLL_WAKE if the poll was awoken using wake() before
- * the timeout expired and no callbacks were invoked and no other file
- * descriptors were ready.
- *
- * Returns ALOOPER_POLL_CALLBACK if one or more callbacks were invoked.
- *
- * Returns ALOOPER_POLL_TIMEOUT if there was no data before the given
- * timeout expired.
- *
- * Returns ALOOPER_POLL_ERROR if an error occurred.
- *
- * Returns a value >= 0 containing an identifier if its file descriptor has data
- * and it has no callback function (requiring the caller here to handle it).
- * In this (and only this) case outFd, outEvents and outData will contain the poll
- * events and data associated with the fd, otherwise they will be set to NULL.
- *
- * This method does not return until it has finished invoking the appropriate callbacks
- * for all file descriptors that were signalled.
- */
-int ALooper_pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);
-
-/**
- * Like ALooper_pollOnce(), but performs all pending callbacks until all
- * data has been consumed or a file descriptor is available with no callback.
- * This function will never return ALOOPER_POLL_CALLBACK.
- */
-int ALooper_pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData);
-
-/**
- * Wakes the poll asynchronously.
- *
- * This method can be called on any thread.
- * This method returns immediately.
- */
-void ALooper_wake(ALooper* looper);
-
-/**
- * Adds a new file descriptor to be polled by the looper.
- * If the same file descriptor was previously added, it is replaced.
- *
- * "fd" is the file descriptor to be added.
- * "ident" is an identifier for this event, which is returned from ALooper_pollOnce().
- * The identifier must be >= 0, or ALOOPER_POLL_CALLBACK if providing a non-NULL callback.
- * "events" are the poll events to wake up on.  Typically this is ALOOPER_EVENT_INPUT.
- * "callback" is the function to call when there is an event on the file descriptor.
- * "data" is a private data pointer to supply to the callback.
- *
- * There are two main uses of this function:
- *
- * (1) If "callback" is non-NULL, then this function will be called when there is
- * data on the file descriptor.  It should execute any events it has pending,
- * appropriately reading from the file descriptor.  The 'ident' is ignored in this case.
- *
- * (2) If "callback" is NULL, the 'ident' will be returned by ALooper_pollOnce
- * when its file descriptor has data available, requiring the caller to take
- * care of processing it.
- *
- * Returns 1 if the file descriptor was added or -1 if an error occurred.
- *
- * This method can be called on any thread.
- * This method may block briefly if it needs to wake the poll.
- */
-int ALooper_addFd(ALooper* looper, int fd, int ident, int events,
-        ALooper_callbackFunc callback, void* data);
-
-/**
- * Removes a previously added file descriptor from the looper.
- *
- * When this method returns, it is safe to close the file descriptor since the looper
- * will no longer have a reference to it.  However, it is possible for the callback to
- * already be running or for it to run one last time if the file descriptor was already
- * signalled.  Calling code is responsible for ensuring that this case is safely handled.
- * For example, if the callback takes care of removing itself during its own execution either
- * by returning 0 or by calling this method, then it can be guaranteed to not be invoked
- * again at any later time unless registered anew.
- *
- * Returns 1 if the file descriptor was removed, 0 if none was previously registered
- * or -1 if an error occurred.
- *
- * This method can be called on any thread.
- * This method may block briefly if it needs to wake the poll.
- */
-int ALooper_removeFd(ALooper* looper, int fd);
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif // ANDROID_NATIVE_WINDOW_H
diff --git a/native/include/android/native_activity.h b/native/include/android/native_activity.h
deleted file mode 100644
index bc70f88..0000000
--- a/native/include/android/native_activity.h
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#ifndef ANDROID_NATIVE_ACTIVITY_H
-#define ANDROID_NATIVE_ACTIVITY_H
-
-#include <stdint.h>
-#include <sys/types.h>
-
-#include <jni.h>
-
-#include <android/asset_manager.h>
-#include <android/input.h>
-#include <android/native_window.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct ANativeActivityCallbacks;
-
-/**
- * This structure defines the native side of an android.app.NativeActivity.
- * It is created by the framework, and handed to the application's native
- * code as it is being launched.
- */
-typedef struct ANativeActivity {
-    /**
-     * Pointer to the callback function table of the native application.
-     * You can set the functions here to your own callbacks.  The callbacks
-     * pointer itself here should not be changed; it is allocated and managed
-     * for you by the framework.
-     */
-    struct ANativeActivityCallbacks* callbacks;
-
-    /**
-     * The global handle on the process's Java VM.
-     */
-    JavaVM* vm;
-
-    /**
-     * JNI context for the main thread of the app.  Note that this field
-     * can ONLY be used from the main thread of the process; that is, the
-     * thread that calls into the ANativeActivityCallbacks.
-     */
-    JNIEnv* env;
-
-    /**
-     * The NativeActivity object handle.
-     *
-     * IMPORTANT NOTE: This member is mis-named. It should really be named
-     * 'activity' instead of 'clazz', since it's a reference to the
-     * NativeActivity instance created by the system for you.
-     *
-     * We unfortunately cannot change this without breaking NDK
-     * source-compatibility.
-     */
-    jobject clazz;
-
-    /**
-     * Path to this application's internal data directory.
-     */
-    const char* internalDataPath;
-    
-    /**
-     * Path to this application's external (removable/mountable) data directory.
-     */
-    const char* externalDataPath;
-    
-    /**
-     * The platform's SDK version code.
-     */
-    int32_t sdkVersion;
-    
-    /**
-     * This is the native instance of the application.  It is not used by
-     * the framework, but can be set by the application to its own instance
-     * state.
-     */
-    void* instance;
-
-    /**
-     * Pointer to the Asset Manager instance for the application.  The application
-     * uses this to access binary assets bundled inside its own .apk file.
-     */
-    AAssetManager* assetManager;
-
-    /**
-     * Available starting with Honeycomb: path to the directory containing
-     * the application's OBB files (if any).  If the app doesn't have any
-     * OBB files, this directory may not exist.
-     */
-    const char* obbPath;
-} ANativeActivity;
-
-/**
- * These are the callbacks the framework makes into a native application.
- * All of these callbacks happen on the main thread of the application.
- * By default, all callbacks are NULL; set to a pointer to your own function
- * to have it called.
- */
-typedef struct ANativeActivityCallbacks {
-    /**
-     * NativeActivity has started.  See Java documentation for Activity.onStart()
-     * for more information.
-     */
-    void (*onStart)(ANativeActivity* activity);
-    
-    /**
-     * NativeActivity has resumed.  See Java documentation for Activity.onResume()
-     * for more information.
-     */
-    void (*onResume)(ANativeActivity* activity);
-    
-    /**
-     * Framework is asking NativeActivity to save its current instance state.
-     * See Java documentation for Activity.onSaveInstanceState() for more
-     * information.  The returned pointer needs to be created with malloc();
-     * the framework will call free() on it for you.  You also must fill in
-     * outSize with the number of bytes in the allocation.  Note that the
-     * saved state will be persisted, so it can not contain any active
-     * entities (pointers to memory, file descriptors, etc).
-     */
-    void* (*onSaveInstanceState)(ANativeActivity* activity, size_t* outSize);
-    
-    /**
-     * NativeActivity has paused.  See Java documentation for Activity.onPause()
-     * for more information.
-     */
-    void (*onPause)(ANativeActivity* activity);
-    
-    /**
-     * NativeActivity has stopped.  See Java documentation for Activity.onStop()
-     * for more information.
-     */
-    void (*onStop)(ANativeActivity* activity);
-    
-    /**
-     * NativeActivity is being destroyed.  See Java documentation for Activity.onDestroy()
-     * for more information.
-     */
-    void (*onDestroy)(ANativeActivity* activity);
-
-    /**
-     * Focus has changed in this NativeActivity's window.  This is often used,
-     * for example, to pause a game when it loses input focus.
-     */
-    void (*onWindowFocusChanged)(ANativeActivity* activity, int hasFocus);
-    
-    /**
-     * The drawing window for this native activity has been created.  You
-     * can use the given native window object to start drawing.
-     */
-    void (*onNativeWindowCreated)(ANativeActivity* activity, ANativeWindow* window);
-
-    /**
-     * The drawing window for this native activity has been resized.  You should
-     * retrieve the new size from the window and ensure that your rendering in
-     * it now matches.
-     */
-    void (*onNativeWindowResized)(ANativeActivity* activity, ANativeWindow* window);
-
-    /**
-     * The drawing window for this native activity needs to be redrawn.  To avoid
-     * transient artifacts during screen changes (such resizing after rotation),
-     * applications should not return from this function until they have finished
-     * drawing their window in its current state.
-     */
-    void (*onNativeWindowRedrawNeeded)(ANativeActivity* activity, ANativeWindow* window);
-
-    /**
-     * The drawing window for this native activity is going to be destroyed.
-     * You MUST ensure that you do not touch the window object after returning
-     * from this function: in the common case of drawing to the window from
-     * another thread, that means the implementation of this callback must
-     * properly synchronize with the other thread to stop its drawing before
-     * returning from here.
-     */
-    void (*onNativeWindowDestroyed)(ANativeActivity* activity, ANativeWindow* window);
-    
-    /**
-     * The input queue for this native activity's window has been created.
-     * You can use the given input queue to start retrieving input events.
-     */
-    void (*onInputQueueCreated)(ANativeActivity* activity, AInputQueue* queue);
-    
-    /**
-     * The input queue for this native activity's window is being destroyed.
-     * You should no longer try to reference this object upon returning from this
-     * function.
-     */
-    void (*onInputQueueDestroyed)(ANativeActivity* activity, AInputQueue* queue);
-
-    /**
-     * The rectangle in the window in which content should be placed has changed.
-     */
-    void (*onContentRectChanged)(ANativeActivity* activity, const ARect* rect);
-
-    /**
-     * The current device AConfiguration has changed.  The new configuration can
-     * be retrieved from assetManager.
-     */
-    void (*onConfigurationChanged)(ANativeActivity* activity);
-
-    /**
-     * The system is running low on memory.  Use this callback to release
-     * resources you do not need, to help the system avoid killing more
-     * important processes.
-     */
-    void (*onLowMemory)(ANativeActivity* activity);
-} ANativeActivityCallbacks;
-
-/**
- * This is the function that must be in the native code to instantiate the
- * application's native activity.  It is called with the activity instance (see
- * above); if the code is being instantiated from a previously saved instance,
- * the savedState will be non-NULL and point to the saved data.  You must make
- * any copy of this data you need -- it will be released after you return from
- * this function.
- */
-typedef void ANativeActivity_createFunc(ANativeActivity* activity,
-        void* savedState, size_t savedStateSize);
-
-/**
- * The name of the function that NativeInstance looks for when launching its
- * native code.  This is the default function that is used, you can specify
- * "android.app.func_name" string meta-data in your manifest to use a different
- * function.
- */
-extern ANativeActivity_createFunc ANativeActivity_onCreate;
-
-/**
- * Finish the given activity.  Its finish() method will be called, causing it
- * to be stopped and destroyed.  Note that this method can be called from
- * *any* thread; it will send a message to the main thread of the process
- * where the Java finish call will take place.
- */
-void ANativeActivity_finish(ANativeActivity* activity);
-
-/**
- * Change the window format of the given activity.  Calls getWindow().setFormat()
- * of the given activity.  Note that this method can be called from
- * *any* thread; it will send a message to the main thread of the process
- * where the Java finish call will take place.
- */
-void ANativeActivity_setWindowFormat(ANativeActivity* activity, int32_t format);
-
-/**
- * Change the window flags of the given activity.  Calls getWindow().setFlags()
- * of the given activity.  Note that this method can be called from
- * *any* thread; it will send a message to the main thread of the process
- * where the Java finish call will take place.  See window.h for flag constants.
- */
-void ANativeActivity_setWindowFlags(ANativeActivity* activity,
-        uint32_t addFlags, uint32_t removeFlags);
-
-/**
- * Flags for ANativeActivity_showSoftInput; see the Java InputMethodManager
- * API for documentation.
- */
-enum {
-    ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT = 0x0001,
-    ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED = 0x0002,
-};
-
-/**
- * Show the IME while in the given activity.  Calls InputMethodManager.showSoftInput()
- * for the given activity.  Note that this method can be called from
- * *any* thread; it will send a message to the main thread of the process
- * where the Java finish call will take place.
- */
-void ANativeActivity_showSoftInput(ANativeActivity* activity, uint32_t flags);
-
-/**
- * Flags for ANativeActivity_hideSoftInput; see the Java InputMethodManager
- * API for documentation.
- */
-enum {
-    ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY = 0x0001,
-    ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS = 0x0002,
-};
-
-/**
- * Hide the IME while in the given activity.  Calls InputMethodManager.hideSoftInput()
- * for the given activity.  Note that this method can be called from
- * *any* thread; it will send a message to the main thread of the process
- * where the Java finish call will take place.
- */
-void ANativeActivity_hideSoftInput(ANativeActivity* activity, uint32_t flags);
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif // ANDROID_NATIVE_ACTIVITY_H
-
diff --git a/native/include/android/native_window.h b/native/include/android/native_window.h
deleted file mode 100644
index 2f4f2d3..0000000
--- a/native/include/android/native_window.h
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_NATIVE_WINDOW_H
-#define ANDROID_NATIVE_WINDOW_H
-
-#include <android/rect.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/*
- * Pixel formats that a window can use.
- */
-enum {
-    WINDOW_FORMAT_RGBA_8888          = 1,
-    WINDOW_FORMAT_RGBX_8888          = 2,
-    WINDOW_FORMAT_RGB_565            = 4,
-};
-
-struct ANativeWindow;
-typedef struct ANativeWindow ANativeWindow;
-
-typedef struct ANativeWindow_Buffer {
-    // The number of pixels that are show horizontally.
-    int32_t width;
-
-    // The number of pixels that are shown vertically.
-    int32_t height;
-
-    // The number of *pixels* that a line in the buffer takes in
-    // memory.  This may be >= width.
-    int32_t stride;
-
-    // The format of the buffer.  One of WINDOW_FORMAT_*
-    int32_t format;
-
-    // The actual bits.
-    void* bits;
-    
-    // Do not touch.
-    uint32_t reserved[6];
-} ANativeWindow_Buffer;
-
-/**
- * Acquire a reference on the given ANativeWindow object.  This prevents the object
- * from being deleted until the reference is removed.
- */
-void ANativeWindow_acquire(ANativeWindow* window);
-
-/**
- * Remove a reference that was previously acquired with ANativeWindow_acquire().
- */
-void ANativeWindow_release(ANativeWindow* window);
-
-/*
- * Return the current width in pixels of the window surface.  Returns a
- * negative value on error.
- */
-int32_t ANativeWindow_getWidth(ANativeWindow* window);
-
-/*
- * Return the current height in pixels of the window surface.  Returns a
- * negative value on error.
- */
-int32_t ANativeWindow_getHeight(ANativeWindow* window);
-
-/*
- * Return the current pixel format of the window surface.  Returns a
- * negative value on error.
- */
-int32_t ANativeWindow_getFormat(ANativeWindow* window);
-
-/*
- * Change the format and size of the window buffers.
- *
- * The width and height control the number of pixels in the buffers, not the
- * dimensions of the window on screen.  If these are different than the
- * window's physical size, then it buffer will be scaled to match that size
- * when compositing it to the screen.
- *
- * For all of these parameters, if 0 is supplied then the window's base
- * value will come back in force.
- *
- * width and height must be either both zero or both non-zero.
- *
- */
-int32_t ANativeWindow_setBuffersGeometry(ANativeWindow* window,
-        int32_t width, int32_t height, int32_t format);
-
-/**
- * Lock the window's next drawing surface for writing.
- * inOutDirtyBounds is used as an in/out parameter, upon entering the
- * function, it contains the dirty region, that is, the region the caller
- * intends to redraw. When the function returns, inOutDirtyBounds is updated
- * with the actual area the caller needs to redraw -- this region is often
- * extended by ANativeWindow_lock.
- */
-int32_t ANativeWindow_lock(ANativeWindow* window, ANativeWindow_Buffer* outBuffer,
-        ARect* inOutDirtyBounds);
-
-/**
- * Unlock the window's drawing surface after previously locking it,
- * posting the new buffer to the display.
- */
-int32_t ANativeWindow_unlockAndPost(ANativeWindow* window);
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif // ANDROID_NATIVE_WINDOW_H
diff --git a/native/include/android/native_window_jni.h b/native/include/android/native_window_jni.h
deleted file mode 100644
index b9e72ef..0000000
--- a/native/include/android/native_window_jni.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_NATIVE_WINDOW_JNI_H
-#define ANDROID_NATIVE_WINDOW_JNI_H
-
-#include <android/native_window.h>
-
-#include <jni.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * Return the ANativeWindow associated with a Java Surface object,
- * for interacting with it through native code.  This acquires a reference
- * on the ANativeWindow that is returned; be sure to use ANativeWindow_release()
- * when done with it so that it doesn't leak.
- */
-ANativeWindow* ANativeWindow_fromSurface(JNIEnv* env, jobject surface);
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif // ANDROID_NATIVE_WINDOW_H
diff --git a/native/include/android/obb.h b/native/include/android/obb.h
deleted file mode 100644
index 65e9b2a..0000000
--- a/native/include/android/obb.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#ifndef ANDROID_OBB_H
-#define ANDROID_OBB_H
-
-#include <sys/types.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct AObbInfo;
-typedef struct AObbInfo AObbInfo;
-
-enum {
-    AOBBINFO_OVERLAY = 0x0001,
-};
-
-/**
- * Scan an OBB and get information about it.
- */
-AObbInfo* AObbScanner_getObbInfo(const char* filename);
-
-/**
- * Destroy the AObbInfo object. You must call this when finished with the object.
- */
-void AObbInfo_delete(AObbInfo* obbInfo);
-
-/**
- * Get the package name for the OBB.
- */
-const char* AObbInfo_getPackageName(AObbInfo* obbInfo);
-
-/**
- * Get the version of an OBB file.
- */
-int32_t AObbInfo_getVersion(AObbInfo* obbInfo);
-
-/**
- * Get the flags of an OBB file.
- */
-int32_t AObbInfo_getFlags(AObbInfo* obbInfo);
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif      // ANDROID_OBB_H
diff --git a/native/include/android/sensor.h b/native/include/android/sensor.h
deleted file mode 100644
index f163f18..0000000
--- a/native/include/android/sensor.h
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#ifndef ANDROID_SENSOR_H
-#define ANDROID_SENSOR_H
-
-/******************************************************************
- *
- * IMPORTANT NOTICE:
- *
- *   This file is part of Android's set of stable system headers
- *   exposed by the Android NDK (Native Development Kit).
- *
- *   Third-party source AND binary code relies on the definitions
- *   here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES.
- *
- *   - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES)
- *   - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS
- *   - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY
- *   - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
- */
-
-/*
- * Structures and functions to receive and process sensor events in
- * native code.
- *
- */
-
-#include <sys/types.h>
-
-#include <android/looper.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-
-/*
- * Sensor types
- * (keep in sync with hardware/sensor.h)
- */
-
-enum {
-    ASENSOR_TYPE_ACCELEROMETER      = 1,
-    ASENSOR_TYPE_MAGNETIC_FIELD     = 2,
-    ASENSOR_TYPE_GYROSCOPE          = 4,
-    ASENSOR_TYPE_LIGHT              = 5,
-    ASENSOR_TYPE_PROXIMITY          = 8
-};
-
-/*
- * Sensor accuracy measure
- */
-enum {
-    ASENSOR_STATUS_UNRELIABLE       = 0,
-    ASENSOR_STATUS_ACCURACY_LOW     = 1,
-    ASENSOR_STATUS_ACCURACY_MEDIUM  = 2,
-    ASENSOR_STATUS_ACCURACY_HIGH    = 3
-};
-
-/*
- * A few useful constants
- */
-
-/* Earth's gravity in m/s^2 */
-#define ASENSOR_STANDARD_GRAVITY            (9.80665f)
-/* Maximum magnetic field on Earth's surface in uT */
-#define ASENSOR_MAGNETIC_FIELD_EARTH_MAX    (60.0f)
-/* Minimum magnetic field on Earth's surface in uT*/
-#define ASENSOR_MAGNETIC_FIELD_EARTH_MIN    (30.0f)
-
-/*
- * A sensor event.
- */
-
-/* NOTE: Must match hardware/sensors.h */
-typedef struct ASensorVector {
-    union {
-        float v[3];
-        struct {
-            float x;
-            float y;
-            float z;
-        };
-        struct {
-            float azimuth;
-            float pitch;
-            float roll;
-        };
-    };
-    int8_t status;
-    uint8_t reserved[3];
-} ASensorVector;
-
-/* NOTE: Must match hardware/sensors.h */
-typedef struct ASensorEvent {
-    int32_t version; /* sizeof(struct ASensorEvent) */
-    int32_t sensor;
-    int32_t type;
-    int32_t reserved0;
-    int64_t timestamp;
-    union {
-        float           data[16];
-        ASensorVector   vector;
-        ASensorVector   acceleration;
-        ASensorVector   magnetic;
-        float           temperature;
-        float           distance;
-        float           light;
-        float           pressure;
-    };
-    int32_t reserved1[4];
-} ASensorEvent;
-
-
-struct ASensorManager;
-typedef struct ASensorManager ASensorManager;
-
-struct ASensorEventQueue;
-typedef struct ASensorEventQueue ASensorEventQueue;
-
-struct ASensor;
-typedef struct ASensor ASensor;
-typedef ASensor const* ASensorRef;
-typedef ASensorRef const* ASensorList;
-
-/*****************************************************************************/
-
-/*
- * Get a reference to the sensor manager. ASensorManager is a singleton.
- *
- * Example:
- *
- *     ASensorManager* sensorManager = ASensorManager_getInstance();
- *
- */
-ASensorManager* ASensorManager_getInstance();
-
-
-/*
- * Returns the list of available sensors.
- */
-int ASensorManager_getSensorList(ASensorManager* manager, ASensorList* list);
-
-/*
- * Returns the default sensor for the given type, or NULL if no sensor
- * of that type exist.
- */
-ASensor const* ASensorManager_getDefaultSensor(ASensorManager* manager, int type);
-
-/*
- * Creates a new sensor event queue and associate it with a looper.
- */
-ASensorEventQueue* ASensorManager_createEventQueue(ASensorManager* manager,
-        ALooper* looper, int ident, ALooper_callbackFunc callback, void* data);
-
-/*
- * Destroys the event queue and free all resources associated to it.
- */
-int ASensorManager_destroyEventQueue(ASensorManager* manager, ASensorEventQueue* queue);
-
-
-/*****************************************************************************/
-
-/*
- * Enable the selected sensor. Returns a negative error code on failure.
- */
-int ASensorEventQueue_enableSensor(ASensorEventQueue* queue, ASensor const* sensor);
-
-/*
- * Disable the selected sensor. Returns a negative error code on failure.
- */
-int ASensorEventQueue_disableSensor(ASensorEventQueue* queue, ASensor const* sensor);
-
-/*
- * Sets the delivery rate of events in microseconds for the given sensor.
- * Note that this is a hint only, generally event will arrive at a higher
- * rate. It is an error to set a rate inferior to the value returned by
- * ASensor_getMinDelay().
- * Returns a negative error code on failure.
- */
-int ASensorEventQueue_setEventRate(ASensorEventQueue* queue, ASensor const* sensor, int32_t usec);
-
-/*
- * Returns true if there are one or more events available in the
- * sensor queue.  Returns 1 if the queue has events; 0 if
- * it does not have events; and a negative value if there is an error.
- */
-int ASensorEventQueue_hasEvents(ASensorEventQueue* queue);
-
-/*
- * Returns the next available events from the queue.  Returns a negative
- * value if no events are available or an error has occurred, otherwise
- * the number of events returned.
- *
- * Examples:
- *   ASensorEvent event;
- *   ssize_t numEvent = ASensorEventQueue_getEvents(queue, &event, 1);
- *
- *   ASensorEvent eventBuffer[8];
- *   ssize_t numEvent = ASensorEventQueue_getEvents(queue, eventBuffer, 8);
- *
- */
-ssize_t ASensorEventQueue_getEvents(ASensorEventQueue* queue,
-                ASensorEvent* events, size_t count);
-
-
-/*****************************************************************************/
-
-/*
- * Returns this sensor's name (non localized)
- */
-const char* ASensor_getName(ASensor const* sensor);
-
-/*
- * Returns this sensor's vendor's name (non localized)
- */
-const char* ASensor_getVendor(ASensor const* sensor);
-
-/*
- * Return this sensor's type
- */
-int ASensor_getType(ASensor const* sensor);
-
-/*
- * Returns this sensors's resolution
- */
-float ASensor_getResolution(ASensor const* sensor);
-
-/*
- * Returns the minimum delay allowed between events in microseconds.
- * A value of zero means that this sensor doesn't report events at a
- * constant rate, but rather only when a new data is available.
- */
-int ASensor_getMinDelay(ASensor const* sensor);
-
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif // ANDROID_SENSOR_H
diff --git a/native/include/android/storage_manager.h b/native/include/android/storage_manager.h
deleted file mode 100644
index bad24913..0000000
--- a/native/include/android/storage_manager.h
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#ifndef ANDROID_STORAGE_MANAGER_H
-#define ANDROID_STORAGE_MANAGER_H
-
-#include <stdint.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct AStorageManager;
-typedef struct AStorageManager AStorageManager;
-
-enum {
-    /*
-     * The OBB container is now mounted and ready for use. Can be returned
-     * as the status for callbacks made during asynchronous OBB actions.
-     */
-    AOBB_STATE_MOUNTED = 1,
-
-    /*
-     * The OBB container is now unmounted and not usable. Can be returned
-     * as the status for callbacks made during asynchronous OBB actions.
-     */
-    AOBB_STATE_UNMOUNTED = 2,
-
-    /*
-     * There was an internal system error encountered while trying to
-     * mount the OBB. Can be returned as the status for callbacks made
-     * during asynchronous OBB actions.
-     */
-    AOBB_STATE_ERROR_INTERNAL = 20,
-
-    /*
-     * The OBB could not be mounted by the system. Can be returned as the
-     * status for callbacks made during asynchronous OBB actions.
-     */
-    AOBB_STATE_ERROR_COULD_NOT_MOUNT = 21,
-
-    /*
-     * The OBB could not be unmounted. This most likely indicates that a
-     * file is in use on the OBB. Can be returned as the status for
-     * callbacks made during asynchronous OBB actions.
-     */
-    AOBB_STATE_ERROR_COULD_NOT_UNMOUNT = 22,
-
-    /*
-     * A call was made to unmount the OBB when it was not mounted. Can be
-     * returned as the status for callbacks made during asynchronous OBB
-     * actions.
-     */
-    AOBB_STATE_ERROR_NOT_MOUNTED = 23,
-
-    /*
-     * The OBB has already been mounted. Can be returned as the status for
-     * callbacks made during asynchronous OBB actions.
-     */
-    AOBB_STATE_ERROR_ALREADY_MOUNTED = 24,
-
-    /*
-     * The current application does not have permission to use this OBB.
-     * This could be because the OBB indicates it's owned by a different
-     * package. Can be returned as the status for callbacks made during
-     * asynchronous OBB actions.
-     */
-    AOBB_STATE_ERROR_PERMISSION_DENIED = 25,
-};
-
-/**
- * Obtains a new instance of AStorageManager.
- */
-AStorageManager* AStorageManager_new();
-
-/**
- * Release AStorageManager instance.
- */
-void AStorageManager_delete(AStorageManager* mgr);
-
-/**
- * Callback function for asynchronous calls made on OBB files.
- */
-typedef void (*AStorageManager_obbCallbackFunc)(const char* filename, const int32_t state, void* data);
-
-/**
- * Attempts to mount an OBB file. This is an asynchronous operation.
- */
-void AStorageManager_mountObb(AStorageManager* mgr, const char* filename, const char* key,
-        AStorageManager_obbCallbackFunc cb, void* data);
-
-/**
- * Attempts to unmount an OBB file. This is an asynchronous operation.
- */
-void AStorageManager_unmountObb(AStorageManager* mgr, const char* filename, const int force,
-        AStorageManager_obbCallbackFunc cb, void* data);
-
-/**
- * Check whether an OBB is mounted.
- */
-int AStorageManager_isObbMounted(AStorageManager* mgr, const char* filename);
-
-/**
- * Get the mounted path for an OBB.
- */
-const char* AStorageManager_getMountedObbPath(AStorageManager* mgr, const char* filename);
-
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif      // ANDROID_STORAGE_MANAGER_H
diff --git a/native/include/android/window.h b/native/include/android/window.h
deleted file mode 100644
index 2ab192b..0000000
--- a/native/include/android/window.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#ifndef ANDROID_WINDOW_H
-#define ANDROID_WINDOW_H
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * Window flags, as per the Java API at android.view.WindowManager.LayoutParams.
- */
-enum {
-    AWINDOW_FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001,
-    AWINDOW_FLAG_DIM_BEHIND                 = 0x00000002,
-    AWINDOW_FLAG_BLUR_BEHIND                = 0x00000004,
-    AWINDOW_FLAG_NOT_FOCUSABLE              = 0x00000008,
-    AWINDOW_FLAG_NOT_TOUCHABLE              = 0x00000010,
-    AWINDOW_FLAG_NOT_TOUCH_MODAL            = 0x00000020,
-    AWINDOW_FLAG_TOUCHABLE_WHEN_WAKING      = 0x00000040,
-    AWINDOW_FLAG_KEEP_SCREEN_ON             = 0x00000080,
-    AWINDOW_FLAG_LAYOUT_IN_SCREEN           = 0x00000100,
-    AWINDOW_FLAG_LAYOUT_NO_LIMITS           = 0x00000200,
-    AWINDOW_FLAG_FULLSCREEN                 = 0x00000400,
-    AWINDOW_FLAG_FORCE_NOT_FULLSCREEN       = 0x00000800,
-    AWINDOW_FLAG_DITHER                     = 0x00001000,
-    AWINDOW_FLAG_SECURE                     = 0x00002000,
-    AWINDOW_FLAG_SCALED                     = 0x00004000,
-    AWINDOW_FLAG_IGNORE_CHEEK_PRESSES       = 0x00008000,
-    AWINDOW_FLAG_LAYOUT_INSET_DECOR         = 0x00010000,
-    AWINDOW_FLAG_ALT_FOCUSABLE_IM           = 0x00020000,
-    AWINDOW_FLAG_WATCH_OUTSIDE_TOUCH        = 0x00040000,
-    AWINDOW_FLAG_SHOW_WHEN_LOCKED           = 0x00080000,
-    AWINDOW_FLAG_SHOW_WALLPAPER             = 0x00100000,
-    AWINDOW_FLAG_TURN_SCREEN_ON             = 0x00200000,
-    AWINDOW_FLAG_DISMISS_KEYGUARD           = 0x00400000,
-};
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif // ANDROID_WINDOW_H
diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java
index 8e2294c..f69fc53 100644
--- a/opengl/java/android/opengl/GLSurfaceView.java
+++ b/opengl/java/android/opengl/GLSurfaceView.java
@@ -1145,11 +1145,15 @@
                 switch(error) {
                 case EGL11.EGL_CONTEXT_LOST:
                     return false;
+                case EGL10.EGL_BAD_CURRENT_SURFACE:
+                    // The current surface is bad, probably because the window manager has closed
+                    // the associated window. Ignore this error, on the assumption that the
+                    // application will be closed soon.
+                    break;
                 case EGL10.EGL_BAD_NATIVE_WINDOW:
-                    // The native window is bad, probably because the
-                    // window manager has closed it. Ignore this error,
-                    // on the expectation that the application will be closed soon.
-                    Log.e("EglHelper", "eglSwapBuffers returned EGL_BAD_NATIVE_WINDOW. tid=" + Thread.currentThread().getId());
+                    // The native window is bad, probably because the window manager has closed it.
+                    // Ignore this error, on the assumption that the application will be closed
+                    // soon.
                     break;
                 default:
                     throwEglException("eglSwapBuffers", error);
@@ -1841,7 +1845,7 @@
                         ! renderer.startsWith(kMSM7K_RENDERER_PREFIX);
                     notifyAll();
                 }
-                mLimitedGLESContexts = !mMultipleGLESContextsAllowed || renderer.startsWith(kADRENO);
+                mLimitedGLESContexts = !mMultipleGLESContextsAllowed;
                 if (LOG_SURFACE) {
                     Log.w(TAG, "checkGLDriver renderer = \"" + renderer + "\" multipleContextsAllowed = "
                         + mMultipleGLESContextsAllowed
@@ -1867,6 +1871,11 @@
             }
         }
 
+        /**
+         * This check was required for some pre-Android-3.0 hardware. Android 3.0 provides
+         * support for hardware-accelerated views, therefore multiple EGL contexts are
+         * supported on all Android 3.0+ EGL drivers.
+         */
         private boolean mGLESVersionCheckComplete;
         private int mGLESVersion;
         private boolean mGLESDriverCheckComplete;
@@ -1875,7 +1884,6 @@
         private static final int kGLES_20 = 0x20000;
         private static final String kMSM7K_RENDERER_PREFIX =
             "Q3Dimension MSM7500 ";
-        private static final String kADRENO = "Adreno";
         private GLThread mEglOwner;
     }
 
diff --git a/opengl/java/com/google/android/gles_jni/GLImpl.java b/opengl/java/com/google/android/gles_jni/GLImpl.java
index 090c0cb7..07f9e91 100644
--- a/opengl/java/com/google/android/gles_jni/GLImpl.java
+++ b/opengl/java/com/google/android/gles_jni/GLImpl.java
@@ -23,6 +23,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.os.Build;
+import android.os.UserId;
 import android.util.Log;
 
 import java.nio.Buffer;
@@ -67,7 +68,7 @@
         int version = 0;
         IPackageManager pm = AppGlobals.getPackageManager();
         try {
-            ApplicationInfo applicationInfo = pm.getApplicationInfo(appName, 0);
+            ApplicationInfo applicationInfo = pm.getApplicationInfo(appName, 0, UserId.myUserId());
             if (applicationInfo != null) {
                 version = applicationInfo.targetSdkVersion;
             }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 330a189..abf713b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -84,6 +84,7 @@
     public DatabaseHelper(Context context) {
         super(context, DATABASE_NAME, null, DATABASE_VERSION);
         mContext = context;
+        setWriteAheadLoggingEnabled(true);
     }
 
     public static boolean isValidTable(String name) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 95fd62d..1fa3695 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -260,7 +260,6 @@
         // Watch for external modifications to the database file,
         // keeping our cache in sync.
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
-        db.enableWriteAheadLogging();
         sObserverInstance = new SettingsFileObserver(db.getPath());
         sObserverInstance.startWatching();
         startAsyncCachePopulation();
diff --git a/data/etc/android.hardware.sensor.barometer.xml b/packages/SystemUI/res/drawable/intruder_row_bg.xml
similarity index 66%
rename from data/etc/android.hardware.sensor.barometer.xml
rename to packages/SystemUI/res/drawable/intruder_row_bg.xml
index ebd392d..1c7c9c4 100644
--- a/data/etc/android.hardware.sensor.barometer.xml
+++ b/packages/SystemUI/res/drawable/intruder_row_bg.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
+<!-- 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.
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<!-- Feature for devices with barometer. -->
-<permissions>
-    <feature name="android.hardware.sensor.barometer" />
-</permissions>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:exitFadeDuration="@android:integer/config_mediumAnimTime">
+    <item android:state_pressed="true"  android:drawable="@drawable/intruder_bg_pressed" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/intruder_window_bg.9.png b/packages/SystemUI/res/drawable/intruder_window_bg.9.png
new file mode 100644
index 0000000..caad169
--- /dev/null
+++ b/packages/SystemUI/res/drawable/intruder_window_bg.9.png
Binary files differ
diff --git a/packages/SystemUI/res/layout/intruder_alert.xml b/packages/SystemUI/res/layout/intruder_alert.xml
index ba4a774..c4141ae 100644
--- a/packages/SystemUI/res/layout/intruder_alert.xml
+++ b/packages/SystemUI/res/layout/intruder_alert.xml
@@ -19,40 +19,22 @@
 -->
 
 <!--    android:background="@drawable/status_bar_closed_default_background" -->
-<FrameLayout
+<com.android.systemui.statusbar.policy.IntruderAlertView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_height="32dip"
+    android:layout_height="wrap_content"
     android:layout_width="match_parent"
-    android:paddingLeft="8dip"
-    android:paddingRight="8dip"
+    android:orientation="vertical"
     >
-        
-    <LinearLayout 
-        android:id="@+id/intruder_alert_content"
+    <FrameLayout
+        android:layout_height="wrap_content"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:gravity="center"
-        android:animationCache="false"
-        android:orientation="horizontal"
-        android:background="@drawable/alert_bar_background"
-        android:clickable="true"
-        android:focusable="true"
-        android:descendantFocusability="afterDescendants"
-        >
-
-        <ImageView
-            android:id="@+id/alertIcon"
-            android:layout_width="25dip"
-            android:layout_height="25dip"
-            android:layout_marginLeft="6dip"
-            android:layout_marginRight="8dip"
-            />
-        <TextView
-            android:id="@+id/alertText"
-            android:textAppearance="@style/TextAppearance.StatusBar.IntruderAlert"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:singleLine="true"
-            />
-    </LinearLayout>
-</FrameLayout>
+        android:id="@+id/contentHolder"
+        android:background="@drawable/intruder_window_bg"
+        />
+<!--    <ImageView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:src="@drawable/title_bar_shadow"
+        android:scaleType="fitXY"
+        /> -->
+</com.android.systemui.statusbar.policy.IntruderAlertView>
diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml
index bb80098..8fbab74 100644
--- a/packages/SystemUI/res/layout/navigation_bar.xml
+++ b/packages/SystemUI/res/layout/navigation_bar.xml
@@ -144,11 +144,11 @@
                 />
         </LinearLayout>
 
-        <View android:id="@+id/deadzone"
+        <com.android.systemui.statusbar.policy.DeadZone
+            android:id="@+id/deadzone"
             android:layout_height="@dimen/navigation_bar_deadzone_size"
             android:layout_width="match_parent"
             android:layout_gravity="top"
-            android:clickable="true"
             />
     </FrameLayout>
 
@@ -272,11 +272,11 @@
                 />
         </LinearLayout>
 
-        <View android:id="@+id/deadzone"
+        <com.android.systemui.statusbar.policy.DeadZone
+            android:id="@+id/deadzone"
             android:layout_width="@dimen/navigation_bar_deadzone_size"
             android:layout_height="match_parent"
             android:layout_gravity="left"
-            android:clickable="true"
             />
     </FrameLayout>
 
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index e2e65b9..13aaacb 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -53,7 +53,7 @@
     <string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"መተግበሪያ <xliff:g id="APPLICATION">%1$s</xliff:g> የUSB ተቀጥላ ላይ እንዲደርስ ፍቀድ?"</string>
     <string name="usb_device_confirm_prompt" msgid="5161205258635253206">"የዚህ USB ተቀጥላ ሲያያዝ <xliff:g id="ACTIVITY">%1$s</xliff:g>ይከፈት?"</string>
     <string name="usb_accessory_confirm_prompt" msgid="3808984931830229888">"የዚህ USB ተቀጥላ ሲያያዝ <xliff:g id="ACTIVITY">%1$s</xliff:g>  ይከፈት?"</string>
-    <string name="usb_accessory_uri_prompt" msgid="513450621413733343">"ምንም የተጫኑ መተግበሪያዎች ከዚህ የUSB ተቀጥላ ጋር አይሰሩም። በ<xliff:g id="URL">%1$s</xliff:g> ስለዚህ ተቀጥላ የበለጠ እወቅ።"</string>
+    <string name="usb_accessory_uri_prompt" msgid="513450621413733343">"ምንም የተጫኑ መተግበሪያዎች ከዚህ የUSB ተቀጥላ ጋር አይሰሩም። በ<xliff:g id="URL">%1$s</xliff:g> ስለዚህ ተቀጥላ የበለጠ ለመረዳት።"</string>
     <string name="title_usb_accessory" msgid="4966265263465181372">"የUSB  ተቀጥላ"</string>
     <string name="label_view" msgid="6304565553218192990">"ዕይታ"</string>
     <string name="always_use_device" msgid="1450287437017315906">"ለዚህ USB  መሣሪያ በነባሪነት ተጠቀም"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 4d9b2cd..0f48f35 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -65,7 +65,7 @@
     <string name="screenshot_saving_ticker" msgid="7403652894056693515">"Ukládání snímku obrazovky..."</string>
     <string name="screenshot_saving_title" msgid="8242282144535555697">"Ukládání snímku obrazovky..."</string>
     <string name="screenshot_saving_text" msgid="2419718443411738818">"Probíhá ukládání snímku obrazovky."</string>
-    <string name="screenshot_saved_title" msgid="6461865960961414961">"Snímek obrazovky byl zachycen."</string>
+    <string name="screenshot_saved_title" msgid="6461865960961414961">"Snímek obrazovky zachycen."</string>
     <string name="screenshot_saved_text" msgid="1152839647677558815">"Snímek obrazovky zobrazíte dotykem."</string>
     <string name="screenshot_failed_title" msgid="705781116746922771">"Snímek obrazovky se nepodařilo zachytit."</string>
     <string name="screenshot_failed_text" msgid="8134011269572415402">"Snímek obrazovky se nepodařilo uložit. Je možné, že je externí úložiště právě používáno."</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 7e0e3c4..1f9c959 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -24,12 +24,12 @@
     <string name="status_bar_do_not_disturb_button" msgid="5812628897510997853">"请勿打扰"</string>
     <string name="status_bar_please_disturb_button" msgid="3345398298841572813">"显示通知"</string>
     <string name="status_bar_recent_remove_item_title" msgid="6026395868129852968">"从列表中删除"</string>
-    <string name="status_bar_recent_inspect_item_title" msgid="7793624864528818569">"应用程序信息"</string>
-    <string name="status_bar_no_recent_apps" msgid="6576392951053994640">"最近没有运行任何应用程序"</string>
-    <string name="status_bar_accessibility_dismiss_recents" msgid="4576076075226540105">"关闭最近运行的应用程序"</string>
+    <string name="status_bar_recent_inspect_item_title" msgid="7793624864528818569">"应用信息"</string>
+    <string name="status_bar_no_recent_apps" msgid="6576392951053994640">"最近没有运行任何应用"</string>
+    <string name="status_bar_accessibility_dismiss_recents" msgid="4576076075226540105">"关闭最近运行的应用"</string>
   <plurals name="status_bar_accessibility_recent_apps">
-    <item quantity="one" msgid="5854176083865845541">"1 个最近运行的应用程序"</item>
-    <item quantity="other" msgid="1040784359794890744">"%d 个最近运行的应用程序"</item>
+    <item quantity="one" msgid="5854176083865845541">"1 个最近运行的应用"</item>
+    <item quantity="other" msgid="1040784359794890744">"%d 个最近运行的应用"</item>
   </plurals>
     <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"无通知"</string>
     <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"正在进行的"</string>
@@ -49,11 +49,11 @@
     <string name="bluetooth_tethered" msgid="7094101612161133267">"已通过蓝牙共享网络"</string>
     <string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"设置输入法"</string>
     <string name="status_bar_use_physical_keyboard" msgid="3695516942412442936">"使用物理键盘"</string>
-    <string name="usb_device_permission_prompt" msgid="834698001271562057">"允许应用程序“<xliff:g id="APPLICATION">%1$s</xliff:g>”访问该 USB 设备吗?"</string>
-    <string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"允许应用程序“<xliff:g id="APPLICATION">%1$s</xliff:g>”访问该 USB 配件吗?"</string>
+    <string name="usb_device_permission_prompt" msgid="834698001271562057">"允许应用“<xliff:g id="APPLICATION">%1$s</xliff:g>”访问该 USB 设备吗?"</string>
+    <string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"允许应用“<xliff:g id="APPLICATION">%1$s</xliff:g>”访问该 USB 配件吗?"</string>
     <string name="usb_device_confirm_prompt" msgid="5161205258635253206">"要在连接此 USB 设备时打开<xliff:g id="ACTIVITY">%1$s</xliff:g>吗?"</string>
     <string name="usb_accessory_confirm_prompt" msgid="3808984931830229888">"要在连接此 USB 配件时打开<xliff:g id="ACTIVITY">%1$s</xliff:g>吗?"</string>
-    <string name="usb_accessory_uri_prompt" msgid="513450621413733343">"未安装此 USB 配件适用的应用程序。要了解此配件的详情,请访问:<xliff:g id="URL">%1$s</xliff:g>"</string>
+    <string name="usb_accessory_uri_prompt" msgid="513450621413733343">"未安装此 USB 配件适用的应用。要了解此配件的详情,请访问:<xliff:g id="URL">%1$s</xliff:g>"</string>
     <string name="title_usb_accessory" msgid="4966265263465181372">"USB 配件"</string>
     <string name="label_view" msgid="6304565553218192990">"查看"</string>
     <string name="always_use_device" msgid="1450287437017315906">"默认情况下用于该 USB 设备"</string>
@@ -61,7 +61,7 @@
     <string name="compat_mode_on" msgid="6623839244840638213">"缩放以填满屏幕"</string>
     <string name="compat_mode_off" msgid="4434467572461327898">"拉伸以填满屏幕"</string>
     <string name="compat_mode_help_header" msgid="7969493989397529910">"兼容性缩放"</string>
-    <string name="compat_mode_help_body" msgid="4946726776359270040">"如果应用程序是针对较小屏幕设计的,则时钟旁会显示缩放控件。"</string>
+    <string name="compat_mode_help_body" msgid="4946726776359270040">"如果应用是针对较小屏幕设计的,则时钟旁会显示缩放控件。"</string>
     <string name="screenshot_saving_ticker" msgid="7403652894056693515">"正在保存屏幕截图..."</string>
     <string name="screenshot_saving_title" msgid="8242282144535555697">"正在保存屏幕截图..."</string>
     <string name="screenshot_saving_text" msgid="2419718443411738818">"正在保存屏幕截图。"</string>
@@ -72,11 +72,11 @@
     <string name="usb_preference_title" msgid="6551050377388882787">"USB 文件传输选项"</string>
     <string name="use_mtp_button_title" msgid="4333504413563023626">"作为媒体播放器 (MTP) 装载"</string>
     <string name="use_ptp_button_title" msgid="7517127540301625751">"作为摄像头 (PTP) 装载"</string>
-    <string name="installer_cd_button_title" msgid="2312667578562201583">"安装适用于苹果机的“Android 文件传输”应用程序"</string>
+    <string name="installer_cd_button_title" msgid="2312667578562201583">"安装适用于苹果机的“Android 文件传输”应用"</string>
     <string name="accessibility_back" msgid="567011538994429120">"返回"</string>
     <string name="accessibility_home" msgid="8217216074895377641">"主屏幕"</string>
     <string name="accessibility_menu" msgid="316839303324695949">"菜单"</string>
-    <string name="accessibility_recent" msgid="8571350598987952883">"最近运行的应用程序"</string>
+    <string name="accessibility_recent" msgid="8571350598987952883">"最近运行的应用"</string>
     <string name="accessibility_ime_switch_button" msgid="5032926134740456424">"输入法切换按钮。"</string>
     <string name="accessibility_compatibility_zoom_button" msgid="8461115318742350699">"兼容性缩放按钮。"</string>
     <string name="accessibility_compatibility_zoom_example" msgid="4220687294564945780">"将小屏幕的图片放大在较大屏幕上显示。"</string>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 7f00c38..c8e3fad 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -30,4 +30,5 @@
     <color name="notification_list_shadow_top">#80000000</color>
     <drawable name="recents_callout_line">#99ffffff</drawable>
     <drawable name="notification_item_background_legacy_color">#ffaaaaaa</drawable>
+    <drawable name="intruder_bg_pressed">#ff33B5E5</drawable>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index 66cb32c..564b07b 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.LayoutTransition;
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -615,10 +616,11 @@
         if (!mFirstScreenful && tasks.size() == 0) {
             return;
         }
-        mNumItemsWaitingForThumbnailsAndIcons =
-                mFirstScreenful ? tasks.size() : mRecentTaskDescriptions.size();
+        mNumItemsWaitingForThumbnailsAndIcons = mFirstScreenful 
+                ? tasks.size() : mRecentTaskDescriptions == null 
+                        ? 0 : mRecentTaskDescriptions.size();
         if (mRecentTaskDescriptions == null) {
-            mRecentTaskDescriptions = new ArrayList(tasks);
+            mRecentTaskDescriptions = new ArrayList<TaskDescription>(tasks);
         } else {
             mRecentTaskDescriptions.addAll(tasks);
         }
@@ -656,22 +658,33 @@
     }
 
     public void handleOnClick(View view) {
-        TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription;
+        ViewHolder holder = (ViewHolder)view.getTag();
+        TaskDescription ad = holder.taskDescription;
         final Context context = view.getContext();
         final ActivityManager am = (ActivityManager)
                 context.getSystemService(Context.ACTIVITY_SERVICE);
+        holder.thumbnailViewImage.setDrawingCacheEnabled(true);
+        Bitmap bm = holder.thumbnailViewImage.getDrawingCache();
+        ActivityOptions opts = ActivityOptions.makeThumbnailScaleUpAnimation(
+                holder.thumbnailViewImage, bm, 0, 0,
+                new ActivityOptions.OnAnimationStartedListener() {
+                    @Override public void onAnimationStarted() {
+                        hide(true);
+                    }
+                });
         if (ad.taskId >= 0) {
             // This is an active task; it should just go to the foreground.
-            am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME);
+            am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME,
+                    opts.toBundle());
         } else {
             Intent intent = ad.intent;
             intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
                     | Intent.FLAG_ACTIVITY_TASK_ON_HOME
                     | Intent.FLAG_ACTIVITY_NEW_TASK);
             if (DEBUG) Log.v(TAG, "Starting activity " + intent);
-            context.startActivity(intent);
+            context.startActivity(intent, opts.toBundle());
         }
-        hide(true);
+        holder.thumbnailViewImage.setDrawingCacheEnabled(false);
     }
 
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 36d9316..3a06127 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -158,4 +158,8 @@
         }
         return vetoButton;
     }
+    
+    public void dismissIntruder() {
+        // pass
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index f721cdc..85e0a8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -56,6 +56,7 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.view.Window;
@@ -77,6 +78,7 @@
 import com.android.internal.statusbar.StatusBarNotification;
 
 import com.android.systemui.R;
+import com.android.systemui.SwipeHelper;
 import com.android.systemui.recent.RecentTasksLoader;
 import com.android.systemui.recent.RecentsPanelView;
 import com.android.systemui.recent.TaskDescription;
@@ -86,6 +88,7 @@
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.policy.DateView;
 import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.IntruderAlertView;
 import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NotificationRowLayout;
@@ -93,7 +96,7 @@
 public class PhoneStatusBar extends BaseStatusBar {
     static final String TAG = "PhoneStatusBar";
     public static final boolean DEBUG = false;
-    public static final boolean SPEW = true;
+    public static final boolean SPEW = DEBUG;
     public static final boolean DUMPTRUCK = true; // extra dumpsys info
 
     // additional instrumentation for testing purposes; intended to be left on during development
@@ -102,7 +105,7 @@
     public static final String ACTION_STATUSBAR_START
             = "com.android.internal.policy.statusbar.START";
 
-    private static final boolean ENABLE_INTRUDERS = false;
+    private static final boolean ENABLE_INTRUDERS = true;
 
     static final int EXPANDED_LEAVE_ALONE = -10000;
     static final int EXPANDED_FULL_OPEN = -10001;
@@ -117,7 +120,7 @@
     private static final int MSG_CLOSE_RECENTS_PANEL = 1021;
 
     // will likely move to a resource or other tunable param at some point
-    private static final int INTRUDER_ALERT_DECAY_MS = 10000;
+    private static final int INTRUDER_ALERT_DECAY_MS = 0; // disabled, was 10000;
 
     private static final boolean CLOSE_PANEL_WHEN_EMPTIED = true;
 
@@ -185,7 +188,7 @@
     DateView mDateView;
 
     // for immersive activities
-    private View mIntruderAlertView;
+    private IntruderAlertView mIntruderAlertView;
 
     // on-screen navigation buttons
     private NavigationBarView mNavigationBarView = null;
@@ -294,9 +297,9 @@
         }
         mNotificationPanel = expanded.findViewById(R.id.notification_panel);
 
-        mIntruderAlertView = View.inflate(context, R.layout.intruder_alert, null);
+        mIntruderAlertView = (IntruderAlertView) View.inflate(context, R.layout.intruder_alert, null);
         mIntruderAlertView.setVisibility(View.GONE);
-        mIntruderAlertView.setClickable(true);
+        mIntruderAlertView.setBar(this);
 
         PhoneStatusBarView sb = (PhoneStatusBarView)View.inflate(context,
                 R.layout.status_bar, null);
@@ -455,6 +458,7 @@
             toggleRecentApps();
         }
     };
+    private StatusBarNotification mCurrentlyIntrudingNotification;
 
     private void prepareNavigationBarView() {
         mNavigationBarView.reorient();
@@ -510,7 +514,7 @@
         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.WRAP_CONTENT,
-                WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
+                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, // above the status bar!
                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                     | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
                     | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
@@ -519,7 +523,7 @@
                     | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
                 PixelFormat.TRANSLUCENT);
         lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
-        lp.y += height * 1.5; // FIXME
+        //lp.y += height * 1.5; // FIXME
         lp.setTitle("IntruderAlert");
         lp.packageName = mContext.getPackageName();
         lp.windowAnimations = R.style.Animation_StatusBar_IntruderAlert;
@@ -562,30 +566,39 @@
         } catch (RemoteException ex) {
         }
         if (ENABLE_INTRUDERS && (
-                   (notification.score >= mIntruderInImmersiveMinScore)
-                || (!immersive && (notification.score > mIntruderMinScore)))) {
+                   // TODO(dsandler): Only if the screen is on
+                notification.notification.intruderView != null)) {
+//                   notification.notification.fullScreenIntent != null
+//                || (notification.score >= mIntruderInImmersiveMinScore)
+//                || (!immersive && (notification.score > mIntruderMinScore)))) {
             Slog.d(TAG, "Presenting high-priority notification");
             // special new transient ticker mode
             // 1. Populate mIntruderAlertView
+            
+            if (notification.notification.intruderView == null) {
+                Slog.e(TAG, notification.notification.toString() + " wanted to intrude but intruderView was null");
+                return;
+            }
 
-            ImageView alertIcon = (ImageView) mIntruderAlertView.findViewById(R.id.alertIcon);
-            TextView alertText = (TextView) mIntruderAlertView.findViewById(R.id.alertText);
-            alertIcon.setImageDrawable(StatusBarIconView.getIcon(
-                alertIcon.getContext(),
-                iconView.getStatusBarIcon()));
-            alertText.setText(notification.notification.tickerText);
+            // bind the click event to the content area
+            PendingIntent contentIntent = notification.notification.contentIntent;
+            final View.OnClickListener listener = (contentIntent != null)
+                    ? new NotificationClicker(contentIntent,
+                            notification.pkg, notification.tag, notification.id)
+                    : null;
 
-            View button = mIntruderAlertView.findViewById(R.id.intruder_alert_content);
-            button.setOnClickListener(
-                new NotificationClicker(notification.notification.contentIntent,
-                    notification.pkg, notification.tag, notification.id));
+            mIntruderAlertView.applyIntruderContent(notification.notification.intruderView, listener);
 
+            mCurrentlyIntrudingNotification = notification;
+            
             // 2. Animate mIntruderAlertView in
             mHandler.sendEmptyMessage(MSG_SHOW_INTRUDER);
 
             // 3. Set alarm to age the notification off (TODO)
             mHandler.removeMessages(MSG_HIDE_INTRUDER);
-            mHandler.sendEmptyMessageDelayed(MSG_HIDE_INTRUDER, INTRUDER_ALERT_DECAY_MS);
+            if (INTRUDER_ALERT_DECAY_MS > 0) {
+                mHandler.sendEmptyMessageDelayed(MSG_HIDE_INTRUDER, INTRUDER_ALERT_DECAY_MS);
+            }
         } else if (notification.notification.fullScreenIntent != null) {
             // not immersive & a full-screen alert should be shown
             Slog.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
@@ -596,8 +609,10 @@
         } else {
             // usual case: status bar visible & not immersive
 
-            // show the ticker
-            tick(notification);
+            // show the ticker if there isn't an intruder too
+            if (mCurrentlyIntrudingNotification == null) {
+                tick(notification);
+            }
         }
 
         // Recalculate the position of the sliding windows and the titles.
@@ -708,11 +723,22 @@
         // Recalculate the position of the sliding windows and the titles.
         setAreThereNotifications();
         updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
+
+        // See if we need to update the intruder.
+        if (oldNotification == mCurrentlyIntrudingNotification) {
+            if (DEBUG) Slog.d(TAG, "updating the current intruder:" + notification);
+            // XXX: this is a hack for Alarms. The real implementation will need to *update* 
+            // the intruder.
+            if (notification.notification.fullScreenIntent == null) { // TODO(dsandler): consistent logic with add()
+                if (DEBUG) Slog.d(TAG, "no longer intrudes!");
+                mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
+            }
+        }
     }
 
     public void removeNotification(IBinder key) {
-        if (SPEW) Slog.d(TAG, "removeNotification key=" + key);
         StatusBarNotification old = removeNotificationViews(key);
+        if (SPEW) Slog.d(TAG, "removeNotification key=" + key + " old=" + old);
 
         if (old != null) {
             // Cancel the ticker if it's still running
@@ -720,6 +746,10 @@
 
             // Recalculate the position of the sliding windows and the titles.
             updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
+            
+            if (old == mCurrentlyIntrudingNotification) {
+                mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
+            }
 
             if (CLOSE_PANEL_WHEN_EMPTIED && mNotificationData.size() == 0 && !mAnimating) {
                 animateCollapse();
@@ -1080,6 +1110,7 @@
                     break;
                 case MSG_HIDE_INTRUDER:
                     setIntruderAlertVisibility(false);
+                    mCurrentlyIntrudingNotification = null;
                     break;
                 case MSG_OPEN_RECENTS_PANEL:
                     if (DEBUG) Slog.d(TAG, "opening recents panel");
@@ -1576,6 +1607,12 @@
         }
 
         public void onClick(View v) {
+            if (DEBUG) {
+                Slog.v(TAG, "NotificationClicker: intent=" + mIntent
+                        + " pkg=" + mPkg
+                        + " tag=" + mTag
+                        + " id=" + mId);
+            }
             try {
                 // The intent we are sending is for the application, which
                 // won't have permission to immediately start an activity after
@@ -2182,9 +2219,25 @@
     };
 
     private void setIntruderAlertVisibility(boolean vis) {
+        if (DEBUG) {
+            Slog.v(TAG, (vis ? "showing" : "hiding") + " intruder alert window");
+        }
         mIntruderAlertView.setVisibility(vis ? View.VISIBLE : View.GONE);
     }
 
+    public void dismissIntruder() {
+        if (mCurrentlyIntrudingNotification == null) return;
+
+        try {
+            mBarService.onNotificationClear(
+                    mCurrentlyIntrudingNotification.pkg,
+                    mCurrentlyIntrudingNotification.tag, 
+                    mCurrentlyIntrudingNotification.id);
+        } catch (android.os.RemoteException ex) {
+            // oh well
+        }
+    }
+    
     /**
      * Reload some of our resources when the configuration changes.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java
new file mode 100644
index 0000000..19fbe96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java
@@ -0,0 +1,41 @@
+/*
+ * 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.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.systemui.R;
+
+public class DeadZone extends View {
+    public DeadZone(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DeadZone(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs);
+    }
+
+    // I made you a touch event
+    @Override
+    public boolean onTouchEvent (MotionEvent event) {
+        return true; // but I eated it
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IntruderAlertView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IntruderAlertView.java
new file mode 100644
index 0000000..ee5c863
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IntruderAlertView.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.LayoutTransition;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+import com.android.systemui.R;
+import com.android.systemui.SwipeHelper;
+import com.android.systemui.statusbar.BaseStatusBar;
+
+import java.util.HashMap;
+
+public class IntruderAlertView extends LinearLayout implements SwipeHelper.Callback {
+    private static final String TAG = "IntruderAlertView";
+    private static final boolean DEBUG = false;
+
+    Rect mTmpRect = new Rect();
+
+    private SwipeHelper mSwipeHelper;
+    
+    BaseStatusBar mBar;
+    private ViewGroup mContentHolder;
+    
+    private RemoteViews mIntruderRemoteViews;
+    private OnClickListener mOnClickListener;
+
+    public IntruderAlertView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public IntruderAlertView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        setOrientation(LinearLayout.VERTICAL);
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        float densityScale = getResources().getDisplayMetrics().density;
+        float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
+        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop);
+        
+        mContentHolder = (ViewGroup) findViewById(R.id.contentHolder);
+        if (mIntruderRemoteViews != null) {
+            // whoops, we're on already!
+            applyIntruderContent(mIntruderRemoteViews, mOnClickListener);
+        }
+    }
+    
+    public void setBar(BaseStatusBar bar) {
+        mBar = bar;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()");
+        return mSwipeHelper.onInterceptTouchEvent(ev) ||
+            super.onInterceptTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return mSwipeHelper.onTouchEvent(ev) ||
+            super.onTouchEvent(ev);
+    }
+
+    public boolean canChildBeDismissed(View v) {
+        return true;
+    }
+
+    public void onChildDismissed(View v) {
+        Slog.v(TAG, "User swiped intruder to dismiss");
+        mBar.dismissIntruder();
+    }
+
+    public void onBeginDrag(View v) {
+    }
+
+    public void onDragCancelled(View v) {
+        mContentHolder.setAlpha(1f); // sometimes this isn't quite reset
+    }
+
+    public View getChildAtPosition(MotionEvent ev) {
+        return mContentHolder;
+    }
+
+    public View getChildContentView(View v) {
+        return v;
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        float densityScale = getResources().getDisplayMetrics().density;
+        mSwipeHelper.setDensityScale(densityScale);
+        float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
+        mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
+    }
+
+    @Override
+    public void onDraw(android.graphics.Canvas c) {
+        super.onDraw(c);
+        if (DEBUG) {
+            //Slog.d(TAG, "onDraw: canvas height: " + c.getHeight() + "px; measured height: "
+            //        + getMeasuredHeight() + "px");
+            c.save();
+            c.clipRect(6, 6, c.getWidth() - 6, getMeasuredHeight() - 6,
+                    android.graphics.Region.Op.DIFFERENCE);
+            c.drawColor(0xFFcc00cc);
+            c.restore();
+        }
+    }
+
+    public void applyIntruderContent(RemoteViews intruderView, OnClickListener listener) {
+        if (DEBUG) {
+            Slog.v(TAG, "applyIntruderContent: view=" + intruderView + " listener=" + listener);
+        }
+        mIntruderRemoteViews = intruderView;
+        mOnClickListener = listener;
+        if (mContentHolder == null) { 
+            // too soon!
+            return;
+        }
+        mContentHolder.setX(0);
+        mContentHolder.setVisibility(View.VISIBLE);
+        mContentHolder.setAlpha(1f);
+        mContentHolder.removeAllViews();
+        final View content = intruderView.apply(getContext(), mContentHolder);
+        if (listener != null) {
+            content.setOnClickListener(listener);
+            
+            //content.setBackgroundResource(R.drawable.intruder_row_bg);
+            Drawable bg = getResources().getDrawable(R.drawable.intruder_row_bg);
+            if (bg == null) {
+                Log.e(TAG, String.format("Can't find background drawable id=0x%08x", R.drawable.intruder_row_bg));
+            } else {
+                content.setBackgroundDrawable(bg);
+            }
+        }
+        mContentHolder.addView(content);
+        
+    }
+}
\ No newline at end of file
diff --git a/packages/VpnDialogs/res/values-zh-rCN/strings.xml b/packages/VpnDialogs/res/values-zh-rCN/strings.xml
index 7fdb368..ed844ad 100644
--- a/packages/VpnDialogs/res/values-zh-rCN/strings.xml
+++ b/packages/VpnDialogs/res/values-zh-rCN/strings.xml
@@ -17,8 +17,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="prompt" msgid="8359175999006833462">"“<xliff:g id="APP">%s</xliff:g>”尝试创建 VPN 连接。"</string>
-    <string name="warning" msgid="5470743576660160079">"继续操作即表示您授予此应用程序拦截所有网络流量的权限。"<b>"除非您信任此应用程序,否则请勿接受此请求。"</b>"如果您在不信任该应用程序的情况下接受了此请求,则可能会面临数据遭到恶意软件盗用的风险。"</string>
-    <string name="accept" msgid="2889226408765810173">"我信任此应用程序。"</string>
+    <string name="warning" msgid="5470743576660160079">"继续操作即表示您授予此应用拦截所有网络流量的权限。"<b>"除非您信任此应用,否则请勿接受此请求。"</b>"如果您在不信任该应用的情况下接受了此请求,则可能会面临数据遭到恶意软件盗用的风险。"</string>
+    <string name="accept" msgid="2889226408765810173">"我信任此应用。"</string>
     <string name="legacy_title" msgid="192936250066580964">"已连接 VPN"</string>
     <string name="configure" msgid="4905518375574791375">"配置"</string>
     <string name="disconnect" msgid="971412338304200056">"断开连接"</string>
diff --git a/policy/src/com/android/internal/policy/impl/GlobalActions.java b/policy/src/com/android/internal/policy/impl/GlobalActions.java
index bcba3c2..4ebabd3 100644
--- a/policy/src/com/android/internal/policy/impl/GlobalActions.java
+++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java
@@ -23,6 +23,7 @@
 
 import android.app.ActivityManagerNative;
 import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -31,14 +32,18 @@
 import android.content.pm.UserInfo;
 import android.media.AudioManager;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemProperties;
+import android.os.Vibrator;
 import android.provider.Settings;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.util.Log;
+import android.view.IWindowManager;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -77,6 +82,8 @@
     private ToggleAction.State mAirplaneState = ToggleAction.State.Off;
     private boolean mIsWaitingForEcmExit = false;
 
+    private IWindowManager mIWindowManager;
+
     /**
      * @param context everything needs a context :(
      */
@@ -119,7 +126,7 @@
      * @return A new dialog.
      */
     private AlertDialog createDialog() {
-        mSilentModeAction = new SilentModeAction(mAudioManager, mHandler);
+        mSilentModeAction = new SilentModeAction(mContext, mAudioManager, mHandler);
 
         mAirplaneModeOn = new ToggleAction(
                 R.drawable.ic_lock_airplane_mode,
@@ -200,6 +207,7 @@
                     public void onPress() {
                         try {
                             ActivityManagerNative.getDefault().switchUser(user.id);
+                            getWindowManager().lockNow();
                         } catch (RemoteException re) {
                             Log.e(TAG, "Couldn't switch user " + re);
                         }
@@ -540,10 +548,15 @@
 
         private final AudioManager mAudioManager;
         private final Handler mHandler;
+        private final boolean mHasVibrator;
+        private final Context mContext;
 
-        SilentModeAction(AudioManager audioManager, Handler handler) {
+        SilentModeAction(Context context, AudioManager audioManager, Handler handler) {
             mAudioManager = audioManager;
             mHandler = handler;
+            mContext = context;
+            Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+            mHasVibrator = vibrator != null && vibrator.hasVibrator();
         }
 
         private int ringerModeToIndex(int ringerMode) {
@@ -567,6 +580,9 @@
                 // Set up click handler
                 itemView.setTag(i);
                 itemView.setOnClickListener(this);
+                if (itemView.getId() == R.id.option2 && !mHasVibrator) {
+                    itemView.setVisibility(View.GONE);
+                }
             }
             return v;
         }
@@ -667,4 +683,12 @@
         intent.putExtra("state", on);
         mContext.sendBroadcast(intent);
     }
+
+    private IWindowManager getWindowManager() {
+        if (mIWindowManager == null) {
+            IBinder b = ServiceManager.getService(Context.WINDOW_SERVICE);
+            mIWindowManager = IWindowManager.Stub.asInterface(b);
+        }
+        return mIWindowManager;
+    }
 }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index a1f7316..b5dace0 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -463,6 +463,7 @@
     // Screenshot trigger states
     // Time to volume and power must be pressed within this interval of each other.
     private static final long SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS = 150;
+    private boolean mScreenshotChordEnabled;
     private boolean mVolumeDownKeyTriggered;
     private long mVolumeDownKeyTime;
     private boolean mVolumeDownKeyConsumedByScreenshotChord;
@@ -636,7 +637,8 @@
     }
 
     private void interceptScreenshotChord() {
-        if (mVolumeDownKeyTriggered && mPowerKeyTriggered && !mVolumeUpKeyTriggered) {
+        if (mScreenshotChordEnabled
+                && mVolumeDownKeyTriggered && mPowerKeyTriggered && !mVolumeUpKeyTriggered) {
             final long now = SystemClock.uptimeMillis();
             if (now <= mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
                     && now <= mPowerKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {
@@ -882,6 +884,9 @@
         mSafeModeEnabledVibePattern = getLongIntArray(mContext.getResources(),
                 com.android.internal.R.array.config_safeModeEnabledVibePattern);
 
+        mScreenshotChordEnabled = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableScreenshotChord);
+
         // Controls rotation and the like.
         initializeHdmiState();
 
@@ -1574,7 +1579,7 @@
         // If we think we might have a volume down & power key chord on the way
         // but we're not sure, then tell the dispatcher to wait a little while and
         // try again later before dispatching.
-        if ((flags & KeyEvent.FLAG_FALLBACK) == 0) {
+        if (mScreenshotChordEnabled && (flags & KeyEvent.FLAG_FALLBACK) == 0) {
             if (mVolumeDownKeyTriggered && !mPowerKeyTriggered) {
                 final long now = SystemClock.uptimeMillis();
                 final long timeoutTime = mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS;
diff --git a/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java b/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java
index ba06996..f3e7d4a 100644
--- a/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java
@@ -106,7 +106,7 @@
         mHeaderText.setSelected(true);
 
         mKeyguardStatusViewManager = new KeyguardStatusViewManager(this, updateMonitor,
-                lockpatternutils, callback, false);
+                lockpatternutils, callback, true);
 
         mPinText.setFocusableInTouchMode(true);
         mPinText.setOnFocusChangeListener(this);
diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk
index 1652cae..257f62c 100644
--- a/services/audioflinger/Android.mk
+++ b/services/audioflinger/Android.mk
@@ -15,6 +15,7 @@
     $(call include-path-for, audio-effects) \
     $(call include-path-for, audio-utils)
 
+# FIXME keep libmedia_native but remove libmedia after split
 LOCAL_SHARED_LIBRARIES := \
     libaudioutils \
     libcommon_time_client \
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index d83d19a..c5ad0f5 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -442,11 +442,9 @@
         audio_format_t format,
         uint32_t channelMask,
         int frameCount,
-        // FIXME dead, remove from IAudioFlinger
-        uint32_t flags,
+        IAudioFlinger::track_flags_t flags,
         const sp<IMemory>& sharedBuffer,
         audio_io_handle_t output,
-        bool isTimed,
         int *sessionId,
         status_t *status)
 {
@@ -504,6 +502,7 @@
         }
         ALOGV("createTrack() lSessionId: %d", lSessionId);
 
+        bool isTimed = (flags & IAudioFlinger::TRACK_TIMED) != 0;
         track = thread->createTrack_l(client, streamType, sampleRate, format,
                 channelMask, frameCount, sharedBuffer, lSessionId, isTimed, &lStatus);
 
@@ -894,7 +893,8 @@
             // indicate output device change to all input threads for pre processing
             AudioParameter param = AudioParameter(keyValuePairs);
             int value;
-            if (param.getInt(String8(AudioParameter::keyRouting), value) == NO_ERROR) {
+            if ((param.getInt(String8(AudioParameter::keyRouting), value) == NO_ERROR) &&
+                    (value != 0)) {
                 for (size_t i = 0; i < mRecordThreads.size(); i++) {
                     mRecordThreads.valueAt(i)->setParameters(keyValuePairs);
                 }
@@ -1588,7 +1588,7 @@
 }
 
 // PlaybackThread::createTrack_l() must be called with AudioFlinger::mLock held
-sp<AudioFlinger::PlaybackThread::Track>  AudioFlinger::PlaybackThread::createTrack_l(
+sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTrack_l(
         const sp<AudioFlinger::Client>& client,
         audio_stream_type_t streamType,
         uint32_t sampleRate,
@@ -2337,7 +2337,7 @@
     size_t tracksWithEffect = 0;
 
     float masterVolume = mMasterVolume;
-    bool  masterMute = mMasterMute;
+    bool masterMute = mMasterMute;
 
     if (masterMute) {
         masterVolume = 0;
@@ -2376,7 +2376,7 @@
                 // +1 for rounding and +1 for additional sample needed for interpolation
                 minFrames = (mFrameCount * t->sampleRate()) / mSampleRate + 1 + 1;
                 // add frames already consumed but not yet released by the resampler
-                // because cblk->framesReady() will  include these frames
+                // because cblk->framesReady() will include these frames
                 minFrames += mAudioMixer->getUnreleasedFrames(track->name());
                 // the minimum track buffer size is normally twice the number of frames necessary
                 // to fill one buffer and the resampler should not leave more than one buffer worth
@@ -2514,6 +2514,7 @@
 
             // reset retry count
             track->mRetryCount = kMaxTrackRetries;
+
             // If one track is ready, set the mixer ready if:
             //  - the mixer was not ready during previous round OR
             //  - no other track is not ready
@@ -3372,19 +3373,19 @@
         }
     } else {
         mCblk = (audio_track_cblk_t *)(new uint8_t[size]);
-            // construct the shared structure in-place.
-            new(mCblk) audio_track_cblk_t();
-            // clear all buffers
-            mCblk->frameCount = frameCount;
-            mCblk->sampleRate = sampleRate;
-            mChannelCount = channelCount;
-            mChannelMask = channelMask;
-            mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
-            memset(mBuffer, 0, frameCount*channelCount*sizeof(int16_t));
-            // Force underrun condition to avoid false underrun callback until first data is
-            // written to buffer (other flags are cleared)
-            mCblk->flags = CBLK_UNDERRUN_ON;
-            mBufferEnd = (uint8_t *)mBuffer + bufferSize;
+        // construct the shared structure in-place.
+        new(mCblk) audio_track_cblk_t();
+        // clear all buffers
+        mCblk->frameCount = frameCount;
+        mCblk->sampleRate = sampleRate;
+        mChannelCount = channelCount;
+        mChannelMask = channelMask;
+        mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
+        memset(mBuffer, 0, frameCount*channelCount*sizeof(int16_t));
+        // Force underrun condition to avoid false underrun callback until first data is
+        // written to buffer (other flags are cleared)
+        mCblk->flags = CBLK_UNDERRUN_ON;
+        mBufferEnd = (uint8_t *)mBuffer + bufferSize;
     }
 }
 
@@ -3479,23 +3480,27 @@
             const sp<IMemory>& sharedBuffer,
             int sessionId)
     :   TrackBase(thread, client, sampleRate, format, channelMask, frameCount, sharedBuffer, sessionId),
-    mMute(false), mSharedBuffer(sharedBuffer), mName(-1), mMainBuffer(NULL), mAuxBuffer(NULL),
+    mMute(false),
+    // mFillingUpStatus ?
+    // mRetryCount initialized later when needed
+    mSharedBuffer(sharedBuffer),
+    mStreamType(streamType),
+    mName(-1),  // see note below
+    mMainBuffer(thread->mixBuffer()),
+    mAuxBuffer(NULL),
     mAuxEffectId(0), mHasVolumeController(false)
 {
     if (mCblk != NULL) {
-        if (thread != NULL) {
-            mName = thread->getTrackName_l();
-            mMainBuffer = thread->mixBuffer();
-        }
-        ALOGV("Track constructor name %d, calling pid %d", mName, IPCThreadState::self()->getCallingPid());
-        if (mName < 0) {
-            ALOGE("no more track names available");
-        }
-        mStreamType = streamType;
         // NOTE: audio_track_cblk_t::frameSize for 8 bit PCM data is based on a sample size of
         // 16 bit because data is converted to 16 bit before being stored in buffer by AudioTrack
         mCblk->frameSize = audio_is_linear_pcm(format) ? mChannelCount * sizeof(int16_t) : sizeof(uint8_t);
+        // to avoid leaking a track name, do not allocate one unless there is an mCblk
+        mName = thread->getTrackName_l();
+        if (mName < 0) {
+            ALOGE("no more track names available");
+        }
     }
+    ALOGV("Track constructor name %d, calling pid %d", mName, IPCThreadState::self()->getCallingPid());
 }
 
 AudioFlinger::PlaybackThread::Track::~Track()
@@ -3792,16 +3797,9 @@
     if (!client->reserveTimedTrack())
         return NULL;
 
-    sp<TimedTrack> track = new TimedTrack(
+    return new TimedTrack(
         thread, client, streamType, sampleRate, format, channelMask, frameCount,
         sharedBuffer, sessionId);
-
-    if (track == NULL) {
-        client->releaseTimedTrack();
-        return NULL;
-    }
-
-    return track;
 }
 
 AudioFlinger::PlaybackThread::TimedTrack::TimedTrack(
@@ -4261,7 +4259,7 @@
         RecordThread *recordThread = (RecordThread *)thread.get();
         recordThread->stop(this);
         TrackBase::reset();
-        // Force overerrun condition to avoid false overrun callback until first data is
+        // Force overrun condition to avoid false overrun callback until first data is
         // read from buffer
         android_atomic_or(CBLK_UNDERRUN_ON, &mCblk->flags);
     }
@@ -4677,8 +4675,7 @@
         audio_format_t format,
         uint32_t channelMask,
         int frameCount,
-        // FIXME dead, remove from IAudioFlinger
-        uint32_t flags,
+        IAudioFlinger::track_flags_t flags,
         int *sessionId,
         status_t *status)
 {
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 0e4b24aa..795807d 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -87,10 +87,9 @@
                                 audio_format_t format,
                                 uint32_t channelMask,
                                 int frameCount,
-                                uint32_t flags,
+                                IAudioFlinger::track_flags_t flags,
                                 const sp<IMemory>& sharedBuffer,
                                 audio_io_handle_t output,
-                                bool isTimed,
                                 int *sessionId,
                                 status_t *status);
 
@@ -101,7 +100,7 @@
                                 audio_format_t format,
                                 uint32_t channelMask,
                                 int frameCount,
-                                uint32_t flags,
+                                IAudioFlinger::track_flags_t flags,
                                 int *sessionId,
                                 status_t *status);
 
@@ -681,9 +680,9 @@
             enum {FS_FILLING, FS_FILLED, FS_ACTIVE};
             mutable uint8_t     mFillingUpStatus;
             int8_t              mRetryCount;
-            sp<IMemory>         mSharedBuffer;
+            const sp<IMemory>   mSharedBuffer;
             bool                mResetDone;
-            audio_stream_type_t mStreamType;
+            const audio_stream_type_t mStreamType;
             int                 mName;
             int16_t             *mMainBuffer;
             int32_t             *mAuxBuffer;
@@ -899,6 +898,7 @@
     protected:
         SortedVector< wp<Track> >       mActiveTracks;
 
+        // Allocate a track name.  Returns name >= 0 if successful, -1 on failure.
         virtual int             getTrackName_l() = 0;
         virtual void            deleteTrackName_l(int name) = 0;
         virtual uint32_t        activeSleepTimeUs();
@@ -1559,9 +1559,10 @@
         uint32_t mNewLeftVolume;       // new volume on left channel
         uint32_t mNewRightVolume;      // new volume on right channel
         uint32_t mStrategy; // strategy for this effect chain
-        // mSuspendedEffects lists all effect currently suspended in the chain
-        // use effect type UUID timelow field as key. There is no real risk of identical
+        // mSuspendedEffects lists all effects currently suspended in the chain.
+        // Use effect type UUID timelow field as key. There is no real risk of identical
         // timeLow fields among effect type UUIDs.
+        // Updated by updateSuspendedSessions_l() only.
         KeyedVector< int, sp<SuspendedEffectDesc> > mSuspendedEffects;
     };
 
diff --git a/services/audioflinger/AudioMixer.cpp b/services/audioflinger/AudioMixer.cpp
index 1ec238b..3f4c19a 100644
--- a/services/audioflinger/AudioMixer.cpp
+++ b/services/audioflinger/AudioMixer.cpp
@@ -42,12 +42,15 @@
 
 // ----------------------------------------------------------------------------
 
-AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate)
-    :   mTrackNames(0), mSampleRate(sampleRate)
+AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate, uint32_t maxNumTracks)
+    :   mTrackNames(0), mConfiguredNames((1 << maxNumTracks) - 1), mSampleRate(sampleRate)
 {
     // AudioMixer is not yet capable of multi-channel beyond stereo
     COMPILE_TIME_ASSERT_FUNCTION_SCOPE(2 == MAX_NUM_CHANNELS);
     
+    ALOG_ASSERT(maxNumTracks <= MAX_NUM_TRACKS, "maxNumTracks %u > MAX_NUM_TRACKS %u",
+            maxNumTracks, MAX_NUM_TRACKS);
+
     LocalClock lc;
 
     mState.enabledTracks= 0;
@@ -57,6 +60,10 @@
     mState.outputTemp   = NULL;
     mState.resampleTemp = NULL;
     // mState.reserved
+
+    // FIXME Most of the following initialization is probably redundant since
+    // tracks[i] should only be referenced if (mTrackNames & (1 << i)) != 0
+    // and mTrackNames is initially 0.  However, leave it here until that's verified.
     track_t* t = mState.tracks;
     for (unsigned i=0 ; i < MAX_NUM_TRACKS ; i++) {
         t->needs = 0;
@@ -103,7 +110,7 @@
 
 int AudioMixer::getTrackName()
 {
-    uint32_t names = ~mTrackNames;
+    uint32_t names = (~mTrackNames) & mConfiguredNames;
     if (names != 0) {
         int n = __builtin_ctz(names);
         ALOGV("add track (%d)", n);
@@ -132,7 +139,7 @@
         invalidateState(1<<name);
     }
     if (track.resampler != NULL) {
-        // delete  the resampler
+        // delete the resampler
         delete track.resampler;
         track.resampler = NULL;
         track.sampleRate = mSampleRate;
diff --git a/services/audioflinger/AudioMixer.h b/services/audioflinger/AudioMixer.h
index b210212..856450c 100644
--- a/services/audioflinger/AudioMixer.h
+++ b/services/audioflinger/AudioMixer.h
@@ -31,7 +31,8 @@
 class AudioMixer
 {
 public:
-                            AudioMixer(size_t frameCount, uint32_t sampleRate);
+                            AudioMixer(size_t frameCount, uint32_t sampleRate,
+                                       uint32_t maxNumTracks = MAX_NUM_TRACKS);
 
     /*virtual*/             ~AudioMixer();  // non-virtual saves a v-table, restore if sub-classed
 
@@ -70,9 +71,14 @@
 
 
     // For all APIs with "name": TRACK0 <= name < TRACK0 + MAX_NUM_TRACKS
+
+    // Allocate a track name.  Returns new track name if successful, -1 on failure.
     int         getTrackName();
+
+    // Free an allocated track by name
     void        deleteTrackName(int name);
 
+    // Enable or disable an allocated track by name
     void        enable(int name);
     void        disable(int name);
 
@@ -184,11 +190,17 @@
         int32_t         *outputTemp;
         int32_t         *resampleTemp;
         int32_t         reserved[2];
+        // FIXME allocate dynamically to save some memory when maxNumTracks < MAX_NUM_TRACKS
         track_t         tracks[MAX_NUM_TRACKS]; __attribute__((aligned(32)));
     };
 
     // bitmask of allocated track names, where bit 0 corresponds to TRACK0 etc.
     uint32_t        mTrackNames;
+
+    // bitmask of configured track names; ~0 if maxNumTracks == MAX_NUM_TRACKS,
+    // but will have fewer bits set if maxNumTracks < MAX_NUM_TRACKS
+    const uint32_t  mConfiguredNames;
+
     const uint32_t  mSampleRate;
 
     state_t         mState __attribute__((aligned(32)));
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index 081f1f4..eb024e9 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -170,6 +170,15 @@
         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
         mContext.registerReceiver(mBroadcastReceiver, sdFilter);
+
+        IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(Intent.ACTION_USER_REMOVED);
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                onUserRemoved(intent.getIntExtra(Intent.EXTRA_USERID, -1));
+            }
+        }, userFilter);
     }
 
     @Override
@@ -192,19 +201,6 @@
         getImplForUser().deleteAllHosts();
     }
 
-    void cancelBroadcasts(Provider p) {
-        if (p.broadcast != null) {
-            mAlarmManager.cancel(p.broadcast);
-            long token = Binder.clearCallingIdentity();
-            try {
-                p.broadcast.cancel();
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-            p.broadcast = null;
-        }
-    }
-
     @Override
     public void bindAppWidgetId(int appWidgetId, ComponentName provider) throws RemoteException {
         getImplForUser().bindAppWidgetId(appWidgetId, provider);
@@ -222,8 +218,15 @@
         return getImplForUser().startListening(host, packageName, hostId, updatedViews);
     }
 
-    // TODO: Call this from PackageManagerService when a user is removed
-    public void removeUser(int userId) {
+    public void onUserRemoved(int userId) {
+        AppWidgetServiceImpl impl = mAppWidgetServices.get(userId);
+        if (userId < 1) return;
+
+        if (impl == null) {
+            AppWidgetServiceImpl.getSettingsFile(userId).delete();
+        } else {
+            impl.onUserRemoved();
+        }
     }
 
     private AppWidgetServiceImpl getImplForUser() {
@@ -325,9 +328,10 @@
                     service.onConfigurationChanged();
                 }
             } else {
-                // TODO: Verify that this only needs to be delivered for the related user and not
-                // all the users
-                getImplForUser().onBroadcastReceived(intent);
+                for (int i = 0; i < mAppWidgetServices.size(); i++) {
+                    AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i);
+                    service.onBroadcastReceived(intent);
+                }
             }
         }
     };
diff --git a/services/java/com/android/server/AppWidgetServiceImpl.java b/services/java/com/android/server/AppWidgetServiceImpl.java
index 9c408c4..b24823e 100644
--- a/services/java/com/android/server/AppWidgetServiceImpl.java
+++ b/services/java/com/android/server/AppWidgetServiceImpl.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import android.app.AlarmManager;
+import android.app.AppGlobals;
 import android.app.PendingIntent;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
@@ -27,6 +28,7 @@
 import android.content.Intent.FilterComparison;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -158,7 +160,7 @@
 
     Context mContext;
     Locale mLocale;
-    PackageManager mPackageManager;
+    IPackageManager mPm;
     AlarmManager mAlarmManager;
     ArrayList<Provider> mInstalledProviders = new ArrayList<Provider>();
     int mNextAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + 1;
@@ -174,7 +176,7 @@
 
     AppWidgetServiceImpl(Context context, int userId) {
         mContext = context;
-        mPackageManager = context.getPackageManager();
+        mPm = AppGlobals.getPackageManager();
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
         mUserId = userId;
     }
@@ -1009,16 +1011,19 @@
     }
 
     void loadAppWidgetList() {
-        PackageManager pm = mPackageManager;
-
         Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
-        List<ResolveInfo> broadcastReceivers = pm.queryBroadcastReceivers(intent,
-                PackageManager.GET_META_DATA);
+        try {
+            List<ResolveInfo> broadcastReceivers = mPm.queryIntentReceivers(intent,
+                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    PackageManager.GET_META_DATA, mUserId);
 
-        final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size();
-        for (int i = 0; i < N; i++) {
-            ResolveInfo ri = broadcastReceivers.get(i);
-            addProviderLocked(ri);
+            final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size();
+            for (int i = 0; i < N; i++) {
+                ResolveInfo ri = broadcastReceivers.get(i);
+                addProviderLocked(ri);
+            }
+        } catch (RemoteException re) {
+            // Shouldn't happen, local call
         }
     }
 
@@ -1131,7 +1136,7 @@
         ActivityInfo activityInfo = ri.activityInfo;
         XmlResourceParser parser = null;
         try {
-            parser = activityInfo.loadXmlMetaData(mPackageManager,
+            parser = activityInfo.loadXmlMetaData(mContext.getPackageManager(),
                     AppWidgetManager.META_DATA_APPWIDGET_PROVIDER);
             if (parser == null) {
                 Slog.w(TAG, "No " + AppWidgetManager.META_DATA_APPWIDGET_PROVIDER
@@ -1159,7 +1164,7 @@
             info.provider = component;
             p.uid = activityInfo.applicationInfo.uid;
 
-            Resources res = mPackageManager
+            Resources res = mContext.getPackageManager()
                     .getResourcesForApplication(activityInfo.applicationInfo);
 
             TypedArray sa = res.obtainAttributes(attrs,
@@ -1188,7 +1193,7 @@
             if (className != null) {
                 info.configure = new ComponentName(component.getPackageName(), className);
             }
-            info.label = activityInfo.loadLabel(mPackageManager).toString();
+            info.label = activityInfo.loadLabel(mContext.getPackageManager()).toString();
             info.icon = ri.getIconResource();
             info.previewImage = sa.getResourceId(
                     com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0);
@@ -1213,7 +1218,12 @@
     }
 
     int getUidForPackage(String packageName) throws PackageManager.NameNotFoundException {
-        PackageInfo pkgInfo = mPackageManager.getPackageInfo(packageName, 0);
+        PackageInfo pkgInfo = null;
+        try {
+            pkgInfo = mPm.getPackageInfo(packageName, 0, mUserId);
+        } catch (RemoteException re) {
+            // Shouldn't happen, local call
+        }
         if (pkgInfo == null || pkgInfo.applicationInfo == null) {
             throw new PackageManager.NameNotFoundException();
         }
@@ -1474,9 +1484,13 @@
         }
     }
 
+    static File getSettingsFile(int userId) {
+        return new File("/data/system/users/" + userId + "/" + SETTINGS_FILENAME);
+    }
+
     AtomicFile savedStateFile() {
         File dir = new File("/data/system/users/" + mUserId);
-        File settingsFile = new File(dir, SETTINGS_FILENAME);
+        File settingsFile = getSettingsFile(mUserId);
         if (!dir.exists()) {
             dir.mkdirs();
             if (mUserId == 0) {
@@ -1490,12 +1504,28 @@
         return new AtomicFile(settingsFile);
     }
 
+    void onUserRemoved() {
+        // prune the ones we don't want to keep
+        int N = mInstalledProviders.size();
+        for (int i = N - 1; i >= 0; i--) {
+            Provider p = mInstalledProviders.get(i);
+            cancelBroadcasts(p);
+        }
+        getSettingsFile(mUserId).delete();
+    }
+
     void addProvidersForPackageLocked(String pkgName) {
         Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
         intent.setPackage(pkgName);
-        List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent,
-                PackageManager.GET_META_DATA);
-
+        List<ResolveInfo> broadcastReceivers;
+        try {
+            broadcastReceivers = mPm.queryIntentReceivers(intent,
+                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    PackageManager.GET_META_DATA, mUserId);
+        } catch (RemoteException re) {
+            // Shouldn't happen, local call
+            return;
+        }
         final int N = broadcastReceivers == null ? 0 : broadcastReceivers.size();
         for (int i = 0; i < N; i++) {
             ResolveInfo ri = broadcastReceivers.get(i);
@@ -1513,8 +1543,15 @@
         HashSet<String> keep = new HashSet<String>();
         Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
         intent.setPackage(pkgName);
-        List<ResolveInfo> broadcastReceivers = mPackageManager.queryBroadcastReceivers(intent,
-                PackageManager.GET_META_DATA);
+        List<ResolveInfo> broadcastReceivers;
+        try {
+            broadcastReceivers = mPm.queryIntentReceivers(intent,
+                intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                PackageManager.GET_META_DATA, mUserId);
+        } catch (RemoteException re) {
+            // Shouldn't happen, local call
+            return;
+        }
 
         // add the missing ones and collect which ones to keep
         int N = broadcastReceivers == null ? 0 : broadcastReceivers.size();
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index a7b08f5..a0d5beb 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -201,9 +201,9 @@
     BackupHandler mBackupHandler;
     PendingIntent mRunBackupIntent, mRunInitIntent;
     BroadcastReceiver mRunBackupReceiver, mRunInitReceiver;
-    // map UIDs to the set of backup client services within that UID's app set
-    final SparseArray<HashSet<ApplicationInfo>> mBackupParticipants
-        = new SparseArray<HashSet<ApplicationInfo>>();
+    // map UIDs to the set of participating packages under that UID
+    final SparseArray<HashSet<String>> mBackupParticipants
+            = new SparseArray<HashSet<String>>();
     // set of backup services that have pending changes
     class BackupRequest {
         public String packageName;
@@ -960,7 +960,6 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_PACKAGE_ADDED);
         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
         filter.addDataScheme("package");
         mContext.registerReceiver(mBroadcastReceiver, filter);
         // Register for events related to sdcard installation.
@@ -1239,12 +1238,13 @@
 
         // Enqueue a new backup of every participant
         synchronized (mBackupParticipants) {
-            int N = mBackupParticipants.size();
+            final int N = mBackupParticipants.size();
             for (int i=0; i<N; i++) {
-                int uid = mBackupParticipants.keyAt(i);
-                HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i);
-                for (ApplicationInfo app: participants) {
-                    dataChangedImpl(app.packageName);
+                HashSet<String> participants = mBackupParticipants.valueAt(i);
+                if (participants != null) {
+                    for (String packageName : participants) {
+                        dataChangedImpl(packageName);
+                    }
                 }
             }
         }
@@ -1302,8 +1302,7 @@
             Bundle extras = intent.getExtras();
             String pkgList[] = null;
             if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
-                    Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
-                    Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
+                    Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
                 Uri uri = intent.getData();
                 if (uri == null) {
                     return;
@@ -1312,14 +1311,8 @@
                 if (pkgName != null) {
                     pkgList = new String[] { pkgName };
                 }
-                if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
-                    // use the existing "add with replacement" logic
-                    if (MORE_DEBUG) Slog.d(TAG, "PACKAGE_REPLACED, updating package " + pkgName);
-                    added = replacing = true;
-                } else {
-                    added = Intent.ACTION_PACKAGE_ADDED.equals(action);
-                    replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false);
-                }
+                added = Intent.ACTION_PACKAGE_ADDED.equals(action);
+                replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false);
             } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
                 added = true;
                 pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
@@ -1331,20 +1324,23 @@
             if (pkgList == null || pkgList.length == 0) {
                 return;
             }
+
+            final int uid = extras.getInt(Intent.EXTRA_UID);
             if (added) {
                 synchronized (mBackupParticipants) {
                     if (replacing) {
-                        updatePackageParticipantsLocked(pkgList);
-                    } else {
-                        addPackageParticipantsLocked(pkgList);
+                        // This is the package-replaced case; we just remove the entry
+                        // under the old uid and fall through to re-add.
+                        removePackageParticipantsLocked(pkgList, uid);
                     }
+                    addPackageParticipantsLocked(pkgList);
                 }
             } else {
                 if (replacing) {
                     // The package is being updated.  We'll receive a PACKAGE_ADDED shortly.
                 } else {
                     synchronized (mBackupParticipants) {
-                        removePackageParticipantsLocked(pkgList);
+                        removePackageParticipantsLocked(pkgList, uid);
                     }
                 }
             }
@@ -1391,12 +1387,12 @@
         for (PackageInfo pkg : targetPkgs) {
             if (packageName == null || pkg.packageName.equals(packageName)) {
                 int uid = pkg.applicationInfo.uid;
-                HashSet<ApplicationInfo> set = mBackupParticipants.get(uid);
+                HashSet<String> set = mBackupParticipants.get(uid);
                 if (set == null) {
-                    set = new HashSet<ApplicationInfo>();
+                    set = new HashSet<String>();
                     mBackupParticipants.put(uid, set);
                 }
-                set.add(pkg.applicationInfo);
+                set.add(pkg.packageName);
                 if (MORE_DEBUG) Slog.v(TAG, "Agent found; added");
 
                 // If we've never seen this app before, schedule a backup for it
@@ -1410,63 +1406,36 @@
     }
 
     // Remove the given packages' entries from our known active set.
-    void removePackageParticipantsLocked(String[] packageNames) {
+    void removePackageParticipantsLocked(String[] packageNames, int oldUid) {
         if (packageNames == null) {
             Slog.w(TAG, "removePackageParticipants with null list");
             return;
         }
 
-        if (DEBUG) Slog.v(TAG, "removePackageParticipantsLocked: #" + packageNames.length);
-        List<PackageInfo> knownPackages = allAgentPackages();
+        if (DEBUG) Slog.v(TAG, "removePackageParticipantsLocked: uid=" + oldUid
+                + " #" + packageNames.length);
         for (String pkg : packageNames) {
-            removePackageParticipantsLockedInner(pkg, knownPackages);
+            // Known previous UID, so we know which package set to check
+            HashSet<String> set = mBackupParticipants.get(oldUid);
+            if (set != null && set.contains(pkg)) {
+                removePackageFromSetLocked(set, pkg);
+                if (set.isEmpty()) {
+                    if (MORE_DEBUG) Slog.v(TAG, "  last one of this uid; purging set");
+                    mBackupParticipants.remove(oldUid);
+                }
+            }
         }
     }
 
-    private void removePackageParticipantsLockedInner(String packageName,
-            List<PackageInfo> allPackages) {
-        if (MORE_DEBUG) {
-            Slog.v(TAG, "removePackageParticipantsLockedInner (" + packageName
-                    + ") removing from " + allPackages.size() + " entries");
-            for (PackageInfo p : allPackages) {
-                Slog.v(TAG, "    - " + p.packageName);
-            }
-        }
-        for (PackageInfo pkg : allPackages) {
-            if (packageName == null || pkg.packageName.equals(packageName)) {
-                /*
-                int uid = -1;
-                try {
-                    PackageInfo info = mPackageManager.getPackageInfo(packageName, 0);
-                    uid = info.applicationInfo.uid;
-                } catch (NameNotFoundException e) {
-                    // we don't know this package name, so just skip it for now
-                    continue;
-                }
-                */
-                final int uid = pkg.applicationInfo.uid;
-                if (MORE_DEBUG) Slog.i(TAG, "   found pkg " + packageName + " uid=" + uid);
-
-                HashSet<ApplicationInfo> set = mBackupParticipants.get(uid);
-                if (set != null) {
-                    // Find the existing entry with the same package name, and remove it.
-                    // We can't just remove(app) because the instances are different.
-                    for (ApplicationInfo entry: set) {
-                        if (MORE_DEBUG) Slog.i(TAG, "      checking against " + entry.packageName);
-                        if (entry.packageName.equals(pkg)) {
-                            if (MORE_DEBUG) Slog.v(TAG, "  removing participant " + pkg);
-                            set.remove(entry);
-                            removeEverBackedUp(pkg.packageName);
-                            break;
-                        }
-                    }
-                    if (set.size() == 0) {
-                        mBackupParticipants.delete(uid);
-                    }
-                } else {
-                    if (MORE_DEBUG) Slog.i(TAG, "   ... not found in uid mapping");
-                }
-            }
+    private void removePackageFromSetLocked(final HashSet<String> set,
+            final String packageName) {
+        if (set.contains(packageName)) {
+            // Found it.  Remove this one package from the bookkeeping, and
+            // if it's the last participating app under this uid we drop the
+            // (now-empty) set as well.
+            if (MORE_DEBUG) Slog.v(TAG, "  removing participant " + packageName);
+            removeEverBackedUp(packageName);
+            set.remove(packageName);
         }
     }
 
@@ -1497,24 +1466,6 @@
         return packages;
     }
 
-    // Reset the given package's known backup participants.  Unlike add/remove, the update
-    // action cannot be passed a null package name.
-    void updatePackageParticipantsLocked(String[] packageNames) {
-        if (packageNames == null) {
-            Slog.e(TAG, "updatePackageParticipants called with null package list");
-            return;
-        }
-        if (DEBUG) Slog.v(TAG, "updatePackageParticipantsLocked: #" + packageNames.length);
-
-        if (packageNames.length > 0) {
-            List<PackageInfo> allApps = allAgentPackages();
-            for (String packageName : packageNames) {
-                removePackageParticipantsLockedInner(packageName, allApps);
-                addPackageParticipantsLockedInner(packageName, allApps);
-            }
-        }
-    }
-
     // Called from the backup task: record that the given app has been successfully
     // backed up at least once
     void logBackupComplete(String packageName) {
@@ -1986,6 +1937,16 @@
             try {
                 mCurrentPackage = mPackageManager.getPackageInfo(request.packageName,
                         PackageManager.GET_SIGNATURES);
+                if (mCurrentPackage.applicationInfo.backupAgentName == null) {
+                    // The manifest has changed but we had a stale backup request pending.
+                    // This won't happen again because the app won't be requesting further
+                    // backups.
+                    Slog.i(TAG, "Package " + request.packageName
+                            + " no longer supports backup; skipping");
+                    addBackupTrace("skipping - no agent, completion is noop");
+                    executeNextState(BackupState.RUNNING_QUEUE);
+                    return;
+                }
 
                 IBackupAgent agent = null;
                 try {
@@ -2547,6 +2508,8 @@
                 finalizeBackup(out);
             } catch (RemoteException e) {
                 Slog.e(TAG, "App died during full backup");
+            } catch (Exception e) {
+                Slog.e(TAG, "Internal exception during full backup", e);
             } finally {
                 tearDown(pkg);
                 try {
@@ -4681,6 +4644,8 @@
                 mTransport.clearBackupData(mPackage);
             } catch (RemoteException e) {
                 // can't happen; the transport is local
+            } catch (Exception e) {
+                Slog.e(TAG, "Transport threw attempting to clear data for " + mPackage);
             } finally {
                 try {
                     // TODO - need to handle failures
@@ -4758,11 +4723,11 @@
     }
 
     private void dataChangedImpl(String packageName) {
-        HashSet<ApplicationInfo> targets = dataChangedTargets(packageName);
+        HashSet<String> targets = dataChangedTargets(packageName);
         dataChangedImpl(packageName, targets);
     }
 
-    private void dataChangedImpl(String packageName, HashSet<ApplicationInfo> targets) {
+    private void dataChangedImpl(String packageName, HashSet<String> targets) {
         // Record that we need a backup pass for the caller.  Since multiple callers
         // may share a uid, we need to note all candidates within that uid and schedule
         // a backup pass for each of them.
@@ -4776,27 +4741,23 @@
 
         synchronized (mQueueLock) {
             // Note that this client has made data changes that need to be backed up
-            for (ApplicationInfo app : targets) {
-                // validate the caller-supplied package name against the known set of
-                // packages associated with this uid
-                if (app.packageName.equals(packageName)) {
-                    // Add the caller to the set of pending backups.  If there is
-                    // one already there, then overwrite it, but no harm done.
-                    BackupRequest req = new BackupRequest(packageName);
-                    if (mPendingBackups.put(app.packageName, req) == null) {
-                        if (DEBUG) Slog.d(TAG, "Now staging backup of " + packageName);
+            if (targets.contains(packageName)) {
+                // Add the caller to the set of pending backups.  If there is
+                // one already there, then overwrite it, but no harm done.
+                BackupRequest req = new BackupRequest(packageName);
+                if (mPendingBackups.put(packageName, req) == null) {
+                    if (DEBUG) Slog.d(TAG, "Now staging backup of " + packageName);
 
-                        // Journal this request in case of crash.  The put()
-                        // operation returned null when this package was not already
-                        // in the set; we want to avoid touching the disk redundantly.
-                        writeToJournalLocked(packageName);
+                    // Journal this request in case of crash.  The put()
+                    // operation returned null when this package was not already
+                    // in the set; we want to avoid touching the disk redundantly.
+                    writeToJournalLocked(packageName);
 
-                        if (MORE_DEBUG) {
-                            int numKeys = mPendingBackups.size();
-                            Slog.d(TAG, "Now awaiting backup for " + numKeys + " participants:");
-                            for (BackupRequest b : mPendingBackups.values()) {
-                                Slog.d(TAG, "    + " + b);
-                            }
+                    if (MORE_DEBUG) {
+                        int numKeys = mPendingBackups.size();
+                        Slog.d(TAG, "Now awaiting backup for " + numKeys + " participants:");
+                        for (BackupRequest b : mPendingBackups.values()) {
+                            Slog.d(TAG, "    + " + b);
                         }
                     }
                 }
@@ -4805,7 +4766,7 @@
     }
 
     // Note: packageName is currently unused, but may be in the future
-    private HashSet<ApplicationInfo> dataChangedTargets(String packageName) {
+    private HashSet<String> dataChangedTargets(String packageName) {
         // If the caller does not hold the BACKUP permission, it can only request a
         // backup of its own data.
         if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(),
@@ -4817,11 +4778,11 @@
 
         // a caller with full permission can ask to back up any participating app
         // !!! TODO: allow backup of ANY app?
-        HashSet<ApplicationInfo> targets = new HashSet<ApplicationInfo>();
+        HashSet<String> targets = new HashSet<String>();
         synchronized (mBackupParticipants) {
             int N = mBackupParticipants.size();
             for (int i = 0; i < N; i++) {
-                HashSet<ApplicationInfo> s = mBackupParticipants.valueAt(i);
+                HashSet<String> s = mBackupParticipants.valueAt(i);
                 if (s != null) {
                     targets.addAll(s);
                 }
@@ -4848,7 +4809,7 @@
     // ----- IBackupManager binder interface -----
 
     public void dataChanged(final String packageName) {
-        final HashSet<ApplicationInfo> targets = dataChangedTargets(packageName);
+        final HashSet<String> targets = dataChangedTargets(packageName);
         if (targets == null) {
             Slog.w(TAG, "dataChanged but no participant pkg='" + packageName + "'"
                    + " uid=" + Binder.getCallingUid());
@@ -4875,7 +4836,7 @@
 
         // If the caller does not hold the BACKUP permission, it can only request a
         // wipe of its own backed-up data.
-        HashSet<ApplicationInfo> apps;
+        HashSet<String> apps;
         if ((mContext.checkPermission(android.Manifest.permission.BACKUP, Binder.getCallingPid(),
                 Binder.getCallingUid())) == PackageManager.PERMISSION_DENIED) {
             apps = mBackupParticipants.get(Binder.getCallingUid());
@@ -4883,30 +4844,27 @@
             // a caller with full permission can ask to back up any participating app
             // !!! TODO: allow data-clear of ANY app?
             if (DEBUG) Slog.v(TAG, "Privileged caller, allowing clear of other apps");
-            apps = new HashSet<ApplicationInfo>();
+            apps = new HashSet<String>();
             int N = mBackupParticipants.size();
             for (int i = 0; i < N; i++) {
-                HashSet<ApplicationInfo> s = mBackupParticipants.valueAt(i);
+                HashSet<String> s = mBackupParticipants.valueAt(i);
                 if (s != null) {
                     apps.addAll(s);
                 }
             }
         }
 
-        // now find the given package in the set of candidate apps
-        for (ApplicationInfo app : apps) {
-            if (app.packageName.equals(packageName)) {
-                if (DEBUG) Slog.v(TAG, "Found the app - running clear process");
-                // found it; fire off the clear request
-                synchronized (mQueueLock) {
-                    long oldId = Binder.clearCallingIdentity();
-                    mWakelock.acquire();
-                    Message msg = mBackupHandler.obtainMessage(MSG_RUN_CLEAR,
-                            new ClearParams(getTransport(mCurrentTransport), info));
-                    mBackupHandler.sendMessage(msg);
-                    Binder.restoreCallingIdentity(oldId);
-                }
-                break;
+        // Is the given app an available participant?
+        if (apps.contains(packageName)) {
+            if (DEBUG) Slog.v(TAG, "Found the app - running clear process");
+            // found it; fire off the clear request
+            synchronized (mQueueLock) {
+                long oldId = Binder.clearCallingIdentity();
+                mWakelock.acquire();
+                Message msg = mBackupHandler.obtainMessage(MSG_RUN_CLEAR,
+                        new ClearParams(getTransport(mCurrentTransport), info));
+                mBackupHandler.sendMessage(msg);
+                Binder.restoreCallingIdentity(oldId);
             }
         }
     }
@@ -5824,9 +5782,9 @@
                 int uid = mBackupParticipants.keyAt(i);
                 pw.print("  uid: ");
                 pw.println(uid);
-                HashSet<ApplicationInfo> participants = mBackupParticipants.valueAt(i);
-                for (ApplicationInfo app: participants) {
-                    pw.println("    " + app.packageName);
+                HashSet<String> participants = mBackupParticipants.valueAt(i);
+                for (String app: participants) {
+                    pw.println("    " + app);
                 }
             }
 
diff --git a/services/java/com/android/server/BootReceiver.java b/services/java/com/android/server/BootReceiver.java
index da65438..22874e6 100644
--- a/services/java/com/android/server/BootReceiver.java
+++ b/services/java/com/android/server/BootReceiver.java
@@ -20,13 +20,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.net.Downloads;
 import android.os.Build;
 import android.os.DropBoxManager;
 import android.os.FileObserver;
 import android.os.FileUtils;
 import android.os.RecoverySystem;
 import android.os.SystemProperties;
+import android.provider.Downloads;
 import android.util.Slog;
 
 import java.io.File;
@@ -78,9 +78,8 @@
         }.start();
     }
 
-    private void removeOldUpdatePackages(Context ctx) {
-        Downloads.ByUri.removeAllDownloadsByPackage(
-            ctx, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS);
+    private void removeOldUpdatePackages(Context context) {
+        Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS);
     }
 
     private void logBootEvents(Context ctx) throws IOException {
diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java
index 062ab74..2e2a278 100644
--- a/services/java/com/android/server/ClipboardService.java
+++ b/services/java/com/android/server/ClipboardService.java
@@ -34,6 +34,7 @@
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.UserId;
 import android.util.Pair;
 import android.util.Slog;
 
@@ -204,7 +205,7 @@
         PackageInfo pi;
         try {
             pi = mPm.getPackageInfo(pkg, 0);
-            if (pi.applicationInfo.uid != uid) {
+            if (!UserId.isSameApp(pi.applicationInfo.uid, uid)) {
                 throw new SecurityException("Calling uid " + uid
                         + " does not own package " + pkg);
             }
diff --git a/services/java/com/android/server/IntentResolver.java b/services/java/com/android/server/IntentResolver.java
index b3d7220..f7e841e 100644
--- a/services/java/com/android/server/IntentResolver.java
+++ b/services/java/com/android/server/IntentResolver.java
@@ -201,7 +201,7 @@
     }
 
     public List<R> queryIntentFromList(Intent intent, String resolvedType, 
-            boolean defaultOnly, ArrayList<ArrayList<F>> listCut) {
+            boolean defaultOnly, ArrayList<ArrayList<F>> listCut, int userId) {
         ArrayList<R> resultList = new ArrayList<R>();
 
         final boolean debug = localLOGV ||
@@ -212,13 +212,14 @@
         int N = listCut.size();
         for (int i = 0; i < N; ++i) {
             buildResolveList(intent, categories, debug, defaultOnly,
-                             resolvedType, scheme, listCut.get(i), resultList);
+                    resolvedType, scheme, listCut.get(i), resultList, userId);
         }
         sortResults(resultList);
         return resultList;
     }
 
-    public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly) {
+    public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,
+            int userId) {
         String scheme = intent.getScheme();
 
         ArrayList<R> finalList = new ArrayList<R>();
@@ -290,19 +291,19 @@
         FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
         if (firstTypeCut != null) {
             buildResolveList(intent, categories, debug, defaultOnly,
-                    resolvedType, scheme, firstTypeCut, finalList);
+                    resolvedType, scheme, firstTypeCut, finalList, userId);
         }
         if (secondTypeCut != null) {
             buildResolveList(intent, categories, debug, defaultOnly,
-                    resolvedType, scheme, secondTypeCut, finalList);
+                    resolvedType, scheme, secondTypeCut, finalList, userId);
         }
         if (thirdTypeCut != null) {
             buildResolveList(intent, categories, debug, defaultOnly,
-                    resolvedType, scheme, thirdTypeCut, finalList);
+                    resolvedType, scheme, thirdTypeCut, finalList, userId);
         }
         if (schemeCut != null) {
             buildResolveList(intent, categories, debug, defaultOnly,
-                    resolvedType, scheme, schemeCut, finalList);
+                    resolvedType, scheme, schemeCut, finalList, userId);
         }
         sortResults(finalList);
 
@@ -329,7 +330,7 @@
      * "stopped," that is whether it should not be included in the result
      * if the intent requests to excluded stopped objects.
      */
-    protected boolean isFilterStopped(F filter) {
+    protected boolean isFilterStopped(F filter, int userId) {
         return false;
     }
 
@@ -341,7 +342,7 @@
     protected abstract String packageForFilter(F filter);
     
     @SuppressWarnings("unchecked")
-    protected R newResult(F filter, int match) {
+    protected R newResult(F filter, int match, int userId) {
         return (R)filter;
     }
 
@@ -504,7 +505,7 @@
 
     private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories,
             boolean debug, boolean defaultOnly,
-            String resolvedType, String scheme, List<F> src, List<R> dest) {
+            String resolvedType, String scheme, List<F> src, List<R> dest, int userId) {
         final String action = intent.getAction();
         final Uri data = intent.getData();
         final String packageName = intent.getPackage();
@@ -519,7 +520,7 @@
             int match;
             if (debug) Slog.v(TAG, "Matching against filter " + filter);
 
-            if (excludingStopped && isFilterStopped(filter)) {
+            if (excludingStopped && isFilterStopped(filter, userId)) {
                 if (debug) {
                     Slog.v(TAG, "  Filter's target is stopped; skipping");
                 }
@@ -547,7 +548,7 @@
                 if (debug) Slog.v(TAG, "  Filter matched!  match=0x" +
                         Integer.toHexString(match));
                 if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
-                    final R oneResult = newResult(filter, match);
+                    final R oneResult = newResult(filter, match, userId);
                     if (oneResult != null) {
                         dest.add(oneResult);
                     }
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 366160b..510bdb2 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -48,6 +48,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.UserId;
 import android.os.storage.IMountService;
 import android.os.storage.IMountServiceListener;
 import android.os.storage.IMountShutdownObserver;
@@ -1674,7 +1675,7 @@
             return false;
         }
 
-        final int packageUid = mPms.getPackageUid(packageName);
+        final int packageUid = mPms.getPackageUid(packageName, UserId.getUserId(callerUid));
 
         if (DEBUG_OBB) {
             Slog.d(TAG, "packageName = " + packageName + ", packageUid = " +
diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java
index 8ee12bc..6d83f30 100644
--- a/services/java/com/android/server/WallpaperManagerService.java
+++ b/services/java/com/android/server/WallpaperManagerService.java
@@ -25,9 +25,11 @@
 import android.app.WallpaperInfo;
 import android.app.backup.BackupManager;
 import android.app.backup.WallpaperBackupHelper;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -36,6 +38,7 @@
 import android.content.res.Resources;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.FileUtils;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -401,41 +404,48 @@
             wallpaper.wallpaperObserver.stopWatching();
         }
     }
-    
+
     public void systemReady() {
         if (DEBUG) Slog.v(TAG, "systemReady");
         WallpaperData wallpaper = mWallpaperMap.get(0);
         switchWallpaper(wallpaper);
         wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
         wallpaper.wallpaperObserver.startWatching();
-        ActivityManagerService ams = (ActivityManagerService) ServiceManager
-                .getService(Context.ACTIVITY_SERVICE);
-        ams.addUserListener(new ActivityManagerService.UserListener() {
 
+        IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(Intent.ACTION_USER_SWITCHED);
+        userFilter.addAction(Intent.ACTION_USER_REMOVED);
+        mContext.registerReceiver(new BroadcastReceiver() {
             @Override
-            public void onUserChanged(int userId) {
-                switchUser(userId);
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                    switchUser(intent.getIntExtra(Intent.EXTRA_USERID, 0));
+                } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                    removeUser(intent.getIntExtra(Intent.EXTRA_USERID, 0));
+                }
             }
-
-            @Override
-            public void onUserAdded(int userId) {
-            }
-
-            @Override
-            public void onUserRemoved(int userId) {
-            }
-
-            @Override
-            public void onUserLoggedOut(int userId) {
-            }
-
-        });
+        }, userFilter);
     }
 
     String getName() {
         return mWallpaperMap.get(0).name;
     }
 
+    void removeUser(int userId) {
+        synchronized (mLock) {
+            WallpaperData wallpaper = mWallpaperMap.get(userId);
+            if (wallpaper != null) {
+                wallpaper.wallpaperObserver.stopWatching();
+                mWallpaperMap.remove(userId);
+            }
+            File wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER);
+            wallpaperFile.delete();
+            File wallpaperInfoFile = new File(getWallpaperDir(userId), WALLPAPER_INFO);
+            wallpaperInfoFile.delete();
+        }
+    }
+
     void switchUser(int userId) {
         synchronized (mLock) {
             mCurrentUserId = userId;
@@ -861,7 +871,7 @@
     }
 
     private static JournaledFile makeJournaledFile(int userId) {
-        final String base = "/data/system/users/" + userId + "/" + WALLPAPER_INFO;
+        final String base = getWallpaperDir(userId) + "/" + WALLPAPER_INFO;
         return new JournaledFile(new File(base), new File(base + ".tmp"));
     }
 
diff --git a/services/java/com/android/server/WiredAccessoryObserver.java b/services/java/com/android/server/WiredAccessoryObserver.java
index 326b940..9b4eddc 100644
--- a/services/java/com/android/server/WiredAccessoryObserver.java
+++ b/services/java/com/android/server/WiredAccessoryObserver.java
@@ -30,8 +30,11 @@
 import android.media.AudioManager;
 import android.util.Log;
 
+import java.io.File;
 import java.io.FileReader;
 import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * <p>WiredAccessoryObserver monitors for a wired headset on the main board or dock.
@@ -39,17 +42,6 @@
 class WiredAccessoryObserver extends UEventObserver {
     private static final String TAG = WiredAccessoryObserver.class.getSimpleName();
     private static final boolean LOG = true;
-    private static final int MAX_AUDIO_PORTS = 3; /* h2w, USB Audio & hdmi */
-    private static final String uEventInfo[][] = { {"DEVPATH=/devices/virtual/switch/h2w",
-                                                    "/sys/class/switch/h2w/state",
-                                                    "/sys/class/switch/h2w/name"},
-                                                   {"DEVPATH=/devices/virtual/switch/usb_audio",
-                                                    "/sys/class/switch/usb_audio/state",
-                                                    "/sys/class/switch/usb_audio/name"},
-                                                   {"DEVPATH=/devices/virtual/switch/hdmi",
-                                                    "/sys/class/switch/hdmi/state",
-                                                    "/sys/class/switch/hdmi/name"} };
-
     private static final int BIT_HEADSET = (1 << 0);
     private static final int BIT_HEADSET_NO_MIC = (1 << 1);
     private static final int BIT_USB_HEADSET_ANLG = (1 << 2);
@@ -60,10 +52,89 @@
                                                    BIT_HDMI_AUDIO);
     private static final int HEADSETS_WITH_MIC = BIT_HEADSET;
 
+    private static class UEventInfo {
+        private final String mDevName;
+        private final int mState1Bits;
+        private final int mState2Bits;
+
+        public UEventInfo(String devName, int state1Bits, int state2Bits) {
+            mDevName = devName;
+            mState1Bits = state1Bits;
+            mState2Bits = state2Bits;
+        }
+
+        public String getDevName() { return mDevName; }
+
+        public String getDevPath() {
+            return String.format("/devices/virtual/switch/%s", mDevName);
+        }
+
+        public String getSwitchStatePath() {
+            return String.format("/sys/class/switch/%s/state", mDevName);
+        }
+
+        public boolean checkSwitchExists() {
+            File f = new File(getSwitchStatePath());
+            return ((null != f) && f.exists());
+        }
+
+        public int computeNewHeadsetState(int headsetState, int switchState) {
+            int preserveMask = ~(mState1Bits | mState2Bits);
+            int setBits = ((switchState == 1) ? mState1Bits :
+                          ((switchState == 2) ? mState2Bits : 0));
+
+            return ((headsetState & preserveMask) | setBits);
+        }
+    }
+
+    private static List<UEventInfo> makeObservedUEventList() {
+        List<UEventInfo> retVal = new ArrayList<UEventInfo>();
+        UEventInfo uei;
+
+        // Monitor h2w
+        uei = new UEventInfo("h2w", BIT_HEADSET, BIT_HEADSET_NO_MIC);
+        if (uei.checkSwitchExists()) {
+            retVal.add(uei);
+        } else {
+            Slog.w(TAG, "This kernel does not have wired headset support");
+        }
+
+        // Monitor USB
+        uei = new UEventInfo("usb_audio", BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL);
+        if (uei.checkSwitchExists()) {
+            retVal.add(uei);
+        } else {
+            Slog.w(TAG, "This kernel does not have usb audio support");
+        }
+
+        // Monitor HDMI
+        //
+        // If the kernel has support for the "hdmi_audio" switch, use that.  It will be signalled
+        // only when the HDMI driver has a video mode configured, and the downstream sink indicates
+        // support for audio in its EDID.
+        //
+        // If the kernel does not have an "hdmi_audio" switch, just fall back on the older "hdmi"
+        // switch instead.
+        uei = new UEventInfo("hdmi_audio", BIT_HDMI_AUDIO, 0);
+        if (uei.checkSwitchExists()) {
+            retVal.add(uei);
+        } else {
+            uei = new UEventInfo("hdmi", BIT_HDMI_AUDIO, 0);
+            if (uei.checkSwitchExists()) {
+                retVal.add(uei);
+            } else {
+                Slog.w(TAG, "This kernel does not have HDMI audio support");
+            }
+        }
+
+        return retVal;
+    }
+
+    private static List<UEventInfo> uEventInfo = makeObservedUEventList();
+
     private int mHeadsetState;
     private int mPrevHeadsetState;
     private String mHeadsetName;
-    private int switchState;
 
     private final Context mContext;
     private final WakeLock mWakeLock;  // held while there is a pending route change
@@ -85,71 +156,60 @@
         // one on the board, one on the dock and one on HDMI:
         // observe three UEVENTs
         init();  // set initial status
-        for (int i = 0; i < MAX_AUDIO_PORTS; i++) {
-            startObserving(uEventInfo[i][0]);
+        for (int i = 0; i < uEventInfo.size(); ++i) {
+            UEventInfo uei = uEventInfo.get(i);
+            startObserving("DEVPATH="+uei.getDevPath());
         }
       }
-  }
+    }
 
     @Override
     public void onUEvent(UEventObserver.UEvent event) {
         if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());
 
         try {
+            String devPath = event.get("DEVPATH");
             String name = event.get("SWITCH_NAME");
             int state = Integer.parseInt(event.get("SWITCH_STATE"));
-            updateState(name, state);
+            updateState(devPath, name, state);
         } catch (NumberFormatException e) {
             Slog.e(TAG, "Could not parse switch state from event " + event);
         }
     }
 
-    private synchronized final void updateState(String name, int state)
+    private synchronized final void updateState(String devPath, String name, int state)
     {
-        if (name.equals("usb_audio")) {
-            switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|BIT_HDMI_AUDIO)) |
-                           ((state == 1) ? BIT_USB_HEADSET_ANLG :
-                                         ((state == 2) ? BIT_USB_HEADSET_DGTL : 0)));
-        } else if (name.equals("hdmi")) {
-            switchState = ((mHeadsetState & (BIT_HEADSET|BIT_HEADSET_NO_MIC|
-                                             BIT_USB_HEADSET_DGTL|BIT_USB_HEADSET_ANLG)) |
-                           ((state == 1) ? BIT_HDMI_AUDIO : 0));
-        } else {
-            switchState = ((mHeadsetState & (BIT_HDMI_AUDIO|BIT_USB_HEADSET_ANLG|
-                                             BIT_USB_HEADSET_DGTL)) |
-                            ((state == 1) ? BIT_HEADSET :
-                                          ((state == 2) ? BIT_HEADSET_NO_MIC : 0)));
+        for (int i = 0; i < uEventInfo.size(); ++i) {
+            UEventInfo uei = uEventInfo.get(i);
+            if (devPath.equals(uei.getDevPath())) {
+                update(name, uei.computeNewHeadsetState(mHeadsetState, state));
+                return;
+            }
         }
-        update(name, switchState);
     }
 
     private synchronized final void init() {
         char[] buffer = new char[1024];
-
-        String newName = mHeadsetName;
-        int newState = mHeadsetState;
         mPrevHeadsetState = mHeadsetState;
 
         if (LOG) Slog.v(TAG, "init()");
 
-        for (int i = 0; i < MAX_AUDIO_PORTS; i++) {
+        for (int i = 0; i < uEventInfo.size(); ++i) {
+            UEventInfo uei = uEventInfo.get(i);
             try {
-                FileReader file = new FileReader(uEventInfo[i][1]);
+                int curState;
+                FileReader file = new FileReader(uei.getSwitchStatePath());
                 int len = file.read(buffer, 0, 1024);
                 file.close();
-                newState = Integer.valueOf((new String(buffer, 0, len)).trim());
+                curState = Integer.valueOf((new String(buffer, 0, len)).trim());
 
-                file = new FileReader(uEventInfo[i][2]);
-                len = file.read(buffer, 0, 1024);
-                file.close();
-                newName = new String(buffer, 0, len).trim();
-
-                if (newState > 0) {
-                    updateState(newName, newState);
+                if (curState > 0) {
+                    updateState(uei.getDevPath(), uei.getDevName(), curState);
                 }
 
             } catch (FileNotFoundException e) {
-                Slog.w(TAG, "This kernel does not have wired headset support");
+                Slog.w(TAG, uei.getSwitchStatePath() +
+                        " not found while attempting to determine initial switch state");
             } catch (Exception e) {
                 Slog.e(TAG, "" , e);
             }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index d21212f..78f17bc 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -34,6 +34,7 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
+import android.app.ActivityOptions;
 import android.app.ActivityThread;
 import android.app.AlertDialog;
 import android.app.AppGlobals;
@@ -151,6 +152,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
@@ -1079,7 +1081,8 @@
                     int uid = msg.arg1;
                     boolean restart = (msg.arg2 == 1);
                     String pkg = (String) msg.obj;
-                    forceStopPackageLocked(pkg, uid, restart, false, true, false);
+                    forceStopPackageLocked(pkg, uid, restart, false, true, false,
+                            UserId.getUserId(uid));
                 }
             } break;
             case FINALIZE_PENDING_INTENT_MSG: {
@@ -1289,7 +1292,7 @@
 
             ApplicationInfo info =
                 mSelf.mContext.getPackageManager().getApplicationInfo(
-                        "android", STOCK_PM_FLAGS);
+                            "android", STOCK_PM_FLAGS);
             mSystemThread.installSystemApplicationInfo(info);
        
             synchronized (mSelf) {
@@ -2352,10 +2355,12 @@
         synchronized (this) {
             ActivityRecord r = mMainStack.isInStackLocked(callingActivity);
             if (r == null) {
+                ActivityOptions.abort(options);
                 return false;
             }
             if (r.app == null || r.app.thread == null) {
                 // The caller is not running...  d'oh!
+                ActivityOptions.abort(options);
                 return false;
             }
             intent = new Intent(intent);
@@ -2369,7 +2374,8 @@
                 List<ResolveInfo> resolves =
                     AppGlobals.getPackageManager().queryIntentActivities(
                             intent, r.resolvedType,
-                            PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS);
+                            PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS,
+                            UserId.getCallingUserId());
 
                 // Look for the original activity in the list...
                 final int N = resolves != null ? resolves.size() : 0;
@@ -2391,6 +2397,7 @@
 
             if (aInfo == null) {
                 // Nobody who is next!
+                ActivityOptions.abort(options);
                 return false;
             }
 
@@ -2420,8 +2427,6 @@
             }
 
             final long origId = Binder.clearCallingIdentity();
-            // XXX we are not dealing with propagating grantedUriPermissions...
-            // those are not yet exposed to user code, so there is no need.
             int res = mMainStack.startActivityLocked(r.app.thread, intent,
                     r.resolvedType, aInfo, resultTo != null ? resultTo.appToken : null,
                     resultWho, requestCode, -1, r.launchedFromUid, 0,
@@ -3034,7 +3039,7 @@
     }
 
     final void logAppTooSlow(ProcessRecord app, long startTime, String msg) {
-        if (IS_USER_BUILD) {
+        if (true || IS_USER_BUILD) {
             return;
         }
         String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
@@ -3291,7 +3296,7 @@
             int pkgUid = -1;
             synchronized(this) {
                 try {
-                    pkgUid = pm.getPackageUid(packageName);
+                    pkgUid = pm.getPackageUid(packageName, userId);
                 } catch (RemoteException e) {
                 }
                 if (pkgUid == -1) {
@@ -3312,7 +3317,7 @@
             
             try {
                 //clear application user data
-                pm.clearApplicationUserData(packageName, observer);
+                pm.clearApplicationUserData(packageName, observer, userId);
                 Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED,
                         Uri.fromParts("package", packageName, null));
                 intent.putExtra(Intent.EXTRA_UID, pkgUid);
@@ -3339,13 +3344,14 @@
             throw new SecurityException(msg);
         }
         
+        int userId = UserId.getCallingUserId();
         long callingId = Binder.clearCallingIdentity();
         try {
             IPackageManager pm = AppGlobals.getPackageManager();
             int pkgUid = -1;
             synchronized(this) {
                 try {
-                    pkgUid = pm.getPackageUid(packageName);
+                    pkgUid = pm.getPackageUid(packageName, userId);
                 } catch (RemoteException e) {
                 }
                 if (pkgUid == -1) {
@@ -3412,16 +3418,14 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
-        final int userId = Binder.getOrigCallingUser();
+        final int userId = UserId.getCallingUserId();
         long callingId = Binder.clearCallingIdentity();
         try {
             IPackageManager pm = AppGlobals.getPackageManager();
             int pkgUid = -1;
             synchronized(this) {
                 try {
-                    pkgUid = pm.getPackageUid(packageName);
-                    // Convert the uid to the one for the calling user
-                    pkgUid = UserId.getUid(userId, pkgUid);
+                    pkgUid = pm.getPackageUid(packageName, userId);
                 } catch (RemoteException e) {
                 }
                 if (pkgUid == -1) {
@@ -3430,7 +3434,7 @@
                 }
                 forceStopPackageLocked(packageName, pkgUid);
                 try {
-                    pm.setPackageStoppedState(packageName, true);
+                    pm.setPackageStoppedState(packageName, true, userId);
                 } catch (RemoteException e) {
                 } catch (IllegalArgumentException e) {
                     Slog.w(TAG, "Failed trying to unstop package "
@@ -3545,7 +3549,7 @@
     }
 
     private void forceStopPackageLocked(final String packageName, int uid) {
-        forceStopPackageLocked(packageName, uid, false, false, true, false);
+        forceStopPackageLocked(packageName, uid, false, false, true, false, UserId.getUserId(uid));
         Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED,
                 Uri.fromParts("package", packageName, null));
         if (!mProcessesReady) {
@@ -3579,9 +3583,14 @@
                     if (doit) {
                         procs.add(app);
                     }
-                } else if ((uid > 0 && uid != Process.SYSTEM_UID && app.info.uid == uid)
-                        || app.processName.equals(packageName)
-                        || app.processName.startsWith(procNamePrefix)) {
+                // If uid is specified and the uid and process name match
+                // Or, the uid is not specified and the process name matches
+                } else if (((uid > 0 && uid != Process.SYSTEM_UID && app.info.uid == uid)
+                            && (app.processName.equals(packageName)
+                                || app.processName.startsWith(procNamePrefix)))
+                           || (uid < 0
+                               && (app.processName.equals(packageName)
+                                       || app.processName.startsWith(procNamePrefix)))) {
                     if (app.setAdj >= minOomAdj) {
                         if (!doit) {
                             return true;
@@ -3602,13 +3611,13 @@
 
     private final boolean forceStopPackageLocked(String name, int uid,
             boolean callerWillRestart, boolean purgeCache, boolean doit,
-            boolean evenPersistent) {
+            boolean evenPersistent, int userId) {
         int i;
         int N;
 
         if (uid < 0) {
             try {
-                uid = AppGlobals.getPackageManager().getPackageUid(name);
+                uid = AppGlobals.getPackageManager().getPackageUid(name, userId);
             } catch (RemoteException e) {
             }
         }
@@ -3632,7 +3641,8 @@
         for (i=0; i<mMainStack.mHistory.size(); i++) {
             ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i);
             final boolean samePackage = r.packageName.equals(name);
-            if ((samePackage || r.task == lastTask)
+            if (r.userId == userId
+                    && (samePackage || r.task == lastTask)
                     && (r.app == null || evenPersistent || !r.app.persistent)) {
                 if (!doit) {
                     if (r.finishing) {
@@ -3652,14 +3662,13 @@
                 }
                 lastTask = r.task;
                 if (r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED,
-                        null, "force-stop")) {
+                        null, "force-stop", true)) {
                     i--;
                 }
             }
         }
 
         ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
-        int userId = UserId.getUserId(uid);
         for (ServiceRecord service : mServiceMap.getAllServices(userId)) {
             if (service.packageName.equals(name)
                     && (service.app == null || evenPersistent || !service.app.persistent)) {
@@ -3683,7 +3692,7 @@
         }
 
         ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>();
-        for (ContentProviderRecord provider : mProviderMap.getProvidersByClass(-1).values()) {
+        for (ContentProviderRecord provider : mProviderMap.getProvidersByClass(userId).values()) {
             if (provider.info.packageName.equals(name)
                     && (provider.proc == null || evenPersistent || !provider.proc.persistent)) {
                 if (!doit) {
@@ -4107,16 +4116,25 @@
                 if (pkgs != null) {
                     for (String pkg : pkgs) {
                         synchronized (ActivityManagerService.this) {
-                          if (forceStopPackageLocked(pkg, -1, false, false, false, false)) {
-                              setResultCode(Activity.RESULT_OK);
-                              return;
-                          }
-                       }
+                            if (forceStopPackageLocked(pkg, -1, false, false, false, false, 0)) {
+                                setResultCode(Activity.RESULT_OK);
+                                return;
+                            }
+                        }
                     }
                 }
             }
         }, pkgFilter);
-        
+
+        IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(Intent.ACTION_USER_REMOVED);
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                onUserRemoved(intent);
+            }
+        }, userFilter);
+
         synchronized (this) {
             // Ensure that any processes we had put on hold are now started
             // up.
@@ -4290,8 +4308,8 @@
             try {
                 if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
                     int uid = AppGlobals.getPackageManager()
-                            .getPackageUid(packageName);
-                    if (UserId.getAppId(callingUid) != uid) {
+                            .getPackageUid(packageName, UserId.getUserId(callingUid));
+                    if (!UserId.isSameApp(callingUid, uid)) {
                         String msg = "Permission Denial: getIntentSender() from pid="
                             + Binder.getCallingPid()
                             + ", uid=" + Binder.getCallingUid()
@@ -4386,7 +4404,7 @@
             PendingIntentRecord rec = (PendingIntentRecord)sender;
             try {
                 int uid = AppGlobals.getPackageManager()
-                        .getPackageUid(rec.key.packageName);
+                        .getPackageUid(rec.key.packageName, UserId.getCallingUserId());
                 if (!UserId.isSameApp(uid, Binder.getCallingUid())) {
                     String msg = "Permission Denial: cancelIntentSender() from pid="
                         + Binder.getCallingPid()
@@ -4796,7 +4814,7 @@
         } else {
             try {
                 pi = pm.resolveContentProvider(name,
-                        PackageManager.GET_URI_PERMISSION_PATTERNS);
+                        PackageManager.GET_URI_PERMISSION_PATTERNS, UserId.getUserId(callingUid));
             } catch (RemoteException ex) {
             }
         }
@@ -4808,7 +4826,7 @@
         int targetUid = lastTargetUid;
         if (targetUid < 0 && targetPkg != null) {
             try {
-                targetUid = pm.getPackageUid(targetPkg);
+                targetUid = pm.getPackageUid(targetPkg, UserId.getUserId(callingUid));
                 if (targetUid < 0) {
                     if (DEBUG_URI_PERMISSION) Slog.v(TAG,
                             "Can't grant URI permission no uid for: " + targetPkg);
@@ -5100,14 +5118,14 @@
 
         final String authority = uri.getAuthority();
         ProviderInfo pi = null;
-        ContentProviderRecord cpr = mProviderMap.getProviderByName(authority,
-                UserId.getUserId(callingUid));
+        int userId = UserId.getUserId(callingUid);
+        ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userId);
         if (cpr != null) {
             pi = cpr.info;
         } else {
             try {
                 pi = pm.resolveContentProvider(authority,
-                        PackageManager.GET_URI_PERMISSION_PATTERNS);
+                        PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
             } catch (RemoteException ex) {
             }
         }
@@ -5202,7 +5220,7 @@
             } else {
                 try {
                     pi = pm.resolveContentProvider(authority,
-                            PackageManager.GET_URI_PERMISSION_PATTERNS);
+                            PackageManager.GET_URI_PERMISSION_PATTERNS, r.userId);
                 } catch (RemoteException ex) {
                 }
             }
@@ -5480,12 +5498,13 @@
                         // Check whether this activity is currently available.
                         try {
                             if (rti.origActivity != null) {
-                                if (pm.getActivityInfo(rti.origActivity, 0) == null) {
+                                if (pm.getActivityInfo(rti.origActivity, 0, callingUserId)
+                                        == null) {
                                     continue;
                                 }
                             } else if (rti.baseIntent != null) {
                                 if (pm.queryIntentActivities(rti.baseIntent,
-                                        null, 0) == null) {
+                                        null, 0, callingUserId) == null) {
                                     continue;
                                 }
                             }
@@ -5685,13 +5704,14 @@
     /**
      * TODO: Add mController hook
      */
-    public void moveTaskToFront(int task, int flags) {
+    public void moveTaskToFront(int task, int flags, Bundle options) {
         enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,
                 "moveTaskToFront()");
 
         synchronized(this) {
             if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
                     Binder.getCallingUid(), "Task to front")) {
+                ActivityOptions.abort(options);
                 return;
             }
             final long origId = Binder.clearCallingIdentity();
@@ -5706,7 +5726,7 @@
                         // we'll just move the home task to the top first.
                         mMainStack.moveHomeToFrontLocked();
                     }
-                    mMainStack.moveTaskToFrontLocked(tr, null);
+                    mMainStack.moveTaskToFrontLocked(tr, null, options);
                     return;
                 }
                 for (int i=mMainStack.mHistory.size()-1; i>=0; i--) {
@@ -5720,13 +5740,14 @@
                             // we'll just move the home task to the top first.
                             mMainStack.moveHomeToFrontLocked();
                         }
-                        mMainStack.moveTaskToFrontLocked(hr.task, null);
+                        mMainStack.moveTaskToFrontLocked(hr.task, null, options);
                         return;
                     }
                 }
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
+            ActivityOptions.abort(options);
         }
     }
 
@@ -6132,15 +6153,14 @@
                 try {
                     cpi = AppGlobals.getPackageManager().
                         resolveContentProvider(name,
-                                STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS);
+                            STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
                 } catch (RemoteException ex) {
                 }
                 if (cpi == null) {
                     return null;
                 }
 
-                cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo,
-                        Binder.getOrigCallingUser());
+                cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);
 
                 String msg;
                 if ((msg=checkContentProviderPermissionLocked(cpi, r)) != null) {
@@ -6157,7 +6177,7 @@
                 }
 
                 ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
-                cpr = mProviderMap.getProviderByClass(comp, Binder.getOrigCallingUser());
+                cpr = mProviderMap.getProviderByClass(comp, userId);
                 final boolean firstClass = cpr == null;
                 if (firstClass) {
                     try {
@@ -6165,13 +6185,13 @@
                             AppGlobals.getPackageManager().
                                 getApplicationInfo(
                                         cpi.applicationInfo.packageName,
-                                        STOCK_PM_FLAGS);
+                                        STOCK_PM_FLAGS, userId);
                         if (ai == null) {
                             Slog.w(TAG, "No package info for content provider "
                                     + cpi.name);
                             return null;
                         }
-                        ai = getAppInfoForUser(ai, Binder.getOrigCallingUser());
+                        ai = getAppInfoForUser(ai, userId);
                         cpr = new ContentProviderRecord(this, cpi, ai, comp);
                     } catch (RemoteException ex) {
                         // pm is in same process, this will never happen.
@@ -6212,7 +6232,7 @@
                         // Content provider is now in use, its package can't be stopped.
                         try {
                             AppGlobals.getPackageManager().setPackageStoppedState(
-                                    cpr.appInfo.packageName, false);
+                                    cpr.appInfo.packageName, false, userId);
                         } catch (RemoteException e) {
                         } catch (IllegalArgumentException e) {
                             Slog.w(TAG, "Failed trying to unstop package "
@@ -6546,7 +6566,7 @@
         // This package really, really can not be stopped.
         try {
             AppGlobals.getPackageManager().setPackageStoppedState(
-                    info.packageName, false);
+                    info.packageName, false, UserId.getUserId(app.uid));
         } catch (RemoteException e) {
         } catch (IllegalArgumentException e) {
             Slog.w(TAG, "Failed trying to unstop package "
@@ -6778,7 +6798,7 @@
             mDebugTransient = !persistent;
             if (packageName != null) {
                 final long origId = Binder.clearCallingIdentity();
-                forceStopPackageLocked(packageName, -1, false, false, true, true);
+                forceStopPackageLocked(packageName, -1, false, false, true, true, 0);
                 Binder.restoreCallingIdentity(origId);
             }
         }
@@ -6993,7 +7013,43 @@
         }
         return killed;
     }
-    
+
+    @Override
+    public boolean killProcessesBelowForeground(String reason) {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("killProcessesBelowForeground() only available to system");
+        }
+
+        return killProcessesBelowAdj(ProcessList.FOREGROUND_APP_ADJ, reason);
+    }
+
+    private boolean killProcessesBelowAdj(int belowAdj, String reason) {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("killProcessesBelowAdj() only available to system");
+        }
+
+        boolean killed = false;
+        synchronized (mPidsSelfLocked) {
+            final int size = mPidsSelfLocked.size();
+            for (int i = 0; i < size; i++) {
+                final int pid = mPidsSelfLocked.keyAt(i);
+                final ProcessRecord proc = mPidsSelfLocked.valueAt(i);
+                if (proc == null) continue;
+
+                final int adj = proc.setAdj;
+                if (adj > belowAdj && !proc.killedBackground) {
+                    Slog.w(TAG, "Killing " + proc + " (adj " + adj + "): " + reason);
+                    EventLog.writeEvent(
+                            EventLogTags.AM_KILL, proc.pid, proc.processName, adj, reason);
+                    killed = true;
+                    proc.killedBackground = true;
+                    Process.killProcessQuiet(pid);
+                }
+            }
+        }
+        return killed;
+    }
+
     public final void startRunning(String pkg, String cls, String action,
             String data) {
         synchronized(this) {
@@ -7137,7 +7193,7 @@
                 List<ResolveInfo> ris = null;
                 try {
                     ris = AppGlobals.getPackageManager().queryIntentReceivers(
-                                intent, null, 0);
+                            intent, null, 0, 0);
                 } catch (RemoteException e) {
                 }
                 if (ris != null) {
@@ -7394,6 +7450,10 @@
     }
 
     private boolean handleAppCrashLocked(ProcessRecord app) {
+        if (mHeadless) {
+            Log.e(TAG, "handleAppCrashLocked: " + app.processName);
+            return false;
+        }
         long now = SystemClock.uptimeMillis();
 
         Long crashTime;
@@ -7441,7 +7501,7 @@
             mMainStack.resumeTopActivityLocked(null);
         } else {
             ActivityRecord r = mMainStack.topRunningActivityLocked(null);
-            if (r.app == app) {
+            if (r != null && r.app == app) {
                 // If the top running activity is from this crashing
                 // process, then terminate it to avoid getting in a loop.
                 Slog.w(TAG, "  Force finishing activity "
@@ -7803,7 +7863,7 @@
             for (String pkg : process.pkgList) {
                 sb.append("Package: ").append(pkg);
                 try {
-                    PackageInfo pi = pm.getPackageInfo(pkg, 0);
+                    PackageInfo pi = pm.getPackageInfo(pkg, 0, 0);
                     if (pi != null) {
                         sb.append(" v").append(pi.versionCode);
                         if (pi.versionName != null) {
@@ -8201,7 +8261,7 @@
             IPackageManager pm = AppGlobals.getPackageManager();
             for (String pkg : extList) {
                 try {
-                    ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
+                    ApplicationInfo info = pm.getApplicationInfo(pkg, 0, UserId.getCallingUserId());
                     if ((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
                         retList.add(info);
                     }
@@ -10682,21 +10742,21 @@
     };
 
     private ServiceLookupResult findServiceLocked(Intent service,
-            String resolvedType) {
+            String resolvedType, int userId) {
         ServiceRecord r = null;
         if (service.getComponent() != null) {
-            r = mServiceMap.getServiceByName(service.getComponent(), Binder.getOrigCallingUser());
+            r = mServiceMap.getServiceByName(service.getComponent(), userId);
         }
         if (r == null) {
             Intent.FilterComparison filter = new Intent.FilterComparison(service);
-            r = mServiceMap.getServiceByIntent(filter, Binder.getOrigCallingUser());
+            r = mServiceMap.getServiceByIntent(filter, userId);
         }
 
         if (r == null) {
             try {
                 ResolveInfo rInfo =
                     AppGlobals.getPackageManager().resolveService(
-                            service, resolvedType, 0);
+                                service, resolvedType, 0, userId);
                 ServiceInfo sInfo =
                     rInfo != null ? rInfo.serviceInfo : null;
                 if (sInfo == null) {
@@ -10767,7 +10827,7 @@
             try {
                 ResolveInfo rInfo =
                     AppGlobals.getPackageManager().resolveService(
-                            service, resolvedType, STOCK_PM_FLAGS);
+                                service, resolvedType, STOCK_PM_FLAGS, userId);
                 ServiceInfo sInfo =
                     rInfo != null ? rInfo.serviceInfo : null;
                 if (sInfo == null) {
@@ -11132,7 +11192,7 @@
         // Service is now being launched, its package can't be stopped.
         try {
             AppGlobals.getPackageManager().setPackageStoppedState(
-                    r.packageName, false);
+                    r.packageName, false, r.userId);
         } catch (RemoteException e) {
         } catch (IllegalArgumentException e) {
             Slog.w(TAG, "Failed trying to unstop package "
@@ -11437,7 +11497,8 @@
             }
 
             // If this service is active, make sure it is stopped.
-            ServiceLookupResult r = findServiceLocked(service, resolvedType);
+            ServiceLookupResult r = findServiceLocked(service, resolvedType,
+                    callerApp == null ? UserId.getCallingUserId() : callerApp.userId);
             if (r != null) {
                 if (r.record != null) {
                     final long origId = Binder.clearCallingIdentity();
@@ -11465,7 +11526,8 @@
         IBinder ret = null;
 
         synchronized(this) {
-            ServiceLookupResult r = findServiceLocked(service, resolvedType);
+            ServiceLookupResult r = findServiceLocked(service, resolvedType,
+                    UserId.getCallingUserId());
             
             if (r != null) {
                 // r.record is null if findServiceLocked() failed the caller permission check
@@ -12094,7 +12156,7 @@
             // Backup agent is now in use, its package can't be stopped.
             try {
                 AppGlobals.getPackageManager().setPackageStoppedState(
-                        app.packageName, false);
+                        app.packageName, false, UserId.getUserId(app.uid));
             } catch (RemoteException e) {
             } catch (IllegalArgumentException e) {
                 Slog.w(TAG, "Failed trying to unstop package "
@@ -12423,7 +12485,7 @@
 
         if (DEBUG_BROADCAST_LIGHT) Slog.v(
             TAG, (sticky ? "Broadcast sticky: ": "Broadcast: ") + intent
-            + " ordered=" + ordered);
+            + " ordered=" + ordered + " userid=" + userId);
         if ((resultTo != null) && !ordered) {
             Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
         }
@@ -12458,7 +12520,7 @@
                         String list[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
                         if (list != null && (list.length > 0)) {
                             for (String pkg : list) {
-                                forceStopPackageLocked(pkg, -1, false, true, true, false);
+                                forceStopPackageLocked(pkg, -1, false, true, true, false, userId);
                             }
                             sendPackageBroadcastLocked(
                                     IApplicationThread.EXTERNAL_STORAGE_UNAVAILABLE, list);
@@ -12469,7 +12531,8 @@
                         if (data != null && (ssp=data.getSchemeSpecificPart()) != null) {
                             if (!intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false)) {
                                 forceStopPackageLocked(ssp,
-                                        intent.getIntExtra(Intent.EXTRA_UID, -1), false, true, true, false);
+                                        intent.getIntExtra(Intent.EXTRA_UID, -1), false, true, true,
+                                        false, userId);
                             }
                             if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
                                 sendPackageBroadcastLocked(IApplicationThread.PACKAGE_REMOVED,
@@ -12586,7 +12649,7 @@
             if (intent.getComponent() != null) {
                 // Broadcast is going to one specific receiver class...
                 ActivityInfo ai = AppGlobals.getPackageManager().
-                    getReceiverInfo(intent.getComponent(), STOCK_PM_FLAGS);
+                        getReceiverInfo(intent.getComponent(), STOCK_PM_FLAGS, userId);
                 if (ai != null) {
                     receivers = new ArrayList();
                     ResolveInfo ri = new ResolveInfo();
@@ -12594,15 +12657,15 @@
                     receivers.add(ri);
                 }
             } else {
-                // TODO: Apply userId
                 // Need to resolve the intent to interested receivers...
                 if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
                          == 0) {
                     receivers =
                         AppGlobals.getPackageManager().queryIntentReceivers(
-                                intent, resolvedType, STOCK_PM_FLAGS);
+                                    intent, resolvedType, STOCK_PM_FLAGS, userId);
                 }
-                registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false);
+                registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false,
+                        userId);
             }
         } catch (RemoteException ex) {
             // pm is in same process, this will never happen.
@@ -12893,7 +12956,7 @@
                 ii = mContext.getPackageManager().getInstrumentationInfo(
                     className, STOCK_PM_FLAGS);
                 ai = mContext.getPackageManager().getApplicationInfo(
-                    ii.targetPackage, STOCK_PM_FLAGS);
+                        ii.targetPackage, STOCK_PM_FLAGS);
             } catch (PackageManager.NameNotFoundException e) {
             }
             if (ii == null) {
@@ -12921,9 +12984,10 @@
                 throw new SecurityException(msg);
             }
 
+            int userId = UserId.getCallingUserId();
             final long origId = Binder.clearCallingIdentity();
             // Instrumentation can kill and relaunch even persistent processes
-            forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true);
+            forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, userId);
             ProcessRecord app = addAppLocked(ai, false);
             app.instrumentationClass = className;
             app.instrumentationInfo = ai;
@@ -12978,11 +13042,12 @@
         app.instrumentationProfileFile = null;
         app.instrumentationArguments = null;
 
-        forceStopPackageLocked(app.processName, -1, false, false, true, true);
+        forceStopPackageLocked(app.processName, -1, false, false, true, true, app.userId);
     }
 
     public void finishInstrumentation(IApplicationThread target,
             int resultCode, Bundle results) {
+        int userId = UserId.getCallingUserId();
         // Refuse possible leaked file descriptors
         if (results != null && results.hasFileDescriptors()) {
             throw new IllegalArgumentException("File descriptors passed in Intent");
@@ -14544,25 +14609,6 @@
 
     private int mCurrentUserId;
     private SparseIntArray mLoggedInUsers = new SparseIntArray(5);
-    private ArrayList<UserListener> mUserListeners = new ArrayList<UserListener>(3);
-
-    public interface UserListener {
-        public void onUserChanged(int userId);
-
-        public void onUserAdded(int userId);
-
-        public void onUserRemoved(int userId);
-
-        public void onUserLoggedOut(int userId);
-    }
-
-    public void addUserListener(UserListener listener) {
-        synchronized (this) {
-            if (!mUserListeners.contains(listener)) {
-                mUserListeners.add(listener);
-            }
-        }
-    }
 
     public boolean switchUser(int userId) {
         final int callingUid = Binder.getCallingUid();
@@ -14573,8 +14619,6 @@
         if (mCurrentUserId == userId)
             return true;
 
-        ArrayList<UserListener> listeners;
-
         synchronized (this) {
             // Check if user is already logged in, otherwise check if user exists first before
             // adding to the list of logged in users.
@@ -14591,23 +14635,44 @@
                 startHomeActivityLocked(userId);
             }
 
-            listeners = (ArrayList<UserListener>) mUserListeners.clone();
         }
-        // Inform the listeners
-        for (UserListener listener : listeners) {
-            listener.onUserChanged(userId);
-        }
+
+        // Inform of user switch
+        Intent addedIntent = new Intent(Intent.ACTION_USER_SWITCHED);
+        addedIntent.putExtra(Intent.EXTRA_USERID, userId);
+        mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_ACCOUNTS);
+
         return true;
     }
 
+    private void onUserRemoved(Intent intent) {
+        int extraUserId = intent.getIntExtra(Intent.EXTRA_USERID, -1);
+        if (extraUserId < 1) return;
+
+        // Kill all the processes for the user
+        ArrayList<Pair<String, Integer>> pkgAndUids = new ArrayList<Pair<String,Integer>>();
+        synchronized (this) {
+            HashMap<String,SparseArray<ProcessRecord>> map = mProcessNames.getMap();
+            for (Entry<String, SparseArray<ProcessRecord>> uidMap : map.entrySet()) {
+                SparseArray<ProcessRecord> uids = uidMap.getValue();
+                for (int i = 0; i < uids.size(); i++) {
+                    if (UserId.getUserId(uids.keyAt(i)) == extraUserId) {
+                        pkgAndUids.add(new Pair<String,Integer>(uidMap.getKey(), uids.keyAt(i)));
+                    }
+                }
+            }
+
+            for (Pair<String,Integer> pkgAndUid : pkgAndUids) {
+                forceStopPackageLocked(pkgAndUid.first, pkgAndUid.second,
+                        false, false, true, true, extraUserId);
+            }
+        }
+    }
+
     private boolean userExists(int userId) {
         try {
-            List<UserInfo> users = AppGlobals.getPackageManager().getUsers();
-            for (UserInfo user : users) {
-                if (user.id == userId) {
-                    return true;
-                }
-            }
+            UserInfo user = AppGlobals.getPackageManager().getUser(userId);
+            return user != null;
         } catch (RemoteException re) {
             // Won't happen, in same process
         }
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index 53cb2b0..d60ff2b 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -26,7 +26,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.CompatibilityInfo;
-import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.os.Build;
@@ -542,24 +541,38 @@
 
     void updateOptionsLocked(Bundle options) {
         if (options != null) {
+            if (pendingOptions != null) {
+                pendingOptions.abort();
+            }
             pendingOptions = new ActivityOptions(options);
         }
     }
 
     void applyOptionsLocked() {
         if (pendingOptions != null) {
-            if (pendingOptions.isCustomAnimation()) {
-                service.mWindowManager.overridePendingAppTransition(
-                        pendingOptions.getPackageName(),
-                        pendingOptions.getCustomEnterResId(),
-                        pendingOptions.getCustomExitResId());
+            switch (pendingOptions.getAnimationType()) {
+                case ActivityOptions.ANIM_CUSTOM:
+                    service.mWindowManager.overridePendingAppTransition(
+                            pendingOptions.getPackageName(),
+                            pendingOptions.getCustomEnterResId(),
+                            pendingOptions.getCustomExitResId());
+                    break;
+                case ActivityOptions.ANIM_THUMBNAIL:
+                    service.mWindowManager.overridePendingAppTransitionThumb(
+                            pendingOptions.getThumbnail(),
+                            pendingOptions.getStartX(), pendingOptions.getStartY(),
+                            pendingOptions.getOnAnimationStartListener());
+                    break;
             }
             pendingOptions = null;
         }
     }
 
     void clearOptionsLocked() {
-        pendingOptions = null;
+        if (pendingOptions != null) {
+            pendingOptions.abort();
+            pendingOptions = null;
+        }
     }
 
     void removeUriPermissionsLocked() {
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 7e8df35..a01ed25 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -1445,9 +1445,8 @@
         // Launching this app's activity, make sure the app is no longer
         // considered stopped.
         try {
-            // TODO: Apply to the correct userId
             AppGlobals.getPackageManager().setPackageStoppedState(
-                    next.packageName, false);
+                    next.packageName, false, next.userId); /* TODO: Verify if correct userid */
         } catch (RemoteException e1) {
         } catch (IllegalArgumentException e) {
             Slog.w(TAG, "Failed trying to unstop package "
@@ -1696,6 +1695,7 @@
                         if (VALIDATE_TOKENS) {
                             validateAppTokensLocked();
                         }
+                        ActivityOptions.abort(options);
                         return;
                     }
                     break;
@@ -1798,6 +1798,7 @@
             // because there is nothing for it to animate on top of.
             mService.mWindowManager.addAppToken(addPos, r.appToken, r.task.taskId,
                     r.info.screenOrientation, r.fullscreen);
+            ActivityOptions.abort(options);
         }
         if (VALIDATE_TOKENS) {
             validateAppTokensLocked();
@@ -2345,6 +2346,7 @@
             // Transfer the result target from the source activity to the new
             // one being started, including any failures.
             if (requestCode >= 0) {
+                ActivityOptions.abort(options);
                 return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
             }
             resultRecord = sourceRecord.resultTo;
@@ -2376,6 +2378,7 @@
                     Activity.RESULT_CANCELED, null);
             }
             mDismissKeyguardOnNextActivity = false;
+            ActivityOptions.abort(options);
             return err;
         }
 
@@ -2426,6 +2429,7 @@
                     // We pretend to the caller that it was really started, but
                     // they will just get a cancel result.
                     mDismissKeyguardOnNextActivity = false;
+                    ActivityOptions.abort(options);
                     return ActivityManager.START_SUCCESS;
                 }
             }
@@ -2448,6 +2452,7 @@
                     pal.startFlags = startFlags;
                     mService.mPendingActivityLaunches.add(pal);
                     mDismissKeyguardOnNextActivity = false;
+                    ActivityOptions.abort(options);
                     return ActivityManager.START_SWITCHES_CANCELED;
                 }
             }
@@ -2602,8 +2607,7 @@
                             // We really do want to push this one into the
                             // user's face, right now.
                             moveHomeToFrontFromLaunchLocked(launchFlags);
-                            r.updateOptionsLocked(options);
-                            moveTaskToFrontLocked(taskTop.task, r);
+                            moveTaskToFrontLocked(taskTop.task, r, options);
                         }
                     }
                     // If the caller has requested that the target task be
@@ -2619,6 +2623,7 @@
                         if (doResume) {
                             resumeTopActivityLocked(null);
                         }
+                        ActivityOptions.abort(options);
                         return ActivityManager.START_RETURN_INTENT_TO_CALLER;
                     }
                     if ((launchFlags &
@@ -2706,6 +2711,7 @@
                         if (doResume) {
                             resumeTopActivityLocked(null);
                         }
+                        ActivityOptions.abort(options);
                         return ActivityManager.START_TASK_TO_FRONT;
                     }
                 }
@@ -2735,6 +2741,7 @@
                             if (doResume) {
                                 resumeTopActivityLocked(null);
                             }
+                            ActivityOptions.abort(options);
                             if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
                                 // We don't need to start a new activity, and
                                 // the client said not to do anything if that
@@ -2754,6 +2761,7 @@
                         r.resultTo, r.resultWho, r.requestCode,
                     Activity.RESULT_CANCELED, null);
             }
+            ActivityOptions.abort(options);
             return ActivityManager.START_CLASS_NOT_FOUND;
         }
 
@@ -2795,6 +2803,7 @@
                     if (doResume) {
                         resumeTopActivityLocked(null);
                     }
+                    ActivityOptions.abort(options);
                     return ActivityManager.START_DELIVERED_TO_TOP;
                 }
             } else if (!addingToTask &&
@@ -2847,7 +2856,7 @@
     }
 
     ActivityInfo resolveActivity(Intent intent, String resolvedType, int startFlags,
-             String profileFile, ParcelFileDescriptor profileFd) {
+            String profileFile, ParcelFileDescriptor profileFd, int userId) {
         // Collect information about the target of the Intent.
         ActivityInfo aInfo;
         try {
@@ -2855,7 +2864,7 @@
                 AppGlobals.getPackageManager().resolveIntent(
                         intent, resolvedType,
                         PackageManager.MATCH_DEFAULT_ONLY
-                        | ActivityManagerService.STOCK_PM_FLAGS);
+                                    | ActivityManagerService.STOCK_PM_FLAGS, userId);
             aInfo = rInfo != null ? rInfo.activityInfo : null;
         } catch (RemoteException e) {
             aInfo = null;
@@ -2909,7 +2918,7 @@
 
         // Collect information about the target of the Intent.
         ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags,
-                profileFile, profileFd);
+                profileFile, profileFd, userId);
         aInfo = mService.getActivityInfoForUser(aInfo, userId);
 
         synchronized (mService) {
@@ -2949,6 +2958,7 @@
                                 Slog.w(TAG, "Unable to find app for caller " + caller
                                       + " (pid=" + realCallingPid + ") when starting: "
                                       + intent.toString());
+                                ActivityOptions.abort(options);
                                 return ActivityManager.START_PERMISSION_DENIED;
                             }
                         }
@@ -2989,7 +2999,7 @@
                                 AppGlobals.getPackageManager().resolveIntent(
                                         intent, null,
                                         PackageManager.MATCH_DEFAULT_ONLY
-                                        | ActivityManagerService.STOCK_PM_FLAGS);
+                                        | ActivityManagerService.STOCK_PM_FLAGS, userId);
                             aInfo = rInfo != null ? rInfo.activityInfo : null;
                             aInfo = mService.getActivityInfoForUser(aInfo, userId);
                         } catch (RemoteException e) {
@@ -3098,7 +3108,7 @@
 
                     // Collect information about the target of the Intent.
                     ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i],
-                            0, null, null);
+                            0, null, null, userId);
                     // TODO: New, check if this is correct
                     aInfo = mService.getActivityInfoForUser(aInfo, userId);
 
@@ -3481,6 +3491,15 @@
      */
     final boolean finishActivityLocked(ActivityRecord r, int index,
             int resultCode, Intent resultData, String reason) {
+        return finishActivityLocked(r, index, resultCode, resultData, reason, false);
+    }
+
+    /**
+     * @return Returns true if this activity has been removed from the history
+     * list, or false if it is still in the list and will be removed later.
+     */
+    final boolean finishActivityLocked(ActivityRecord r, int index,
+            int resultCode, Intent resultData, String reason, boolean immediate) {
         if (r.finishing) {
             Slog.w(TAG, "Duplicate finish request for " + r);
             return false;
@@ -3522,7 +3541,10 @@
             mService.mCancelledThumbnails.add(r);
         }
 
-        if (mResumedActivity == r) {
+        if (immediate) {
+            return finishCurrentActivityLocked(r, index,
+                    FINISH_IMMEDIATELY) == null;
+        } else if (mResumedActivity == r) {
             boolean endTask = index <= 0
                     || (mHistory.get(index-1)).task != r.task;
             if (DEBUG_TRANSITION) Slog.v(TAG,
@@ -3888,12 +3910,12 @@
             }
         }
         if (homeTask != null) {
-            moveTaskToFrontLocked(homeTask, null);
+            moveTaskToFrontLocked(homeTask, null, null);
         }
     }
 
 
-    final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason) {
+    final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason, Bundle options) {
         if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr);
 
         final int task = tr.taskId;
@@ -3901,6 +3923,7 @@
 
         if (top < 0 || (mHistory.get(top)).task.taskId == task) {
             // nothing to do!
+            ActivityOptions.abort(options);
             return;
         }
 
@@ -3942,7 +3965,16 @@
             if (r != null) {
                 mNoAnimActivities.add(r);
             }
+            ActivityOptions.abort(options);
         } else {
+            if (options != null) {
+                ActivityRecord r = topRunningActivityLocked(null);
+                if (r != null && r.state != ActivityState.RESUMED) {
+                    r.updateOptionsLocked(options);
+                } else {
+                    ActivityOptions.abort(options);
+                }
+            }
             mService.mWindowManager.prepareAppTransition(
                     WindowManagerPolicy.TRANSIT_TASK_TO_FRONT, false);
         }
diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java
index 39b63db..1b83e0b 100644
--- a/services/java/com/android/server/am/BroadcastQueue.java
+++ b/services/java/com/android/server/am/BroadcastQueue.java
@@ -732,7 +732,7 @@
             // Broadcast is being executed, its package can't be stopped.
             try {
                 AppGlobals.getPackageManager().setPackageStoppedState(
-                        r.curComponent.getPackageName(), false);
+                        r.curComponent.getPackageName(), false, UserId.getUserId(r.callingUid));
             } catch (RemoteException e) {
             } catch (IllegalArgumentException e) {
                 Slog.w(TAG, "Failed trying to unstop package "
diff --git a/services/java/com/android/server/am/CompatModePackages.java b/services/java/com/android/server/am/CompatModePackages.java
index cd72202..3ba3fbb 100644
--- a/services/java/com/android/server/am/CompatModePackages.java
+++ b/services/java/com/android/server/am/CompatModePackages.java
@@ -121,7 +121,7 @@
     public void handlePackageAddedLocked(String packageName, boolean updated) {
         ApplicationInfo ai = null;
         try {
-            ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0);
+            ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0, 0);
         } catch (RemoteException e) {
         }
         if (ai == null) {
@@ -220,7 +220,7 @@
     public int getPackageScreenCompatModeLocked(String packageName) {
         ApplicationInfo ai = null;
         try {
-            ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0);
+            ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0, 0);
         } catch (RemoteException e) {
         }
         if (ai == null) {
@@ -232,7 +232,7 @@
     public void setPackageScreenCompatModeLocked(String packageName, int mode) {
         ApplicationInfo ai = null;
         try {
-            ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0);
+            ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0, 0);
         } catch (RemoteException e) {
         }
         if (ai == null) {
@@ -365,7 +365,7 @@
                 }
                 ApplicationInfo ai = null;
                 try {
-                    ai = pm.getApplicationInfo(pkg, 0);
+                    ai = pm.getApplicationInfo(pkg, 0, 0);
                 } catch (RemoteException e) {
                 }
                 if (ai == null) {
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 2ad24e2..1f1e720 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -41,7 +41,6 @@
 import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
 import static android.net.NetworkPolicyManager.dumpPolicy;
 import static android.net.NetworkPolicyManager.dumpRules;
-import static android.net.NetworkPolicyManager.isUidValidForPolicy;
 import static android.net.NetworkTemplate.MATCH_ETHERNET;
 import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
 import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
@@ -74,6 +73,7 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.IConnectivityManager;
@@ -96,6 +96,7 @@
 import android.os.MessageQueue.IdleHandler;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.UserId;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.text.format.Formatter;
@@ -157,6 +158,8 @@
     private static final int VERSION_ADDED_METERED = 4;
     private static final int VERSION_SPLIT_SNOOZE = 5;
     private static final int VERSION_ADDED_TIMEZONE = 6;
+    private static final int VERSION_ADDED_INFERRED = 7;
+    private static final int VERSION_SWITCH_APP_ID = 8;
 
     // @VisibleForTesting
     public static final int TYPE_WARNING = 0x1;
@@ -166,6 +169,7 @@
     private static final String TAG_POLICY_LIST = "policy-list";
     private static final String TAG_NETWORK_POLICY = "network-policy";
     private static final String TAG_UID_POLICY = "uid-policy";
+    private static final String TAG_APP_POLICY = "app-policy";
 
     private static final String ATTR_VERSION = "version";
     private static final String ATTR_RESTRICT_BACKGROUND = "restrictBackground";
@@ -179,7 +183,9 @@
     private static final String ATTR_LAST_WARNING_SNOOZE = "lastWarningSnooze";
     private static final String ATTR_LAST_LIMIT_SNOOZE = "lastLimitSnooze";
     private static final String ATTR_METERED = "metered";
+    private static final String ATTR_INFERRED = "inferred";
     private static final String ATTR_UID = "uid";
+    private static final String ATTR_APP_ID = "appId";
     private static final String ATTR_POLICY = "policy";
 
     private static final String TAG_ALLOW_BACKGROUND = TAG + ":allowBackground";
@@ -221,8 +227,8 @@
     /** Currently active network rules for ifaces. */
     private HashMap<NetworkPolicy, String[]> mNetworkRules = Maps.newHashMap();
 
-    /** Defined UID policies. */
-    private SparseIntArray mUidPolicy = new SparseIntArray();
+    /** Defined app policies. */
+    private SparseIntArray mAppPolicy = new SparseIntArray();
     /** Currently derived rules for each UID. */
     private SparseIntArray mUidRules = new SparseIntArray();
 
@@ -377,18 +383,26 @@
 
             final String action = intent.getAction();
             final int uid = intent.getIntExtra(EXTRA_UID, 0);
+            final int appId = UserId.getAppId(uid);
             synchronized (mRulesLock) {
                 if (ACTION_PACKAGE_ADDED.equals(action)) {
+                    // NOTE: PACKAGE_ADDED is currently only sent once, and is
+                    // not broadcast when users are added.
+
                     // update rules for UID, since it might be subject to
                     // global background data policy.
                     if (LOGV) Slog.v(TAG, "ACTION_PACKAGE_ADDED for uid=" + uid);
-                    updateRulesForUidLocked(uid);
+                    updateRulesForAppLocked(appId);
 
                 } else if (ACTION_UID_REMOVED.equals(action)) {
+                    // NOTE: UID_REMOVED is currently only sent once, and is not
+                    // broadcast when users are removed.
+
                     // remove any policy and update rules to clean up.
                     if (LOGV) Slog.v(TAG, "ACTION_UID_REMOVED for uid=" + uid);
-                    mUidPolicy.delete(uid);
-                    updateRulesForUidLocked(uid);
+
+                    mAppPolicy.delete(appId);
+                    updateRulesForAppLocked(appId);
                     writePolicyLocked();
                 }
             }
@@ -522,6 +536,11 @@
             case MATCH_MOBILE_3G_LOWER:
             case MATCH_MOBILE_4G:
             case MATCH_MOBILE_ALL:
+                // mobile templates aren't relevant in airplane mode
+                if (isAirplaneModeOn(mContext)) {
+                    return false;
+                }
+
                 // mobile templates are relevant when subscriberid is active
                 return Objects.equal(getActiveSubscriberId(), template.getSubscriberId());
         }
@@ -932,7 +951,7 @@
 
             final NetworkTemplate template = buildTemplateMobileAll(subscriberId);
             mNetworkPolicy.put(template, new NetworkPolicy(template, cycleDay, cycleTimezone,
-                    warningBytes, LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, true));
+                    warningBytes, LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, true, true));
             writePolicyLocked();
         }
     }
@@ -942,7 +961,7 @@
 
         // clear any existing policy and read from disk
         mNetworkPolicy.clear();
-        mUidPolicy.clear();
+        mAppPolicy.clear();
 
         FileInputStream fis = null;
         try {
@@ -1004,22 +1023,38 @@
                         } else {
                             lastWarningSnooze = SNOOZE_NEVER;
                         }
+                        final boolean inferred;
+                        if (version >= VERSION_ADDED_INFERRED) {
+                            inferred = readBooleanAttribute(in, ATTR_INFERRED);
+                        } else {
+                            inferred = false;
+                        }
 
                         final NetworkTemplate template = new NetworkTemplate(
                                 networkTemplate, subscriberId);
                         mNetworkPolicy.put(template, new NetworkPolicy(template, cycleDay,
                                 cycleTimezone, warningBytes, limitBytes, lastWarningSnooze,
-                                lastLimitSnooze, metered));
+                                lastLimitSnooze, metered, inferred));
 
                     } else if (TAG_UID_POLICY.equals(tag)) {
                         final int uid = readIntAttribute(in, ATTR_UID);
                         final int policy = readIntAttribute(in, ATTR_POLICY);
 
-                        if (isUidValidForPolicy(mContext, uid)) {
-                            setUidPolicyUnchecked(uid, policy, false);
+                        final int appId = UserId.getAppId(uid);
+                        if (UserId.isApp(appId)) {
+                            setAppPolicyUnchecked(appId, policy, false);
                         } else {
                             Slog.w(TAG, "unable to apply policy to UID " + uid + "; ignoring");
                         }
+                    } else if (TAG_APP_POLICY.equals(tag)) {
+                        final int appId = readIntAttribute(in, ATTR_APP_ID);
+                        final int policy = readIntAttribute(in, ATTR_POLICY);
+
+                        if (UserId.isApp(appId)) {
+                            setAppPolicyUnchecked(appId, policy, false);
+                        } else {
+                            Slog.w(TAG, "unable to apply policy to appId " + appId + "; ignoring");
+                        }
                     }
                 }
             }
@@ -1064,7 +1099,7 @@
             out.startDocument(null, true);
 
             out.startTag(null, TAG_POLICY_LIST);
-            writeIntAttribute(out, ATTR_VERSION, VERSION_ADDED_TIMEZONE);
+            writeIntAttribute(out, ATTR_VERSION, VERSION_SWITCH_APP_ID);
             writeBooleanAttribute(out, ATTR_RESTRICT_BACKGROUND, mRestrictBackground);
 
             // write all known network policies
@@ -1084,21 +1119,22 @@
                 writeLongAttribute(out, ATTR_LAST_WARNING_SNOOZE, policy.lastWarningSnooze);
                 writeLongAttribute(out, ATTR_LAST_LIMIT_SNOOZE, policy.lastLimitSnooze);
                 writeBooleanAttribute(out, ATTR_METERED, policy.metered);
+                writeBooleanAttribute(out, ATTR_INFERRED, policy.inferred);
                 out.endTag(null, TAG_NETWORK_POLICY);
             }
 
             // write all known uid policies
-            for (int i = 0; i < mUidPolicy.size(); i++) {
-                final int uid = mUidPolicy.keyAt(i);
-                final int policy = mUidPolicy.valueAt(i);
+            for (int i = 0; i < mAppPolicy.size(); i++) {
+                final int appId = mAppPolicy.keyAt(i);
+                final int policy = mAppPolicy.valueAt(i);
 
                 // skip writing empty policies
                 if (policy == POLICY_NONE) continue;
 
-                out.startTag(null, TAG_UID_POLICY);
-                writeIntAttribute(out, ATTR_UID, uid);
+                out.startTag(null, TAG_APP_POLICY);
+                writeIntAttribute(out, ATTR_APP_ID, appId);
                 writeIntAttribute(out, ATTR_POLICY, policy);
-                out.endTag(null, TAG_UID_POLICY);
+                out.endTag(null, TAG_APP_POLICY);
             }
 
             out.endTag(null, TAG_POLICY_LIST);
@@ -1113,24 +1149,24 @@
     }
 
     @Override
-    public void setUidPolicy(int uid, int policy) {
+    public void setAppPolicy(int appId, int policy) {
         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
 
-        if (!isUidValidForPolicy(mContext, uid)) {
-            throw new IllegalArgumentException("cannot apply policy to UID " + uid);
+        if (!UserId.isApp(appId)) {
+            throw new IllegalArgumentException("cannot apply policy to appId " + appId);
         }
 
-        setUidPolicyUnchecked(uid, policy, true);
+        setAppPolicyUnchecked(appId, policy, true);
     }
 
-    private void setUidPolicyUnchecked(int uid, int policy, boolean persist) {
+    private void setAppPolicyUnchecked(int appId, int policy, boolean persist) {
         final int oldPolicy;
         synchronized (mRulesLock) {
-            oldPolicy = getUidPolicy(uid);
-            mUidPolicy.put(uid, policy);
+            oldPolicy = getAppPolicy(appId);
+            mAppPolicy.put(appId, policy);
 
             // uid policy changed, recompute rules and persist policy.
-            updateRulesForUidLocked(uid);
+            updateRulesForAppLocked(appId);
             if (persist) {
                 writePolicyLocked();
             }
@@ -1138,11 +1174,11 @@
     }
 
     @Override
-    public int getUidPolicy(int uid) {
+    public int getAppPolicy(int appId) {
         mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
 
         synchronized (mRulesLock) {
-            return mUidPolicy.get(uid, POLICY_NONE);
+            return mAppPolicy.get(appId, POLICY_NONE);
         }
     }
 
@@ -1333,27 +1369,29 @@
                 fout.print("  "); fout.println(policy.toString());
             }
 
-            fout.println("Policy status for known UIDs:");
+            fout.println("Policy for apps:");
+            int size = mAppPolicy.size();
+            for (int i = 0; i < size; i++) {
+                final int appId = mAppPolicy.keyAt(i);
+                final int policy = mAppPolicy.valueAt(i);
+                fout.print("  appId=");
+                fout.print(appId);
+                fout.print(" policy=");
+                dumpPolicy(fout, policy);
+                fout.println();
+            }
 
             final SparseBooleanArray knownUids = new SparseBooleanArray();
-            collectKeys(mUidPolicy, knownUids);
             collectKeys(mUidForeground, knownUids);
             collectKeys(mUidRules, knownUids);
 
-            final int size = knownUids.size();
+            fout.println("Status for known UIDs:");
+            size = knownUids.size();
             for (int i = 0; i < size; i++) {
                 final int uid = knownUids.keyAt(i);
                 fout.print("  UID=");
                 fout.print(uid);
 
-                fout.print(" policy=");
-                final int policyIndex = mUidPolicy.indexOfKey(uid);
-                if (policyIndex < 0) {
-                    fout.print("UNKNOWN");
-                } else {
-                    dumpPolicy(fout, mUidPolicy.valueAt(policyIndex));
-                }
-
                 fout.print(" foreground=");
                 final int foregroundIndex = mUidPidForeground.indexOfKey(uid);
                 if (foregroundIndex < 0) {
@@ -1443,7 +1481,8 @@
         final PackageManager pm = mContext.getPackageManager();
         final List<ApplicationInfo> apps = pm.getInstalledApplications(0);
         for (ApplicationInfo app : apps) {
-            updateRulesForUidLocked(app.uid);
+            final int appId = UserId.getAppId(app.uid);
+            updateRulesForAppLocked(appId);
         }
 
         // and catch system UIDs
@@ -1462,13 +1501,21 @@
         }
     }
 
+    private void updateRulesForAppLocked(int appId) {
+        for (UserInfo user : mContext.getPackageManager().getUsers()) {
+            final int uid = UserId.getUid(user.id, appId);
+            updateRulesForUidLocked(uid);
+        }
+    }
+
     private void updateRulesForUidLocked(int uid) {
-        final int uidPolicy = getUidPolicy(uid);
+        final int appId = UserId.getAppId(uid);
+        final int appPolicy = getAppPolicy(appId);
         final boolean uidForeground = isUidForeground(uid);
 
         // derive active rules based on policy and active state
         int uidRules = RULE_ALLOW_ALL;
-        if (!uidForeground && (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) {
+        if (!uidForeground && (appPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) {
             // uid in background, and policy says to block metered data
             uidRules = RULE_REJECT_METERED;
         }
@@ -1714,6 +1761,11 @@
         mHandler.getLooper().getQueue().addIdleHandler(handler);
     }
 
+    public static boolean isAirplaneModeOn(Context context) {
+        return Settings.System.getInt(
+                context.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) != 0;
+    }
+
     private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
         final int size = source.size();
         for (int i = 0; i < size; i++) {
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index bc98f86..1593707 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -134,6 +134,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
@@ -395,7 +396,7 @@
     static final int MCS_GIVE_UP = 11;
     static final int UPDATED_MEDIA_STATUS = 12;
     static final int WRITE_SETTINGS = 13;
-    static final int WRITE_STOPPED_PACKAGES = 14;
+    static final int WRITE_PACKAGE_RESTRICTIONS = 14;
     static final int PACKAGE_VERIFIED = 15;
     static final int CHECK_PENDING_VERIFICATION = 16;
 
@@ -404,7 +405,10 @@
     // Delay time in millisecs
     static final int BROADCAST_DELAY = 10 * 1000;
 
-    final UserManager mUserManager;
+    static UserManager sUserManager;
+
+    // Stores a list of users whose package restrictions file needs to be updated
+    private HashSet<Integer> mDirtyUsers = new HashSet<Integer>();
 
     final private DefaultContainerConnection mDefContainerConn =
             new DefaultContainerConnection();
@@ -629,7 +633,7 @@
                             packages[i] = ent.getKey();
                             components[i] = ent.getValue();
                             PackageSetting ps = mSettings.mPackages.get(ent.getKey());
-                            uids[i] = (ps != null) ? ps.userId : -1;
+                            uids[i] = (ps != null) ? ps.appId : -1;
                             i++;
                         }
                         size = i;
@@ -673,14 +677,15 @@
                             }
                             sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
                                     res.pkg.applicationInfo.packageName,
-                                    extras, null, null);
+                                    extras, null, null, UserId.USER_ALL);
                             if (update) {
                                 sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
                                         res.pkg.applicationInfo.packageName,
-                                        extras, null, null);
+                                        extras, null, null, UserId.USER_ALL);
                                 sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
                                         null, null,
-                                        res.pkg.applicationInfo.packageName, null);
+                                        res.pkg.applicationInfo.packageName, null,
+                                        UserId.USER_ALL);
                             }
                             if (res.removedInfo.args != null) {
                                 // Remove the replaced package's older resources safely now
@@ -735,16 +740,20 @@
                     Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                     synchronized (mPackages) {
                         removeMessages(WRITE_SETTINGS);
-                        removeMessages(WRITE_STOPPED_PACKAGES);
+                        removeMessages(WRITE_PACKAGE_RESTRICTIONS);
                         mSettings.writeLPr();
+                        mDirtyUsers.clear();
                     }
                     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                 } break;
-                case WRITE_STOPPED_PACKAGES: {
+                case WRITE_PACKAGE_RESTRICTIONS: {
                     Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                     synchronized (mPackages) {
-                        removeMessages(WRITE_STOPPED_PACKAGES);
-                        mSettings.writeStoppedLPr();
+                        removeMessages(WRITE_PACKAGE_RESTRICTIONS);
+                        for (int userId : mDirtyUsers) {
+                            mSettings.writePackageRestrictionsLPr(userId);
+                        }
+                        mDirtyUsers.clear();
                     }
                     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                 } break;
@@ -811,10 +820,12 @@
             mHandler.sendEmptyMessageDelayed(WRITE_SETTINGS, WRITE_SETTINGS_DELAY);
         }
     }
-    
-    void scheduleWriteStoppedPackagesLocked() {
-        if (!mHandler.hasMessages(WRITE_STOPPED_PACKAGES)) {
-            mHandler.sendEmptyMessageDelayed(WRITE_STOPPED_PACKAGES, WRITE_SETTINGS_DELAY);
+
+    void scheduleWritePackageRestrictionsLocked(int userId) {
+        if (!sUserManager.exists(userId)) return;
+        mDirtyUsers.add(userId);
+        if (!mHandler.hasMessages(WRITE_PACKAGE_RESTRICTIONS)) {
+            mHandler.sendEmptyMessageDelayed(WRITE_PACKAGE_RESTRICTIONS, WRITE_SETTINGS_DELAY);
         }
     }
 
@@ -912,11 +923,11 @@
             mUserAppDataDir = new File(dataDir, "user");
             mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
 
-            mUserManager = new UserManager(mInstaller, mUserAppDataDir);
+            sUserManager = new UserManager(mInstaller, mUserAppDataDir);
 
             readPermissions();
 
-            mRestoredSettings = mSettings.readLPw();
+            mRestoredSettings = mSettings.readLPw(getUsers());
             long startTime = SystemClock.uptimeMillis();
 
             EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START,
@@ -1078,7 +1089,7 @@
                                 + " no longer exists; wiping its data";
                         reportSettingsProblem(Log.WARN, msg);
                         mInstaller.remove(ps.name, 0);
-                        mUserManager.removePackageForAllUsers(ps.name);
+                        sUserManager.removePackageForAllUsers(ps.name);
                     }
                 }
             }
@@ -1180,7 +1191,7 @@
     private String getRequiredVerifierLPr() {
         final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
         final List<ResolveInfo> receivers = queryIntentReceivers(verification, PACKAGE_MIME_TYPE,
-                PackageManager.GET_DISABLED_COMPONENTS);
+                PackageManager.GET_DISABLED_COMPONENTS, 0 /* TODO: Which userId? */);
 
         String requiredVerifier = null;
 
@@ -1234,7 +1245,7 @@
             Slog.w(TAG, "Couldn't remove app data directory for package: "
                        + ps.name + ", retcode=" + retCode);
         } else {
-            mUserManager.removePackageForAllUsers(ps.name);
+            sUserManager.removePackageForAllUsers(ps.name);
         }
         if (ps.codePath != null) {
             if (!ps.codePath.delete()) {
@@ -1498,31 +1509,42 @@
         return cur;
     }
 
-    PackageInfo generatePackageInfo(PackageParser.Package p, int flags) {
+    PackageInfo generatePackageInfo(PackageParser.Package p, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
+        PackageInfo pi;
         if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) {
             // The package has been uninstalled but has retained data and resources.
-            return PackageParser.generatePackageInfo(p, null, flags, 0, 0, null);
+            pi = PackageParser.generatePackageInfo(p, null, flags, 0, 0, null, false, 0, userId);
+        } else {
+            final PackageSetting ps = (PackageSetting) p.mExtras;
+            if (ps == null) {
+                return null;
+            }
+            final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
+            pi = PackageParser.generatePackageInfo(p, gp.gids, flags,
+                    ps.firstInstallTime, ps.lastUpdateTime, gp.grantedPermissions,
+                    ps.getStopped(userId), ps.getEnabled(userId), userId);
+            pi.applicationInfo.enabledSetting = ps.getEnabled(userId);
+            pi.applicationInfo.enabled =
+                    pi.applicationInfo.enabledSetting == COMPONENT_ENABLED_STATE_DEFAULT
+                    || pi.applicationInfo.enabledSetting == COMPONENT_ENABLED_STATE_ENABLED;
         }
-        final PackageSetting ps = (PackageSetting)p.mExtras;
-        if (ps == null) {
-            return null;
-        }
-        final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
-        return PackageParser.generatePackageInfo(p, gp.gids, flags,
-                ps.firstInstallTime, ps.lastUpdateTime, gp.grantedPermissions);
+        return pi;
     }
 
-    public PackageInfo getPackageInfo(String packageName, int flags) {
+    @Override
+    public PackageInfo getPackageInfo(String packageName, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
         // reader
         synchronized (mPackages) {
             PackageParser.Package p = mPackages.get(packageName);
             if (DEBUG_PACKAGE_INFO)
                 Log.v(TAG, "getPackageInfo " + packageName + ": " + p);
             if (p != null) {
-                return generatePackageInfo(p, flags);
+                return generatePackageInfo(p, flags, userId);
             }
             if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) {
-                return generatePackageInfoFromSettingsLPw(packageName, flags);
+                return generatePackageInfoFromSettingsLPw(packageName, flags, userId);
             }
         }
         return null;
@@ -1551,20 +1573,22 @@
         }
         return out;
     }
-    
-    public int getPackageUid(String packageName) {
+
+    @Override
+    public int getPackageUid(String packageName, int userId) {
+        if (!sUserManager.exists(userId)) return -1;
         // reader
         synchronized (mPackages) {
             PackageParser.Package p = mPackages.get(packageName);
             if(p != null) {
-                return p.applicationInfo.uid;
+                return UserId.getUid(userId, p.applicationInfo.uid);
             }
             PackageSetting ps = mSettings.mPackages.get(packageName);
             if((ps == null) || (ps.pkg == null) || (ps.pkg.applicationInfo == null)) {
                 return -1;
             }
             p = ps.pkg;
-            return p != null ? p.applicationInfo.uid : -1;
+            return p != null ? UserId.getUid(userId, p.applicationInfo.uid) : -1;
         }
     }
 
@@ -1577,7 +1601,16 @@
             if (p != null) {
                 final PackageSetting ps = (PackageSetting)p.mExtras;
                 final SharedUserSetting suid = ps.sharedUser;
-                return suid != null ? suid.gids : ps.gids;
+                int[] gids = suid != null ? suid.gids : ps.gids;
+
+                // include GIDs for any unenforced permissions
+                if (!isPermissionEnforcedLocked(READ_EXTERNAL_STORAGE)) {
+                    final BasePermission basePerm = mSettings.mPermissions.get(
+                            READ_EXTERNAL_STORAGE);
+                    gids = appendInts(gids, basePerm.gids);
+                }
+
+                return gids;
             }
         }
         // stupid thing to indicate an error.
@@ -1652,24 +1685,30 @@
         }
     }
 
-    private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags) {
+    private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags,
+            int userId) {
+        if (!sUserManager.exists(userId)) return null;
         PackageSetting ps = mSettings.mPackages.get(packageName);
         if (ps != null) {
             if (ps.pkg == null) {
-                PackageInfo pInfo = generatePackageInfoFromSettingsLPw(packageName, flags);
+                PackageInfo pInfo = generatePackageInfoFromSettingsLPw(packageName, flags, userId);
                 if (pInfo != null) {
                     return pInfo.applicationInfo;
                 }
                 return null;
             }
-            return PackageParser.generateApplicationInfo(ps.pkg, flags);
+            return PackageParser.generateApplicationInfo(ps.pkg, flags, ps.getStopped(userId),
+                    ps.getEnabled(userId), userId);
         }
         return null;
     }
 
-    private PackageInfo generatePackageInfoFromSettingsLPw(String packageName, int flags) {
+    private PackageInfo generatePackageInfoFromSettingsLPw(String packageName, int flags,
+            int userId) {
+        if (!sUserManager.exists(userId)) return null;
         PackageSetting ps = mSettings.mPackages.get(packageName);
         if (ps != null) {
+            PackageParser.Package pkg = new PackageParser.Package(packageName);
             if (ps.pkg == null) {
                 ps.pkg = new PackageParser.Package(packageName);
                 ps.pkg.applicationInfo.packageName = packageName;
@@ -1679,15 +1718,17 @@
                 ps.pkg.applicationInfo.dataDir =
                         getDataPathForPackage(ps.pkg.packageName, 0).getPath();
                 ps.pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString;
-                ps.pkg.mSetEnabled = ps.enabled;
-                ps.pkg.mSetStopped = ps.stopped;
             }
-            return generatePackageInfo(ps.pkg, flags);
+            // ps.pkg.mSetEnabled = ps.getEnabled(userId);
+            // ps.pkg.mSetStopped = ps.getStopped(userId);
+            return generatePackageInfo(ps.pkg, flags, userId);
         }
         return null;
     }
 
-    public ApplicationInfo getApplicationInfo(String packageName, int flags) {
+    @Override
+    public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
         // writer
         synchronized (mPackages) {
             PackageParser.Package p = mPackages.get(packageName);
@@ -1695,14 +1736,17 @@
                     TAG, "getApplicationInfo " + packageName
                     + ": " + p);
             if (p != null) {
+                PackageSetting ps = mSettings.mPackages.get(packageName);
+                if (ps == null) return null;
                 // Note: isEnabledLP() does not apply here - always return info
-                return PackageParser.generateApplicationInfo(p, flags);
+                return PackageParser.generateApplicationInfo(p, flags, ps.getStopped(userId),
+                        ps.getEnabled(userId));
             }
             if ("android".equals(packageName)||"system".equals(packageName)) {
                 return mAndroidApplication;
             }
             if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) {
-                return generateApplicationInfoFromSettingsLPw(packageName, flags);
+                return generateApplicationInfoFromSettingsLPw(packageName, flags, userId);
             }
         }
         return null;
@@ -1758,17 +1802,18 @@
         });
     }
 
-    public ActivityInfo getActivityInfo(ComponentName component, int flags) {
-        return getActivityInfo(component, flags, Binder.getOrigCallingUser());
-    }
-
-    ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
+    @Override
+    public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
         synchronized (mPackages) {
             PackageParser.Activity a = mActivities.mActivities.get(component);
 
             if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);
-            if (a != null && mSettings.isEnabledLPr(a.info, flags)) {
-                return PackageParser.generateActivityInfo(a, flags, userId);
+            if (a != null && mSettings.isEnabledLPr(a.info, flags, userId)) {
+                PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
+                if (ps == null) return null;
+                return PackageParser.generateActivityInfo(a, flags, ps.getStopped(userId),
+                        ps.getEnabled(userId), userId);
             }
             if (mResolveComponentName.equals(component)) {
                 return mResolveActivity;
@@ -1777,49 +1822,52 @@
         return null;
     }
 
-    public ActivityInfo getReceiverInfo(ComponentName component, int flags) {
-        return getReceiverInfo(component, flags, Binder.getOrigCallingUser());
-    }
-
-    ActivityInfo getReceiverInfo(ComponentName component, int flags, int userId) {
+    @Override
+    public ActivityInfo getReceiverInfo(ComponentName component, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
         synchronized (mPackages) {
             PackageParser.Activity a = mReceivers.mActivities.get(component);
             if (DEBUG_PACKAGE_INFO) Log.v(
                 TAG, "getReceiverInfo " + component + ": " + a);
-            if (a != null && mSettings.isEnabledLPr(a.info, flags)) {
-                return PackageParser.generateActivityInfo(a, flags, userId);
+            if (a != null && mSettings.isEnabledLPr(a.info, flags, userId)) {
+                PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
+                if (ps == null) return null;
+                return PackageParser.generateActivityInfo(a, flags, ps.getStopped(userId),
+                        ps.getEnabled(userId), userId);
             }
         }
         return null;
     }
 
-    public ServiceInfo getServiceInfo(ComponentName component, int flags) {
-        return getServiceInfo(component, flags, Binder.getOrigCallingUser());
-    }
-
-    ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) {
+    @Override
+    public ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
         synchronized (mPackages) {
             PackageParser.Service s = mServices.mServices.get(component);
             if (DEBUG_PACKAGE_INFO) Log.v(
                 TAG, "getServiceInfo " + component + ": " + s);
-            if (s != null && mSettings.isEnabledLPr(s.info, flags)) {
-                return PackageParser.generateServiceInfo(s, flags, userId);
+            if (s != null && mSettings.isEnabledLPr(s.info, flags, userId)) {
+                PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
+                if (ps == null) return null;
+                return PackageParser.generateServiceInfo(s, flags, ps.getStopped(userId),
+                        ps.getEnabled(userId), userId);
             }
         }
         return null;
     }
 
-    public ProviderInfo getProviderInfo(ComponentName component, int flags) {
-        return getProviderInfo(component, flags, UserId.getUserId(Binder.getCallingUid()));
-    }
-
-    ProviderInfo getProviderInfo(ComponentName component, int flags, int userId) {
+    @Override
+    public ProviderInfo getProviderInfo(ComponentName component, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
         synchronized (mPackages) {
             PackageParser.Provider p = mProvidersByComponent.get(component);
             if (DEBUG_PACKAGE_INFO) Log.v(
                 TAG, "getProviderInfo " + component + ": " + p);
-            if (p != null && mSettings.isEnabledLPr(p.info, flags)) {
-                return PackageParser.generateProviderInfo(p, flags, userId);
+            if (p != null && mSettings.isEnabledLPr(p.info, flags, userId)) {
+                PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
+                if (ps == null) return null;
+                return PackageParser.generateProviderInfo(p, flags, ps.getStopped(userId),
+                        ps.getEnabled(userId), userId);
             }
         }
         return null;
@@ -1863,6 +1911,14 @@
         }
     }
 
+    private void checkValidCaller(int uid, int userId) {
+        if (UserId.getUserId(uid) == userId || uid == Process.SYSTEM_UID || uid == 0)
+            return;
+
+        throw new SecurityException("Caller uid=" + uid
+                + " is not privileged to communicate with user=" + userId);
+    }
+
     public int checkPermission(String permName, String pkgName) {
         synchronized (mPackages) {
             PackageParser.Package p = mPackages.get(pkgName);
@@ -1997,7 +2053,7 @@
             if (!async) {
                 mSettings.writeLPr();
             } else {
-                scheduleWriteSettingsLocked();            
+                scheduleWriteSettingsLocked();
             }
         }
         return added;
@@ -2232,14 +2288,16 @@
         }
     }
 
+    @Override
     public ResolveInfo resolveIntent(Intent intent, String resolvedType,
-            int flags) {
-        List<ResolveInfo> query = queryIntentActivities(intent, resolvedType, flags);
-        return chooseBestActivity(intent, resolvedType, flags, query);
+            int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
+        List<ResolveInfo> query = queryIntentActivities(intent, resolvedType, flags, userId);
+        return chooseBestActivity(intent, resolvedType, flags, query, userId);
     }
 
     private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
-                                           int flags, List<ResolveInfo> query) {
+            int flags, List<ResolveInfo> query, int userId) {
         if (query != null) {
             final int N = query.size();
             if (N == 1) {
@@ -2263,7 +2321,7 @@
                 // If we have saved a preference for a preferred activity for
                 // this Intent, use that.
                 ResolveInfo ri = findPreferredActivity(intent, resolvedType,
-                        flags, query, r0.priority);
+                        flags, query, r0.priority, userId);
                 if (ri != null) {
                     return ri;
                 }
@@ -2274,7 +2332,8 @@
     }
 
     ResolveInfo findPreferredActivity(Intent intent, String resolvedType,
-            int flags, List<ResolveInfo> query, int priority) {
+            int flags, List<ResolveInfo> query, int priority, int userId) {
+        if (!sUserManager.exists(userId)) return null;
         // writer
         synchronized (mPackages) {
             if (intent.getSelector() != null) {
@@ -2283,7 +2342,7 @@
             if (DEBUG_PREFERRED) intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
             List<PreferredActivity> prefs =
                     mSettings.mPreferredActivities.queryIntent(intent, resolvedType,
-                            (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0);
+                            (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId);
             if (prefs != null && prefs.size() > 0) {
                 // First figure out how good the original match set is.
                 // We will only allow preferred activities that came
@@ -2317,7 +2376,7 @@
                     if (pa.mPref.mMatch != match) {
                         continue;
                     }
-                    final ActivityInfo ai = getActivityInfo(pa.mPref.mComponent, flags);
+                    final ActivityInfo ai = getActivityInfo(pa.mPref.mComponent, flags, userId);
                     if (DEBUG_PREFERRED) {
                         Log.v(TAG, "Got preferred activity:");
                         if (ai != null) {
@@ -2367,8 +2426,10 @@
         return null;
     }
 
+    @Override
     public List<ResolveInfo> queryIntentActivities(Intent intent,
-            String resolvedType, int flags) {
+            String resolvedType, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
         ComponentName comp = intent.getComponent();
         if (comp == null) {
             if (intent.getSelector() != null) {
@@ -2379,7 +2440,7 @@
 
         if (comp != null) {
             final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
-            final ActivityInfo ai = getActivityInfo(comp, flags);
+            final ActivityInfo ai = getActivityInfo(comp, flags, userId);
             if (ai != null) {
                 final ResolveInfo ri = new ResolveInfo();
                 ri.activityInfo = ai;
@@ -2392,24 +2453,26 @@
         synchronized (mPackages) {
             final String pkgName = intent.getPackage();
             if (pkgName == null) {
-                return mActivities.queryIntent(intent, resolvedType, flags);
+                return mActivities.queryIntent(intent, resolvedType, flags, userId);
             }
             final PackageParser.Package pkg = mPackages.get(pkgName);
             if (pkg != null) {
                 return mActivities.queryIntentForPackage(intent, resolvedType, flags,
-                        pkg.activities);
+                        pkg.activities, userId);
             }
             return new ArrayList<ResolveInfo>();
         }
     }
 
+    @Override
     public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller,
             Intent[] specifics, String[] specificTypes, Intent intent,
-            String resolvedType, int flags) {
+            String resolvedType, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
         final String resultsAction = intent.getAction();
 
         List<ResolveInfo> results = queryIntentActivities(intent, resolvedType, flags
-                | PackageManager.GET_RESOLVED_FILTER);
+                | PackageManager.GET_RESOLVED_FILTER, userId);
 
         if (DEBUG_INTENT_MATCHING) {
             Log.v(TAG, "Query " + intent + ": " + results);
@@ -2452,7 +2515,7 @@
                     ri = resolveIntent(
                         sintent,
                         specificTypes != null ? specificTypes[i] : null,
-                        flags);
+                            flags, userId);
                     if (ri == null) {
                         continue;
                     }
@@ -2463,7 +2526,7 @@
                     comp = new ComponentName(ai.applicationInfo.packageName,
                             ai.name);
                 } else {
-                    ai = getActivityInfo(comp, flags);
+                    ai = getActivityInfo(comp, flags, userId);
                     if (ai == null) {
                         continue;
                     }
@@ -2572,7 +2635,10 @@
         return results;
     }
 
-    public List<ResolveInfo> queryIntentReceivers(Intent intent, String resolvedType, int flags) {
+    @Override
+    public List<ResolveInfo> queryIntentReceivers(Intent intent, String resolvedType, int flags,
+            int userId) {
+        if (!sUserManager.exists(userId)) return null;
         ComponentName comp = intent.getComponent();
         if (comp == null) {
             if (intent.getSelector() != null) {
@@ -2582,7 +2648,7 @@
         }
         if (comp != null) {
             List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
-            ActivityInfo ai = getReceiverInfo(comp, flags);
+            ActivityInfo ai = getReceiverInfo(comp, flags, userId);
             if (ai != null) {
                 ResolveInfo ri = new ResolveInfo();
                 ri.activityInfo = ai;
@@ -2595,18 +2661,21 @@
         synchronized (mPackages) {
             String pkgName = intent.getPackage();
             if (pkgName == null) {
-                return mReceivers.queryIntent(intent, resolvedType, flags);
+                return mReceivers.queryIntent(intent, resolvedType, flags, userId);
             }
             final PackageParser.Package pkg = mPackages.get(pkgName);
             if (pkg != null) {
-                return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers);
+                return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers,
+                        userId);
             }
             return null;
         }
     }
 
-    public ResolveInfo resolveService(Intent intent, String resolvedType, int flags) {
-        List<ResolveInfo> query = queryIntentServices(intent, resolvedType, flags);
+    @Override
+    public ResolveInfo resolveService(Intent intent, String resolvedType, int flags, int userId) {
+        List<ResolveInfo> query = queryIntentServices(intent, resolvedType, flags, userId);
+        if (!sUserManager.exists(userId)) return null;
         if (query != null) {
             if (query.size() >= 1) {
                 // If there is more than one service with the same priority,
@@ -2617,7 +2686,10 @@
         return null;
     }
 
-    public List<ResolveInfo> queryIntentServices(Intent intent, String resolvedType, int flags) {
+    @Override
+    public List<ResolveInfo> queryIntentServices(Intent intent, String resolvedType, int flags,
+            int userId) {
+        if (!sUserManager.exists(userId)) return null;
         ComponentName comp = intent.getComponent();
         if (comp == null) {
             if (intent.getSelector() != null) {
@@ -2627,7 +2699,7 @@
         }
         if (comp != null) {
             final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
-            final ServiceInfo si = getServiceInfo(comp, flags);
+            final ServiceInfo si = getServiceInfo(comp, flags, userId);
             if (si != null) {
                 final ResolveInfo ri = new ResolveInfo();
                 ri.serviceInfo = si;
@@ -2640,11 +2712,12 @@
         synchronized (mPackages) {
             String pkgName = intent.getPackage();
             if (pkgName == null) {
-                return mServices.queryIntent(intent, resolvedType, flags);
+                return mServices.queryIntent(intent, resolvedType, flags, userId);
             }
             final PackageParser.Package pkg = mPackages.get(pkgName);
             if (pkg != null) {
-                return mServices.queryIntentForPackage(intent, resolvedType, flags, pkg.services);
+                return mServices.queryIntentForPackage(intent, resolvedType, flags, pkg.services,
+                        userId);
             }
             return null;
         }
@@ -2669,6 +2742,7 @@
         final ParceledListSlice<PackageInfo> list = new ParceledListSlice<PackageInfo>();
         final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0;
         final String[] keys;
+        int userId = UserId.getCallingUserId();
 
         // writer
         synchronized (mPackages) {
@@ -2689,12 +2763,12 @@
                 if (listUninstalled) {
                     final PackageSetting ps = mSettings.mPackages.get(packageName);
                     if (ps != null) {
-                        pi = generatePackageInfoFromSettingsLPw(ps.name, flags);
+                        pi = generatePackageInfoFromSettingsLPw(ps.name, flags, userId);
                     }
                 } else {
                     final PackageParser.Package p = mPackages.get(packageName);
                     if (p != null) {
-                        pi = generatePackageInfo(p, flags);
+                        pi = generatePackageInfo(p, flags, userId);
                     }
                 }
 
@@ -2711,8 +2785,10 @@
         return list;
     }
 
+    @Override
     public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags,
-            String lastRead) {
+            String lastRead, int userId) {
+        if (!sUserManager.exists(userId)) return null;
         final ParceledListSlice<ApplicationInfo> list = new ParceledListSlice<ApplicationInfo>();
         final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0;
         final String[] keys;
@@ -2728,21 +2804,21 @@
             Arrays.sort(keys);
             int i = getContinuationPoint(keys, lastRead);
             final int N = keys.length;
-            final int userId = UserId.getUserId(Binder.getCallingUid());
 
             while (i < N) {
                 final String packageName = keys[i++];
 
                 ApplicationInfo ai = null;
+                final PackageSetting ps = mSettings.mPackages.get(packageName);
                 if (listUninstalled) {
-                    final PackageSetting ps = mSettings.mPackages.get(packageName);
                     if (ps != null) {
-                        ai = generateApplicationInfoFromSettingsLPw(ps.name, flags);
+                        ai = generateApplicationInfoFromSettingsLPw(ps.name, flags, userId);
                     }
                 } else {
                     final PackageParser.Package p = mPackages.get(packageName);
-                    if (p != null) {
-                        ai = PackageParser.generateApplicationInfo(p, flags, userId);
+                    if (p != null && ps != null) {
+                        ai = PackageParser.generateApplicationInfo(p, flags, ps.getStopped(userId),
+                                ps.getEnabled(userId), userId);
                     }
                 }
 
@@ -2765,13 +2841,17 @@
         // reader
         synchronized (mPackages) {
             final Iterator<PackageParser.Package> i = mPackages.values().iterator();
-            final int userId = UserId.getUserId(Binder.getCallingUid());
+            final int userId = UserId.getCallingUserId();
             while (i.hasNext()) {
                 final PackageParser.Package p = i.next();
                 if (p.applicationInfo != null
                         && (p.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) != 0
                         && (!mSafeMode || isSystemApp(p))) {
-                    finalList.add(PackageParser.generateApplicationInfo(p, flags, userId));
+                    PackageSetting ps = mSettings.mPackages.get(p.packageName);
+                    finalList.add(PackageParser.generateApplicationInfo(p, flags,
+                            ps != null ? ps.getStopped(userId) : false,
+                            ps != null ? ps.getEnabled(userId) : COMPONENT_ENABLED_STATE_DEFAULT,
+                            userId));
                 }
             }
         }
@@ -2779,16 +2859,23 @@
         return finalList;
     }
 
-    public ProviderInfo resolveContentProvider(String name, int flags) {
+    @Override
+    public ProviderInfo resolveContentProvider(String name, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
         // reader
         synchronized (mPackages) {
             final PackageParser.Provider provider = mProviders.get(name);
+            PackageSetting ps = provider != null
+                    ? mSettings.mPackages.get(provider.owner.packageName)
+                    : null;
             return provider != null
-                    && mSettings.isEnabledLPr(provider.info, flags)
+                    && mSettings.isEnabledLPr(provider.info, flags, userId)
                     && (!mSafeMode || (provider.info.applicationInfo.flags
                             &ApplicationInfo.FLAG_SYSTEM) != 0)
                     ? PackageParser.generateProviderInfo(provider, flags,
-                            UserId.getUserId(Binder.getCallingUid()))
+                            ps != null ? ps.getStopped(userId) : false,
+                            ps != null ? ps.getEnabled(userId) : COMPONENT_ENABLED_STATE_DEFAULT,
+                            userId)
                     : null;
         }
     }
@@ -2802,16 +2889,20 @@
         synchronized (mPackages) {
             final Iterator<Map.Entry<String, PackageParser.Provider>> i = mProviders.entrySet()
                     .iterator();
-            final int userId = UserId.getUserId(Binder.getCallingUid());
+            final int userId = UserId.getCallingUserId();
             while (i.hasNext()) {
                 Map.Entry<String, PackageParser.Provider> entry = i.next();
                 PackageParser.Provider p = entry.getValue();
+                PackageSetting ps = mSettings.mPackages.get(p.owner.packageName);
 
                 if (p.syncable
                         && (!mSafeMode || (p.info.applicationInfo.flags
                                 &ApplicationInfo.FLAG_SYSTEM) != 0)) {
                     outNames.add(entry.getKey());
-                    outInfo.add(PackageParser.generateProviderInfo(p, 0, userId));
+                    outInfo.add(PackageParser.generateProviderInfo(p, 0,
+                            ps != null ? ps.getStopped(userId) : false,
+                            ps != null ? ps.getEnabled(userId) : COMPONENT_ENABLED_STATE_DEFAULT,
+                            userId));
                 }
             }
         }
@@ -2824,21 +2915,25 @@
         // reader
         synchronized (mPackages) {
             final Iterator<PackageParser.Provider> i = mProvidersByComponent.values().iterator();
-            final int userId = UserId.getUserId(Binder.getCallingUid());
+            final int userId = processName != null ?
+                    UserId.getUserId(uid) : UserId.getCallingUserId();
             while (i.hasNext()) {
                 final PackageParser.Provider p = i.next();
+                PackageSetting ps = mSettings.mPackages.get(p.owner.packageName);
                 if (p.info.authority != null
                         && (processName == null
                                 || (p.info.processName.equals(processName)
-                                        && UserId.getAppId(p.info.applicationInfo.uid)
-                                            == UserId.getAppId(uid)))
-                        && mSettings.isEnabledLPr(p.info, flags)
+                                        && UserId.isSameApp(p.info.applicationInfo.uid, uid)))
+                        && mSettings.isEnabledLPr(p.info, flags, userId)
                         && (!mSafeMode
                                 || (p.info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)) {
                     if (finalList == null) {
                         finalList = new ArrayList<ProviderInfo>(3);
                     }
-                    finalList.add(PackageParser.generateProviderInfo(p, flags, userId));
+                    finalList.add(PackageParser.generateProviderInfo(p, flags,
+                            ps != null ? ps.getStopped(userId) : false,
+                            ps != null ? ps.getEnabled(userId) : COMPONENT_ENABLED_STATE_DEFAULT,
+                            userId));
                 }
             }
         }
@@ -3482,7 +3577,7 @@
                 pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
             }
 
-            pkg.applicationInfo.uid = pkgSetting.userId;
+            pkg.applicationInfo.uid = pkgSetting.appId;
             pkg.mExtras = pkgSetting;
 
             if (!verifySignaturesLP(pkgSetting, pkg)) {
@@ -3589,7 +3684,7 @@
                         if (ret >= 0) {
                             // TODO: Kill the processes first
                             // Remove the data directories for all users
-                            mUserManager.removePackageForAllUsers(pkgName);
+                            sUserManager.removePackageForAllUsers(pkgName);
                             // Old data gone!
                             String msg = "System package " + pkg.packageName
                                     + " has changed from uid: "
@@ -3610,7 +3705,7 @@
                                 return null;
                             }
                             // Create data directories for all users
-                            mUserManager.installPackageForAllUsers(pkgName,
+                            sUserManager.installPackageForAllUsers(pkgName,
                                     pkg.applicationInfo.uid);
                         }
                         if (!recovered) {
@@ -3652,7 +3747,7 @@
                     return null;
                 }
                 // Create data directories for all users
-                mUserManager.installPackageForAllUsers(pkgName, pkg.applicationInfo.uid);
+                sUserManager.installPackageForAllUsers(pkgName, pkg.applicationInfo.uid);
 
                 if (dataPath.exists()) {
                     pkg.applicationInfo.dataDir = dataPath.getPath();
@@ -4480,19 +4575,23 @@
     private final class ActivityIntentResolver
             extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> {
         public List<ResolveInfo> queryIntent(Intent intent, String resolvedType,
-                boolean defaultOnly) {
+                boolean defaultOnly, int userId) {
+            if (!sUserManager.exists(userId)) return null;
             mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
-            return super.queryIntent(intent, resolvedType, defaultOnly);
+            return super.queryIntent(intent, resolvedType, defaultOnly, userId);
         }
 
-        public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags) {
+        public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags,
+                int userId) {
+            if (!sUserManager.exists(userId)) return null;
             mFlags = flags;
             return super.queryIntent(intent, resolvedType,
-                (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0);
+                    (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId);
         }
 
         public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType,
-                int flags, ArrayList<PackageParser.Activity> packageActivities) {
+                int flags, ArrayList<PackageParser.Activity> packageActivities, int userId) {
+            if (!sUserManager.exists(userId)) return null;
             if (packageActivities == null) {
                 return null;
             }
@@ -4509,7 +4608,7 @@
                     listCut.add(intentFilters);
                 }
             }
-            return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut);
+            return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId);
         }
 
         public final void addActivity(PackageParser.Activity a, String type) {
@@ -4574,7 +4673,8 @@
         }
 
         @Override
-        protected boolean isFilterStopped(PackageParser.ActivityIntentInfo filter) {
+        protected boolean isFilterStopped(PackageParser.ActivityIntentInfo filter, int userId) {
+            if (!sUserManager.exists(userId)) return true;
             PackageParser.Package p = filter.activity.owner;
             if (p != null) {
                 PackageSetting ps = (PackageSetting)p.mExtras;
@@ -4582,7 +4682,7 @@
                     // System apps are never considered stopped for purposes of
                     // filtering, because there may be no way for the user to
                     // actually re-launch them.
-                    return ps.stopped && (ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0;
+                    return ps.getStopped(userId) && (ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0;
                 }
             }
             return false;
@@ -4595,8 +4695,9 @@
         
         @Override
         protected ResolveInfo newResult(PackageParser.ActivityIntentInfo info,
-                int match) {
-            if (!mSettings.isEnabledLPr(info.activity.info, mFlags)) {
+                int match, int userId) {
+            if (!sUserManager.exists(userId)) return null;
+            if (!mSettings.isEnabledLPr(info.activity.info, mFlags, userId)) {
                 return null;
             }
             final PackageParser.Activity activity = info.activity;
@@ -4605,8 +4706,11 @@
                 return null;
             }
             final ResolveInfo res = new ResolveInfo();
+            PackageSetting ps = (PackageSetting) activity.owner.mExtras;
             res.activityInfo = PackageParser.generateActivityInfo(activity, mFlags,
-                    Binder.getOrigCallingUser());
+                    ps != null ? ps.getStopped(userId) : false,
+                    ps != null ? ps.getEnabled(userId) : COMPONENT_ENABLED_STATE_DEFAULT,
+                    userId);
             if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) {
                 res.filter = info;
             }
@@ -4660,19 +4764,22 @@
     private final class ServiceIntentResolver
             extends IntentResolver<PackageParser.ServiceIntentInfo, ResolveInfo> {
         public List<ResolveInfo> queryIntent(Intent intent, String resolvedType,
-                boolean defaultOnly) {
+                boolean defaultOnly, int userId) {
             mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
-            return super.queryIntent(intent, resolvedType, defaultOnly);
+            return super.queryIntent(intent, resolvedType, defaultOnly, userId);
         }
 
-        public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags) {
+        public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags,
+                int userId) {
+            if (!sUserManager.exists(userId)) return null;
             mFlags = flags;
             return super.queryIntent(intent, resolvedType,
-                (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0);
+                    (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId);
         }
 
         public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType,
-                int flags, ArrayList<PackageParser.Service> packageServices) {
+                int flags, ArrayList<PackageParser.Service> packageServices, int userId) {
+            if (!sUserManager.exists(userId)) return null;
             if (packageServices == null) {
                 return null;
             }
@@ -4689,7 +4796,7 @@
                     listCut.add(intentFilters);
                 }
             }
-            return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut);
+            return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId);
         }
 
         public final void addService(PackageParser.Service s) {
@@ -4749,7 +4856,8 @@
         }
 
         @Override
-        protected boolean isFilterStopped(PackageParser.ServiceIntentInfo filter) {
+        protected boolean isFilterStopped(PackageParser.ServiceIntentInfo filter, int userId) {
+            if (!sUserManager.exists(userId)) return true;
             PackageParser.Package p = filter.service.owner;
             if (p != null) {
                 PackageSetting ps = (PackageSetting)p.mExtras;
@@ -4757,7 +4865,8 @@
                     // System apps are never considered stopped for purposes of
                     // filtering, because there may be no way for the user to
                     // actually re-launch them.
-                    return ps.stopped && (ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0;
+                    return ps.getStopped(userId)
+                            && (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0;
                 }
             }
             return false;
@@ -4770,9 +4879,10 @@
         
         @Override
         protected ResolveInfo newResult(PackageParser.ServiceIntentInfo filter,
-                int match) {
+                int match, int userId) {
+            if (!sUserManager.exists(userId)) return null;
             final PackageParser.ServiceIntentInfo info = (PackageParser.ServiceIntentInfo)filter;
-            if (!mSettings.isEnabledLPr(info.service.info, mFlags)) {
+            if (!mSettings.isEnabledLPr(info.service.info, mFlags, userId)) {
                 return null;
             }
             final PackageParser.Service service = info.service;
@@ -4781,8 +4891,11 @@
                 return null;
             }
             final ResolveInfo res = new ResolveInfo();
+            PackageSetting ps = (PackageSetting) service.owner.mExtras;
             res.serviceInfo = PackageParser.generateServiceInfo(service, mFlags,
-                    Binder.getOrigCallingUser());
+                    ps != null ? ps.getStopped(userId) : false,
+                    ps != null ? ps.getEnabled(userId) : COMPONENT_ENABLED_STATE_DEFAULT,
+                    userId);
             if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) {
                 res.filter = filter;
             }
@@ -4873,23 +4986,32 @@
     };
 
     static final void sendPackageBroadcast(String action, String pkg,
-            Bundle extras, String targetPkg, IIntentReceiver finishedReceiver) {
+            Bundle extras, String targetPkg, IIntentReceiver finishedReceiver, int userId) {
         IActivityManager am = ActivityManagerNative.getDefault();
         if (am != null) {
             try {
-                final Intent intent = new Intent(action,
-                        pkg != null ? Uri.fromParts("package", pkg, null) : null);
-                if (extras != null) {
-                    intent.putExtras(extras);
+                int[] userIds = userId == UserId.USER_ALL
+                        ? sUserManager.getUserIds() 
+                        : new int[] {userId};
+                for (int id : userIds) {
+                    final Intent intent = new Intent(action,
+                            pkg != null ? Uri.fromParts("package", pkg, null) : null);
+                    if (extras != null) {
+                        intent.putExtras(extras);
+                    }
+                    if (targetPkg != null) {
+                        intent.setPackage(targetPkg);
+                    }
+                    // Modify the UID when posting to other users
+                    int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                    if (uid > 0 && id > 0) {
+                        uid = UserId.getUid(id, UserId.getAppId(uid));
+                        intent.putExtra(Intent.EXTRA_UID, uid);
+                    }
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                    am.broadcastIntent(null, intent, null, finishedReceiver,
+                            0, null, null, null, finishedReceiver != null, false, id);
                 }
-                if (targetPkg != null) {
-                    intent.setPackage(targetPkg);
-                }
-                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-                // TODO: Fix the userId argument
-                am.broadcastIntent(null, intent, null, finishedReceiver,
-                        0, null, null, null, finishedReceiver != null, false,
-                        Binder.getOrigCallingUser());
             } catch (RemoteException ex) {
             }
         }
@@ -5032,13 +5154,13 @@
                 extras.putInt(Intent.EXTRA_UID, removedUid);
                 extras.putBoolean(Intent.EXTRA_DATA_REMOVED, false);
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
-                        extras, null, null);
+                        extras, null, null, UserId.USER_ALL);
             }
             if (addedPackage != null) {
                 Bundle extras = new Bundle(1);
                 extras.putInt(Intent.EXTRA_UID, addedUid);
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage,
-                        extras, null, null);
+                        extras, null, null, UserId.USER_ALL);
             }
         }
 
@@ -5635,14 +5757,14 @@
                  * do, then we'll defer to them to verify the packages.
                  */
                 final int requiredUid = mRequiredVerifierPackage == null ? -1
-                        : getPackageUid(mRequiredVerifierPackage);
+                        : getPackageUid(mRequiredVerifierPackage, 0);
                 if (requiredUid != -1 && isVerificationEnabled()) {
                     final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
                     verification.setDataAndType(packageURI, PACKAGE_MIME_TYPE);
                     verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 
                     final List<ResolveInfo> receivers = queryIntentReceivers(verification, null,
-                            PackageManager.GET_DISABLED_COMPONENTS);
+                            PackageManager.GET_DISABLED_COMPONENTS, 0 /* TODO: Which userId? */);
 
                     if (DEBUG_VERIFY) {
                         Slog.d(TAG, "Found " + receivers.size() + " verifiers for intent "
@@ -7062,11 +7184,11 @@
                 extras.putBoolean(Intent.EXTRA_REPLACING, true);
 
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
-                        extras, null, null);
+                        extras, null, null, UserId.USER_ALL);
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
-                        extras, null, null);
+                        extras, null, null, UserId.USER_ALL);
                 sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null,
-                        null, packageName, null);
+                        null, packageName, null, UserId.USER_ALL);
             }
         }
         // Force a gc here.
@@ -7099,14 +7221,15 @@
             }
             if (removedPackage != null) {
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
-                        extras, null, null);
+                        extras, null, null, UserId.USER_ALL);
                 if (fullRemove && !replacing) {
                     sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage,
-                            extras, null, null);
+                            extras, null, null, UserId.USER_ALL);
                 }
             }
             if (removedUid >= 0) {
-                sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null, null);
+                sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null, null,
+                        UserId.getUserId(removedUid));
             }
         }
     }
@@ -7138,7 +7261,7 @@
                 // we don't consider this to be a failure of the core package deletion
             } else {
                 // TODO: Kill the processes first
-                mUserManager.removePackageForAllUsers(packageName);
+                sUserManager.removePackageForAllUsers(packageName);
             }
             schedulePackageCleaning(packageName);
         }
@@ -7316,17 +7439,19 @@
         return ret;
     }
 
+    @Override
     public void clearApplicationUserData(final String packageName,
-            final IPackageDataObserver observer) {
+            final IPackageDataObserver observer, final int userId) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.CLEAR_APP_USER_DATA, null);
+        checkValidCaller(Binder.getCallingUid(), userId);
         // Queue up an async operation since the package deletion may take a little while.
         mHandler.post(new Runnable() {
             public void run() {
                 mHandler.removeCallbacks(this);
                 final boolean succeeded;
                 synchronized (mInstallLock) {
-                    succeeded = clearApplicationUserDataLI(packageName);
+                    succeeded = clearApplicationUserDataLI(packageName, userId);
                 }
                 if (succeeded) {
                     // invoke DeviceStorageMonitor's update method to clear any notifications
@@ -7347,7 +7472,7 @@
         });
     }
 
-    private boolean clearApplicationUserDataLI(String packageName) {
+    private boolean clearApplicationUserDataLI(String packageName, int userId) {
         if (packageName == null) {
             Slog.w(TAG, "Attempt to delete null packageName.");
             return false;
@@ -7379,7 +7504,7 @@
                 return false;
             }
         }
-        int retCode = mInstaller.clearUserData(packageName, 0); // TODO - correct userId
+        int retCode = mInstaller.clearUserData(packageName, userId);
         if (retCode < 0) {
             Slog.w(TAG, "Couldn't remove cache files for package: "
                     + packageName);
@@ -7393,12 +7518,13 @@
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.DELETE_CACHE_FILES, null);
         // Queue up an async operation since the package deletion may take a little while.
+        final int userId = UserId.getCallingUserId();
         mHandler.post(new Runnable() {
             public void run() {
                 mHandler.removeCallbacks(this);
                 final boolean succeded;
                 synchronized (mInstallLock) {
-                    succeded = deleteApplicationCacheFilesLI(packageName);
+                    succeded = deleteApplicationCacheFilesLI(packageName, userId);
                 }
                 if(observer != null) {
                     try {
@@ -7411,7 +7537,7 @@
         });
     }
 
-    private boolean deleteApplicationCacheFilesLI(String packageName) {
+    private boolean deleteApplicationCacheFilesLI(String packageName, int userId) {
         if (packageName == null) {
             Slog.w(TAG, "Attempt to delete null packageName.");
             return false;
@@ -7429,6 +7555,7 @@
             Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
             return false;
         }
+        // TODO: Pass userId to deleteCacheFiles
         int retCode = mInstaller.deleteCacheFiles(packageName);
         if (retCode < 0) {
             Slog.w(TAG, "Couldn't remove cache files for package: "
@@ -7695,19 +7822,23 @@
         return num;
     }
 
+    @Override
     public void setApplicationEnabledSetting(String appPackageName,
-            int newState, int flags) {
-        setEnabledSetting(appPackageName, null, newState, flags);
+            int newState, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return;
+        setEnabledSetting(appPackageName, null, newState, flags, userId);
     }
 
+    @Override
     public void setComponentEnabledSetting(ComponentName componentName,
-            int newState, int flags) {
+            int newState, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return;
         setEnabledSetting(componentName.getPackageName(),
-                componentName.getClassName(), newState, flags);
+                componentName.getClassName(), newState, flags, userId);
     }
 
     private void setEnabledSetting(
-            final String packageName, String className, int newState, final int flags) {
+            final String packageName, String className, int newState, final int flags, int userId) {
         if (!(newState == COMPONENT_ENABLED_STATE_DEFAULT
               || newState == COMPONENT_ENABLED_STATE_ENABLED
               || newState == COMPONENT_ENABLED_STATE_DISABLED
@@ -7719,6 +7850,7 @@
         final int uid = Binder.getCallingUid();
         final int permission = mContext.checkCallingPermission(
                 android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
+        checkValidCaller(uid, userId);
         final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
         boolean sendNow = false;
         boolean isApp = (className == null);
@@ -7738,20 +7870,21 @@
                         "Unknown component: " + packageName
                         + "/" + className);
             }
-            if (!allowedByPermission && (!UserId.isSameApp(uid, pkgSetting.userId))) {
+            // Allow root and verify that userId is not being specified by a different user
+            if (!allowedByPermission && !UserId.isSameApp(uid, pkgSetting.appId)) {
                 throw new SecurityException(
                         "Permission Denial: attempt to change component state from pid="
                         + Binder.getCallingPid()
-                        + ", uid=" + uid + ", package uid=" + pkgSetting.userId);
+                        + ", uid=" + uid + ", package uid=" + pkgSetting.appId);
             }
             if (className == null) {
                 // We're dealing with an application/package level state change
-                if (pkgSetting.enabled == newState) {
+                if (pkgSetting.getEnabled(userId) == newState) {
                     // Nothing to do
                     return;
                 }
-                pkgSetting.enabled = newState;
-                pkgSetting.pkg.mSetEnabled = newState;
+                pkgSetting.setEnabled(newState, userId);
+                // pkgSetting.pkg.mSetEnabled = newState;
             } else {
                 // We're dealing with a component level state change
                 // First, verify that this is a valid class name.
@@ -7767,17 +7900,17 @@
                 }
                 switch (newState) {
                 case COMPONENT_ENABLED_STATE_ENABLED:
-                    if (!pkgSetting.enableComponentLPw(className)) {
+                    if (!pkgSetting.enableComponentLPw(className, userId)) {
                         return;
                     }
                     break;
                 case COMPONENT_ENABLED_STATE_DISABLED:
-                    if (!pkgSetting.disableComponentLPw(className)) {
+                    if (!pkgSetting.disableComponentLPw(className, userId)) {
                         return;
                     }
                     break;
                 case COMPONENT_ENABLED_STATE_DEFAULT:
-                    if (!pkgSetting.restoreComponentLPw(className)) {
+                    if (!pkgSetting.restoreComponentLPw(className, userId)) {
                         return;
                     }
                     break;
@@ -7786,8 +7919,8 @@
                     return;
                 }
             }
-            mSettings.writeLPr();
-            packageUid = pkgSetting.userId;
+            mSettings.writePackageRestrictionsLPr(userId);
+            packageUid = UserId.getUid(userId, pkgSetting.appId);
             components = mPendingBroadcasts.get(packageName);
             final boolean newPackage = components == null;
             if (newPackage) {
@@ -7835,19 +7968,22 @@
         extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList);
         extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag);
         extras.putInt(Intent.EXTRA_UID, packageUid);
-        sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED,  packageName, extras, null, null);
+        sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED,  packageName, extras, null, null,
+                UserId.getUserId(packageUid));
     }
 
-    public void setPackageStoppedState(String packageName, boolean stopped) {
+    public void setPackageStoppedState(String packageName, boolean stopped, int userId) {
+        if (!sUserManager.exists(userId)) return;
         final int uid = Binder.getCallingUid();
         final int permission = mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
         final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
+        checkValidCaller(uid, userId);
         // writer
         synchronized (mPackages) {
             if (mSettings.setPackageStoppedStateLPw(packageName, stopped, allowedByPermission,
-                    uid)) {
-                scheduleWriteStoppedPackagesLocked();
+                    uid, userId)) {
+                scheduleWritePackageRestrictionsLocked(userId);
             }
         }
     }
@@ -7859,17 +7995,25 @@
         }
     }
 
-    public int getApplicationEnabledSetting(String packageName) {
+    @Override
+    public int getApplicationEnabledSetting(String packageName, int userId) {
+        if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED;
+        int uid = Binder.getCallingUid();
+        checkValidCaller(uid, userId);
         // reader
         synchronized (mPackages) {
-            return mSettings.getApplicationEnabledSettingLPr(packageName);
+            return mSettings.getApplicationEnabledSettingLPr(packageName, userId);
         }
     }
 
-    public int getComponentEnabledSetting(ComponentName componentName) {
+    @Override
+    public int getComponentEnabledSetting(ComponentName componentName, int userId) {
+        if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED;
+        int uid = Binder.getCallingUid();
+        checkValidCaller(uid, userId);
         // reader
         synchronized (mPackages) {
-            return mSettings.getComponentEnabledSettingLPr(componentName);
+            return mSettings.getComponentEnabledSettingLPr(componentName, userId);
         }
     }
 
@@ -8073,7 +8217,7 @@
                 pw.print("  Required: ");
                 pw.print(mRequiredVerifierPackage);
                 pw.print(" (uid=");
-                pw.print(getPackageUid(mRequiredVerifierPackage));
+                pw.print(getPackageUid(mRequiredVerifierPackage, 0));
                 pw.println(")");
             }
 
@@ -8338,7 +8482,7 @@
                                     + " at code path: " + ps.codePathString);
                         // We do have a valid package installed on sdcard
                         processCids.put(args, ps.codePathString);
-                        int uid = ps.userId;
+                        int uid = ps.appId;
                         if (uid != -1) {
                             uidList[num++] = uid;
                         }
@@ -8391,7 +8535,7 @@
             }
             String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
                     : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
-            sendPackageBroadcast(action, null, extras, null, finishedReceiver);
+            sendPackageBroadcast(action, null, extras, null, finishedReceiver, UserId.USER_ALL);
         }
     }
 
@@ -8815,8 +8959,12 @@
         // TODO(kroot): Add a real permission for creating users
         enforceSystemOrRoot("Only the system can create users");
 
-        // TODO(kroot): fix this API
-        UserInfo userInfo = mUserManager.createUser(name, flags, new ArrayList<ApplicationInfo>());
+        UserInfo userInfo = sUserManager.createUser(name, flags);
+        if (userInfo != null) {
+            Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
+            addedIntent.putExtra(Intent.EXTRA_USERID, userInfo.id);
+            mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_ACCOUNTS);
+        }
         return userInfo;
     }
 
@@ -8824,13 +8972,34 @@
         // TODO(kroot): Add a real permission for removing users
         enforceSystemOrRoot("Only the system can remove users");
 
-        if (userId == 0) {
+        if (userId == 0 || !sUserManager.exists(userId)) {
             return false;
         }
-        mUserManager.removeUser(userId);
+
+        cleanUpUser(userId);
+
+        if (sUserManager.removeUser(userId)) {
+            // Let other services shutdown any activity
+            Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
+            addedIntent.putExtra(Intent.EXTRA_USERID, userId);
+            mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_ACCOUNTS);
+        }
+        sUserManager.removePackageFolders(userId);
         return true;
     }
 
+    private void cleanUpUser(int userId) {
+        // Disable all the packages for the user first
+        synchronized (mPackages) {
+            Set<Entry<String, PackageSetting>> entries = mSettings.mPackages.entrySet();
+            for (Entry<String, PackageSetting> entry : entries) {
+                entry.getValue().removeUser(userId);
+            }
+            if (mDirtyUsers.remove(userId));
+            mSettings.removeUserLPr(userId);
+        }
+    }
+
     @Override
     public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException {
         mContext.enforceCallingOrSelfPermission(
@@ -8842,8 +9011,22 @@
         }
     }
 
+    @Override
     public List<UserInfo> getUsers() {
-        return mUserManager.getUsers();
+        enforceSystemOrRoot("Only the system can query users");
+        return sUserManager.getUsers();
+    }
+
+    @Override
+    public UserInfo getUser(int userId) {
+        enforceSystemOrRoot("Only the system can remove users");
+        return sUserManager.getUser(userId);
+    }
+
+    @Override
+    public void updateUserName(int userId, String name) {
+        enforceSystemOrRoot("Only the system can rename users");
+        sUserManager.updateUserName(userId, name);
     }
 
     @Override
@@ -8854,6 +9037,19 @@
                 if (mSettings.mReadExternalStorageEnforcement != enforcement) {
                     mSettings.mReadExternalStorageEnforcement = enforcement;
                     mSettings.writeLPr();
+
+                    // kill any non-foreground processes so we restart them and
+                    // grant/revoke the GID.
+                    final IActivityManager am = ActivityManagerNative.getDefault();
+                    if (am != null) {
+                        final long token = Binder.clearCallingIdentity();
+                        try {
+                            am.killProcessesBelowForeground("setPermissionEnforcement");
+                        } catch (RemoteException e) {
+                        } finally {
+                            Binder.restoreCallingIdentity(token);
+                        }
+                    }
                 }
             }
         } else {
diff --git a/services/java/com/android/server/pm/PackageSetting.java b/services/java/com/android/server/pm/PackageSetting.java
index efdc2b3..f7f0870 100644
--- a/services/java/com/android/server/pm/PackageSetting.java
+++ b/services/java/com/android/server/pm/PackageSetting.java
@@ -24,7 +24,7 @@
  * Settings data for a particular package we know about.
  */
 final class PackageSetting extends PackageSettingBase {
-    int userId;
+    int appId;
     PackageParser.Package pkg;
     SharedUserSetting sharedUser;
 
@@ -41,7 +41,7 @@
     PackageSetting(PackageSetting orig) {
         super(orig);
 
-        userId = orig.userId;
+        appId = orig.appId;
         pkg = orig.pkg;
         sharedUser = orig.sharedUser;
     }
@@ -50,6 +50,6 @@
     public String toString() {
         return "PackageSetting{"
             + Integer.toHexString(System.identityHashCode(this))
-            + " " + name + "/" + userId + "}";
+            + " " + name + "/" + appId + "}";
     }
 }
\ No newline at end of file
diff --git a/services/java/com/android/server/pm/PackageSettingBase.java b/services/java/com/android/server/pm/PackageSettingBase.java
index e2f83ad..56f2166 100644
--- a/services/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/java/com/android/server/pm/PackageSettingBase.java
@@ -20,6 +20,8 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
+import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 import java.io.File;
 import java.util.HashSet;
@@ -62,20 +64,22 @@
 
     // Whether this package is currently stopped, thus can not be
     // started until explicitly launched by the user.
-    public boolean stopped;
+    private SparseArray<Boolean> stopped = new SparseArray<Boolean>();
 
     // Set to true if we have never launched this app.
-    public boolean notLaunched;
+    private SparseArray<Boolean> notLaunched = new SparseArray<Boolean>();
 
     /* Explicitly disabled components */
-    HashSet<String> disabledComponents = new HashSet<String>(0);
+    private SparseArray<HashSet<String>> disabledComponents = new SparseArray<HashSet<String>>();
     /* Explicitly enabled components */
-    HashSet<String> enabledComponents = new HashSet<String>(0);
-    int enabled = COMPONENT_ENABLED_STATE_DEFAULT;
+    private SparseArray<HashSet<String>> enabledComponents = new SparseArray<HashSet<String>>();
+    /* Enabled state */
+    private SparseIntArray enabled = new SparseIntArray();
+
     int installStatus = PKG_INSTALL_COMPLETE;
 
     PackageSettingBase origPackage;
-    
+
     /* package name of the app that installed this package */
     String installerPackageName;
     PackageSettingBase(String name, String realName, File codePath, File resourcePath,
@@ -111,14 +115,12 @@
 
         permissionsFixed = base.permissionsFixed;
         haveGids = base.haveGids;
-        stopped = base.stopped;
         notLaunched = base.notLaunched;
 
-        disabledComponents = (HashSet<String>) base.disabledComponents.clone();
-
-        enabledComponents = (HashSet<String>) base.enabledComponents.clone();
-
-        enabled = base.enabled;
+        disabledComponents = (SparseArray<HashSet<String>>) base.disabledComponents.clone();
+        enabledComponents = (SparseArray<HashSet<String>>) base.enabledComponents.clone();
+        enabled = (SparseIntArray) base.enabled.clone();
+        stopped = (SparseArray<Boolean>) base.stopped.clone();
         installStatus = base.installStatus;
 
         origPackage = base.origPackage;
@@ -177,31 +179,107 @@
         installStatus = base.installStatus;
     }
 
-    boolean enableComponentLPw(String componentClassName) {
-        boolean changed = disabledComponents.remove(componentClassName);
-        changed |= enabledComponents.add(componentClassName);
+    void setEnabled(int state, int userId) {
+        enabled.put(userId, state);
+    }
+
+    int getEnabled(int userId) {
+        return enabled.get(userId, COMPONENT_ENABLED_STATE_DEFAULT);
+    }
+
+    boolean getStopped(int userId) {
+        return stopped.get(userId, false);
+    }
+
+    void setStopped(boolean stop, int userId) {
+        stopped.put(userId, stop);
+    }
+
+    boolean getNotLaunched(int userId) {
+        return notLaunched.get(userId, false);
+    }
+
+    void setNotLaunched(boolean stop, int userId) {
+        notLaunched.put(userId, stop);
+    }
+
+    HashSet<String> getEnabledComponents(int userId) {
+        return getComponentHashSet(enabledComponents, userId);
+    }
+
+    HashSet<String> getDisabledComponents(int userId) {
+        return getComponentHashSet(disabledComponents, userId);
+    }
+
+    void setEnabledComponents(HashSet<String> components, int userId) {
+        enabledComponents.put(userId, components);
+    }
+
+    void setDisabledComponents(HashSet<String> components, int userId) {
+        disabledComponents.put(userId, components);
+    }
+
+    private HashSet<String> getComponentHashSet(SparseArray<HashSet<String>> setArray, int userId) {
+        HashSet<String> set = setArray.get(userId);
+        if (set == null) {
+            set = new HashSet<String>(1);
+            setArray.put(userId, set);
+        }
+        return set;
+    }
+
+    void addDisabledComponent(String componentClassName, int userId) {
+        HashSet<String> disabled = getComponentHashSet(disabledComponents, userId);
+        disabled.add(componentClassName);
+    }
+
+    void addEnabledComponent(String componentClassName, int userId) {
+        HashSet<String> enabled = getComponentHashSet(enabledComponents, userId);
+        enabled.add(componentClassName);
+    }
+
+    boolean enableComponentLPw(String componentClassName, int userId) {
+        HashSet<String> disabled = getComponentHashSet(disabledComponents, userId);
+        HashSet<String> enabled = getComponentHashSet(enabledComponents, userId);
+        boolean changed = disabled.remove(componentClassName);
+        changed |= enabled.add(componentClassName);
         return changed;
     }
 
-    boolean disableComponentLPw(String componentClassName) {
-        boolean changed = enabledComponents.remove(componentClassName);
-        changed |= disabledComponents.add(componentClassName);
+    boolean disableComponentLPw(String componentClassName, int userId) {
+        HashSet<String> disabled = getComponentHashSet(disabledComponents, userId);
+        HashSet<String> enabled = getComponentHashSet(enabledComponents, userId);
+        boolean changed = enabled.remove(componentClassName);
+        changed |= disabled.add(componentClassName);
         return changed;
     }
 
-    boolean restoreComponentLPw(String componentClassName) {
-        boolean changed = enabledComponents.remove(componentClassName);
-        changed |= disabledComponents.remove(componentClassName);
+    boolean restoreComponentLPw(String componentClassName, int userId) {
+        HashSet<String> disabled = getComponentHashSet(disabledComponents, userId);
+        HashSet<String> enabled = getComponentHashSet(enabledComponents, userId);
+        boolean changed = enabled.remove(componentClassName);
+        changed |= disabled.remove(componentClassName);
         return changed;
     }
 
-    int getCurrentEnabledStateLPr(String componentName) {
-        if (enabledComponents.contains(componentName)) {
+    int getCurrentEnabledStateLPr(String componentName, int userId) {
+        HashSet<String> disabled = getComponentHashSet(disabledComponents, userId);
+        HashSet<String> enabled = getComponentHashSet(enabledComponents, userId);
+        if (enabled.contains(componentName)) {
             return COMPONENT_ENABLED_STATE_ENABLED;
-        } else if (disabledComponents.contains(componentName)) {
+        } else if (disabled.contains(componentName)) {
             return COMPONENT_ENABLED_STATE_DISABLED;
         } else {
             return COMPONENT_ENABLED_STATE_DEFAULT;
         }
     }
-}
\ No newline at end of file
+
+    void removeUser(int userId) {
+        enabled.delete(userId);
+        stopped.delete(userId);
+        enabledComponents.delete(userId);
+        disabledComponents.delete(userId);
+        notLaunched.delete(userId);
+    }
+
+}
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index 363d020..bb7f4fc 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -32,6 +32,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
+import android.app.AppGlobals;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -40,11 +41,14 @@
 import android.content.pm.PackageParser;
 import android.content.pm.PermissionInfo;
 import android.content.pm.Signature;
+import android.content.pm.UserInfo;
 import android.content.pm.VerifierDeviceIdentity;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserId;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -63,6 +67,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 
 import libcore.io.IoUtils;
@@ -78,6 +83,17 @@
     private static final String TAG_READ_EXTERNAL_STORAGE = "read-external-storage";
     private static final String ATTR_ENFORCEMENT = "enforcement";
 
+    private static final String TAG_ITEM = "item";
+    private static final String TAG_DISABLED_COMPONENTS = "disabled-components";
+    private static final String TAG_ENABLED_COMPONENTS = "enabled-components";
+    private static final String TAG_PACKAGE_RESTRICTIONS = "package-restrictions";
+    private static final String TAG_PACKAGE = "pkg";
+
+    private static final String ATTR_NAME = "name";
+    private static final String ATTR_NOT_LAUNCHED = "nl";
+    private static final String ATTR_ENABLED = "enabled";
+    private static final String ATTR_STOPPED = "stopped";
+
     private final File mSettingsFilename;
     private final File mBackupSettingsFilename;
     private final File mPackageListFilename;
@@ -153,19 +169,24 @@
      */
     private final ArrayList<PendingPackage> mPendingPackages = new ArrayList<PendingPackage>();
 
+    private final File mSystemDir;
     Settings() {
-        File dataDir = Environment.getDataDirectory();
-        File systemDir = new File(dataDir, "system");
-        systemDir.mkdirs();
-        FileUtils.setPermissions(systemDir.toString(),
+        this(Environment.getDataDirectory());
+    }
+
+    Settings(File dataDir) {
+        mSystemDir = new File(dataDir, "system");
+        mSystemDir.mkdirs();
+        FileUtils.setPermissions(mSystemDir.toString(),
                 FileUtils.S_IRWXU|FileUtils.S_IRWXG
                 |FileUtils.S_IROTH|FileUtils.S_IXOTH,
                 -1, -1);
-        mSettingsFilename = new File(systemDir, "packages.xml");
-        mBackupSettingsFilename = new File(systemDir, "packages-backup.xml");
-        mPackageListFilename = new File(systemDir, "packages.list");
-        mStoppedPackagesFilename = new File(systemDir, "packages-stopped.xml");
-        mBackupStoppedPackagesFilename = new File(systemDir, "packages-stopped-backup.xml");
+        mSettingsFilename = new File(mSystemDir, "packages.xml");
+        mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
+        mPackageListFilename = new File(mSystemDir, "packages.list");
+        // Deprecated: Needed for migration
+        mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
+        mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
     }
 
     PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage,
@@ -254,7 +275,7 @@
             p.pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
         }
         PackageSetting ret = addPackageLPw(name, p.realName, p.codePath, p.resourcePath,
-                p.nativeLibraryPathString, p.userId, p.versionCode, p.pkgFlags);
+                p.nativeLibraryPathString, p.appId, p.versionCode, p.pkgFlags);
         mDisabledSysPackages.remove(name);
         return ret;
     }
@@ -263,7 +284,7 @@
             String nativeLibraryPathString, int uid, int vc, int pkgFlags) {
         PackageSetting p = mPackages.get(name);
         if (p != null) {
-            if (p.userId == uid) {
+            if (p.appId == uid) {
                 return p;
             }
             PackageManagerService.reportSettingsProblem(Log.ERROR,
@@ -272,7 +293,7 @@
         }
         p = new PackageSetting(name, realName, codePath, resourcePath, nativeLibraryPathString,
                 vc, pkgFlags);
-        p.userId = uid;
+        p.appId = uid;
         if (addUserIdLPw(uid, p, name)) {
             mPackages.put(name, p);
             return p;
@@ -323,7 +344,7 @@
             }
         }
     }
-    
+
     private PackageSetting getPackageLPw(String name, PackageSetting origPackage,
             String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
             String nativeLibraryPathString, int vc, int pkgFlags, boolean create, boolean add) {
@@ -335,13 +356,13 @@
                     // This is an updated system app with versions in both system
                     // and data partition. Just let the most recent version
                     // take precedence.
-                    Slog.w(PackageManagerService.TAG, "Trying to update system app code path from " +
-                            p.codePathString + " to " + codePath.toString());
+                    Slog.w(PackageManagerService.TAG, "Trying to update system app code path from "
+                            + p.codePathString + " to " + codePath.toString());
                 } else {
                     // Just a change in the code path is not an issue, but
                     // let's log a message about it.
-                    Slog.i(PackageManagerService.TAG, "Package " + name + " codePath changed from " + p.codePath
-                            + " to " + codePath + "; Retaining data and using new");
+                    Slog.i(PackageManagerService.TAG, "Package " + name + " codePath changed from "
+                            + p.codePath + " to " + codePath + "; Retaining data and using new");
                     /*
                      * Since we've changed paths, we need to prefer the new
                      * native library path over the one stored in the
@@ -378,15 +399,15 @@
                 // We are consuming the data from an existing package.
                 p = new PackageSetting(origPackage.name, name, codePath, resourcePath,
                         nativeLibraryPathString, vc, pkgFlags);
-                if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package " + name
-                        + " is adopting original package " + origPackage.name);
+                if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
+                        + name + " is adopting original package " + origPackage.name);
                 // Note that we will retain the new package's signature so
                 // that we can keep its data.
                 PackageSignatures s = p.signatures;
                 p.copyFrom(origPackage);
                 p.signatures = s;
                 p.sharedUser = origPackage.sharedUser;
-                p.userId = origPackage.userId;
+                p.appId = origPackage.appId;
                 p.origPackage = origPackage;
                 mRenamedPackages.put(name, origPackage.name);
                 name = origPackage.name;
@@ -404,11 +425,17 @@
                         e.fillInStackTrace();
                         Slog.i(PackageManagerService.TAG, "Stopping package " + name, e);
                     }
-                    p.stopped = true;
-                    p.notLaunched = true;
+                    List<UserInfo> users = getAllUsers();
+                    if (users != null) {
+                        for (UserInfo user : users) {
+                            p.setStopped(true, user.id);
+                            p.setNotLaunched(true, user.id);
+                            writePackageRestrictionsLPr(user.id);
+                        }
+                    }
                 }
                 if (sharedUser != null) {
-                    p.userId = sharedUser.userId;
+                    p.appId = sharedUser.userId;
                 } else {
                     // Clone the setting here for disabled system packages
                     PackageSetting dis = mDisabledSysPackages.get(name);
@@ -420,21 +447,31 @@
                         if (dis.signatures.mSignatures != null) {
                             p.signatures.mSignatures = dis.signatures.mSignatures.clone();
                         }
-                        p.userId = dis.userId;
+                        p.appId = dis.appId;
                         // Clone permissions
                         p.grantedPermissions = new HashSet<String>(dis.grantedPermissions);
                         // Clone component info
-                        p.disabledComponents = new HashSet<String>(dis.disabledComponents);
-                        p.enabledComponents = new HashSet<String>(dis.enabledComponents);
+                        List<UserInfo> users = getAllUsers();
+                        if (users != null) {
+                            for (UserInfo user : users) {
+                                int userId = user.id;
+                                p.setDisabledComponents(
+                                        new HashSet<String>(dis.getDisabledComponents(userId)),
+                                        userId);
+                                p.setEnabledComponents(
+                                        new HashSet<String>(dis.getEnabledComponents(userId)),
+                                        userId);
+                            }
+                        }
                         // Add new setting to list of user ids
-                        addUserIdLPw(p.userId, p, name);
+                        addUserIdLPw(p.appId, p, name);
                     } else {
                         // Assign new user id
-                        p.userId = newUserIdLPw(p);
+                        p.appId = newUserIdLPw(p);
                     }
                 }
             }
-            if (p.userId < 0) {
+            if (p.appId < 0) {
                 PackageManagerService.reportSettingsProblem(Log.WARN,
                         "Package " + name + " could not be assigned a valid uid");
                 return null;
@@ -450,8 +487,8 @@
 
     void insertPackageSettingLPw(PackageSetting p, PackageParser.Package pkg) {
         p.pkg = pkg;
-        pkg.mSetEnabled = p.enabled;
-        pkg.mSetStopped = p.stopped;
+        // pkg.mSetEnabled = p.getEnabled(userId);
+        // pkg.mSetStopped = p.getStopped(userId);
         final String codePath = pkg.applicationInfo.sourceDir;
         final String resourcePath = pkg.applicationInfo.publicSourceDir;
         // Update code path if needed
@@ -475,18 +512,18 @@
             p.nativeLibraryPathString = nativeLibraryPath;
         }
         // Update version code if needed
-         if (pkg.mVersionCode != p.versionCode) {
+        if (pkg.mVersionCode != p.versionCode) {
             p.versionCode = pkg.mVersionCode;
         }
-         // Update signatures if needed.
-         if (p.signatures.mSignatures == null) {
-             p.signatures.assignSignatures(pkg.mSignatures);
-         }
-         // If this app defines a shared user id initialize
-         // the shared user signatures as well.
-         if (p.sharedUser != null && p.sharedUser.signatures.mSignatures == null) {
-             p.sharedUser.signatures.assignSignatures(pkg.mSignatures);
-         }
+        // Update signatures if needed.
+        if (p.signatures.mSignatures == null) {
+            p.signatures.assignSignatures(pkg.mSignatures);
+        }
+        // If this app defines a shared user id initialize
+        // the shared user signatures as well.
+        if (p.sharedUser != null && p.sharedUser.signatures.mSignatures == null) {
+            p.sharedUser.signatures.assignSignatures(pkg.mSignatures);
+        }
         addPackageSettingLPw(p, pkg.packageName, p.sharedUser);
     }
 
@@ -502,9 +539,9 @@
                         + p.sharedUser + " but is now " + sharedUser
                         + "; I am not changing its files so it will probably fail!");
                 p.sharedUser.packages.remove(p);
-            } else if (p.userId != sharedUser.userId) {
+            } else if (p.appId != sharedUser.userId) {
                 PackageManagerService.reportSettingsProblem(Log.ERROR,
-                    "Package " + p.name + " was user id " + p.userId
+                    "Package " + p.name + " was user id " + p.appId
                     + " but is now user " + sharedUser
                     + " with id " + sharedUser.userId
                     + "; I am not changing its files so it will probably fail!");
@@ -512,7 +549,7 @@
 
             sharedUser.packages.add(p);
             p.sharedUser = sharedUser;
-            p.userId = sharedUser.userId;
+            p.appId = sharedUser.userId;
         }
     }
 
@@ -577,8 +614,8 @@
                     return p.sharedUser.userId;
                 }
             } else {
-                removeUserIdLPw(p.userId);
-                return p.userId;
+                removeUserIdLPw(p.appId);
+                return p.appId;
             }
         }
         return -1;
@@ -591,7 +628,7 @@
                 p.sharedUser.packages.remove(p);
                 p.sharedUser.packages.add(newp);
             } else {
-                replaceUserIdLPw(p.userId, newp);
+                replaceUserIdLPw(p.appId, newp);
             }
         }
         mPackages.put(name, newp);
@@ -658,50 +695,269 @@
         }
     }
 
-    void writeStoppedLPr() {
+    private File getUserPackagesStateFile(int userId) {
+        return new File(mSystemDir,
+                "users/" + userId + "/package-restrictions.xml");
+    }
+
+    private File getUserPackagesStateBackupFile(int userId) {
+        return new File(mSystemDir,
+                "users/" + userId + "/package-restrictions-backup.xml");
+    }
+
+    void writeAllUsersPackageRestrictionsLPr() {
+        List<UserInfo> users = getAllUsers();
+        if (users == null) return;
+
+        for (UserInfo user : users) {
+            writePackageRestrictionsLPr(user.id);
+        }
+    }
+
+    void readAllUsersPackageRestrictionsLPr() {
+        List<UserInfo> users = getAllUsers();
+        if (users == null) {
+            readPackageRestrictionsLPr(0);
+            return;
+        }
+
+        for (UserInfo user : users) {
+            readPackageRestrictionsLPr(user.id);
+        }
+    }
+
+    void readPackageRestrictionsLPr(int userId) {
+        FileInputStream str = null;
+        File userPackagesStateFile = getUserPackagesStateFile(userId);
+        File backupFile = getUserPackagesStateBackupFile(userId);
+        if (backupFile.exists()) {
+            try {
+                str = new FileInputStream(backupFile);
+                mReadMessages.append("Reading from backup stopped packages file\n");
+                PackageManagerService.reportSettingsProblem(Log.INFO,
+                        "Need to read from backup stopped packages file");
+                if (userPackagesStateFile.exists()) {
+                    // If both the backup and normal file exist, we
+                    // ignore the normal one since it might have been
+                    // corrupted.
+                    Slog.w(PackageManagerService.TAG, "Cleaning up stopped packages file "
+                            + userPackagesStateFile);
+                    userPackagesStateFile.delete();
+                }
+            } catch (java.io.IOException e) {
+                // We'll try for the normal settings file.
+            }
+        }
+
+        try {
+            if (str == null) {
+                if (!userPackagesStateFile.exists()) {
+                    mReadMessages.append("No stopped packages file found\n");
+                    PackageManagerService.reportSettingsProblem(Log.INFO,
+                            "No stopped packages file; "
+                            + "assuming all started");
+                    // At first boot, make sure no packages are stopped.
+                    // We usually want to have third party apps initialize
+                    // in the stopped state, but not at first boot.
+                    for (PackageSetting pkg : mPackages.values()) {
+                        pkg.setStopped(false, userId);
+                        pkg.setNotLaunched(false, userId);
+                    }
+                    return;
+                }
+                str = new FileInputStream(userPackagesStateFile);
+            }
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(str, null);
+
+            int type;
+            while ((type=parser.next()) != XmlPullParser.START_TAG
+                       && type != XmlPullParser.END_DOCUMENT) {
+                ;
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                mReadMessages.append("No start tag found in package restrictions file\n");
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "No start tag found in package manager stopped packages");
+                return;
+            }
+
+            int outerDepth = parser.getDepth();
+            PackageSetting ps = null;
+            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                   && (type != XmlPullParser.END_TAG
+                           || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG
+                        || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+
+                String tagName = parser.getName();
+                if (tagName.equals(TAG_PACKAGE)) {
+                    String name = parser.getAttributeValue(null, ATTR_NAME);
+                    ps = mPackages.get(name);
+                    if (ps == null) {
+                        Slog.w(PackageManagerService.TAG, "No package known for stopped package: "
+                                + name);
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
+                    int enabled = enabledStr == null ? COMPONENT_ENABLED_STATE_DEFAULT
+                            : Integer.parseInt(enabledStr);
+                    ps.setEnabled(enabled, userId);
+                    String stoppedStr = parser.getAttributeValue(null, ATTR_STOPPED);
+                    boolean stopped = stoppedStr == null ? false : Boolean.parseBoolean(stoppedStr);
+                    ps.setStopped(stopped, userId);
+                    String notLaunchedStr = parser.getAttributeValue(null, ATTR_NOT_LAUNCHED);
+                    boolean notLaunched = stoppedStr == null ? false
+                            : Boolean.parseBoolean(notLaunchedStr);
+                    ps.setNotLaunched(notLaunched, userId);
+
+                    int packageDepth = parser.getDepth();
+                    while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                            && (type != XmlPullParser.END_TAG
+                            || parser.getDepth() > packageDepth)) {
+                        if (type == XmlPullParser.END_TAG
+                                || type == XmlPullParser.TEXT) {
+                            continue;
+                        }
+                        tagName = parser.getName();
+                        if (tagName.equals(TAG_ENABLED_COMPONENTS)) {
+                            HashSet<String> components = readComponentsLPr(parser);
+                            ps.setEnabledComponents(components, userId);
+                        } else if (tagName.equals(TAG_DISABLED_COMPONENTS)) {
+                            HashSet<String> components = readComponentsLPr(parser);
+                            ps.setDisabledComponents(components, userId);
+                        }
+                    }
+                } else {
+                    Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: "
+                          + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+
+            str.close();
+
+        } catch (XmlPullParserException e) {
+            mReadMessages.append("Error reading: " + e.toString());
+            PackageManagerService.reportSettingsProblem(Log.ERROR,
+                    "Error reading stopped packages: " + e);
+            Log.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", e);
+
+        } catch (java.io.IOException e) {
+            mReadMessages.append("Error reading: " + e.toString());
+            PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
+            Log.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", e);
+        }
+    }
+
+    private HashSet<String> readComponentsLPr(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        HashSet<String> components = new HashSet<String>();
+        int type;
+        int outerDepth = parser.getDepth();
+        String tagName;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG
+                || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG
+                    || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            tagName = parser.getName();
+            if (tagName.equals(TAG_ITEM)) {
+                String componentName = parser.getAttributeValue(null, ATTR_NAME);
+                if (componentName != null) {
+                    components.add(componentName);
+                }
+            }
+        }
+        return components;
+    }
+
+    void writePackageRestrictionsLPr(int userId) {
         // Keep the old stopped packages around until we know the new ones have
         // been successfully written.
-        if (mStoppedPackagesFilename.exists()) {
+        File userPackagesStateFile = getUserPackagesStateFile(userId);
+        File backupFile = getUserPackagesStateBackupFile(userId);
+        new File(userPackagesStateFile.getParent()).mkdirs();
+        if (userPackagesStateFile.exists()) {
             // Presence of backup settings file indicates that we failed
             // to persist packages earlier. So preserve the older
             // backup for future reference since the current packages
             // might have been corrupted.
-            if (!mBackupStoppedPackagesFilename.exists()) {
-                if (!mStoppedPackagesFilename.renameTo(mBackupStoppedPackagesFilename)) {
-                    Log.wtf(PackageManagerService.TAG, "Unable to backup package manager stopped packages, "
+            if (!backupFile.exists()) {
+                if (!userPackagesStateFile.renameTo(backupFile)) {
+                    Log.wtf(PackageManagerService.TAG, "Unable to backup user packages state file, "
                             + "current changes will be lost at reboot");
                     return;
                 }
             } else {
-                mStoppedPackagesFilename.delete();
+                userPackagesStateFile.delete();
                 Slog.w(PackageManagerService.TAG, "Preserving older stopped packages backup");
             }
         }
 
         try {
-            final FileOutputStream fstr = new FileOutputStream(mStoppedPackagesFilename);
+            final FileOutputStream fstr = new FileOutputStream(userPackagesStateFile);
             final BufferedOutputStream str = new BufferedOutputStream(fstr);
 
-            //XmlSerializer serializer = XmlUtils.serializerInstance();
             final XmlSerializer serializer = new FastXmlSerializer();
             serializer.setOutput(str, "utf-8");
             serializer.startDocument(null, true);
             serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
 
-            serializer.startTag(null, "stopped-packages");
+            serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS);
 
             for (final PackageSetting pkg : mPackages.values()) {
-                if (pkg.stopped) {
-                    serializer.startTag(null, "pkg");
-                    serializer.attribute(null, "name", pkg.name);
-                    if (pkg.notLaunched) {
-                        serializer.attribute(null, "nl", "1");
+                if (pkg.getStopped(userId)
+                        || pkg.getNotLaunched(userId)
+                        || pkg.getEnabled(userId) != COMPONENT_ENABLED_STATE_DEFAULT
+                        || pkg.getEnabledComponents(userId).size() > 0
+                        || pkg.getDisabledComponents(userId).size() > 0) {
+                    serializer.startTag(null, TAG_PACKAGE);
+                    serializer.attribute(null, ATTR_NAME, pkg.name);
+                    boolean stopped = pkg.getStopped(userId);
+                    boolean notLaunched = pkg.getNotLaunched(userId);
+                    int enabled = pkg.getEnabled(userId);
+                    HashSet<String> enabledComponents = pkg.getEnabledComponents(userId);
+                    HashSet<String> disabledComponents = pkg.getDisabledComponents(userId);
+
+                    if (stopped) {
+                        serializer.attribute(null, ATTR_STOPPED, "true");
                     }
-                    serializer.endTag(null, "pkg");
+                    if (notLaunched) {
+                        serializer.attribute(null, ATTR_NOT_LAUNCHED, "true");
+                    }
+                    if (enabled != COMPONENT_ENABLED_STATE_DEFAULT) {
+                        serializer.attribute(null, ATTR_ENABLED, Integer.toString(enabled));
+                    }
+                    if (enabledComponents.size() > 0) {
+                        serializer.startTag(null, TAG_ENABLED_COMPONENTS);
+                        for (final String name : enabledComponents) {
+                            serializer.startTag(null, TAG_ITEM);
+                            serializer.attribute(null, ATTR_NAME, name);
+                            serializer.endTag(null, TAG_ITEM);
+                        }
+                        serializer.endTag(null, TAG_ENABLED_COMPONENTS);
+                    }
+                    if (disabledComponents.size() > 0) {
+                        serializer.startTag(null, TAG_DISABLED_COMPONENTS);
+                        for (final String name : disabledComponents) {
+                            serializer.startTag(null, TAG_ITEM);
+                            serializer.attribute(null, ATTR_NAME, name);
+                            serializer.endTag(null, TAG_ITEM);
+                        }
+                        serializer.endTag(null, TAG_DISABLED_COMPONENTS);
+                    }
+                    serializer.endTag(null, TAG_PACKAGE);
                 }
             }
 
-            serializer.endTag(null, "stopped-packages");
+            serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS);
 
             serializer.endDocument();
 
@@ -711,8 +967,8 @@
 
             // New settings successfully written, old ones are no longer
             // needed.
-            mBackupStoppedPackagesFilename.delete();
-            FileUtils.setPermissions(mStoppedPackagesFilename.toString(),
+            backupFile.delete();
+            FileUtils.setPermissions(userPackagesStateFile.toString(),
                     FileUtils.S_IRUSR|FileUtils.S_IWUSR
                     |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
                     -1, -1);
@@ -720,26 +976,30 @@
             // Done, all is good!
             return;
         } catch(java.io.IOException e) {
-            Log.wtf(PackageManagerService.TAG, "Unable to write package manager stopped packages, "
+            Log.wtf(PackageManagerService.TAG,
+                    "Unable to write package manager user packages state, "
                     + " current changes will be lost at reboot", e);
         }
 
         // Clean up partially written files
-        if (mStoppedPackagesFilename.exists()) {
-            if (!mStoppedPackagesFilename.delete()) {
-                Log.i(PackageManagerService.TAG, "Failed to clean up mangled file: " + mStoppedPackagesFilename);
+        if (userPackagesStateFile.exists()) {
+            if (!userPackagesStateFile.delete()) {
+                Log.i(PackageManagerService.TAG, "Failed to clean up mangled file: "
+                        + mStoppedPackagesFilename);
             }
         }
     }
 
     // Note: assumed "stopped" field is already cleared in all packages.
+    // Legacy reader, used to read in the old file format after an upgrade. Not used after that.
     void readStoppedLPw() {
         FileInputStream str = null;
         if (mBackupStoppedPackagesFilename.exists()) {
             try {
                 str = new FileInputStream(mBackupStoppedPackagesFilename);
                 mReadMessages.append("Reading from backup stopped packages file\n");
-                PackageManagerService.reportSettingsProblem(Log.INFO, "Need to read from backup stopped packages file");
+                PackageManagerService.reportSettingsProblem(Log.INFO,
+                        "Need to read from backup stopped packages file");
                 if (mSettingsFilename.exists()) {
                     // If both the backup and normal file exist, we
                     // ignore the normal one since it might have been
@@ -757,14 +1017,14 @@
             if (str == null) {
                 if (!mStoppedPackagesFilename.exists()) {
                     mReadMessages.append("No stopped packages file found\n");
-                    PackageManagerService.reportSettingsProblem(Log.INFO, "No stopped packages file file; "
-                            + "assuming all started");
+                    PackageManagerService.reportSettingsProblem(Log.INFO,
+                            "No stopped packages file file; assuming all started");
                     // At first boot, make sure no packages are stopped.
                     // We usually want to have third party apps initialize
                     // in the stopped state, but not at first boot.
                     for (PackageSetting pkg : mPackages.values()) {
-                        pkg.stopped = false;
-                        pkg.notLaunched = false;
+                        pkg.setStopped(false, 0);
+                        pkg.setNotLaunched(false, 0);
                     }
                     return;
                 }
@@ -796,16 +1056,17 @@
                 }
 
                 String tagName = parser.getName();
-                if (tagName.equals("pkg")) {
-                    String name = parser.getAttributeValue(null, "name");
+                if (tagName.equals(TAG_PACKAGE)) {
+                    String name = parser.getAttributeValue(null, ATTR_NAME);
                     PackageSetting ps = mPackages.get(name);
                     if (ps != null) {
-                        ps.stopped = true;
-                        if ("1".equals(parser.getAttributeValue(null, "nl"))) {
-                            ps.notLaunched = true;
+                        ps.setStopped(true, 0);
+                        if ("1".equals(parser.getAttributeValue(null, ATTR_NOT_LAUNCHED))) {
+                            ps.setNotLaunched(true, 0);
                         }
                     } else {
-                        Slog.w(PackageManagerService.TAG, "No package known for stopped package: " + name);
+                        Slog.w(PackageManagerService.TAG,
+                                "No package known for stopped package: " + name);
                     }
                     XmlUtils.skipCurrentTag(parser);
                 } else {
@@ -817,12 +1078,13 @@
 
             str.close();
 
-        } catch(XmlPullParserException e) {
+        } catch (XmlPullParserException e) {
             mReadMessages.append("Error reading: " + e.toString());
-            PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading stopped packages: " + e);
+            PackageManagerService.reportSettingsProblem(Log.ERROR,
+                    "Error reading stopped packages: " + e);
             Log.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", e);
 
-        } catch(java.io.IOException e) {
+        } catch (java.io.IOException e) {
             mReadMessages.append("Error reading: " + e.toString());
             PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
             Log.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", e);
@@ -906,23 +1168,23 @@
 
             serializer.startTag(null, "preferred-activities");
             for (final PreferredActivity pa : mPreferredActivities.filterSet()) {
-                serializer.startTag(null, "item");
+                serializer.startTag(null, TAG_ITEM);
                 pa.writeToXml(serializer);
-                serializer.endTag(null, "item");
+                serializer.endTag(null, TAG_ITEM);
             }
             serializer.endTag(null, "preferred-activities");
 
             for (final SharedUserSetting usr : mSharedUsers.values()) {
                 serializer.startTag(null, "shared-user");
-                serializer.attribute(null, "name", usr.name);
+                serializer.attribute(null, ATTR_NAME, usr.name);
                 serializer.attribute(null, "userId",
                         Integer.toString(usr.userId));
                 usr.signatures.writeXml(serializer, "sigs", mPastSignatures);
                 serializer.startTag(null, "perms");
                 for (String name : usr.grantedPermissions) {
-                    serializer.startTag(null, "item");
-                    serializer.attribute(null, "name", name);
-                    serializer.endTag(null, "item");
+                    serializer.startTag(null, TAG_ITEM);
+                    serializer.attribute(null, ATTR_NAME, name);
+                    serializer.endTag(null, TAG_ITEM);
                 }
                 serializer.endTag(null, "perms");
                 serializer.endTag(null, "shared-user");
@@ -931,7 +1193,7 @@
             if (mPackagesToBeCleaned.size() > 0) {
                 for (int i=0; i<mPackagesToBeCleaned.size(); i++) {
                     serializer.startTag(null, "cleaning-package");
-                    serializer.attribute(null, "name", mPackagesToBeCleaned.get(i));
+                    serializer.attribute(null, ATTR_NAME, mPackagesToBeCleaned.get(i));
                     serializer.endTag(null, "cleaning-package");
                 }
             }
@@ -1016,8 +1278,7 @@
                     |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
                     -1, -1);
 
-            writeStoppedLPr();
-
+            writeAllUsersPackageRestrictionsLPr();
             return;
 
         } catch(XmlPullParserException e) {
@@ -1030,7 +1291,8 @@
         // Clean up partially written files
         if (mSettingsFilename.exists()) {
             if (!mSettingsFilename.delete()) {
-                Log.wtf(PackageManagerService.TAG, "Failed to clean up mangled file: " + mSettingsFilename);
+                Log.wtf(PackageManagerService.TAG, "Failed to clean up mangled file: "
+                        + mSettingsFilename);
             }
         }
         //Debug.stopMethodTracing();
@@ -1039,7 +1301,7 @@
     void writeDisabledSysPackageLPr(XmlSerializer serializer, final PackageSetting pkg)
             throws java.io.IOException {
         serializer.startTag(null, "updated-package");
-        serializer.attribute(null, "name", pkg.name);
+        serializer.attribute(null, ATTR_NAME, pkg.name);
         if (pkg.realName != null) {
             serializer.attribute(null, "realName", pkg.realName);
         }
@@ -1055,9 +1317,9 @@
             serializer.attribute(null, "nativeLibraryPath", pkg.nativeLibraryPathString);
         }
         if (pkg.sharedUser == null) {
-            serializer.attribute(null, "userId", Integer.toString(pkg.userId));
+            serializer.attribute(null, "userId", Integer.toString(pkg.appId));
         } else {
-            serializer.attribute(null, "sharedUserId", Integer.toString(pkg.userId));
+            serializer.attribute(null, "sharedUserId", Integer.toString(pkg.appId));
         }
         serializer.startTag(null, "perms");
         if (pkg.sharedUser == null) {
@@ -1072,9 +1334,9 @@
                     // this wont
                     // match the semantics of grantedPermissions. So write all
                     // permissions.
-                    serializer.startTag(null, "item");
-                    serializer.attribute(null, "name", name);
-                    serializer.endTag(null, "item");
+                    serializer.startTag(null, TAG_ITEM);
+                    serializer.attribute(null, ATTR_NAME, name);
+                    serializer.endTag(null, TAG_ITEM);
                 }
             }
         }
@@ -1085,7 +1347,7 @@
     void writePackageLPr(XmlSerializer serializer, final PackageSetting pkg)
             throws java.io.IOException {
         serializer.startTag(null, "package");
-        serializer.attribute(null, "name", pkg.name);
+        serializer.attribute(null, ATTR_NAME, pkg.name);
         if (pkg.realName != null) {
             serializer.attribute(null, "realName", pkg.realName);
         }
@@ -1102,16 +1364,13 @@
         serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime));
         serializer.attribute(null, "version", String.valueOf(pkg.versionCode));
         if (pkg.sharedUser == null) {
-            serializer.attribute(null, "userId", Integer.toString(pkg.userId));
+            serializer.attribute(null, "userId", Integer.toString(pkg.appId));
         } else {
-            serializer.attribute(null, "sharedUserId", Integer.toString(pkg.userId));
+            serializer.attribute(null, "sharedUserId", Integer.toString(pkg.appId));
         }
         if (pkg.uidError) {
             serializer.attribute(null, "uidError", "true");
         }
-        if (pkg.enabled != COMPONENT_ENABLED_STATE_DEFAULT) {
-            serializer.attribute(null, "enabled", Integer.toString(pkg.enabled));
-        }
         if (pkg.installStatus == PackageSettingBase.PKG_INSTALL_INCOMPLETE) {
             serializer.attribute(null, "installStatus", "false");
         }
@@ -1127,31 +1386,13 @@
                 // empty permissions list so permissionsFixed will
                 // be set.
                 for (final String name : pkg.grantedPermissions) {
-                    serializer.startTag(null, "item");
-                    serializer.attribute(null, "name", name);
-                    serializer.endTag(null, "item");
+                    serializer.startTag(null, TAG_ITEM);
+                    serializer.attribute(null, ATTR_NAME, name);
+                    serializer.endTag(null, TAG_ITEM);
                 }
             }
             serializer.endTag(null, "perms");
         }
-        if (pkg.disabledComponents.size() > 0) {
-            serializer.startTag(null, "disabled-components");
-            for (final String name : pkg.disabledComponents) {
-                serializer.startTag(null, "item");
-                serializer.attribute(null, "name", name);
-                serializer.endTag(null, "item");
-            }
-            serializer.endTag(null, "disabled-components");
-        }
-        if (pkg.enabledComponents.size() > 0) {
-            serializer.startTag(null, "enabled-components");
-            for (final String name : pkg.enabledComponents) {
-                serializer.startTag(null, "item");
-                serializer.attribute(null, "name", name);
-                serializer.endTag(null, "item");
-            }
-            serializer.endTag(null, "enabled-components");
-        }
 
         serializer.endTag(null, "package");
     }
@@ -1159,8 +1400,8 @@
     void writePermissionLPr(XmlSerializer serializer, BasePermission bp)
             throws XmlPullParserException, java.io.IOException {
         if (bp.type != BasePermission.TYPE_BUILTIN && bp.sourcePackage != null) {
-            serializer.startTag(null, "item");
-            serializer.attribute(null, "name", bp.name);
+            serializer.startTag(null, TAG_ITEM);
+            serializer.attribute(null, ATTR_NAME, bp.name);
             serializer.attribute(null, "package", bp.sourcePackage);
             if (bp.protectionLevel != PermissionInfo.PROTECTION_NORMAL) {
                 serializer.attribute(null, "protection", Integer.toString(bp.protectionLevel));
@@ -1180,7 +1421,7 @@
                     }
                 }
             }
-            serializer.endTag(null, "item");
+            serializer.endTag(null, TAG_ITEM);
         }
     }
 
@@ -1198,7 +1439,7 @@
         return ret;
     }
 
-    boolean readLPw() {
+    boolean readLPw(List<UserInfo> users) {
         FileInputStream str = null;
         if (mBackupSettingsFilename.exists()) {
             try {
@@ -1273,7 +1514,7 @@
                 } else if (tagName.equals("updated-package")) {
                     readDisabledSysPackageLPw(parser);
                 } else if (tagName.equals("cleaning-package")) {
-                    String name = parser.getAttributeValue(null, "name");
+                    String name = parser.getAttributeValue(null, ATTR_NAME);
                     if (name != null) {
                         mPackagesToBeCleaned.add(name);
                     }
@@ -1366,14 +1607,29 @@
         final Iterator<PackageSetting> disabledIt = mDisabledSysPackages.values().iterator();
         while (disabledIt.hasNext()) {
             final PackageSetting disabledPs = disabledIt.next();
-            final Object id = getUserIdLPr(disabledPs.userId);
+            final Object id = getUserIdLPr(disabledPs.appId);
             if (id != null && id instanceof SharedUserSetting) {
                 disabledPs.sharedUser = (SharedUserSetting) id;
             }
         }
 
-        readStoppedLPw();
-
+        if (mBackupStoppedPackagesFilename.exists()
+                || mStoppedPackagesFilename.exists()) {
+            // Read old file
+            readStoppedLPw();
+            mBackupStoppedPackagesFilename.delete();
+            mStoppedPackagesFilename.delete();
+            // Migrate to new file format
+            writePackageRestrictionsLPr(0);
+        } else {
+            if (users == null) {
+                readPackageRestrictionsLPr(0);
+            } else {
+                for (UserInfo user : users) {
+                    readPackageRestrictionsLPr(user.id);
+                }
+            }
+        }
         mReadMessages.append("Read completed successfully: " + mPackages.size() + " packages, "
                 + mSharedUsers.size() + " shared uids\n");
 
@@ -1407,8 +1663,8 @@
             }
 
             final String tagName = parser.getName();
-            if (tagName.equals("item")) {
-                final String name = parser.getAttributeValue(null, "name");
+            if (tagName.equals(TAG_ITEM)) {
+                final String name = parser.getAttributeValue(null, ATTR_NAME);
                 final String sourcePackage = parser.getAttributeValue(null, "package");
                 final String ptype = parser.getAttributeValue(null, "type");
                 if (name != null && sourcePackage != null) {
@@ -1444,7 +1700,7 @@
 
     private void readDisabledSysPackageLPw(XmlPullParser parser) throws XmlPullParserException,
             IOException {
-        String name = parser.getAttributeValue(null, "name");
+        String name = parser.getAttributeValue(null, ATTR_NAME);
         String realName = parser.getAttributeValue(null, "realName");
         String codePathStr = parser.getAttributeValue(null, "codePath");
         String resourcePathStr = parser.getAttributeValue(null, "resourcePath");
@@ -1497,10 +1753,10 @@
             }
         }
         String idStr = parser.getAttributeValue(null, "userId");
-        ps.userId = idStr != null ? Integer.parseInt(idStr) : 0;
-        if (ps.userId <= 0) {
+        ps.appId = idStr != null ? Integer.parseInt(idStr) : 0;
+        if (ps.appId <= 0) {
             String sharedIdStr = parser.getAttributeValue(null, "sharedUserId");
-            ps.userId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0;
+            ps.appId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0;
         }
         int outerDepth = parser.getDepth();
         int type;
@@ -1541,7 +1797,7 @@
         String version = null;
         int versionCode = 0;
         try {
-            name = parser.getAttributeValue(null, "name");
+            name = parser.getAttributeValue(null, ATTR_NAME);
             realName = parser.getAttributeValue(null, "realName");
             idStr = parser.getAttributeValue(null, "userId");
             uidError = parser.getAttributeValue(null, "uidError");
@@ -1672,17 +1928,18 @@
             packageSetting.uidError = "true".equals(uidError);
             packageSetting.installerPackageName = installerPackageName;
             packageSetting.nativeLibraryPathString = nativeLibraryPathStr;
-            final String enabledStr = parser.getAttributeValue(null, "enabled");
+            // Handle legacy string here for single-user mode
+            final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
             if (enabledStr != null) {
                 try {
-                    packageSetting.enabled = Integer.parseInt(enabledStr);
+                    packageSetting.setEnabled(Integer.parseInt(enabledStr), 0 /* userId */);
                 } catch (NumberFormatException e) {
                     if (enabledStr.equalsIgnoreCase("true")) {
-                        packageSetting.enabled = COMPONENT_ENABLED_STATE_ENABLED;
+                        packageSetting.setEnabled(COMPONENT_ENABLED_STATE_ENABLED, 0);
                     } else if (enabledStr.equalsIgnoreCase("false")) {
-                        packageSetting.enabled = COMPONENT_ENABLED_STATE_DISABLED;
+                        packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, 0);
                     } else if (enabledStr.equalsIgnoreCase("default")) {
-                        packageSetting.enabled = COMPONENT_ENABLED_STATE_DEFAULT;
+                        packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0);
                     } else {
                         PackageManagerService.reportSettingsProblem(Log.WARN,
                                 "Error in package manager settings: package " + name
@@ -1691,8 +1948,9 @@
                     }
                 }
             } else {
-                packageSetting.enabled = COMPONENT_ENABLED_STATE_DEFAULT;
+                packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0);
             }
+
             final String installStatusStr = parser.getAttributeValue(null, "installStatus");
             if (installStatusStr != null) {
                 if (installStatusStr.equalsIgnoreCase("false")) {
@@ -1711,10 +1969,11 @@
                 }
 
                 String tagName = parser.getName();
-                if (tagName.equals("disabled-components")) {
-                    readDisabledComponentsLPw(packageSetting, parser);
-                } else if (tagName.equals("enabled-components")) {
-                    readEnabledComponentsLPw(packageSetting, parser);
+                // Legacy 
+                if (tagName.equals(TAG_DISABLED_COMPONENTS)) {
+                    readDisabledComponentsLPw(packageSetting, parser, 0);
+                } else if (tagName.equals(TAG_ENABLED_COMPONENTS)) {
+                    readEnabledComponentsLPw(packageSetting, parser, 0);
                 } else if (tagName.equals("sigs")) {
                     packageSetting.signatures.readXml(parser, mPastSignatures);
                 } else if (tagName.equals("perms")) {
@@ -1731,8 +1990,8 @@
         }
     }
 
-    private void readDisabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser)
-            throws IOException, XmlPullParserException {
+    private void readDisabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser,
+            int userId) throws IOException, XmlPullParserException {
         int outerDepth = parser.getDepth();
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1742,10 +2001,10 @@
             }
 
             String tagName = parser.getName();
-            if (tagName.equals("item")) {
-                String name = parser.getAttributeValue(null, "name");
+            if (tagName.equals(TAG_ITEM)) {
+                String name = parser.getAttributeValue(null, ATTR_NAME);
                 if (name != null) {
-                    packageSetting.disabledComponents.add(name.intern());
+                    packageSetting.addDisabledComponent(name.intern(), userId);
                 } else {
                     PackageManagerService.reportSettingsProblem(Log.WARN,
                             "Error in package manager settings: <disabled-components> has"
@@ -1759,8 +2018,8 @@
         }
     }
 
-    private void readEnabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser)
-            throws IOException, XmlPullParserException {
+    private void readEnabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser,
+            int userId) throws IOException, XmlPullParserException {
         int outerDepth = parser.getDepth();
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1770,10 +2029,10 @@
             }
 
             String tagName = parser.getName();
-            if (tagName.equals("item")) {
-                String name = parser.getAttributeValue(null, "name");
+            if (tagName.equals(TAG_ITEM)) {
+                String name = parser.getAttributeValue(null, ATTR_NAME);
                 if (name != null) {
-                    packageSetting.enabledComponents.add(name.intern());
+                    packageSetting.addEnabledComponent(name.intern(), userId);
                 } else {
                     PackageManagerService.reportSettingsProblem(Log.WARN,
                             "Error in package manager settings: <enabled-components> has"
@@ -1787,13 +2046,13 @@
         }
     }
 
-    private void readSharedUserLPw(XmlPullParser parser) throws XmlPullParserException, IOException {
+    private void readSharedUserLPw(XmlPullParser parser) throws XmlPullParserException,IOException {
         String name = null;
         String idStr = null;
         int pkgFlags = 0;
         SharedUserSetting su = null;
         try {
-            name = parser.getAttributeValue(null, "name");
+            name = parser.getAttributeValue(null, ATTR_NAME);
             idStr = parser.getAttributeValue(null, "userId");
             int userId = idStr != null ? Integer.parseInt(idStr) : 0;
             if ("true".equals(parser.getAttributeValue(null, "system"))) {
@@ -1859,8 +2118,8 @@
             }
 
             String tagName = parser.getName();
-            if (tagName.equals("item")) {
-                String name = parser.getAttributeValue(null, "name");
+            if (tagName.equals(TAG_ITEM)) {
+                String name = parser.getAttributeValue(null, ATTR_NAME);
                 if (name != null) {
                     outPerms.add(name.intern());
                 } else {
@@ -1887,7 +2146,7 @@
             }
 
             String tagName = parser.getName();
-            if (tagName.equals("item")) {
+            if (tagName.equals(TAG_ITEM)) {
                 PreferredActivity pa = new PreferredActivity(parser);
                 if (pa.mPref.getParseError() == null) {
                     mPreferredActivities.addFilter(pa);
@@ -1905,6 +2164,13 @@
         }
     }
 
+    void removeUserLPr(int userId) {
+        File file = getUserPackagesStateFile(userId);
+        file.delete();
+        file = getUserPackagesStateBackupFile(userId);
+        file.delete();
+    }
+
     // Returns -1 if we could not find an available UserId to assign
     private int newUserIdLPw(Object obj) {
         // Let's be stupidly inefficient for now...
@@ -1940,32 +2206,34 @@
         return ps;
     }
 
-    boolean isEnabledLPr(ComponentInfo componentInfo, int flags) {
+    boolean isEnabledLPr(ComponentInfo componentInfo, int flags, int userId) {
         if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
             return true;
         }
-        final PackageSetting packageSettings = mPackages.get(componentInfo.packageName);
+        final String pkgName = componentInfo.packageName;
+        final PackageSetting packageSettings = mPackages.get(pkgName);
         if (PackageManagerService.DEBUG_SETTINGS) {
-            Log.v(PackageManagerService.TAG, "isEnabledLock - packageName = " + componentInfo.packageName
-                       + " componentName = " + componentInfo.name);
+            Log.v(PackageManagerService.TAG, "isEnabledLock - packageName = "
+                    + componentInfo.packageName + " componentName = " + componentInfo.name);
             Log.v(PackageManagerService.TAG, "enabledComponents: "
-                       + Arrays.toString(packageSettings.enabledComponents.toArray()));
+                    + Arrays.toString(packageSettings.getEnabledComponents(userId).toArray()));
             Log.v(PackageManagerService.TAG, "disabledComponents: "
-                       + Arrays.toString(packageSettings.disabledComponents.toArray()));
+                    + Arrays.toString(packageSettings.getDisabledComponents(userId).toArray()));
         }
         if (packageSettings == null) {
             return false;
         }
-        if (packageSettings.enabled == COMPONENT_ENABLED_STATE_DISABLED
-                || packageSettings.enabled == COMPONENT_ENABLED_STATE_DISABLED_USER
+        final int enabled = packageSettings.getEnabled(userId);
+        if (enabled == COMPONENT_ENABLED_STATE_DISABLED
+                || enabled == COMPONENT_ENABLED_STATE_DISABLED_USER
                 || (packageSettings.pkg != null && !packageSettings.pkg.applicationInfo.enabled
-                        && packageSettings.enabled == COMPONENT_ENABLED_STATE_DEFAULT)) {
+                    && enabled == COMPONENT_ENABLED_STATE_DEFAULT)) {
             return false;
         }
-        if (packageSettings.enabledComponents.contains(componentInfo.name)) {
+        if (packageSettings.getEnabledComponents(userId).contains(componentInfo.name)) {
             return true;
         }
-        if (packageSettings.disabledComponents.contains(componentInfo.name)) {
+        if (packageSettings.getDisabledComponents(userId).contains(componentInfo.name)) {
             return false;
         }
         return componentInfo.enabled;
@@ -1979,35 +2247,36 @@
         return pkg.installerPackageName;
     }
 
-    int getApplicationEnabledSettingLPr(String packageName) {
+    int getApplicationEnabledSettingLPr(String packageName, int userId) {
         final PackageSetting pkg = mPackages.get(packageName);
         if (pkg == null) {
             throw new IllegalArgumentException("Unknown package: " + packageName);
         }
-        return pkg.enabled;
+        return pkg.getEnabled(userId);
     }
 
-    int getComponentEnabledSettingLPr(ComponentName componentName) {
+    int getComponentEnabledSettingLPr(ComponentName componentName, int userId) {
         final String packageName = componentName.getPackageName();
         final PackageSetting pkg = mPackages.get(packageName);
         if (pkg == null) {
             throw new IllegalArgumentException("Unknown component: " + componentName);
         }
         final String classNameStr = componentName.getClassName();
-        return pkg.getCurrentEnabledStateLPr(classNameStr);
+        return pkg.getCurrentEnabledStateLPr(classNameStr, userId);
     }
-    
+
     boolean setPackageStoppedStateLPw(String packageName, boolean stopped,
-            boolean allowedByPermission, int uid) {
+            boolean allowedByPermission, int uid, int userId) {
+        int appId = UserId.getAppId(uid);
         final PackageSetting pkgSetting = mPackages.get(packageName);
         if (pkgSetting == null) {
             throw new IllegalArgumentException("Unknown package: " + packageName);
         }
-        if (!allowedByPermission && (uid != pkgSetting.userId)) {
+        if (!allowedByPermission && (appId != pkgSetting.appId)) {
             throw new SecurityException(
                     "Permission Denial: attempt to change stopped state from pid="
                     + Binder.getCallingPid()
-                    + ", uid=" + uid + ", package uid=" + pkgSetting.userId);
+                    + ", uid=" + uid + ", package uid=" + pkgSetting.appId);
         }
         if (DEBUG_STOPPED) {
             if (stopped) {
@@ -2016,22 +2285,33 @@
                 Slog.i(TAG, "Stopping package " + packageName, e);
             }
         }
-        if (pkgSetting.stopped != stopped) {
-            pkgSetting.stopped = stopped;
-            pkgSetting.pkg.mSetStopped = stopped;
-            if (pkgSetting.notLaunched) {
+        if (pkgSetting.getStopped(userId) != stopped) {
+            pkgSetting.setStopped(stopped, userId);
+            // pkgSetting.pkg.mSetStopped = stopped;
+            if (pkgSetting.getNotLaunched(userId)) {
                 if (pkgSetting.installerPackageName != null) {
                     PackageManagerService.sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH,
                             pkgSetting.name, null,
-                            pkgSetting.installerPackageName, null);
+                            pkgSetting.installerPackageName, null, userId);
                 }
-                pkgSetting.notLaunched = false;
+                pkgSetting.setNotLaunched(false, userId);
             }
             return true;
         }
         return false;
     }
 
+    private List<UserInfo> getAllUsers() {
+        try {
+            return AppGlobals.getPackageManager().getUsers();
+        } catch (RemoteException re) {
+            // Local to system process, shouldn't happen
+        } catch (NullPointerException npe) {
+            // packagemanager not yet initialized
+        }
+        return null;
+    }
+
     static final void printFlags(PrintWriter pw, int val, Object[] spec) {
         pw.print("[ ");
         for (int i=0; i<spec.length; i+=2) {
@@ -2096,7 +2376,7 @@
                 pw.println(ps.name);
             }
 
-            pw.print("    userId="); pw.print(ps.userId);
+            pw.print("    userId="); pw.print(ps.appId);
             pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids));
             pw.print("    sharedUser="); pw.println(ps.sharedUser);
             pw.print("    pkg="); pw.println(ps.pkg);
@@ -2169,18 +2449,24 @@
             pw.print(" haveGids="); pw.println(ps.haveGids);
             pw.print("    pkgFlags=0x"); pw.print(Integer.toHexString(ps.pkgFlags));
             pw.print(" installStatus="); pw.print(ps.installStatus);
-            pw.print(" stopped="); pw.print(ps.stopped);
-            pw.print(" enabled="); pw.println(ps.enabled);
-            if (ps.disabledComponents.size() > 0) {
-                pw.println("    disabledComponents:");
-                for (String s : ps.disabledComponents) {
-                    pw.print("      "); pw.println(s);
+            List<UserInfo> users = getAllUsers();
+            for (UserInfo user : users) {
+                pw.print(" User "); pw.print(user.id); pw.print(": ");
+                pw.print(" stopped=");
+                pw.print(ps.getStopped(user.id));
+                pw.print(" enabled=");
+                pw.println(ps.getEnabled(user.id));
+                if (ps.getDisabledComponents(user.id).size() > 0) {
+                    pw.println("    disabledComponents:");
+                    for (String s : ps.getDisabledComponents(user.id)) {
+                        pw.print("      "); pw.println(s);
+                    }
                 }
-            }
-            if (ps.enabledComponents.size() > 0) {
-                pw.println("    enabledComponents:");
-                for (String s : ps.enabledComponents) {
-                    pw.print("      "); pw.println(s);
+                if (ps.getEnabledComponents(user.id).size() > 0) {
+                    pw.println("    enabledComponents:");
+                    for (String s : ps.getEnabledComponents(user.id)) {
+                        pw.print("      "); pw.println(s);
+                    }
                 }
             }
             if (ps.grantedPermissions.size() > 0) {
@@ -2234,7 +2520,7 @@
                     pw.println(ps.name);
                 }
                 pw.print("    userId=");
-                pw.println(ps.userId);
+                pw.println(ps.appId);
                 pw.print("    sharedUser=");
                 pw.println(ps.sharedUser);
                 pw.print("    codePath=");
@@ -2244,7 +2530,7 @@
             }
         }
     }
-    
+
     void dumpPermissionsLPr(PrintWriter pw, String packageName, DumpState dumpState) {
         boolean printedSomething = false;
         for (BasePermission p : mPermissions.values()) {
diff --git a/services/java/com/android/server/pm/UserManager.java b/services/java/com/android/server/pm/UserManager.java
index 5eacf4a..4e9e666 100644
--- a/services/java/com/android/server/pm/UserManager.java
+++ b/services/java/com/android/server/pm/UserManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 
 import android.content.pm.ApplicationInfo;
@@ -58,7 +59,7 @@
     private static final String USER_INFO_DIR = "system" + File.separator + "users";
     private static final String USER_LIST_FILENAME = "userlist.xml";
 
-    private SparseArray<UserInfo> mUsers;
+    private SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
 
     private final File mUsersDir;
     private final File mUserListFile;
@@ -73,6 +74,9 @@
     UserManager(File dataDir, File baseUserPath) {
         mUsersDir = new File(dataDir, USER_INFO_DIR);
         mUsersDir.mkdirs();
+        // Make zeroth user directory, for services to migrate their files to that location
+        File userZeroDir = new File(mUsersDir, "0");
+        userZeroDir.mkdirs();
         mBaseUserPath = baseUserPath;
         FileUtils.setPermissions(mUsersDir.toString(),
                 FileUtils.S_IRWXU|FileUtils.S_IRWXG
@@ -88,11 +92,36 @@
     }
 
     public List<UserInfo> getUsers() {
-        ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
-        for (int i = 0; i < mUsers.size(); i++) {
-            users.add(mUsers.valueAt(i));
+        synchronized (mUsers) {
+            ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
+            for (int i = 0; i < mUsers.size(); i++) {
+                users.add(mUsers.valueAt(i));
+            }
+            return users;
         }
-        return users;
+    }
+
+    public UserInfo getUser(int userId) {
+        synchronized (mUsers) {
+            UserInfo info = mUsers.get(userId);
+            return info;
+        }
+    }
+
+    public boolean exists(int userId) {
+        synchronized (mUsers) {
+            return ArrayUtils.contains(mUserIds, userId);
+        }
+    }
+
+    public void updateUserName(int userId, String name) {
+        synchronized (mUsers) {
+            UserInfo info = mUsers.get(userId);
+            if (name != null && !name.equals(info.name)) {
+                info.name = name;
+                writeUserLocked(info);
+            }
+        }
     }
 
     /**
@@ -105,9 +134,14 @@
     }
 
     private void readUserList() {
-        mUsers = new SparseArray<UserInfo>();
+        synchronized (mUsers) {
+            readUserListLocked();
+        }
+    }
+
+    private void readUserListLocked() {
         if (!mUserListFile.exists()) {
-            fallbackToSingleUser();
+            fallbackToSingleUserLocked();
             return;
         }
         FileInputStream fis = null;
@@ -123,7 +157,7 @@
 
             if (type != XmlPullParser.START_TAG) {
                 Slog.e(LOG_TAG, "Unable to read user list");
-                fallbackToSingleUser();
+                fallbackToSingleUserLocked();
                 return;
             }
 
@@ -136,11 +170,11 @@
                     }
                 }
             }
-            updateUserIds();
+            updateUserIdsLocked();
         } catch (IOException ioe) {
-            fallbackToSingleUser();
+            fallbackToSingleUserLocked();
         } catch (XmlPullParserException pe) {
-            fallbackToSingleUser();
+            fallbackToSingleUserLocked();
         } finally {
             if (fis != null) {
                 try {
@@ -151,15 +185,15 @@
         }
     }
 
-    private void fallbackToSingleUser() {
+    private void fallbackToSingleUserLocked() {
         // Create the primary user
         UserInfo primary = new UserInfo(0, "Primary",
                 UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
         mUsers.put(0, primary);
-        updateUserIds();
+        updateUserIdsLocked();
 
-        writeUserList();
-        writeUser(primary);
+        writeUserListLocked();
+        writeUserLocked(primary);
     }
 
     /*
@@ -169,7 +203,7 @@
      *   <name>Primary</name>
      * </user>
      */
-    private void writeUser(UserInfo userInfo) {
+    private void writeUserLocked(UserInfo userInfo) {
         FileOutputStream fos = null;
         try {
             final File mUserFile = new File(mUsersDir, userInfo.id + ".xml");
@@ -213,7 +247,7 @@
      *   <user id="2"></user>
      * </users>
      */
-    private void writeUserList() {
+    private void writeUserListLocked() {
         FileOutputStream fos = null;
         try {
             fos = new FileOutputStream(mUserListFile);
@@ -306,17 +340,19 @@
         return null;
     }
 
-    public UserInfo createUser(String name, int flags, List<ApplicationInfo> apps) {
+    public UserInfo createUser(String name, int flags) {
         int userId = getNextAvailableId();
         UserInfo userInfo = new UserInfo(userId, name, flags);
         File userPath = new File(mBaseUserPath, Integer.toString(userId));
-        if (!createPackageFolders(userId, userPath, apps)) {
+        if (!createPackageFolders(userId, userPath)) {
             return null;
         }
-        mUsers.put(userId, userInfo);
-        writeUserList();
-        writeUser(userInfo);
-        updateUserIds();
+        synchronized (mUsers) {
+            mUsers.put(userId, userInfo);
+            writeUserListLocked();
+            writeUserLocked(userInfo);
+            updateUserIdsLocked();
+        }
         return userInfo;
     }
 
@@ -325,7 +361,13 @@
      * after the user's processes have been terminated.
      * @param id the user's id
      */
-    public void removeUser(int id) {
+    public boolean removeUser(int id) {
+        synchronized (mUsers) {
+            return removeUserLocked(id);
+        }
+    }
+
+    private boolean removeUserLocked(int id) {
         // Remove from the list
         UserInfo userInfo = mUsers.get(id);
         if (userInfo != null) {
@@ -335,11 +377,11 @@
             File userFile = new File(mUsersDir, id + ".xml");
             userFile.delete();
             // Update the user list
-            writeUserList();
-            // Remove the data directories for all packages for this user
-            removePackageFolders(id);
-            updateUserIds();
+            writeUserListLocked();
+            updateUserIdsLocked();
+            return true;
         }
+        return false;
     }
 
     public void installPackageForAllUsers(String packageName, int uid) {
@@ -373,7 +415,7 @@
     /**
      * Caches the list of user ids in an array, adjusting the array size when necessary.
      */
-    private void updateUserIds() {
+    private void updateUserIdsLocked() {
         if (mUserIds == null || mUserIds.length != mUsers.size()) {
             mUserIds = new int[mUsers.size()];
         }
@@ -399,11 +441,10 @@
         return i;
     }
 
-    private boolean createPackageFolders(int id, File userPath, final List<ApplicationInfo> apps) {
+    private boolean createPackageFolders(int id, File userPath) {
         // mInstaller may not be available for unit-tests.
-        if (mInstaller == null || apps == null) return true;
+        if (mInstaller == null) return true;
 
-        final long startTime = SystemClock.elapsedRealtime();
         // Create the user path
         userPath.mkdir();
         FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
@@ -411,13 +452,10 @@
 
         mInstaller.cloneUserData(0, id, false);
 
-        final long stopTime = SystemClock.elapsedRealtime();
-        Log.i(LOG_TAG,
-                "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms");
         return true;
     }
 
-    private boolean removePackageFolders(int id) {
+    boolean removePackageFolders(int id) {
         // mInstaller may not be available for unit-tests.
         if (mInstaller == null) return true;
 
diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java
index ed83fbe..c2ded8a 100644
--- a/services/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/java/com/android/server/usb/UsbDeviceManager.java
@@ -43,6 +43,7 @@
 import android.os.Process;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UEventObserver;
 import android.provider.Settings;
@@ -206,6 +207,9 @@
     }
 
      private static String addFunction(String functions, String function) {
+         if ("none".equals(functions)) {
+             return function;
+         }
         if (!containsFunction(functions, function)) {
             if (functions.length() > 0) {
                 functions += ",";
@@ -222,6 +226,9 @@
                 split[i] = null;
             }
         }
+        if (split.length == 1 && split[0] == null) {
+            return "none";
+        }
         StringBuilder builder = new StringBuilder();
          for (int i = 0; i < split.length; i++) {
             String s = split[i];
@@ -365,11 +372,7 @@
             for (int i = 0; i < 20; i++) {
                 // State transition is done when sys.usb.state is set to the new configuration
                 if (state.equals(SystemProperties.get("sys.usb.state"))) return true;
-                try {
-                    // try again in 50ms
-                    Thread.sleep(50);
-                } catch (InterruptedException e) {
-                }
+                SystemClock.sleep(50);
             }
             Slog.e(TAG, "waitForState(" + state + ") FAILED");
             return false;
diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java
index 67b667a..6269420 100644
--- a/services/java/com/android/server/wm/AppWindowToken.java
+++ b/services/java/com/android/server/wm/AppWindowToken.java
@@ -21,10 +21,12 @@
 import com.android.server.wm.WindowManagerService.H;
 
 import android.content.pm.ActivityInfo;
+import android.graphics.Matrix;
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Slog;
 import android.view.IApplicationToken;
+import android.view.Surface;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicy;
@@ -49,14 +51,15 @@
     int groupId = -1;
     boolean appFullscreen;
     int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-    
+
     // The input dispatching timeout for this application token in nanoseconds.
     long inputDispatchingTimeoutNanos;
 
     // These are used for determining when all windows associated with
     // an activity have been drawn, so they can be made visible together
     // at the same time.
-    int lastTransactionSequence;
+    // initialize so that it doesn't match mTransactionSequence which is an int.
+    long lastTransactionSequence = Long.MIN_VALUE;
     int numInterestingWindows;
     int numDrawnWindows;
     boolean inPendingTransaction;
@@ -89,6 +92,7 @@
 
     boolean animating;
     Animation animation;
+    boolean animInitialized;
     boolean hasTransformation;
     final Transformation transformation = new Transformation();
 
@@ -104,6 +108,15 @@
     boolean startingMoved;
     boolean firstWindowDrawn;
 
+    // Special surface for thumbnail animation.
+    Surface thumbnail;
+    int thumbnailTransactionSeq;
+    int thumbnailX;
+    int thumbnailY;
+    int thumbnailLayer;
+    Animation thumbnailAnimation;
+    final Transformation thumbnailTransformation = new Transformation();
+
     // Input application handle used by the input dispatcher.
     final InputApplicationHandle mInputApplicationHandle;
 
@@ -113,14 +126,14 @@
         appWindowToken = this;
         appToken = _token;
         mInputApplicationHandle = new InputApplicationHandle(this);
-        lastTransactionSequence = service.mTransactionSequence-1;
     }
 
-    public void setAnimation(Animation anim) {
+    public void setAnimation(Animation anim, boolean initialized) {
         if (WindowManagerService.localLOGV) Slog.v(
             WindowManagerService.TAG, "Setting animation in " + this + ": " + anim);
         animation = anim;
         animating = false;
+        animInitialized = initialized;
         anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION);
         anim.scaleCurrentDuration(service.mTransitionAnimationScale);
         int zorder = anim.getZAdjustment();
@@ -135,6 +148,10 @@
             animLayerAdjustment = adj;
             updateLayers();
         }
+        // Start out animation gone if window is gone, or visible if window is visible.
+        transformation.clear();
+        transformation.setAlpha(reportedVisible ? 1 : 0);
+        hasTransformation = true;
     }
 
     public void setDummyAnimation() {
@@ -142,6 +159,7 @@
             if (WindowManagerService.localLOGV) Slog.v(
                 WindowManagerService.TAG, "Setting dummy animation in " + this);
             animation = WindowManagerService.sDummyAnimation;
+            animInitialized = false;
         }
     }
 
@@ -149,15 +167,28 @@
         if (animation != null) {
             animation = null;
             animating = true;
+            animInitialized = false;
+        }
+        clearThumbnail();
+    }
+
+    public void clearThumbnail() {
+        if (thumbnail != null) {
+            thumbnail.destroy();
+            thumbnail = null;
         }
     }
 
     void updateLayers() {
         final int N = allAppWindows.size();
         final int adj = animLayerAdjustment;
+        thumbnailLayer = -1;
         for (int i=0; i<N; i++) {
             WindowState w = allAppWindows.get(i);
             w.mAnimLayer = w.mLayer + adj;
+            if (w.mAnimLayer > thumbnailLayer) {
+                thumbnailLayer = w.mAnimLayer;
+            }
             if (WindowManagerService.DEBUG_LAYERS) Slog.v(WindowManagerService.TAG, "Updating layer " + w + ": "
                     + w.mAnimLayer);
             if (w == service.mInputMethodTarget && !service.mInputMethodTargetWaitingAnim) {
@@ -186,16 +217,51 @@
         }
     }
 
-    void showAllWindowsLocked() {
+    boolean showAllWindowsLocked() {
+        boolean isAnimating = false;
         final int NW = allAppWindows.size();
         for (int i=0; i<NW; i++) {
             WindowState w = allAppWindows.get(i);
             if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG,
                     "performing show on: " + w);
             w.performShowLocked();
+            isAnimating |= w.mWinAnimator.isAnimating();
         }
+        return isAnimating;
     }
 
+    private void stepThumbnailAnimation(long currentTime) {
+        thumbnailTransformation.clear();
+        thumbnailAnimation.getTransformation(currentTime, thumbnailTransformation);
+        thumbnailTransformation.getMatrix().preTranslate(thumbnailX, thumbnailY);
+        final boolean screenAnimation = service.mAnimator.mScreenRotationAnimation != null
+                && service.mAnimator.mScreenRotationAnimation.isAnimating();
+        if (screenAnimation) {
+            thumbnailTransformation.postCompose(
+                    service.mAnimator.mScreenRotationAnimation.getEnterTransformation());
+        }
+        // cache often used attributes locally
+        final float tmpFloats[] = service.mTmpFloats;
+        thumbnailTransformation.getMatrix().getValues(tmpFloats);
+        if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
+                "thumbnail", "POS " + tmpFloats[Matrix.MTRANS_X]
+                + ", " + tmpFloats[Matrix.MTRANS_Y], null);
+        thumbnail.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
+        if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
+                "thumbnail", "alpha=" + thumbnailTransformation.getAlpha()
+                + " layer=" + thumbnailLayer
+                + " matrix=[" + tmpFloats[Matrix.MSCALE_X]
+                + "," + tmpFloats[Matrix.MSKEW_Y]
+                + "][" + tmpFloats[Matrix.MSKEW_X]
+                + "," + tmpFloats[Matrix.MSCALE_Y] + "]", null);
+        thumbnail.setAlpha(thumbnailTransformation.getAlpha());
+        // The thumbnail is layered below the window immediately above this
+        // token's anim layer.
+        thumbnail.setLayer(thumbnailLayer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
+                - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
+        thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
+                tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
+    }
 
     private boolean stepAnimation(long currentTime) {
         if (animation == null) {
@@ -208,6 +274,7 @@
             ": more=" + more + ", xform=" + transformation);
         if (!more) {
             animation = null;
+            clearThumbnail();
             if (WindowManagerService.DEBUG_ANIM) Slog.v(
                 WindowManagerService.TAG, "Finished animation in " + this +
                 " @ " + currentTime);
@@ -218,7 +285,7 @@
 
     // This must be called while inside a transaction.
     boolean stepAnimationLocked(long currentTime, int dw, int dh) {
-        if (!service.mDisplayFrozen && service.mPolicy.isScreenOnFully()) {
+        if (service.okToDisplay()) {
             // We will run animations as long as the display isn't frozen.
 
             if (animation == WindowManagerService.sDummyAnimation) {
@@ -236,12 +303,22 @@
                         " @ " + currentTime + ": dw=" + dw + " dh=" + dh
                         + " scale=" + service.mTransitionAnimationScale
                         + " allDrawn=" + allDrawn + " animating=" + animating);
-                    animation.initialize(dw, dh, dw, dh);
+                    if (!animInitialized) {
+                        animation.initialize(dw, dh, dw, dh);
+                    }
                     animation.setStartTime(currentTime);
                     animating = true;
+                    if (thumbnail != null) {
+                        thumbnail.show();
+                        thumbnailAnimation.setStartTime(currentTime);
+                    }
                 }
                 if (stepAnimation(currentTime)) {
-                    // we're done!
+                    // animation isn't over, step any thumbnail and that's
+                    // it for now.
+                    if (thumbnail != null) {
+                        stepThumbnailAnimation(currentTime);
+                    }
                     return true;
                 }
             }
@@ -259,6 +336,10 @@
         }
 
         service.mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+        if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+            service.debugLayoutRepeats("AppWindowToken");
+        }
+
         clearAnimation();
         animating = false;
         if (animLayerAdjustment != 0) {
@@ -277,7 +358,7 @@
 
         final int N = windows.size();
         for (int i=0; i<N; i++) {
-            windows.get(i).finishExit();
+            windows.get(i).mWinAnimator.finishExit();
         }
         updateReportedVisibilityLocked();
 
@@ -307,7 +388,7 @@
             if (WindowManagerService.DEBUG_VISIBILITY) {
                 Slog.v(WindowManagerService.TAG, "Win " + win + ": isDrawn="
                         + win.isDrawnLw()
-                        + ", isAnimating=" + win.isAnimating());
+                        + ", isAnimating=" + win.mWinAnimator.isAnimating());
                 if (!win.isDrawnLw()) {
                     Slog.v(WindowManagerService.TAG, "Not displayed: s=" + win.mSurface
                             + " pv=" + win.mPolicyVisibility
@@ -317,17 +398,17 @@
                             + " th="
                             + (win.mAppToken != null
                                     ? win.mAppToken.hiddenRequested : false)
-                            + " a=" + win.mAnimating);
+                            + " a=" + win.mWinAnimator.mAnimating);
                 }
             }
             numInteresting++;
             if (win.isDrawnLw()) {
                 numDrawn++;
-                if (!win.isAnimating()) {
+                if (!win.mWinAnimator.isAnimating()) {
                     numVisible++;
                 }
                 nowGone = false;
-            } else if (win.isAnimating()) {
+            } else if (win.mWinAnimator.isAnimating()) {
                 nowGone = false;
             }
         }
@@ -433,6 +514,15 @@
                     pw.print(" startingDisplayed="); pw.print(startingDisplayed);
                     pw.print(" startingMoved"); pw.println(startingMoved);
         }
+        if (thumbnail != null) {
+            pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail);
+                    pw.print(" x="); pw.print(thumbnailX);
+                    pw.print(" y="); pw.print(thumbnailY);
+                    pw.print(" layer="); pw.println(thumbnailLayer);
+            pw.print(prefix); pw.print("thumbnailAnimation="); pw.println(thumbnailAnimation);
+            pw.print(prefix); pw.print("thumbnailTransformation=");
+                    pw.println(thumbnailTransformation.toShortString());
+        }
     }
 
     @Override
diff --git a/services/java/com/android/server/wm/BlackFrame.java b/services/java/com/android/server/wm/BlackFrame.java
index 40e452a..c915932 100644
--- a/services/java/com/android/server/wm/BlackFrame.java
+++ b/services/java/com/android/server/wm/BlackFrame.java
@@ -32,12 +32,14 @@
     class BlackSurface {
         final int left;
         final int top;
+        final int layer;
         final Surface surface;
 
         BlackSurface(SurfaceSession session, int layer, int l, int t, int r, int b)
                 throws Surface.OutOfResourcesException {
             left = l;
             top = t;
+            this.layer = layer;
             int w = r-l;
             int h = b-t;
             surface = new Surface(session, 0, "BlackSurface",
@@ -45,8 +47,6 @@
             if (WindowManagerService.SHOW_TRANSACTIONS ||
                     WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG,
                             "  BLACK " + surface + ": CREATE layer=" + layer);
-            surface.setAlpha(1.0f);
-            surface.setLayer(layer);
         }
 
         void setMatrix(Matrix matrix) {
@@ -58,6 +58,8 @@
             surface.setMatrix(
                     mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
                     mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
+            surface.setAlpha(1.0f);
+            surface.setLayer(layer);
             if (false) {
                 Slog.i(WindowManagerService.TAG, "Black Surface @ (" + left + "," + top + "): ("
                         + mTmpFloats[Matrix.MTRANS_X] + ","
diff --git a/services/java/com/android/server/wm/DimAnimator.java b/services/java/com/android/server/wm/DimAnimator.java
index a9d4e01..d8f2254 100644
--- a/services/java/com/android/server/wm/DimAnimator.java
+++ b/services/java/com/android/server/wm/DimAnimator.java
@@ -82,7 +82,7 @@
 
     /**
      * Set's the dim surface's layer and update dim parameters that will be used in
-     * {@link updateSurface} after all windows are examined.
+     * {@link #updateSurface} after all windows are examined.
      */
     void updateParameters(Resources res, WindowState w, long currentTime) {
         mDimSurface.setLayer(w.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM);
@@ -94,15 +94,15 @@
             // If the desired dim level has changed, then
             // start an animation to it.
             mLastDimAnimTime = currentTime;
-            long duration = (w.mAnimating && w.mAnimation != null)
-                    ? w.mAnimation.computeDurationHint()
+            long duration = (w.mWinAnimator.mAnimating && w.mWinAnimator.mAnimation != null)
+                    ? w.mWinAnimator.mAnimation.computeDurationHint()
                     : WindowManagerService.DEFAULT_DIM_DURATION;
             if (target > mDimTargetAlpha) {
                 TypedValue tv = new TypedValue();
                 res.getValue(com.android.internal.R.fraction.config_dimBehindFadeDuration,
                         tv, true);
                 if (tv.type == TypedValue.TYPE_FRACTION) {
-                    duration = (long)tv.getFraction((float)duration, (float)duration);
+                    duration = (long)tv.getFraction(duration, duration);
                 } else if (tv.type >= TypedValue.TYPE_FIRST_INT
                         && tv.type <= TypedValue.TYPE_LAST_INT) {
                     duration = tv.data;
@@ -130,33 +130,31 @@
             }
         }
 
-        boolean animating = false;
-        if (mLastDimAnimTime != 0) {
+        boolean animating = mLastDimAnimTime != 0;
+        if (animating) {
             mDimCurrentAlpha += mDimDeltaPerMs
                     * (currentTime-mLastDimAnimTime);
-            boolean more = true;
             if (displayFrozen) {
                 // If the display is frozen, there is no reason to animate.
-                more = false;
+                animating = false;
             } else if (mDimDeltaPerMs > 0) {
                 if (mDimCurrentAlpha > mDimTargetAlpha) {
-                    more = false;
+                    animating = false;
                 }
             } else if (mDimDeltaPerMs < 0) {
                 if (mDimCurrentAlpha < mDimTargetAlpha) {
-                    more = false;
+                    animating = false;
                 }
             } else {
-                more = false;
+                animating = false;
             }
 
             // Do we need to continue animating?
-            if (more) {
+            if (animating) {
                 if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, "  DIM "
                         + mDimSurface + ": alpha=" + mDimCurrentAlpha);
                 mLastDimAnimTime = currentTime;
                 mDimSurface.setAlpha(mDimCurrentAlpha);
-                animating = true;
             } else {
                 mDimCurrentAlpha = mDimTargetAlpha;
                 mLastDimAnimTime = 0;
diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java
new file mode 100644
index 0000000..81e0a17
--- /dev/null
+++ b/services/java/com/android/server/wm/WindowAnimator.java
@@ -0,0 +1,629 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.Slog;
+import android.view.Surface;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.WindowManagerPolicy;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+
+import com.android.internal.policy.impl.PhoneWindowManager;
+
+/**
+ * @author cmautner@google.com (Craig Mautner)
+ * Singleton class that carries out the animations and Surface operations in a separate task
+ * on behalf of WindowManagerService.
+ */
+public class WindowAnimator {
+    private static final String TAG = "WindowAnimator";
+
+    final WindowManagerService mService;
+    final Context mContext;
+    final WindowManagerPolicy mPolicy;
+
+    boolean mAnimating;
+    boolean mUpdateRotation;
+    boolean mTokenMayBeDrawn;
+    boolean mForceHiding;
+    WindowState mWindowAnimationBackground;
+    int mWindowAnimationBackgroundColor;
+    int mAdjResult;
+
+    int mPendingLayoutChanges;
+
+    /** Overall window dimensions */
+    int mDw, mDh;
+
+    /** Interior window dimensions */
+    int mInnerDw, mInnerDh;
+
+    /** Time of current animation step. Reset on each iteration */
+    long mCurrentTime;
+
+    /** Skip repeated AppWindowTokens initialization. Note that AppWindowsToken's version of this
+     * is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
+    private int mTransactionSequence;
+
+    /** The one and only screen rotation if one is happening */
+    ScreenRotationAnimation mScreenRotationAnimation = null;
+
+    WindowAnimator(final WindowManagerService service, final Context context,
+            final WindowManagerPolicy policy) {
+        mService = service;
+        mContext = context;
+        mPolicy = policy;
+    }
+
+    private void updateWindowsAppsAndRotationAnimationsLocked() {
+        int i;
+        final int NAT = mService.mAppTokens.size();
+        for (i=0; i<NAT; i++) {
+            final AppWindowToken appToken = mService.mAppTokens.get(i);
+            final boolean wasAnimating = appToken.animation != null
+                    && appToken.animation != WindowManagerService.sDummyAnimation;
+            if (appToken.stepAnimationLocked(mCurrentTime, mInnerDw, mInnerDh)) {
+                mAnimating = true;
+            } else if (wasAnimating) {
+                // stopped animating, do one more pass through the layout
+                mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+                if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                    mService.debugLayoutRepeats("appToken " + appToken + " done");
+                }
+            }
+        }
+
+        final int NEAT = mService.mExitingAppTokens.size();
+        for (i=0; i<NEAT; i++) {
+            final AppWindowToken appToken = mService.mExitingAppTokens.get(i);
+            final boolean wasAnimating = appToken.animation != null
+                    && appToken.animation != WindowManagerService.sDummyAnimation;
+            if (appToken.stepAnimationLocked(mCurrentTime, mInnerDw, mInnerDh)) {
+                mAnimating = true;
+            } else if (wasAnimating) {
+                // stopped animating, do one more pass through the layout
+                mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+                if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                    mService.debugLayoutRepeats("exiting appToken " + appToken + " done");
+                }
+            }
+        }
+
+        if (mScreenRotationAnimation != null &&
+                (mScreenRotationAnimation.isAnimating() ||
+                        mScreenRotationAnimation.mFinishAnimReady)) {
+            if (mScreenRotationAnimation.stepAnimationLocked(mCurrentTime)) {
+                mUpdateRotation = false;
+                mAnimating = true;
+            } else {
+                mUpdateRotation = true;
+                mScreenRotationAnimation.kill();
+                mScreenRotationAnimation = null;
+            }
+        }
+    }
+
+    private void updateWindowsAndWallpaperLocked() {
+        ++mTransactionSequence;
+
+        for (int i = mService.mWindows.size() - 1; i >= 0; i--) {
+            WindowState w = mService.mWindows.get(i);
+            WindowStateAnimator winAnimator = w.mWinAnimator;
+
+            final WindowManager.LayoutParams attrs = w.mAttrs;
+
+            if (w.mSurface != null) {
+                // Take care of the window being ready to display.
+                if (w.commitFinishDrawingLocked(mCurrentTime)) {
+                    if ((w.mAttrs.flags
+                            & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) {
+                        if (WindowManagerService.DEBUG_WALLPAPER) Slog.v(TAG,
+                                "First draw done in potential wallpaper target " + w);
+                        mService.mInnerFields.mWallpaperMayChange = true;
+                        mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+                        if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                            mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 1");
+                        }
+                    }
+                }
+
+                // If the window has moved due to its containing
+                // content frame changing, then we'd like to animate
+                // it.  The checks here are ordered by what is least
+                // likely to be true first.
+                if (w.shouldAnimateMove()) {
+                    // Frame has moved, containing content frame
+                    // has also moved, and we're not currently animating...
+                    // let's do something.
+                    Animation a = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.window_move_from_decor);
+                    winAnimator.setAnimation(a);
+                    w.mAnimDw = w.mLastFrame.left - w.mFrame.left;
+                    w.mAnimDh = w.mLastFrame.top - w.mFrame.top;
+                } else {
+                    w.mAnimDw = mInnerDw;
+                    w.mAnimDh = mInnerDh;
+                }
+
+                final boolean wasAnimating = winAnimator.mWasAnimating;
+                final boolean nowAnimating = winAnimator.stepAnimationLocked(mCurrentTime);
+
+                if (WindowManagerService.DEBUG_WALLPAPER) {
+                    Slog.v(TAG, w + ": wasAnimating=" + wasAnimating +
+                            ", nowAnimating=" + nowAnimating);
+                }
+
+                // If this window is animating, make a note that we have
+                // an animating window and take care of a request to run
+                // a detached wallpaper animation.
+                if (nowAnimating) {
+                    if (winAnimator.mAnimation != null) {
+                        if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0
+                                && winAnimator.mAnimation.getDetachWallpaper()) {
+                            mService.mInnerFields.mDetachedWallpaper = w;
+                        }
+                        if (winAnimator.mAnimation.getBackgroundColor() != 0) {
+                            if (mWindowAnimationBackground == null
+                                    || (w.mAnimLayer < mWindowAnimationBackground.mAnimLayer)) {
+                                mWindowAnimationBackground = w;
+                                mWindowAnimationBackgroundColor =
+                                        winAnimator.mAnimation.getBackgroundColor();
+                            }
+                        }
+                    }
+                    mAnimating = true;
+                }
+
+                // If this window's app token is running a detached wallpaper
+                // animation, make a note so we can ensure the wallpaper is
+                // displayed behind it.
+                if (w.mAppToken != null && w.mAppToken.animation != null
+                        && w.mAppToken.animating) {
+                    if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0
+                            && w.mAppToken.animation.getDetachWallpaper()) {
+                        mService.mInnerFields.mDetachedWallpaper = w;
+                    }
+                    if (w.mAppToken.animation.getBackgroundColor() != 0) {
+                        if (mWindowAnimationBackground == null
+                                || (w.mAnimLayer <
+                                        mWindowAnimationBackground.mAnimLayer)) {
+                            mWindowAnimationBackground = w;
+                            mWindowAnimationBackgroundColor =
+                                    w.mAppToken.animation.getBackgroundColor();
+                        }
+                    }
+                }
+
+                if (wasAnimating && !winAnimator.mAnimating && mService.mWallpaperTarget == w) {
+                    mService.mInnerFields.mWallpaperMayChange = true;
+                    mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+                    if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                        mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 2");
+                    }
+                }
+
+                if (mPolicy.doesForceHide(w, attrs)) {
+                    if (!wasAnimating && nowAnimating) {
+                        if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
+                                "Animation started that could impact force hide: "
+                                + w);
+                        mService.mInnerFields.mWallpaperForceHidingChanged = true;
+                        mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+                        if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                            mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 3");
+                        }
+                        mService.mFocusMayChange = true;
+                    } else if (w.isReadyForDisplay() && winAnimator.mAnimation == null) {
+                        mForceHiding = true;
+                    }
+                } else if (mPolicy.canBeForceHidden(w, attrs)) {
+                    boolean changed;
+                    if (mForceHiding) {
+                        changed = w.hideLw(false, false);
+                        if (WindowManagerService.DEBUG_VISIBILITY && changed) Slog.v(TAG,
+                                "Now policy hidden: " + w);
+                    } else {
+                        changed = w.showLw(false, false);
+                        if (WindowManagerService.DEBUG_VISIBILITY && changed) Slog.v(TAG,
+                                "Now policy shown: " + w);
+                        if (changed) {
+                            if (mService.mInnerFields.mWallpaperForceHidingChanged
+                                    && w.isVisibleNow() /*w.isReadyForDisplay()*/) {
+                                // Assume we will need to animate.  If
+                                // we don't (because the wallpaper will
+                                // stay with the lock screen), then we will
+                                // clean up later.
+                                Animation a = mPolicy.createForceHideEnterAnimation();
+                                if (a != null) {
+                                    winAnimator.setAnimation(a);
+                                }
+                            }
+                            if (mCurrentFocus == null || mCurrentFocus.mLayer < w.mLayer) {
+                                // We are showing on to of the current
+                                // focus, so re-evaluate focus to make
+                                // sure it is correct.
+                                mService.mFocusMayChange = true;
+                            }
+                        }
+                    }
+                    if (changed && (attrs.flags
+                            & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) {
+                        mService.mInnerFields.mWallpaperMayChange = true;
+                        mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+                        if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                            mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 4");
+                        }
+                    }
+                }
+            }
+
+            final AppWindowToken atoken = w.mAppToken;
+            if (atoken != null && (!atoken.allDrawn || atoken.freezingScreen)) {
+                if (atoken.lastTransactionSequence != mTransactionSequence) {
+                    atoken.lastTransactionSequence = mTransactionSequence;
+                    atoken.numInterestingWindows = atoken.numDrawnWindows = 0;
+                    atoken.startingDisplayed = false;
+                }
+                if ((w.isOnScreen() || w.mAttrs.type
+                        == WindowManager.LayoutParams.TYPE_BASE_APPLICATION)
+                        && !w.mExiting && !w.mDestroying) {
+                    if (WindowManagerService.DEBUG_VISIBILITY ||
+                            WindowManagerService.DEBUG_ORIENTATION) {
+                        Slog.v(TAG, "Eval win " + w + ": isDrawn="
+                                + w.isDrawnLw()
+                                + ", isAnimating=" + winAnimator.isAnimating());
+                        if (!w.isDrawnLw()) {
+                            Slog.v(TAG, "Not displayed: s=" + w.mSurface
+                                    + " pv=" + w.mPolicyVisibility
+                                    + " dp=" + w.mDrawPending
+                                    + " cdp=" + w.mCommitDrawPending
+                                    + " ah=" + w.mAttachedHidden
+                                    + " th=" + atoken.hiddenRequested
+                                    + " a=" + winAnimator.mAnimating);
+                        }
+                    }
+                    if (w != atoken.startingWindow) {
+                        if (!atoken.freezingScreen || !w.mAppFreezing) {
+                            atoken.numInterestingWindows++;
+                            if (w.isDrawnLw()) {
+                                atoken.numDrawnWindows++;
+                                if (WindowManagerService.DEBUG_VISIBILITY ||
+                                        WindowManagerService.DEBUG_ORIENTATION) Slog.v(TAG,
+                                        "tokenMayBeDrawn: " + atoken
+                                        + " freezingScreen=" + atoken.freezingScreen
+                                        + " mAppFreezing=" + w.mAppFreezing);
+                                mTokenMayBeDrawn = true;
+                            }
+                        }
+                    } else if (w.isDrawnLw()) {
+                        atoken.startingDisplayed = true;
+                    }
+                }
+            } else if (w.mReadyToShow) {
+                if (w.performShowLocked()) {
+                    mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+                    if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                        mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 5");
+                    }
+                }
+            }
+            if (atoken != null && atoken.thumbnail != null) {
+                if (atoken.thumbnailTransactionSeq != mTransactionSequence) {
+                    atoken.thumbnailTransactionSeq = mTransactionSequence;
+                    atoken.thumbnailLayer = 0;
+                }
+                if (atoken.thumbnailLayer < w.mAnimLayer) {
+                    atoken.thumbnailLayer = w.mAnimLayer;
+                }
+            }
+        } // end forall windows
+    }
+
+    private void testTokenMayBeDrawnLocked() {
+        // See if any windows have been drawn, so they (and others
+        // associated with them) can now be shown.
+        final int NT = mService.mAppTokens.size();
+        for (int i=0; i<NT; i++) {
+            AppWindowToken wtoken = mService.mAppTokens.get(i);
+            if (wtoken.freezingScreen) {
+                int numInteresting = wtoken.numInterestingWindows;
+                if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) {
+                    if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
+                            "allDrawn: " + wtoken
+                            + " interesting=" + numInteresting
+                            + " drawn=" + wtoken.numDrawnWindows);
+                    wtoken.showAllWindowsLocked();
+                    mService.unsetAppFreezingScreenLocked(wtoken, false, true);
+                    if (WindowManagerService.DEBUG_ORIENTATION) Slog.i(TAG,
+                            "Setting mOrientationChangeComplete=true because wtoken "
+                            + wtoken + " numInteresting=" + numInteresting
+                            + " numDrawn=" + wtoken.numDrawnWindows);
+                    mService.mInnerFields.mOrientationChangeComplete = true;
+                }
+            } else if (!wtoken.allDrawn) {
+                int numInteresting = wtoken.numInterestingWindows;
+                if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) {
+                    if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
+                            "allDrawn: " + wtoken
+                            + " interesting=" + numInteresting
+                            + " drawn=" + wtoken.numDrawnWindows);
+                    wtoken.allDrawn = true;
+                    mPendingLayoutChanges |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM;
+                    if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                        mService.debugLayoutRepeats("testTokenMayBeDrawnLocked");
+                    }
+
+                    // We can now show all of the drawn windows!
+                    if (!mService.mOpeningApps.contains(wtoken)) {
+                        mAnimating |= wtoken.showAllWindowsLocked();
+                    }
+                }
+            }
+        }
+    }
+
+    private void performAnimationsLocked() {
+        if (WindowManagerService.DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: seq="
+                + mTransactionSequence + " mAnimating="
+                + mAnimating);
+
+        mTokenMayBeDrawn = false;
+        mService.mInnerFields.mWallpaperMayChange = false;
+        mForceHiding = false;
+        mService.mInnerFields.mDetachedWallpaper = null;
+        mWindowAnimationBackground = null;
+        mWindowAnimationBackgroundColor = 0;
+
+        updateWindowsAndWallpaperLocked();
+
+        if (mTokenMayBeDrawn) {
+            testTokenMayBeDrawnLocked();
+        }
+
+        if (WindowManagerService.DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: changes=0x"
+                + Integer.toHexString(mPendingLayoutChanges));
+    }
+
+    public void prepareSurfaceLocked(final WindowState w, final boolean recoveringMemory) {
+        if (w.mSurface == null) {
+            if (w.mOrientationChanging) {
+                if (WindowManagerService.DEBUG_ORIENTATION) {
+                    Slog.v(TAG, "Orientation change skips hidden " + w);
+                }
+                w.mOrientationChanging = false;
+            }
+            return;
+        }
+
+        boolean displayed = false;
+
+        w.computeShownFrameLocked();
+
+        int width, height;
+        if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) {
+            // for a scaled surface, we just want to use
+            // the requested size.
+            width  = w.mRequestedWidth;
+            height = w.mRequestedHeight;
+        } else {
+            width = w.mCompatFrame.width();
+            height = w.mCompatFrame.height();
+        }
+
+        if (width < 1) {
+            width = 1;
+        }
+        if (height < 1) {
+            height = 1;
+        }
+        final boolean surfaceResized = w.mSurfaceW != width || w.mSurfaceH != height;
+        if (surfaceResized) {
+            w.mSurfaceW = width;
+            w.mSurfaceH = height;
+        }
+
+        if (w.mSurfaceX != w.mShownFrame.left
+                || w.mSurfaceY != w.mShownFrame.top) {
+            try {
+                if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
+                        "POS " + w.mShownFrame.left
+                        + ", " + w.mShownFrame.top, null);
+                w.mSurfaceX = w.mShownFrame.left;
+                w.mSurfaceY = w.mShownFrame.top;
+                w.mSurface.setPosition(w.mShownFrame.left, w.mShownFrame.top);
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Error positioning surface of " + w
+                        + " pos=(" + w.mShownFrame.left
+                        + "," + w.mShownFrame.top + ")", e);
+                if (!recoveringMemory) {
+                    mService.reclaimSomeSurfaceMemoryLocked(w, "position", true);
+                }
+            }
+        }
+
+        if (surfaceResized) {
+            try {
+                if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
+                        "SIZE " + width + "x" + height, null);
+                w.mSurfaceResized = true;
+                w.mSurface.setSize(width, height);
+            } catch (RuntimeException e) {
+                // If something goes wrong with the surface (such
+                // as running out of memory), don't take down the
+                // entire system.
+                Slog.e(TAG, "Error resizing surface of " + w
+                        + " size=(" + width + "x" + height + ")", e);
+                if (!recoveringMemory) {
+                    mService.reclaimSomeSurfaceMemoryLocked(w, "size", true);
+                }
+            }
+        }
+
+        if (w.mAttachedHidden || !w.isReadyForDisplay()) {
+            if (!w.mLastHidden) {
+                //dump();
+                w.mLastHidden = true;
+                if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
+                        "HIDE (performLayout)", null);
+                if (w.mSurface != null) {
+                    w.mSurfaceShown = false;
+                    try {
+                        w.mSurface.hide();
+                    } catch (RuntimeException e) {
+                        Slog.w(TAG, "Exception hiding surface in " + w);
+                    }
+                }
+            }
+            // If we are waiting for this window to handle an
+            // orientation change, well, it is hidden, so
+            // doesn't really matter.  Note that this does
+            // introduce a potential glitch if the window
+            // becomes unhidden before it has drawn for the
+            // new orientation.
+            if (w.mOrientationChanging) {
+                w.mOrientationChanging = false;
+                if (WindowManagerService.DEBUG_ORIENTATION) Slog.v(TAG,
+                        "Orientation change skips hidden " + w);
+            }
+        } else if (w.mLastLayer != w.mAnimLayer
+                || w.mLastAlpha != w.mShownAlpha
+                || w.mLastDsDx != w.mDsDx
+                || w.mLastDtDx != w.mDtDx
+                || w.mLastDsDy != w.mDsDy
+                || w.mLastDtDy != w.mDtDy
+                || w.mLastHScale != w.mHScale
+                || w.mLastVScale != w.mVScale
+                || w.mLastHidden) {
+            displayed = true;
+            w.mLastAlpha = w.mShownAlpha;
+            w.mLastLayer = w.mAnimLayer;
+            w.mLastDsDx = w.mDsDx;
+            w.mLastDtDx = w.mDtDx;
+            w.mLastDsDy = w.mDsDy;
+            w.mLastDtDy = w.mDtDy;
+            w.mLastHScale = w.mHScale;
+            w.mLastVScale = w.mVScale;
+            if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
+                    "alpha=" + w.mShownAlpha + " layer=" + w.mAnimLayer
+                    + " matrix=[" + (w.mDsDx*w.mHScale)
+                    + "," + (w.mDtDx*w.mVScale)
+                    + "][" + (w.mDsDy*w.mHScale)
+                    + "," + (w.mDtDy*w.mVScale) + "]", null);
+            if (w.mSurface != null) {
+                try {
+                    w.mSurfaceAlpha = w.mShownAlpha;
+                    w.mSurface.setAlpha(w.mShownAlpha);
+                    w.mSurfaceLayer = w.mAnimLayer;
+                    w.mSurface.setLayer(w.mAnimLayer);
+                    w.mSurface.setMatrix(
+                            w.mDsDx*w.mHScale, w.mDtDx*w.mVScale,
+                            w.mDsDy*w.mHScale, w.mDtDy*w.mVScale);
+                } catch (RuntimeException e) {
+                    Slog.w(TAG, "Error updating surface in " + w, e);
+                    if (!recoveringMemory) {
+                        mService.reclaimSomeSurfaceMemoryLocked(w, "update", true);
+                    }
+                }
+            }
+
+            if (w.mLastHidden && w.isDrawnLw()
+                    && !w.mReadyToShow) {
+                if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
+                        "SHOW (performLayout)", null);
+                if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + w
+                        + " during relayout");
+                if (mService.showSurfaceRobustlyLocked(w)) {
+                    w.mHasDrawn = true;
+                    w.mLastHidden = false;
+                } else {
+                    w.mOrientationChanging = false;
+                }
+            }
+            if (w.mSurface != null) {
+                w.mToken.hasVisible = true;
+            }
+        } else {
+            displayed = true;
+        }
+
+        if (displayed) {
+            if (w.mOrientationChanging) {
+                if (!w.isDrawnLw()) {
+                    mService.mInnerFields.mOrientationChangeComplete = false;
+                    if (WindowManagerService.DEBUG_ORIENTATION) Slog.v(TAG,
+                            "Orientation continue waiting for draw in " + w);
+                } else {
+                    w.mOrientationChanging = false;
+                    if (WindowManagerService.DEBUG_ORIENTATION) Slog.v(TAG,
+                            "Orientation change complete in " + w);
+                }
+            }
+            w.mToken.hasVisible = true;
+        }
+    }
+
+    void animate() {
+        mPendingLayoutChanges = 0;
+        mCurrentTime = SystemClock.uptimeMillis();
+
+        // Update animations of all applications, including those
+        // associated with exiting/removed apps
+        Surface.openTransaction();
+
+        try {
+            updateWindowsAppsAndRotationAnimationsLocked();
+            performAnimationsLocked();
+
+            // THIRD LOOP: Update the surfaces of all windows.
+
+            if (mScreenRotationAnimation != null) {
+                mScreenRotationAnimation.updateSurfaces();
+            }
+
+            final int N = mService.mWindows.size();
+            for (int i=N-1; i>=0; i--) {
+                WindowState w = mService.mWindows.get(i);
+                prepareSurfaceLocked(w, true);
+            }
+
+            if (mService.mDimAnimator != null && mService.mDimAnimator.mDimShown) {
+                mAnimating |= mService.mDimAnimator.updateSurface(mService.mInnerFields.mDimming,
+                            mCurrentTime, !mService.okToDisplay());
+            }
+
+            if (mService.mBlackFrame != null) {
+                if (mScreenRotationAnimation != null) {
+                    mService.mBlackFrame.setMatrix(
+                            mScreenRotationAnimation.getEnterTransformation().getMatrix());
+                } else {
+                    mService.mBlackFrame.clearMatrix();
+                }
+            }
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Unhandled exception in Window Manager", e);
+        } finally {
+            Surface.closeTransaction();
+        }
+    }
+
+    WindowState mCurrentFocus;
+    void setCurrentFocus(WindowState currentFocus) {
+        mCurrentFocus = currentFocus;
+    }
+
+    void setDisplayDimensions(final int curWidth, final int curHeight,
+                        final int appWidth, final int appHeight) {
+        mDw = curWidth;
+        mDh = curHeight;
+        mInnerDw = appWidth;
+        mInnerDh = appHeight;
+    }
+
+}
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 6993657..9635b33 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -18,7 +18,6 @@
 
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
-import static android.view.WindowManager.LayoutParams.FLAG_BLUR_BEHIND;
 import static android.view.WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
 import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
@@ -90,6 +89,7 @@
 import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
+import android.util.FloatMath;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -118,8 +118,12 @@
 import android.view.WindowManagerPolicy;
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManagerPolicy.FakeWindow;
+import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
 import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.ScaleAnimation;
 import android.view.animation.Transformation;
 
 import java.io.BufferedWriter;
@@ -167,13 +171,14 @@
     static final boolean DEBUG_SCREEN_ON = false;
     static final boolean DEBUG_SCREENSHOT = false;
     static final boolean DEBUG_BOOT = false;
+    static final boolean DEBUG_LAYOUT_REPEATS = false;
     static final boolean SHOW_SURFACE_ALLOC = false;
     static final boolean SHOW_TRANSACTIONS = false;
     static final boolean SHOW_LIGHT_TRANSACTIONS = false || SHOW_TRANSACTIONS;
     static final boolean HIDE_STACK_CRAWLS = true;
+    static final int LAYOUT_REPEAT_THRESHOLD = 4;
 
     static final boolean PROFILE_ORIENTATION = false;
-    static final boolean BLUR = true;
     static final boolean localLOGV = DEBUG;
 
     /** How much to multiply the policy's type layer, to reserve room
@@ -201,6 +206,13 @@
     static final int LAYER_OFFSET_BLUR = 2;
 
     /**
+     * Animation thumbnail is as far as possible below the window above
+     * the thumbnail (or in other words as far as possible above the window
+     * below it).
+     */
+    static final int LAYER_OFFSET_THUMBNAIL = WINDOW_LAYER_MULTIPLIER-1;
+
+    /**
      * Layer at which to put the rotation freeze snapshot.
      */
     static final int FREEZE_LAYER = (TYPE_LAYER_MULTIPLIER * 200) + 1;
@@ -415,17 +427,12 @@
     IInputMethodManager mInputMethodManager;
 
     SurfaceSession mFxSession;
-    private DimAnimator mDimAnimator = null;
-    Surface mBlurSurface;
-    boolean mBlurShown;
+    DimAnimator mDimAnimator = null;
     Watermark mWatermark;
     StrictModeFlash mStrictModeFlash;
-    ScreenRotationAnimation mScreenRotationAnimation;
 
     BlackFrame mBlackFrame;
 
-    int mTransactionSequence = 0;
-
     final float[] mTmpFloats = new float[9];
 
     boolean mSafeMode;
@@ -491,8 +498,12 @@
     // made visible or hidden at the next transition.
     int mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET;
     String mNextAppTransitionPackage;
+    Bitmap mNextAppTransitionThumbnail;
+    IRemoteCallback mNextAppTransitionCallback;
     int mNextAppTransitionEnter;
     int mNextAppTransitionExit;
+    int mNextAppTransitionStartX;
+    int mNextAppTransitionStartY;
     boolean mAppTransitionReady = false;
     boolean mAppTransitionRunning = false;
     boolean mAppTransitionTimeout = false;
@@ -584,27 +595,24 @@
 
     /** Pulled out of performLayoutAndPlaceSurfacesLockedInner in order to refactor into multiple
      * methods. */
-    private class LayoutAndSurfaceFields {
-        private boolean mAnimating = false;
-        private boolean mWallpaperForceHidingChanged = false;
-        private boolean mTokenMayBeDrawn = false;
-        private boolean mWallpaperMayChange = false;
-        private boolean mForceHiding = false;
-        private WindowState mDetachedWallpaper = null;
-        private WindowState mWindowAnimationBackground = null;
-        private int mWindowAnimationBackgroundColor = 0;
-        private boolean mOrientationChangeComplete = true;
+    class LayoutAndSurfaceFields {
+        boolean mWallpaperForceHidingChanged = false;
+        boolean mWallpaperMayChange = false;
+        WindowState mDetachedWallpaper = null;
+        boolean mOrientationChangeComplete = true;
         private int mAdjResult = 0;
         private Session mHoldScreen = null;
         private boolean mObscured = false;
-        private boolean mBlurring = false;
-        private boolean mDimming = false;
+        boolean mDimming = false;
         private boolean mSyswin = false;
         private float mScreenBrightness = -1;
         private float mButtonBrightness = -1;
         private boolean mUpdateRotation = false;
     }
-    private LayoutAndSurfaceFields mInnerFields = new LayoutAndSurfaceFields();
+    LayoutAndSurfaceFields mInnerFields = new LayoutAndSurfaceFields();
+
+    /** Only do a maximum of 6 repeated layouts. After that quit */
+    private int mLayoutRepeatCount;
 
     private final class AnimationRunnable implements Runnable {
         @Override
@@ -617,6 +625,8 @@
     }
     final AnimationRunnable mAnimationRunnable = new AnimationRunnable();
     boolean mAnimationScheduled;
+    
+    final WindowAnimator mAnimator;
 
     final class DragInputEventReceiver extends InputEventReceiver {
         public DragInputEventReceiver(InputChannel inputChannel, Looper looper) {
@@ -735,6 +745,7 @@
             mAllowBootMessages = allowBootMsgs;
         }
 
+        @Override
         public void run() {
             Looper.prepare();
             WindowManagerService s = new WindowManagerService(mContext, mPM,
@@ -774,6 +785,7 @@
             mPM = pm;
         }
 
+        @Override
         public void run() {
             Looper.prepare();
             WindowManagerPolicyThread.set(this, Looper.myLooper());
@@ -836,6 +848,7 @@
         mHoldingScreenWakeLock.setReferenceCounted(false);
 
         mInputManager = new InputManager(context, this);
+        mAnimator = new WindowAnimator(this, context, mPolicy);
 
         PolicyThread thr = new PolicyThread(mPolicy, this, context, pm);
         thr.start();
@@ -1225,7 +1238,7 @@
                 if (highestTarget != null) {
                     if (DEBUG_INPUT_METHOD) Slog.v(TAG, "mNextAppTransition="
                             + mNextAppTransition + " " + highestTarget
-                            + " animating=" + highestTarget.isAnimating()
+                            + " animating=" + highestTarget.mWinAnimator.isAnimating()
                             + " layer=" + highestTarget.mAnimLayer
                             + " new layer=" + w.mAnimLayer);
 
@@ -1235,7 +1248,7 @@
                         mInputMethodTargetWaitingAnim = true;
                         mInputMethodTarget = highestTarget;
                         return highestPos + 1;
-                    } else if (highestTarget.isAnimating() &&
+                    } else if (highestTarget.mWinAnimator.isAnimating() &&
                             highestTarget.mAnimLayer > w.mAnimLayer) {
                         // If the window we are currently targeting is involved
                         // with an animation, and it is on top of the next target
@@ -1588,7 +1601,7 @@
                 foundI = i;
                 if (w == mWallpaperTarget && ((w.mAppToken != null
                         && w.mAppToken.animation != null)
-                        || w.mAnimation != null)) {
+                        || w.mWinAnimator.mAnimation != null)) {
                     // The current wallpaper target is animating, so we'll
                     // look behind it for another possible target and figure
                     // out what is going on below.
@@ -1645,9 +1658,9 @@
             // Now what is happening...  if the current and new targets are
             // animating, then we are in our super special mode!
             if (foundW != null && oldW != null) {
-                boolean oldAnim = oldW.mAnimation != null
+                boolean oldAnim = oldW.mWinAnimator.mAnimation != null
                         || (oldW.mAppToken != null && oldW.mAppToken.animation != null);
-                boolean foundAnim = foundW.mAnimation != null
+                boolean foundAnim = foundW.mWinAnimator.mAnimation != null
                         || (foundW.mAppToken != null && foundW.mAppToken.animation != null);
                 if (DEBUG_WALLPAPER) {
                     Slog.v(TAG, "New animation: " + foundAnim
@@ -1699,10 +1712,10 @@
 
         } else if (mLowerWallpaperTarget != null) {
             // Is it time to stop animating?
-            boolean lowerAnimating = mLowerWallpaperTarget.mAnimation != null
+            boolean lowerAnimating = mLowerWallpaperTarget.mWinAnimator.mAnimation != null
                     || (mLowerWallpaperTarget.mAppToken != null
                             && mLowerWallpaperTarget.mAppToken.animation != null);
-            boolean upperAnimating = mUpperWallpaperTarget.mAnimation != null
+            boolean upperAnimating = mUpperWallpaperTarget.mWinAnimator.mAnimation != null
                     || (mUpperWallpaperTarget.mAppToken != null
                             && mUpperWallpaperTarget.mAppToken.animation != null);
             if (!lowerAnimating || !upperAnimating) {
@@ -1910,7 +1923,7 @@
             rawChanged = true;
         }
 
-        if (rawChanged && (wallpaperWin.getAttrs().privateFlags &
+        if (rawChanged && (wallpaperWin.mAttrs.privateFlags &
                     WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS) != 0) {
             try {
                 if (DEBUG_WALLPAPER) Slog.v(TAG, "Report new wp offset "
@@ -2217,7 +2230,7 @@
             if (mInTouchMode) {
                 res |= WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE;
             }
-            if (win == null || win.mAppToken == null || !win.mAppToken.clientHidden) {
+            if (win.mAppToken == null || !win.mAppToken.clientHidden) {
                 res |= WindowManagerImpl.ADD_FLAG_APP_VISIBLE;
             }
 
@@ -2284,13 +2297,13 @@
             + ", surface=" + win.mSurface);
 
         final long origId = Binder.clearCallingIdentity();
-        
+
         win.disposeInputChannel();
 
         if (DEBUG_APP_TRANSITIONS) Slog.v(
                 TAG, "Remove " + win + ": mSurface=" + win.mSurface
                 + " mExiting=" + win.mExiting
-                + " isAnimating=" + win.isAnimating()
+                + " isAnimating=" + win.mWinAnimator.isAnimating()
                 + " app-animation="
                 + (win.mAppToken != null ? win.mAppToken.animation : null)
                 + " inPendingTransaction="
@@ -2302,14 +2315,14 @@
         // to hold off on removing the window until the animation is done.
         // If the display is frozen, just remove immediately, since the
         // animation wouldn't be seen.
-        if (win.mSurface != null && !mDisplayFrozen && mDisplayEnabled
-                && mPolicy.isScreenOnFully()) {
+        if (win.mSurface != null && okToDisplay()) {
             // If we are not currently running the exit animation, we
             // need to see about starting one.
-            if (wasVisible=win.isWinVisibleLw()) {
+            wasVisible = win.isWinVisibleLw();
+            if (wasVisible) {
 
                 int transit = WindowManagerPolicy.TRANSIT_EXIT;
-                if (win.getAttrs().type == TYPE_APPLICATION_STARTING) {
+                if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
                     transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
                 }
                 // Try starting an animation.
@@ -2317,7 +2330,7 @@
                     win.mExiting = true;
                 }
             }
-            if (win.mExiting || win.isAnimating()) {
+            if (win.mExiting || win.mWinAnimator.isAnimating()) {
                 // The exit animation is running... wait for it!
                 //Slog.i(TAG, "*** Running exit animation...");
                 win.mExiting = true;
@@ -2454,6 +2467,15 @@
             Slog.i(TAG, str);
         }
     }
+
+    static void logSurface(Surface s, String title, String msg, RuntimeException where) {
+        String str = "  SURFACE " + s + ": " + msg + " / " + title;
+        if (where != null) {
+            Slog.i(TAG, str, where);
+        } else {
+            Slog.i(TAG, str);
+        }
+    }
     
     void setTransparentRegionWindow(Session session, IWindow client, Region region) {
         long origId = Binder.clearCallingIdentity();
@@ -2677,7 +2699,7 @@
                     (win.mAppToken == null || !win.mAppToken.clientHidden)) {
                 displayed = !win.isVisibleLw();
                 if (win.mExiting) {
-                    win.cancelExitAnimationForNextAnimationLocked();
+                    win.mWinAnimator.cancelExitAnimationForNextAnimationLocked();
                 }
                 if (win.mDestroying) {
                     win.mDestroying = false;
@@ -2687,8 +2709,7 @@
                     win.mEnterAnimationPending = true;
                 }
                 if (displayed) {
-                    if (win.isDrawnLw() && !mDisplayFrozen
-                            && mDisplayEnabled && mPolicy.isScreenOnFully()) {
+                    if (win.isDrawnLw() && okToDisplay()) {
                         applyEnterAnimationLocked(win);
                     }
                     if ((win.mAttrs.flags
@@ -2775,14 +2796,14 @@
                         // Try starting an animation; if there isn't one, we
                         // can destroy the surface right away.
                         int transit = WindowManagerPolicy.TRANSIT_EXIT;
-                        if (win.getAttrs().type == TYPE_APPLICATION_STARTING) {
+                        if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
                             transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
                         }
                         if (!win.mSurfacePendingDestroy && win.isWinVisibleLw() &&
                               applyAnimationLocked(win, transit, false)) {
                             focusMayChange = true;
                             win.mExiting = true;
-                        } else if (win.isAnimating()) {
+                        } else if (win.mWinAnimator.isAnimating()) {
                             // Currently in a hide animation... turn this into
                             // an exit.
                             win.mExiting = true;
@@ -2791,7 +2812,7 @@
                             // window, we need to change both of them inside
                             // of a transaction to avoid artifacts.
                             win.mExiting = true;
-                            win.mAnimating = true;
+                            win.mWinAnimator.mAnimating = true;
                         } else {
                             if (mInputMethodWindow == win) {
                                 mInputMethodWindow = null;
@@ -3005,7 +3026,8 @@
      */
     boolean applyAnimationLocked(WindowState win,
             int transit, boolean isEntrance) {
-        if (win.mLocalAnimating && win.mAnimationIsEntrance == isEntrance) {
+        if (win.mWinAnimator.mLocalAnimating &&
+                win.mWinAnimator.mAnimationIsEntrance == isEntrance) {
             // If we are trying to apply an animation, but already running
             // an animation of the same type, then just leave that one alone.
             return true;
@@ -3015,7 +3037,7 @@
         // frozen, there is no reason to animate and it can cause strange
         // artifacts when we unfreeze the display if some different animation
         // is running.
-        if (!mDisplayFrozen && mDisplayEnabled && mPolicy.isScreenOnFully()) {
+        if (okToDisplay()) {
             int anim = mPolicy.selectAnimationLw(win, transit);
             int attr = -1;
             Animation a = null;
@@ -3042,7 +3064,7 @@
             }
             if (DEBUG_ANIM) Slog.v(TAG, "applyAnimation: win=" + win
                     + " anim=" + anim + " attr=0x" + Integer.toHexString(attr)
-                    + " mAnimation=" + win.mAnimation
+                    + " mAnimation=" + win.mWinAnimator.mAnimation
                     + " isEntrance=" + isEntrance);
             if (a != null) {
                 if (DEBUG_ANIM) {
@@ -3053,14 +3075,14 @@
                     }
                     Slog.v(TAG, "Loaded animation " + a + " for " + win, e);
                 }
-                win.setAnimation(a);
-                win.mAnimationIsEntrance = isEntrance;
+                win.mWinAnimator.setAnimation(a);
+                win.mWinAnimator.mAnimationIsEntrance = isEntrance;
             }
         } else {
-            win.clearAnimation();
+            win.mWinAnimator.clearAnimation();
         }
 
-        return win.mAnimation != null;
+        return win.mWinAnimator.mAnimation != null;
     }
 
     private Animation loadAnimation(WindowManager.LayoutParams lp, int animAttr) {
@@ -3095,15 +3117,76 @@
         return null;
     }
 
+    private Animation createThumbnailAnimationLocked(int transit,
+            boolean enter, boolean thumb) {
+        Animation a;
+        final float thumbWidth = mNextAppTransitionThumbnail.getWidth();
+        final float thumbHeight = mNextAppTransitionThumbnail.getHeight();
+        // Pick the desired duration.  If this is an inter-activity transition,
+        // it  is the standard duration for that.  Otherwise we use the longer
+        // task transition duration.
+        int duration;
+        switch (transit) {
+            case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN:
+            case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE:
+                duration = mContext.getResources().getInteger(
+                        com.android.internal.R.integer.config_shortAnimTime);
+                break;
+            default:
+                duration = 500;
+                break;
+            
+        }
+        if (thumb) {
+            // Animation for zooming thumbnail from its initial size to
+            // filling the screen.
+            Animation scale = new ScaleAnimation(
+                    1, mAppDisplayWidth/thumbWidth,
+                    1, mAppDisplayHeight/thumbHeight,
+                    mNextAppTransitionStartX + thumbWidth/2,
+                    mNextAppTransitionStartY + thumbHeight/2);
+            AnimationSet set = new AnimationSet(true);
+            Animation alpha = new AlphaAnimation(1, 0);
+            scale.setDuration(duration);
+            set.addAnimation(scale);
+            alpha.setDuration(duration);
+            set.addAnimation(alpha);
+            a = set;
+        } else if (enter) {
+            // Entering app zooms out from the center of the thumbnail.
+            a = new ScaleAnimation(
+                    thumbWidth/mAppDisplayWidth, 1,
+                    thumbHeight/mAppDisplayHeight, 1,
+                    mNextAppTransitionStartX + thumbWidth/2,
+                    mNextAppTransitionStartY + thumbHeight/2);
+            a.setDuration(duration);
+        } else {
+            // Exiting app just holds in place.
+            a = new AlphaAnimation(1, 1);
+            a.setDuration(duration);
+        }
+        a.setFillAfter(true);
+        final Interpolator interpolator = AnimationUtils.loadInterpolator(mContext,
+                com.android.internal.R.interpolator.decelerate_quint);
+        a.setInterpolator(interpolator);
+        a.initialize(mAppDisplayWidth, mAppDisplayHeight,
+                mAppDisplayWidth, mAppDisplayHeight);
+        return a;
+    }
+
     private boolean applyAnimationLocked(AppWindowToken wtoken,
             WindowManager.LayoutParams lp, int transit, boolean enter) {
         // Only apply an animation if the display isn't frozen.  If it is
         // frozen, there is no reason to animate and it can cause strange
         // artifacts when we unfreeze the display if some different animation
         // is running.
-        if (!mDisplayFrozen && mDisplayEnabled && mPolicy.isScreenOnFully()) {
+        if (okToDisplay()) {
             Animation a;
-            if (mNextAppTransitionPackage != null) {
+            boolean initialized = false;
+            if (mNextAppTransitionThumbnail != null) {
+                a = createThumbnailAnimationLocked(transit, enter, false);
+                initialized = true;
+            } else if (mNextAppTransitionPackage != null) {
                 a = loadAnimation(mNextAppTransitionPackage, enter ?
                         mNextAppTransitionEnter : mNextAppTransitionExit);
             } else {
@@ -3175,7 +3258,7 @@
                     }
                     Slog.v(TAG, "Loaded animation " + a + " for " + wtoken, e);
                 }
-                wtoken.setAnimation(a);
+                wtoken.setAnimation(a, initialized);
             }
         } else {
             wtoken.clearAnimation();
@@ -3234,6 +3317,10 @@
         Slog.w(TAG, msg);
         return false;
     }
+    
+    boolean okToDisplay() {
+        return !mDisplayFrozen && mDisplayEnabled && mPolicy.isScreenOnFully();
+    }
 
     AppWindowToken findAppWindowToken(IBinder token) {
         WindowToken wtoken = mTokenMap.get(token);
@@ -3243,6 +3330,7 @@
         return wtoken.appWindowToken;
     }
 
+    @Override
     public void addWindowToken(IBinder token, int type) {
         if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
                 "addWindowToken()")) {
@@ -3283,7 +3371,7 @@
                     for (int i=0; i<N; i++) {
                         WindowState win = wtoken.windows.get(i);
 
-                        if (win.isAnimating()) {
+                        if (win.mWinAnimator.isAnimating()) {
                             delayed = true;
                         }
 
@@ -3665,7 +3753,7 @@
             if (DEBUG_APP_TRANSITIONS) Slog.v(
                     TAG, "Prepare app transition: transit=" + transit
                     + " mNextAppTransition=" + mNextAppTransition);
-            if (!mDisplayFrozen && mDisplayEnabled && mPolicy.isScreenOnFully()) {
+            if (okToDisplay()) {
                 if (mNextAppTransition == WindowManagerPolicy.TRANSIT_UNSET
                         || mNextAppTransition == WindowManagerPolicy.TRANSIT_NONE) {
                     mNextAppTransition = transit;
@@ -3699,11 +3787,23 @@
             int enterAnim, int exitAnim) {
         if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
             mNextAppTransitionPackage = packageName;
+            mNextAppTransitionThumbnail = null;
             mNextAppTransitionEnter = enterAnim;
             mNextAppTransitionExit = exitAnim;
         }
     }
 
+    public void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX,
+            int startY, IRemoteCallback startedCallback) {
+        if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
+            mNextAppTransitionPackage = null;
+            mNextAppTransitionThumbnail = srcThumb;
+            mNextAppTransitionStartX = startX;
+            mNextAppTransitionStartY = startY;
+            mNextAppTransitionCallback = startedCallback;
+        }
+    }
+
     public void executeAppTransition() {
         if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
                 "executeAppTransition()")) {
@@ -3749,7 +3849,7 @@
             // If the display is frozen, we won't do anything until the
             // actual window is displayed so there is no reason to put in
             // the starting window.
-            if (mDisplayFrozen || !mDisplayEnabled || !mPolicy.isScreenOnFully()) {
+            if (!okToDisplay()) {
                 return;
             }
 
@@ -3847,6 +3947,19 @@
                         mH.sendMessageAtFrontOfQueue(m);
                         return;
                     }
+                    if (ttoken.thumbnail != null) {
+                        // The old token is animating with a thumbnail, transfer
+                        // that to the new token.
+                        if (wtoken.thumbnail != null) {
+                            wtoken.thumbnail.destroy();
+                        }
+                        wtoken.thumbnail = ttoken.thumbnail;
+                        wtoken.thumbnailX = ttoken.thumbnailX;
+                        wtoken.thumbnailY = ttoken.thumbnailY;
+                        wtoken.thumbnailLayer = ttoken.thumbnailLayer;
+                        wtoken.thumbnailAnimation = ttoken.thumbnailAnimation;
+                        ttoken.thumbnail = null;
+                    }
                 }
             }
 
@@ -3951,7 +4064,7 @@
                     continue;
                 }
 
-                if (win.isAnimating()) {
+                if (win.mWinAnimator.isAnimating()) {
                     delayed = true;
                 }
 
@@ -4039,8 +4152,7 @@
 
             // If we are preparing an app transition, then delay changing
             // the visibility of this token until we execute that transition.
-            if (!mDisplayFrozen && mDisplayEnabled && mPolicy.isScreenOnFully()
-                    && mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
+            if (okToDisplay() && mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
                 // Already in requested state, don't do anything more.
                 if (wtoken.hiddenRequested != visible) {
                     return;
@@ -4168,7 +4280,7 @@
         }
 
         synchronized(mWindowMap) {
-            if (configChanges == 0 && !mDisplayFrozen && mPolicy.isScreenOnFully()) {
+            if (configChanges == 0 && okToDisplay()) {
                 if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping set freeze of " + token);
                 return;
             }
@@ -4243,6 +4355,7 @@
                     // Make sure there is no animation running on this token,
                     // so any windows associated with it will be removed as
                     // soon as their animations are complete
+                    wtoken.clearAnimation();
                     wtoken.animation = null;
                     wtoken.animating = false;
                 }
@@ -5150,8 +5263,8 @@
         synchronized(mWindowMap) {
             long ident = Binder.clearCallingIdentity();
 
-            dw = mAppDisplayWidth;
-            dh = mAppDisplayHeight;
+            dw = mCurDisplayWidth;
+            dh = mCurDisplayHeight;
 
             int aboveAppLayer = mPolicy.windowTypeToLayerLw(
                     WindowManager.LayoutParams.TYPE_APPLICATION) * TYPE_LAYER_MULTIPLIER
@@ -5271,7 +5384,7 @@
         Bitmap bm = Bitmap.createBitmap(width, height, rawss.getConfig());
         Matrix matrix = new Matrix();
         ScreenRotationAnimation.createRotationMatrix(rot, dw, dh, matrix);
-        matrix.postTranslate(-(int)(frame.left*scale), -(int)(frame.top*scale));
+        matrix.postTranslate(-FloatMath.ceil(frame.left*scale), -FloatMath.ceil(frame.top*scale));
         Canvas canvas = new Canvas(bm);
         canvas.drawBitmap(rawss, matrix, null);
         canvas.setBitmap(null);
@@ -5389,7 +5502,8 @@
             return false;
         }
 
-        if (mScreenRotationAnimation != null && mScreenRotationAnimation.isAnimating()) {
+        if (mAnimator.mScreenRotationAnimation != null &&
+                mAnimator.mScreenRotationAnimation.isAnimating()) {
             // Rotation updates cannot be performed while the previous rotation change
             // animation is still in progress.  Skip this update.  We will try updating
             // again after the animation is finished and the display is unfrozen.
@@ -5459,9 +5573,9 @@
         try {
             // NOTE: We disable the rotation in the emulator because
             //       it doesn't support hardware OpenGL emulation yet.
-            if (CUSTOM_SCREEN_ROTATION && mScreenRotationAnimation != null
-                    && mScreenRotationAnimation.hasScreenshot()) {
-                if (mScreenRotationAnimation.setRotation(rotation, mFxSession,
+            if (CUSTOM_SCREEN_ROTATION && mAnimator.mScreenRotationAnimation != null
+                    && mAnimator.mScreenRotationAnimation.hasScreenshot()) {
+                if (mAnimator.mScreenRotationAnimation.setRotation(rotation, mFxSession,
                         MAX_ANIMATION_DURATION, mTransitionAnimationScale,
                         mCurDisplayWidth, mCurDisplayHeight)) {
                     scheduleAnimationLocked();
@@ -5476,7 +5590,7 @@
             }
         }
 
-        rebuildBlackFrame(inTransaction);
+        rebuildBlackFrame();
 
         for (int i=mWindows.size()-1; i>=0; i--) {
             WindowState w = mWindows.get(i);
@@ -6163,6 +6277,8 @@
         synchronized(mDisplaySizeLock) {
             mAppDisplayWidth = appWidth;
             mAppDisplayHeight = appHeight;
+            mAnimator.setDisplayDimensions(mCurDisplayWidth, mCurDisplayHeight,
+                    mAppDisplayWidth, mAppDisplayHeight);
         }
         if (false) {
             Slog.i(TAG, "Set app display size: " + mAppDisplayWidth
@@ -6549,6 +6665,8 @@
                 }
                 mBaseDisplayWidth = mCurDisplayWidth = mAppDisplayWidth = mInitialDisplayWidth;
                 mBaseDisplayHeight = mCurDisplayHeight = mAppDisplayHeight = mInitialDisplayHeight;
+                mAnimator.setDisplayDimensions(mCurDisplayWidth, mCurDisplayHeight,
+                        mAppDisplayWidth, mAppDisplayHeight);
             }
             mInputManager.setDisplaySize(Display.DEFAULT_DISPLAY,
                     mDisplay.getRawWidth(), mDisplay.getRawHeight(),
@@ -7151,45 +7269,32 @@
         }
     }
 
-    private void rebuildBlackFrame(boolean inTransaction) {
-        if (!inTransaction) {
-            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
-                    ">>> OPEN TRANSACTION rebuildBlackFrame");
-            Surface.openTransaction();
+    private void rebuildBlackFrame() {
+        if (mBlackFrame != null) {
+            mBlackFrame.kill();
+            mBlackFrame = null;
         }
-        try {
-            if (mBlackFrame != null) {
-                mBlackFrame.kill();
-                mBlackFrame = null;
+        if (mBaseDisplayWidth < mInitialDisplayWidth
+                || mBaseDisplayHeight < mInitialDisplayHeight) {
+            int initW, initH, baseW, baseH;
+            final boolean rotated = (mRotation == Surface.ROTATION_90
+                    || mRotation == Surface.ROTATION_270);
+            if (rotated) {
+                initW = mInitialDisplayHeight;
+                initH = mInitialDisplayWidth;
+                baseW = mBaseDisplayHeight;
+                baseH = mBaseDisplayWidth;
+            } else {
+                initW = mInitialDisplayWidth;
+                initH = mInitialDisplayHeight;
+                baseW = mBaseDisplayWidth;
+                baseH = mBaseDisplayHeight;
             }
-            if (mBaseDisplayWidth < mInitialDisplayWidth
-                    || mBaseDisplayHeight < mInitialDisplayHeight) {
-                int initW, initH, baseW, baseH;
-                final boolean rotated = (mRotation == Surface.ROTATION_90
-                        || mRotation == Surface.ROTATION_270);
-                if (rotated) {
-                    initW = mInitialDisplayHeight;
-                    initH = mInitialDisplayWidth;
-                    baseW = mBaseDisplayHeight;
-                    baseH = mBaseDisplayWidth;
-                } else {
-                    initW = mInitialDisplayWidth;
-                    initH = mInitialDisplayHeight;
-                    baseW = mBaseDisplayWidth;
-                    baseH = mBaseDisplayHeight;
-                }
-                Rect outer = new Rect(0, 0, initW, initH);
-                Rect inner = new Rect(0, 0, baseW, baseH);
-                try {
-                    mBlackFrame = new BlackFrame(mFxSession, outer, inner, MASK_LAYER);
-                } catch (Surface.OutOfResourcesException e) {
-                }
-            }
-        } finally {
-            if (!inTransaction) {
-                Surface.closeTransaction();
-                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
-                        "<<< CLOSE TRANSACTION rebuildBlackFrame");
+            Rect outer = new Rect(0, 0, initW, initH);
+            Rect inner = new Rect(0, 0, baseW, baseH);
+            try {
+                mBlackFrame = new BlackFrame(mFxSession, outer, inner, MASK_LAYER);
+            } catch (Surface.OutOfResourcesException e) {
             }
         }
     }
@@ -7240,7 +7345,7 @@
             mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
         }
 
-        rebuildBlackFrame(false);
+        rebuildBlackFrame();
 
         performLayoutAndPlaceSurfacesLocked();
     }
@@ -7478,10 +7583,25 @@
 
             } else {
                 mInLayout = false;
-                if (mLayoutNeeded) {
-                    requestTraversalLocked();
-                }
             }
+
+            if (mLayoutNeeded) {
+                if (++mLayoutRepeatCount < 6) {
+                    requestTraversalLocked();
+                } else {
+                    Slog.e(TAG, "Performed 6 layouts in a row. Skipping");
+                    mLayoutRepeatCount = 0;
+                }
+            } else {
+                mLayoutRepeatCount = 0;
+            }
+            
+            if (mAnimator.mAnimating) {
+                // Do this even if requestTraversalLocked was called above so we get a frame drawn
+                // at the proper time as well as the one drawn early.
+                scheduleAnimationLocked();
+            }
+
             if (mWindowsChanged && !mWindowChangeListeners.isEmpty()) {
                 mH.removeMessages(H.REPORT_WINDOWS_CHANGE);
                 mH.sendMessage(mH.obtainMessage(H.REPORT_WINDOWS_CHANGE));
@@ -7625,7 +7745,7 @@
         // If the screen is currently frozen or off, then keep
         // it frozen/off until this window draws at its new
         // orientation.
-        if (mDisplayFrozen || !mPolicy.isScreenOnFully()) {
+        if (!okToDisplay()) {
             if (DEBUG_ORIENTATION) Slog.v(TAG,
                     "Changing surface while display frozen: " + w);
             w.mOrientationChanging = true;
@@ -7642,351 +7762,6 @@
 
     /**
      * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method.
-     * Update animations of all applications, including those associated with exiting/removed apps.
-     *
-     * @param currentTime The time which animations use for calculating transitions.
-     * @param innerDw Width of app window.
-     * @param innerDh Height of app window.
-     */
-    private void updateWindowsAppsAndRotationAnimationsLocked(long currentTime,
-                                                          int innerDw, int innerDh) {
-        int i;
-        final int NAT = mAppTokens.size();
-        for (i=0; i<NAT; i++) {
-            final AppWindowToken appToken = mAppTokens.get(i);
-            if (appToken.stepAnimationLocked(currentTime, innerDw, innerDh)) {
-                mInnerFields.mAnimating = true;
-            }
-        }
-        final int NEAT = mExitingAppTokens.size();
-        for (i=0; i<NEAT; i++) {
-            final AppWindowToken appToken = mExitingAppTokens.get(i);
-            if (appToken.stepAnimationLocked(currentTime, innerDw, innerDh)) {
-                mInnerFields.mAnimating = true;
-            }
-        }
-
-        if (mScreenRotationAnimation != null &&
-                (mScreenRotationAnimation.isAnimating() ||
-                        mScreenRotationAnimation.mFinishAnimReady)) {
-            if (mScreenRotationAnimation.stepAnimationLocked(currentTime)) {
-                mInnerFields.mUpdateRotation = false;
-                mInnerFields.mAnimating = true;
-            } else {
-                mInnerFields.mUpdateRotation = true;
-                mScreenRotationAnimation.kill();
-                mScreenRotationAnimation = null;
-            }
-        }
-    }
-
-    private void animateAndUpdateSurfaces(final long currentTime, final int dw, final int dh,
-                                          final int innerDw, final int innerDh, 
-                                          final boolean recoveringMemory) {
-        // Update animations of all applications, including those
-        // associated with exiting/removed apps
-        Surface.openTransaction();
-
-        try {
-            updateWindowsAppsAndRotationAnimationsLocked(currentTime, innerDw, innerDh);
-            mPendingLayoutChanges = performAnimationsLocked(currentTime, dw, dh,
-                    innerDw, innerDh);
-        
-            // THIRD LOOP: Update the surfaces of all windows.
-            
-            if (mScreenRotationAnimation != null) {
-                mScreenRotationAnimation.updateSurfaces();
-            }
-        
-            final int N = mWindows.size();
-            for (int i=N-1; i>=0; i--) {
-                WindowState w = mWindows.get(i);
-                prepareSurfaceLocked(w, recoveringMemory);
-            }
-        
-            if (mDimAnimator != null && mDimAnimator.mDimShown) {
-                mInnerFields.mAnimating |=
-                        mDimAnimator.updateSurface(mInnerFields.mDimming, currentTime,
-                            mDisplayFrozen || !mDisplayEnabled || !mPolicy.isScreenOnFully());
-            }
-        
-            if (!mInnerFields.mBlurring && mBlurShown) {
-                if (SHOW_TRANSACTIONS) Slog.i(TAG, "  BLUR " + mBlurSurface
-                        + ": HIDE");
-                try {
-                    mBlurSurface.hide();
-                } catch (IllegalArgumentException e) {
-                    Slog.w(TAG, "Illegal argument exception hiding blur surface");
-                }
-                mBlurShown = false;
-            }
-        
-            if (mBlackFrame != null) {
-                if (mScreenRotationAnimation != null) {
-                    mBlackFrame.setMatrix(
-                            mScreenRotationAnimation.getEnterTransformation().getMatrix());
-                } else {
-                    mBlackFrame.clearMatrix();
-                }
-            }
-        } catch (RuntimeException e) {
-            Log.wtf(TAG, "Unhandled exception in Window Manager", e);
-        } finally {
-            Surface.closeTransaction();
-        }
-    }
-    
-    /**
-     * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method.
-     *
-     * @param currentTime The time which animations use for calculating transitions.
-     * @param dw Width of app window.
-     * @param dh Height of app window.
-     * @param innerDw Width of app window.
-     * @param innerDh Height of app window.
-     */
-    private int updateWindowsAndWallpaperLocked(final long currentTime, final int dw, final int dh,
-                                                final int innerDw, final int innerDh) {
-        int changes = 0;
-        for (int i = mWindows.size() - 1; i >= 0; i--) {
-            WindowState w = mWindows.get(i);
-
-            final WindowManager.LayoutParams attrs = w.mAttrs;
-
-            if (w.mSurface != null) {
-                // Take care of the window being ready to display.
-                if (w.commitFinishDrawingLocked(currentTime)) {
-                    if ((w.mAttrs.flags
-                            & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) {
-                        if (DEBUG_WALLPAPER) Slog.v(TAG,
-                                "First draw done in potential wallpaper target " + w);
-                        mInnerFields.mWallpaperMayChange = true;
-                        changes |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-                    }
-                }
-
-                // If the window has moved due to its containing
-                // content frame changing, then we'd like to animate
-                // it.  The checks here are ordered by what is least
-                // likely to be true first.
-                if (w.shouldAnimateMove()) {
-                    // Frame has moved, containing content frame
-                    // has also moved, and we're not currently animating...
-                    // let's do something.
-                    Animation a = AnimationUtils.loadAnimation(mContext,
-                            com.android.internal.R.anim.window_move_from_decor);
-                    w.setAnimation(a);
-                    w.mAnimDw = w.mLastFrame.left - w.mFrame.left;
-                    w.mAnimDh = w.mLastFrame.top - w.mFrame.top;
-                } else {
-                    w.mAnimDw = innerDw;
-                    w.mAnimDh = innerDh;
-                }
-
-                final boolean wasAnimating = w.mWasAnimating;
-                final boolean nowAnimating = w.stepAnimationLocked(currentTime);
-
-                if (DEBUG_WALLPAPER) {
-                    Slog.v(TAG, w + ": wasAnimating=" + wasAnimating +
-                            ", nowAnimating=" + nowAnimating);
-                }
-
-                // If this window is animating, make a note that we have
-                // an animating window and take care of a request to run
-                // a detached wallpaper animation.
-                if (nowAnimating) {
-                    if (w.mAnimation != null) {
-                        if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0
-                                && w.mAnimation.getDetachWallpaper()) {
-                            mInnerFields.mDetachedWallpaper = w;
-                        }
-                        if (w.mAnimation.getBackgroundColor() != 0) {
-                            if (mInnerFields.mWindowAnimationBackground == null
-                                    || (w.mAnimLayer <
-                                            mInnerFields.mWindowAnimationBackground.mAnimLayer)) {
-                                mInnerFields.mWindowAnimationBackground = w;
-                                mInnerFields.mWindowAnimationBackgroundColor =
-                                        w.mAnimation.getBackgroundColor();
-                            }
-                        }
-                    }
-                    mInnerFields.mAnimating = true;
-                }
-
-                // If this window's app token is running a detached wallpaper
-                // animation, make a note so we can ensure the wallpaper is
-                // displayed behind it.
-                if (w.mAppToken != null && w.mAppToken.animation != null
-                        && w.mAppToken.animating) {
-                    if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0
-                            && w.mAppToken.animation.getDetachWallpaper()) {
-                        mInnerFields.mDetachedWallpaper = w;
-                    }
-                    if (w.mAppToken.animation.getBackgroundColor() != 0) {
-                        if (mInnerFields.mWindowAnimationBackground == null
-                                || (w.mAnimLayer <
-                                        mInnerFields.mWindowAnimationBackground.mAnimLayer)) {
-                            mInnerFields.mWindowAnimationBackground = w;
-                            mInnerFields.mWindowAnimationBackgroundColor =
-                                    w.mAppToken.animation.getBackgroundColor();
-                        }
-                    }
-                }
-
-                if (wasAnimating && !w.mAnimating && mWallpaperTarget == w) {
-                    mInnerFields.mWallpaperMayChange = true;
-                    changes |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-                }
-
-                if (mPolicy.doesForceHide(w, attrs)) {
-                    if (!wasAnimating && nowAnimating) {
-                        if (DEBUG_VISIBILITY) Slog.v(TAG,
-                                "Animation started that could impact force hide: "
-                                + w);
-                        mInnerFields.mWallpaperForceHidingChanged = true;
-                        changes |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-                        mFocusMayChange = true;
-                    } else if (w.isReadyForDisplay() && w.mAnimation == null) {
-                        mInnerFields.mForceHiding = true;
-                    }
-                } else if (mPolicy.canBeForceHidden(w, attrs)) {
-                    boolean changed;
-                    if (mInnerFields.mForceHiding) {
-                        changed = w.hideLw(false, false);
-                        if (DEBUG_VISIBILITY && changed) Slog.v(TAG,
-                                "Now policy hidden: " + w);
-                    } else {
-                        changed = w.showLw(false, false);
-                        if (DEBUG_VISIBILITY && changed) Slog.v(TAG,
-                                "Now policy shown: " + w);
-                        if (changed) {
-                            if (mInnerFields.mWallpaperForceHidingChanged
-                                    && w.isVisibleNow() /*w.isReadyForDisplay()*/) {
-                                // Assume we will need to animate.  If
-                                // we don't (because the wallpaper will
-                                // stay with the lock screen), then we will
-                                // clean up later.
-                                Animation a = mPolicy.createForceHideEnterAnimation();
-                                if (a != null) {
-                                    w.setAnimation(a);
-                                }
-                            }
-                            if (mCurrentFocus == null ||
-                                    mCurrentFocus.mLayer < w.mLayer) {
-                                // We are showing on to of the current
-                                // focus, so re-evaluate focus to make
-                                // sure it is correct.
-                                mFocusMayChange = true;
-                            }
-                        }
-                    }
-                    if (changed && (attrs.flags
-                            & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) {
-                        mInnerFields.mWallpaperMayChange = true;
-                        changes |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-                    }
-                }
-            }
-
-            final AppWindowToken atoken = w.mAppToken;
-            if (atoken != null && (!atoken.allDrawn || atoken.freezingScreen)) {
-                if (atoken.lastTransactionSequence != mTransactionSequence) {
-                    atoken.lastTransactionSequence = mTransactionSequence;
-                    atoken.numInterestingWindows = atoken.numDrawnWindows = 0;
-                    atoken.startingDisplayed = false;
-                }
-                if ((w.isOnScreen() || w.mAttrs.type
-                        == WindowManager.LayoutParams.TYPE_BASE_APPLICATION)
-                        && !w.mExiting && !w.mDestroying) {
-                    if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) {
-                        Slog.v(TAG, "Eval win " + w + ": isDrawn="
-                                + w.isDrawnLw()
-                                + ", isAnimating=" + w.isAnimating());
-                        if (!w.isDrawnLw()) {
-                            Slog.v(TAG, "Not displayed: s=" + w.mSurface
-                                    + " pv=" + w.mPolicyVisibility
-                                    + " dp=" + w.mDrawPending
-                                    + " cdp=" + w.mCommitDrawPending
-                                    + " ah=" + w.mAttachedHidden
-                                    + " th=" + atoken.hiddenRequested
-                                    + " a=" + w.mAnimating);
-                        }
-                    }
-                    if (w != atoken.startingWindow) {
-                        if (!atoken.freezingScreen || !w.mAppFreezing) {
-                            atoken.numInterestingWindows++;
-                            if (w.isDrawnLw()) {
-                                atoken.numDrawnWindows++;
-                                if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) Slog.v(TAG,
-                                        "tokenMayBeDrawn: " + atoken
-                                        + " freezingScreen=" + atoken.freezingScreen
-                                        + " mAppFreezing=" + w.mAppFreezing);
-                                mInnerFields.mTokenMayBeDrawn = true;
-                            }
-                        }
-                    } else if (w.isDrawnLw()) {
-                        atoken.startingDisplayed = true;
-                    }
-                }
-            } else if (w.mReadyToShow) {
-                w.performShowLocked();
-                changes |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
-            }
-        } // end forall windows
-
-        return changes;
-    }
-
-    /**
-     * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method.
-     *
-     * @return bitmap indicating if another pass through layout must be made.
-     */
-    private int testTokenMayBeDrawnLocked() {
-        int changes = 0;
-        // See if any windows have been drawn, so they (and others
-        // associated with them) can now be shown.
-        final int NT = mAppTokens.size();
-        for (int i=0; i<NT; i++) {
-            AppWindowToken wtoken = mAppTokens.get(i);
-            if (wtoken.freezingScreen) {
-                int numInteresting = wtoken.numInterestingWindows;
-                if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) {
-                    if (DEBUG_VISIBILITY) Slog.v(TAG,
-                            "allDrawn: " + wtoken
-                            + " interesting=" + numInteresting
-                            + " drawn=" + wtoken.numDrawnWindows);
-                    wtoken.showAllWindowsLocked();
-                    unsetAppFreezingScreenLocked(wtoken, false, true);
-                    if (DEBUG_ORIENTATION) Slog.i(TAG,
-                            "Setting mOrientationChangeComplete=true because wtoken "
-                            + wtoken + " numInteresting=" + numInteresting
-                            + " numDrawn=" + wtoken.numDrawnWindows);
-                    mInnerFields.mOrientationChangeComplete = true;
-                }
-            } else if (!wtoken.allDrawn) {
-                int numInteresting = wtoken.numInterestingWindows;
-                if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) {
-                    if (DEBUG_VISIBILITY) Slog.v(TAG,
-                            "allDrawn: " + wtoken
-                            + " interesting=" + numInteresting
-                            + " drawn=" + wtoken.numDrawnWindows);
-                    wtoken.allDrawn = true;
-                    changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM;
-
-                    // We can now show all of the drawn windows!
-                    if (!mOpeningApps.contains(wtoken)) {
-                        wtoken.showAllWindowsLocked();
-                    }
-                }
-            }
-        }
-
-        return changes;
-    }
-
-    /**
-     * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method.
      *
      * @return bitmap indicating if another pass through layout must be made.
      */
@@ -8148,11 +7923,15 @@
                 animLp = null;
             }
 
+            AppWindowToken topOpeningApp = null;
+            int topOpeningLayer = 0;
+
             NN = mOpeningApps.size();
             for (i=0; i<NN; i++) {
                 AppWindowToken wtoken = mOpeningApps.get(i);
                 if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                         "Now opening app" + wtoken);
+                wtoken.clearThumbnail();
                 wtoken.reportedVisible = false;
                 wtoken.inPendingTransaction = false;
                 wtoken.animation = null;
@@ -8160,13 +7939,27 @@
                         transit, false);
                 wtoken.updateReportedVisibilityLocked();
                 wtoken.waitingToShow = false;
-                wtoken.showAllWindowsLocked();
+                mAnimator.mAnimating |= wtoken.showAllWindowsLocked();
+                if (animLp != null) {
+                    int layer = -1;
+                    for (int j=0; j<wtoken.windows.size(); j++) {
+                        WindowState win = wtoken.windows.get(j);
+                        if (win.mAnimLayer > layer) {
+                            layer = win.mAnimLayer;
+                        }
+                    }
+                    if (topOpeningApp == null || layer > topOpeningLayer) {
+                        topOpeningApp = wtoken;
+                        topOpeningLayer = layer;
+                    }
+                }
             }
             NN = mClosingApps.size();
             for (i=0; i<NN; i++) {
                 AppWindowToken wtoken = mClosingApps.get(i);
                 if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                         "Now closing app" + wtoken);
+                wtoken.clearThumbnail();
                 wtoken.inPendingTransaction = false;
                 wtoken.animation = null;
                 setTokenVisibilityLocked(wtoken, animLp, false,
@@ -8179,7 +7972,47 @@
                 wtoken.allDrawn = true;
             }
 
+            if (mNextAppTransitionThumbnail != null && topOpeningApp != null
+                    && topOpeningApp.animation != null) {
+                // This thumbnail animation is very special, we need to have
+                // an extra surface with the thumbnail included with the animation.
+                Rect dirty = new Rect(0, 0, mNextAppTransitionThumbnail.getWidth(),
+                        mNextAppTransitionThumbnail.getHeight());
+                try {
+                    Surface surface = new Surface(mFxSession, Process.myPid(),
+                            "thumbnail anim", 0, dirty.width(), dirty.height(),
+                            PixelFormat.TRANSLUCENT, Surface.HIDDEN);
+                    topOpeningApp.thumbnail = surface;
+                    if (SHOW_TRANSACTIONS) Slog.i(TAG, "  THUMBNAIL "
+                            + surface + ": CREATE");
+                    Surface drawSurface = new Surface();
+                    drawSurface.copyFrom(surface);
+                    Canvas c = drawSurface.lockCanvas(dirty);
+                    c.drawBitmap(mNextAppTransitionThumbnail, 0, 0, null);
+                    drawSurface.unlockCanvasAndPost(c);
+                    drawSurface.release();
+                    topOpeningApp.thumbnailLayer = topOpeningLayer;
+                    Animation anim = createThumbnailAnimationLocked(transit, true, true);
+                    topOpeningApp.thumbnailAnimation = anim;
+                    anim.restrictDuration(MAX_ANIMATION_DURATION);
+                    anim.scaleCurrentDuration(mTransitionAnimationScale);
+                    topOpeningApp.thumbnailX = mNextAppTransitionStartX;
+                    topOpeningApp.thumbnailY = mNextAppTransitionStartY;
+                } catch (Surface.OutOfResourcesException e) {
+                    Slog.e(TAG, "Can't allocate thumbnail surface w=" + dirty.width()
+                            + " h=" + dirty.height(), e);
+                    topOpeningApp.clearThumbnail();
+                }
+            }
+
             mNextAppTransitionPackage = null;
+            mNextAppTransitionThumbnail = null;
+            if (mNextAppTransitionCallback != null) {
+                try {
+                    mNextAppTransitionCallback.sendResult(null);
+                } catch (RemoteException e) {
+                }
+            }
 
             mOpeningApps.clear();
             mClosingApps.clear();
@@ -8258,20 +8091,20 @@
         if (mLowerWallpaperTarget == null) {
             // Whoops, we don't need a special wallpaper animation.
             // Clear them out.
-            mInnerFields.mForceHiding = false;
+            mAnimator.mForceHiding = false;
             for (int i=mWindows.size()-1; i>=0; i--) {
                 WindowState w = mWindows.get(i);
                 if (w.mSurface != null) {
                     final WindowManager.LayoutParams attrs = w.mAttrs;
                     if (mPolicy.doesForceHide(w, attrs) && w.isVisibleLw()) {
                         if (DEBUG_FOCUS) Slog.i(TAG, "win=" + w + " force hides other windows");
-                        mInnerFields.mForceHiding = true;
+                        mAnimator.mForceHiding = true;
                     } else if (mPolicy.canBeForceHidden(w, attrs)) {
-                        if (!w.mAnimating) {
+                        if (!w.mWinAnimator.mAnimating) {
                             // We set the animation above so it
                             // is not yet running.
                             // TODO(cmautner): We lose the enter animation when this occurs.
-                            w.clearAnimation();
+                            w.mWinAnimator.clearAnimation();
                         }
                     }
                 }
@@ -8296,14 +8129,14 @@
             mInnerFields.mWallpaperMayChange = true;
         }
 
-        if (mInnerFields.mWindowAnimationBackgroundColor != 0) {
+        if (mAnimator.mWindowAnimationBackgroundColor != 0) {
             // If the window that wants black is the current wallpaper
             // target, then the black goes *below* the wallpaper so we
             // don't cause the wallpaper to suddenly disappear.
-            WindowState target = mInnerFields.mWindowAnimationBackground;
-            if (mWallpaperTarget == mInnerFields.mWindowAnimationBackground
-                    || mLowerWallpaperTarget == mInnerFields.mWindowAnimationBackground
-                    || mUpperWallpaperTarget == mInnerFields.mWindowAnimationBackground) {
+            WindowState target = mAnimator.mWindowAnimationBackground;
+            if (mWallpaperTarget == target
+                    || mLowerWallpaperTarget == target
+                    || mUpperWallpaperTarget == target) {
                 for (int i=0; i<mWindows.size(); i++) {
                     WindowState w = mWindows.get(i);
                     if (w.mIsWallpaper) {
@@ -8319,7 +8152,7 @@
             final int dh = mCurDisplayHeight;
             mWindowAnimationBackgroundSurface.show(dw, dh,
                     target.mAnimLayer - LAYER_OFFSET_DIM,
-                    mInnerFields.mWindowAnimationBackgroundColor);
+                    mAnimator.mWindowAnimationBackgroundColor);
         } else if (mWindowAnimationBackgroundSurface != null) {
             mWindowAnimationBackgroundSurface.hide();
         }
@@ -8422,206 +8255,6 @@
     /**
      * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method.
      *
-     * @param w WindowState whos Surface is being prepared.
-     * @param recoveringMemory true if the caller will reclaim surface memory on error.
-     */
-    public void prepareSurfaceLocked(final WindowState w, final boolean recoveringMemory) {
-        // XXX NOTE: The logic here could be improved.  We have
-        // the decision about whether to resize a window separated
-        // from whether to hide the surface.  This can cause us to
-        // resize a surface even if we are going to hide it.  You
-        // can see this by (1) holding device in landscape mode on
-        // home screen; (2) tapping browser icon (device will rotate
-        // to landscape; (3) tap home.  The wallpaper will be resized
-        // in step 2 but then immediately hidden, causing us to
-        // have to resize and then redraw it again in step 3.  It
-        // would be nice to figure out how to avoid this, but it is
-        // difficult because we do need to resize surfaces in some
-        // cases while they are hidden such as when first showing a
-        // window.
-        
-        if (w.mSurface == null) {
-            if (w.mOrientationChanging) {
-                if (DEBUG_ORIENTATION) {
-                    Slog.v(TAG, "Orientation change skips hidden " + w);
-                }
-                w.mOrientationChanging = false;
-            }
-            return;
-        }
-        
-        boolean displayed = false;
-
-        w.computeShownFrameLocked();
-
-        int width, height;
-        if ((w.mAttrs.flags & w.mAttrs.FLAG_SCALED) != 0) {
-            // for a scaled surface, we just want to use
-            // the requested size.
-            width  = w.mRequestedWidth;
-            height = w.mRequestedHeight;
-        } else {
-            width = w.mCompatFrame.width();
-            height = w.mCompatFrame.height();
-        }
-
-        if (width < 1) {
-            width = 1;
-        }
-        if (height < 1) {
-            height = 1;
-        }
-        final boolean surfaceResized = w.mSurfaceW != width || w.mSurfaceH != height;
-        if (surfaceResized) {
-            w.mSurfaceW = width;
-            w.mSurfaceH = height;
-        }
-
-        if (w.mSurfaceX != w.mShownFrame.left
-                || w.mSurfaceY != w.mShownFrame.top) {
-            try {
-                if (SHOW_TRANSACTIONS) logSurface(w,
-                        "POS " + w.mShownFrame.left
-                        + ", " + w.mShownFrame.top, null);
-                w.mSurfaceX = w.mShownFrame.left;
-                w.mSurfaceY = w.mShownFrame.top;
-                w.mSurface.setPosition(w.mShownFrame.left, w.mShownFrame.top);
-            } catch (RuntimeException e) {
-                Slog.w(TAG, "Error positioning surface of " + w
-                        + " pos=(" + w.mShownFrame.left
-                        + "," + w.mShownFrame.top + ")", e);
-                if (!recoveringMemory) {
-                    reclaimSomeSurfaceMemoryLocked(w, "position", true);
-                }
-            }
-        }
-
-        if (surfaceResized) {
-            try {
-                if (SHOW_TRANSACTIONS) logSurface(w,
-                        "SIZE " + width + "x" + height, null);
-                w.mSurfaceResized = true;
-                w.mSurface.setSize(width, height);
-            } catch (RuntimeException e) {
-                // If something goes wrong with the surface (such
-                // as running out of memory), don't take down the
-                // entire system.
-                Slog.e(TAG, "Error resizing surface of " + w
-                        + " size=(" + width + "x" + height + ")", e);
-                if (!recoveringMemory) {
-                    reclaimSomeSurfaceMemoryLocked(w, "size", true);
-                }
-            }
-        }
-
-        updateResizingWindows(w);
-
-        if (w.mAttachedHidden || !w.isReadyForDisplay()) {
-            if (!w.mLastHidden) {
-                //dump();
-                w.mLastHidden = true;
-                if (SHOW_TRANSACTIONS) logSurface(w,
-                        "HIDE (performLayout)", null);
-                if (w.mSurface != null) {
-                    w.mSurfaceShown = false;
-                    try {
-                        w.mSurface.hide();
-                    } catch (RuntimeException e) {
-                        Slog.w(TAG, "Exception hiding surface in " + w);
-                    }
-                }
-            }
-            // If we are waiting for this window to handle an
-            // orientation change, well, it is hidden, so
-            // doesn't really matter.  Note that this does
-            // introduce a potential glitch if the window
-            // becomes unhidden before it has drawn for the
-            // new orientation.
-            if (w.mOrientationChanging) {
-                w.mOrientationChanging = false;
-                if (DEBUG_ORIENTATION) Slog.v(TAG,
-                        "Orientation change skips hidden " + w);
-            }
-        } else if (w.mLastLayer != w.mAnimLayer
-                || w.mLastAlpha != w.mShownAlpha
-                || w.mLastDsDx != w.mDsDx
-                || w.mLastDtDx != w.mDtDx
-                || w.mLastDsDy != w.mDsDy
-                || w.mLastDtDy != w.mDtDy
-                || w.mLastHScale != w.mHScale
-                || w.mLastVScale != w.mVScale
-                || w.mLastHidden) {
-            displayed = true;
-            w.mLastAlpha = w.mShownAlpha;
-            w.mLastLayer = w.mAnimLayer;
-            w.mLastDsDx = w.mDsDx;
-            w.mLastDtDx = w.mDtDx;
-            w.mLastDsDy = w.mDsDy;
-            w.mLastDtDy = w.mDtDy;
-            w.mLastHScale = w.mHScale;
-            w.mLastVScale = w.mVScale;
-            if (SHOW_TRANSACTIONS) logSurface(w,
-                    "alpha=" + w.mShownAlpha + " layer=" + w.mAnimLayer
-                    + " matrix=[" + (w.mDsDx*w.mHScale)
-                    + "," + (w.mDtDx*w.mVScale)
-                    + "][" + (w.mDsDy*w.mHScale)
-                    + "," + (w.mDtDy*w.mVScale) + "]", null);
-            if (w.mSurface != null) {
-                try {
-                    w.mSurfaceAlpha = w.mShownAlpha;
-                    w.mSurface.setAlpha(w.mShownAlpha);
-                    w.mSurfaceLayer = w.mAnimLayer;
-                    w.mSurface.setLayer(w.mAnimLayer);
-                    w.mSurface.setMatrix(
-                            w.mDsDx*w.mHScale, w.mDtDx*w.mVScale,
-                            w.mDsDy*w.mHScale, w.mDtDy*w.mVScale);
-                } catch (RuntimeException e) {
-                    Slog.w(TAG, "Error updating surface in " + w, e);
-                    if (!recoveringMemory) {
-                        reclaimSomeSurfaceMemoryLocked(w, "update", true);
-                    }
-                }
-            }
-
-            if (w.mLastHidden && w.isDrawnLw()
-                    && !w.mReadyToShow) {
-                if (SHOW_TRANSACTIONS) logSurface(w,
-                        "SHOW (performLayout)", null);
-                if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + w
-                        + " during relayout");
-                if (showSurfaceRobustlyLocked(w)) {
-                    w.mHasDrawn = true;
-                    w.mLastHidden = false;
-                } else {
-                    w.mOrientationChanging = false;
-                }
-            }
-            if (w.mSurface != null) {
-                w.mToken.hasVisible = true;
-            }
-        } else {
-            displayed = true;
-        }
-
-        if (displayed) {
-            if (w.mOrientationChanging) {
-                if (!w.isDrawnLw()) {
-                    mInnerFields.mOrientationChangeComplete = false;
-                    if (DEBUG_ORIENTATION) Slog.v(TAG,
-                            "Orientation continue waiting for draw in " + w);
-                } else {
-                    w.mOrientationChanging = false;
-                    if (DEBUG_ORIENTATION) Slog.v(TAG,
-                            "Orientation change complete in " + w);
-                }
-            }
-            w.mToken.hasVisible = true;
-        }
-    }
-
-    /**
-     * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method.
-     *
      * @param w WindowState this method is applied to.
      * @param currentTime The time which animations use for calculating transitions.
      * @param innerDw Width of app window.
@@ -8656,133 +8289,28 @@
         boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn();
         if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) {
             // This window completely covers everything behind it,
-            // so we want to leave all of them as unblurred (for
+            // so we want to leave all of them as undimmed (for
             // performance reasons).
             mInnerFields.mObscured = true;
-        } else if (canBeSeen && (attrFlags & (FLAG_BLUR_BEHIND | FLAG_DIM_BEHIND)) != 0) {
-            if (localLOGV) Slog.v(TAG, "Win " + w
-                    + ": blurring=" + mInnerFields.mBlurring
-                    + " obscured=" + mInnerFields.mObscured);
-            if ((attrFlags&FLAG_DIM_BEHIND) != 0) {
-                if (!mInnerFields.mDimming) {
-                    //Slog.i(TAG, "DIM BEHIND: " + w);
-                    mInnerFields.mDimming = true;
-                    if (mDimAnimator == null) {
-                        mDimAnimator = new DimAnimator(mFxSession);
-                    }
-                    if (attrs.type == WindowManager.LayoutParams.TYPE_BOOT_PROGRESS) {
-                        mDimAnimator.show(mCurDisplayWidth, mCurDisplayHeight);
-                    } else {
-                        mDimAnimator.show(innerDw, innerDh);
-                    }
+        } else if (canBeSeen && (attrFlags & FLAG_DIM_BEHIND) != 0) {
+            if (localLOGV) Slog.v(TAG, "Win " + w + " obscured=" + mInnerFields.mObscured);
+            if (!mInnerFields.mDimming) {
+                //Slog.i(TAG, "DIM BEHIND: " + w);
+                mInnerFields.mDimming = true;
+                if (mDimAnimator == null) {
+                    mDimAnimator = new DimAnimator(mFxSession);
+                }
+                if (attrs.type == WindowManager.LayoutParams.TYPE_BOOT_PROGRESS) {
+                    mDimAnimator.show(mCurDisplayWidth, mCurDisplayHeight);
+                } else {
                     mDimAnimator.show(innerDw, innerDh);
-                    mDimAnimator.updateParameters(mContext.getResources(),
-                            w, currentTime);
                 }
-            }
-            if ((attrFlags & FLAG_BLUR_BEHIND) != 0) {
-                if (!mInnerFields.mBlurring) {
-                    //Slog.i(TAG, "BLUR BEHIND: " + w);
-                    mInnerFields.mBlurring = true;
-                    if (mBlurSurface == null) {
-                        try {
-                            mBlurSurface = new Surface(mFxSession, 0,
-                                    "BlurSurface",
-                                    -1, 16, 16,
-                                    PixelFormat.OPAQUE,
-                                    Surface.FX_SURFACE_BLUR);
-                        } catch (Exception e) {
-                            Slog.e(TAG, "Exception creating Blur surface", e);
-                        }
-                        if (SHOW_TRANSACTIONS) Slog.i(TAG, "  BLUR "
-                                + mBlurSurface + ": CREATE");
-                    }
-                    final int dw = mCurDisplayWidth;
-                    final int dh = mCurDisplayHeight;
-                    if (mBlurSurface != null) {
-                        if (SHOW_TRANSACTIONS) Slog.i(TAG, "  BLUR "
-                                + mBlurSurface + ": pos=(0,0) (" +
-                                dw + "x" + dh + "), layer=" + (w.mAnimLayer-1));
-                        mBlurSurface.setPosition(0, 0);
-                        mBlurSurface.setSize(dw, dh);
-                        mBlurSurface.setLayer(w.mAnimLayer-LAYER_OFFSET_BLUR);
-                        if (!mBlurShown) {
-                            try {
-                                if (SHOW_TRANSACTIONS) Slog.i(TAG, "  BLUR "
-                                        + mBlurSurface + ": SHOW");
-                                mBlurSurface.show();
-                            } catch (RuntimeException e) {
-                                Slog.w(TAG, "Failure showing blur surface", e);
-                            }
-                            mBlurShown = true;
-                        }
-                    }
-                }
+                mDimAnimator.updateParameters(mContext.getResources(),
+                        w, currentTime);
             }
         }
     }
 
-    private final int performAnimationsLocked(long currentTime, int dw, int dh,
-            int innerDw, int innerDh) {
-        ++mTransactionSequence;
-
-        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: seq="
-                + mTransactionSequence + " mAnimating="
-                + mInnerFields.mAnimating);
-
-        mInnerFields.mTokenMayBeDrawn = false;
-        mInnerFields.mWallpaperMayChange = false;
-        mInnerFields.mForceHiding = false;
-        mInnerFields.mDetachedWallpaper = null;
-        mInnerFields.mWindowAnimationBackground = null;
-        mInnerFields.mWindowAnimationBackgroundColor = 0;
-
-        int changes = updateWindowsAndWallpaperLocked(currentTime, dw, dh, innerDw, innerDh);
-
-        if (mInnerFields.mTokenMayBeDrawn) {
-            changes |= testTokenMayBeDrawnLocked();
-        }
-
-        // If we are ready to perform an app transition, check through
-        // all of the app tokens to be shown and see if they are ready
-        // to go.
-        if (mAppTransitionReady) {
-            changes |= handleAppTransitionReadyLocked();
-        }
-
-        mInnerFields.mAdjResult = 0;
-
-        if (!mInnerFields.mAnimating && mAppTransitionRunning) {
-            // We have finished the animation of an app transition.  To do
-            // this, we have delayed a lot of operations like showing and
-            // hiding apps, moving apps in Z-order, etc.  The app token list
-            // reflects the correct Z-order, but the window list may now
-            // be out of sync with it.  So here we will just rebuild the
-            // entire app window list.  Fun!
-            changes |= handleAnimatingStoppedAndTransitionLocked();
-        }
-
-        if (mInnerFields.mWallpaperForceHidingChanged && changes == 0 && !mAppTransitionReady) {
-            // At this point, there was a window with a wallpaper that
-            // was force hiding other windows behind it, but now it
-            // is going away.  This may be simple -- just animate
-            // away the wallpaper and its window -- or it may be
-            // hard -- the wallpaper now needs to be shown behind
-            // something that was hidden.
-            changes |= animateAwayWallpaperLocked();
-        }
-
-        changes |= testWallpaperAndBackgroundLocked();
-
-        if (mLayoutNeeded) {
-            changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT;
-        }
-
-        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** ANIM STEP: changes=0x"
-                + Integer.toHexString(changes));
-        return changes;
-    }
-
     // "Something has changed!  Let's make it correct now."
     private final void performLayoutAndPlaceSurfacesLockedInner(
             boolean recoveringMemory) {
@@ -8820,7 +8348,7 @@
         mInnerFields.mScreenBrightness = -1;
         mInnerFields.mButtonBrightness = -1;
         boolean focusDisplayed = false;
-        mInnerFields.mAnimating = false;
+        mAnimator.mAnimating = false;
         boolean createWatermark = false;
 
         if (mFxSession == null) {
@@ -8855,12 +8383,15 @@
                     break;
                 }
 
+                if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("On entry to LockedInner");
+
                 if ((mPendingLayoutChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
                     if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) {
                         assignLayersLocked();
                         mLayoutNeeded = true;
                     }
                 }
+
                 if ((mPendingLayoutChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) {
                     if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout");
                     if (updateOrientationFromAppTokensLocked(true)) {
@@ -8868,6 +8399,7 @@
                         mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
                     }
                 }
+
                 if ((mPendingLayoutChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) {
                     mLayoutNeeded = true;
                 }
@@ -8882,6 +8414,7 @@
                 // FIRST AND ONE HALF LOOP: Make WindowManagerPolicy think
                 // it is animating.
                 mPendingLayoutChanges = 0;
+                if (DEBUG_LAYOUT_REPEATS)  debugLayoutRepeats("loop number " + mLayoutRepeatCount);
                 mPolicy.beginAnimationLw(dw, dh);
                 for (i = mWindows.size() - 1; i >= 0; i--) {
                     WindowState w = mWindows.get(i);
@@ -8890,13 +8423,12 @@
                     }
                 }
                 mPendingLayoutChanges |= mPolicy.finishAnimationLw();
-                
+                if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after finishAnimationLw");
             } while (mPendingLayoutChanges != 0);
 
             final boolean someoneLosingFocus = !mLosingFocus.isEmpty();
 
             mInnerFields.mObscured = false;
-            mInnerFields.mBlurring = false;
             mInnerFields.mDimming = false;
             mInnerFields.mSyswin = false;
             
@@ -8931,9 +8463,59 @@
             Surface.closeTransaction();
         }
 
+        // If we are ready to perform an app transition, check through
+        // all of the app tokens to be shown and see if they are ready
+        // to go.
+        if (mAppTransitionReady) {
+            mPendingLayoutChanges |= handleAppTransitionReadyLocked();
+            if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after handleAppTransitionReadyLocked");
+        }
+
+        mInnerFields.mAdjResult = 0;
+
+        if (!mAnimator.mAnimating && mAppTransitionRunning) {
+            // We have finished the animation of an app transition.  To do
+            // this, we have delayed a lot of operations like showing and
+            // hiding apps, moving apps in Z-order, etc.  The app token list
+            // reflects the correct Z-order, but the window list may now
+            // be out of sync with it.  So here we will just rebuild the
+            // entire app window list.  Fun!
+            mPendingLayoutChanges |= handleAnimatingStoppedAndTransitionLocked();
+            if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after handleAnimStopAndXitionLock");
+        }
+
+        if (mInnerFields.mWallpaperForceHidingChanged && mPendingLayoutChanges == 0 &&
+                !mAppTransitionReady) {
+            // At this point, there was a window with a wallpaper that
+            // was force hiding other windows behind it, but now it
+            // is going away.  This may be simple -- just animate
+            // away the wallpaper and its window -- or it may be
+            // hard -- the wallpaper now needs to be shown behind
+            // something that was hidden.
+            mPendingLayoutChanges |= animateAwayWallpaperLocked();
+            if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after animateAwayWallpaperLocked");
+        }
+
+        mPendingLayoutChanges |= testWallpaperAndBackgroundLocked();
+        if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after testWallpaperAndBackgroundLocked");
+
+        if (mLayoutNeeded) {
+            mPendingLayoutChanges |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT;
+            if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("mLayoutNeeded");
+        }
+
+        final int N = mWindows.size();
+        for (i=N-1; i>=0; i--) {
+            WindowState w = mWindows.get(i);
+            // TODO(cmautner): Can this move up to the loop at the end of try/catch above?
+            updateResizingWindows(w);
+        }
+
         // Update animations of all applications, including those
         // associated with exiting/removed apps
-        animateAndUpdateSurfaces(currentTime, dw, dh, innerDw, innerDh, recoveringMemory);
+        mAnimator.animate();
+        mPendingLayoutChanges |= mAnimator.mPendingLayoutChanges;
+        if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after animate()");
 
         if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                 "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
@@ -9026,6 +8608,7 @@
                 // Make sure there is no animation running on this token,
                 // so any windows associated with it will be removed as
                 // soon as their animations are complete
+                token.clearAnimation();
                 token.animation = null;
                 token.animating = false;
                 if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
@@ -9035,9 +8618,7 @@
             }
         }
 
-        boolean needRelayout = false;
-
-        if (!mInnerFields.mAnimating && mAppTransitionRunning) {
+        if (!mAnimator.mAnimating && mAppTransitionRunning) {
             // We have finished the animation of an app transition.  To do
             // this, we have delayed a lot of operations like showing and
             // hiding apps, moving apps in Z-order, etc.  The app token list
@@ -9045,7 +8626,7 @@
             // be out of sync with it.  So here we will just rebuild the
             // entire app window list.  Fun!
             mAppTransitionRunning = false;
-            needRelayout = true;
+            mLayoutNeeded = true;
             rebuildAppWindowListLocked();
             assignLayersLocked();
             // Clear information about apps that were moving.
@@ -9056,19 +8637,10 @@
             mH.sendEmptyMessage(H.REPORT_LOSING_FOCUS);
         }
         if (wallpaperDestroyed) {
-            needRelayout = adjustWallpaperWindowsLocked() != 0;
+            mLayoutNeeded |= adjustWallpaperWindowsLocked() != 0;
         }
-        if ((mPendingLayoutChanges & (
-                WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER |
-                ADJUST_WALLPAPER_LAYERS_CHANGED |
-                WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG |
-                WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT)) != 0) {
-            needRelayout = true;
-        }
-        if (needRelayout) {
-            requestTraversalLocked();
-        } else if (mInnerFields.mAnimating) {
-            scheduleAnimationLocked();
+        if (mPendingLayoutChanges != 0) {
+            mLayoutNeeded = true;
         }
 
         // Finally update all input windows now that the window changes have stabilized.
@@ -9111,7 +8683,7 @@
             }
         }
 
-        if (mInnerFields.mOrientationChangeComplete && !needRelayout &&
+        if (mInnerFields.mOrientationChangeComplete && !mLayoutNeeded &&
                 !mInnerFields.mUpdateRotation) {
             checkDrawnWindowsLocked();
         }
@@ -9333,6 +8905,7 @@
                 TAG, "Changing focus from " + mCurrentFocus + " to " + newFocus);
             final WindowState oldFocus = mCurrentFocus;
             mCurrentFocus = newFocus;
+            mAnimator.setCurrentFocus(mCurrentFocus);
             mLosingFocus.remove(newFocus);
             int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus);
 
@@ -9464,6 +9037,7 @@
         if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
             mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET;
             mNextAppTransitionPackage = null;
+            mNextAppTransitionThumbnail = null;
             mAppTransitionReady = true;
         }
 
@@ -9473,16 +9047,16 @@
         }
 
         if (CUSTOM_SCREEN_ROTATION) {
-            if (mScreenRotationAnimation != null) {
-                mScreenRotationAnimation.kill();
-                mScreenRotationAnimation = null;
+            if (mAnimator.mScreenRotationAnimation != null) {
+                mAnimator.mScreenRotationAnimation.kill();
+                mAnimator.mScreenRotationAnimation = null;
             }
-            if (mScreenRotationAnimation == null) {
-                mScreenRotationAnimation = new ScreenRotationAnimation(mContext,
+            if (mAnimator.mScreenRotationAnimation == null) {
+                mAnimator.mScreenRotationAnimation = new ScreenRotationAnimation(mContext,
                         mFxSession, inTransaction, mCurDisplayWidth, mCurDisplayHeight,
                         mDisplay.getRotation());
             }
-            if (!mScreenRotationAnimation.hasScreenshot()) {
+            if (!mAnimator.mScreenRotationAnimation.hasScreenshot()) {
                 Surface.freezeDisplay(0);
             }
         } else {
@@ -9507,20 +9081,20 @@
 
         boolean updateRotation = false;
         
-        if (CUSTOM_SCREEN_ROTATION && mScreenRotationAnimation != null
-                && mScreenRotationAnimation.hasScreenshot()) {
+        if (CUSTOM_SCREEN_ROTATION && mAnimator.mScreenRotationAnimation != null
+                && mAnimator.mScreenRotationAnimation.hasScreenshot()) {
             if (DEBUG_ORIENTATION) Slog.i(TAG, "**** Dismissing screen rotation animation");
-            if (mScreenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION,
+            if (mAnimator.mScreenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION,
                     mTransitionAnimationScale, mCurDisplayWidth, mCurDisplayHeight)) {
                 scheduleAnimationLocked();
             } else {
-                mScreenRotationAnimation = null;
+                mAnimator.mScreenRotationAnimation = null;
                 updateRotation = true;
             }
         } else {
-            if (mScreenRotationAnimation != null) {
-                mScreenRotationAnimation.kill();
-                mScreenRotationAnimation = null;
+            if (mAnimator.mScreenRotationAnimation != null) {
+                mAnimator.mScreenRotationAnimation.kill();
+                mAnimator.mScreenRotationAnimation = null;
             }
             updateRotation = true;
         }
@@ -9987,8 +9561,7 @@
             }
             pw.print("  mSystemBooted="); pw.print(mSystemBooted);
                     pw.print(" mDisplayEnabled="); pw.println(mDisplayEnabled);
-            pw.print("  mLayoutNeeded="); pw.print(mLayoutNeeded);
-                    pw.print(" mBlurShown="); pw.println(mBlurShown);
+            pw.print("  mLayoutNeeded="); pw.println(mLayoutNeeded);
             if (mDimAnimator != null) {
                 pw.println("  mDimAnimator:");
                 mDimAnimator.printTo("    ", pw);
@@ -10004,9 +9577,9 @@
             pw.print("  mLastWindowForcedOrientation"); pw.print(mLastWindowForcedOrientation);
                     pw.print(" mForcedAppOrientation="); pw.println(mForcedAppOrientation);
             pw.print("  mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount);
-            if (mScreenRotationAnimation != null) {
+            if (mAnimator.mScreenRotationAnimation != null) {
                 pw.println("  mScreenRotationAnimation:");
-                mScreenRotationAnimation.printTo("    ", pw);
+                mAnimator.mScreenRotationAnimation.printTo("    ", pw);
             }
             pw.print("  mWindowAnimationScale="); pw.print(mWindowAnimationScale);
                     pw.print(" mTransitionWindowAnimationScale="); pw.print(mTransitionAnimationScale);
@@ -10025,6 +9598,12 @@
                     pw.print(" mNextAppTransitionExit=0x");
                     pw.print(Integer.toHexString(mNextAppTransitionExit));
             }
+            if (mNextAppTransitionThumbnail != null) {
+                pw.print("  mNextAppTransitionThumbnail=");
+                    pw.print(mNextAppTransitionThumbnail);
+                    pw.print(" mNextAppTransitionStartX="); pw.print(mNextAppTransitionStartX);
+                    pw.print(" mNextAppTransitionStartY="); pw.println(mNextAppTransitionStartY);
+            }
             pw.print("  mStartingIconInTransition="); pw.print(mStartingIconInTransition);
                     pw.print(", mSkipAppTransitionAnimation="); pw.println(mSkipAppTransitionAnimation);
         }
@@ -10192,4 +9771,11 @@
     public interface OnHardKeyboardStatusChangeListener {
         public void onHardKeyboardStatusChange(boolean available, boolean enabled);
     }
+
+    void debugLayoutRepeats(final String msg) {
+        if (mLayoutRepeatCount >= LAYOUT_REPEAT_THRESHOLD) {
+            Slog.v(TAG, "Layouts looping: " + msg);
+            Slog.v(TAG, "mPendingLayoutChanges = 0x" + Integer.toHexString(mPendingLayoutChanges));
+        }
+    }
 }
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index 57d0374..615cd80 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -45,7 +45,6 @@
 import android.view.WindowManager;
 import android.view.WindowManagerPolicy;
 import android.view.WindowManager.LayoutParams;
-import android.view.animation.Animation;
 import android.view.animation.Transformation;
 
 import java.io.PrintWriter;
@@ -91,7 +90,6 @@
     boolean mAttachedHidden;    // is our parent window hidden?
     boolean mLastHidden;        // was this window last hidden?
     boolean mWallpaperVisible;  // for wallpaper, what was last vis report?
-    boolean mWasAnimating;      // Were we animating going into the most recent animation step?
 
     /**
      * The window size that was requested by the application.  These are in
@@ -110,9 +108,9 @@
     boolean mTurnOnScreen;
 
     int mLayoutSeq = -1;
-    
+
     Configuration mConfiguration = null;
-    
+
     /**
      * Actual frame shown on-screen (may be modified by animation).  These
      * are in the screen's coordinate space (WITH the compatibility scale
@@ -212,15 +210,6 @@
     // an enter animation.
     boolean mEnterAnimationPending;
 
-    // Currently running animation.
-    boolean mAnimating;
-    boolean mLocalAnimating;
-    Animation mAnimation;
-    boolean mAnimationIsEntrance;
-    boolean mHasTransformation;
-    boolean mHasLocalTransformation;
-    final Transformation mTransformation = new Transformation();
-
     // If a window showing a wallpaper: the requested offset for the
     // wallpaper; if a wallpaper window: the currently applied offset.
     float mWallpaperX = -1;
@@ -305,6 +294,8 @@
     int mAnimDw;
     int mAnimDh;
 
+    final WindowStateAnimator mWinAnimator;
+
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState attachedWindow, int seq, WindowManager.LayoutParams a,
            int viewVisibility) {
@@ -333,6 +324,7 @@
             mBaseLayer = 0;
             mSubLayer = 0;
             mInputWindowHandle = null;
+            mWinAnimator = null;
             return;
         }
         mDeathRecipient = deathRecipient;
@@ -369,9 +361,11 @@
             mIsFloatingLayer = mIsImWindow || mIsWallpaper;
         }
 
+        mWinAnimator = new WindowStateAnimator(service, this, mAttachedWindow);
+
         WindowState appWin = this;
         while (appWin.mAttachedWindow != null) {
-            appWin = mAttachedWindow;
+            appWin = appWin.mAttachedWindow;
         }
         WindowToken appToken = appWin.mToken;
         while (appToken.appWindowToken == null) {
@@ -405,6 +399,7 @@
         mSession.windowAddedLocked();
     }
 
+    @Override
     public void computeFrameLw(Rect pf, Rect df, Rect cf, Rect vf) {
         mHaveFrame = true;
 
@@ -547,38 +542,47 @@
         }
     }
 
+    @Override
     public Rect getFrameLw() {
         return mFrame;
     }
 
+    @Override
     public RectF getShownFrameLw() {
         return mShownFrame;
     }
 
+    @Override
     public Rect getDisplayFrameLw() {
         return mDisplayFrame;
     }
 
+    @Override
     public Rect getContentFrameLw() {
         return mContentFrame;
     }
 
+    @Override
     public Rect getVisibleFrameLw() {
         return mVisibleFrame;
     }
 
+    @Override
     public boolean getGivenInsetsPendingLw() {
         return mGivenInsetsPending;
     }
 
+    @Override
     public Rect getGivenContentInsetsLw() {
         return mGivenContentInsets;
     }
 
+    @Override
     public Rect getGivenVisibleInsetsLw() {
         return mGivenVisibleInsets;
     }
 
+    @Override
     public WindowManager.LayoutParams getAttrs() {
         return mAttrs;
     }
@@ -632,37 +636,6 @@
         return mAppToken != null ? mAppToken.firstWindowDrawn : false;
     }
 
-    public void setAnimation(Animation anim) {
-        if (WindowManagerService.localLOGV) Slog.v(
-            WindowManagerService.TAG, "Setting animation in " + this + ": " + anim);
-        mAnimating = false;
-        mLocalAnimating = false;
-        mAnimation = anim;
-        mAnimation.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION);
-        mAnimation.scaleCurrentDuration(mService.mWindowAnimationScale);
-    }
-
-    public void clearAnimation() {
-        if (mAnimation != null) {
-            mAnimating = true;
-            mLocalAnimating = false;
-            mAnimation.cancel();
-            mAnimation = null;
-        }
-    }
-
-    // TODO: Fix and call finishExit() instead of cancelExitAnimationForNextAnimationLocked()
-    // for avoiding the code duplication.
-    void cancelExitAnimationForNextAnimationLocked() {
-        if (!mExiting) return;
-        if (mAnimation != null) {
-            mAnimation.cancel();
-            mAnimation = null;
-            destroySurfaceLocked();
-        }
-        mExiting = false;
-    }
-
     Surface createSurfaceLocked() {
         if (mSurface == null) {
             mReportDestroySurface = false;
@@ -927,19 +900,21 @@
                     + (mAppToken != null ? mAppToken.hiddenRequested : false)
                     + " tok.hidden="
                     + (mAppToken != null ? mAppToken.hidden : false)
-                    + " animating=" + mAnimating
+                    + " animating=" + mWinAnimator.mAnimating
                     + " tok animating="
                     + (mAppToken != null ? mAppToken.animating : false));
             if (!mService.showSurfaceRobustlyLocked(this)) {
                 return false;
             }
+            
+            mService.enableScreenIfNeededLocked();
+
+            mService.applyEnterAnimationLocked(this);
+
             mLastAlpha = -1;
             mHasDrawn = true;
             mLastHidden = false;
             mReadyToShow = false;
-            mService.enableScreenIfNeededLocked();
-
-            mService.applyEnterAnimationLocked(this);
 
             int i = mChildWindows.size();
             while (i > 0) {
@@ -972,206 +947,23 @@
                     // will do an animation to reveal it from behind the
                     // starting window, so there is no need for it to also
                     // be doing its own stuff.
-                    if (mAnimation != null) {
-                        mAnimation.cancel();
-                        mAnimation = null;
+                    if (mWinAnimator.mAnimation != null) {
+                        mWinAnimator.mAnimation.cancel();
+                        mWinAnimator.mAnimation = null;
                         // Make sure we clean up the animation.
-                        mAnimating = true;
+                        mWinAnimator.mAnimating = true;
                     }
                     mService.mFinishedStarting.add(mAppToken);
                     mService.mH.sendEmptyMessage(H.FINISHED_STARTING);
                 }
                 mAppToken.updateReportedVisibilityLocked();
             }
+        } else {
+            return false;
         }
         return true;
     }
 
-    private boolean stepAnimation(long currentTime) {
-        if ((mAnimation == null) || !mLocalAnimating) {
-            return false;
-        }
-        mTransformation.clear();
-        final boolean more = mAnimation.getTransformation(currentTime, mTransformation);
-        if (WindowManagerService.DEBUG_ANIM) Slog.v(
-            WindowManagerService.TAG, "Stepped animation in " + this +
-            ": more=" + more + ", xform=" + mTransformation);
-        return more;
-    }
-
-    // This must be called while inside a transaction.  Returns true if
-    // there is more animation to run.
-    boolean stepAnimationLocked(long currentTime) {
-        // Save the animation state as it was before this step so WindowManagerService can tell if
-        // we just started or just stopped animating by comparing mWasAnimating with isAnimating().
-        mWasAnimating = mAnimating;
-        if (!mService.mDisplayFrozen && mService.mPolicy.isScreenOnFully()) {
-            // We will run animations as long as the display isn't frozen.
-
-            if (isDrawnLw() && mAnimation != null) {
-                mHasTransformation = true;
-                mHasLocalTransformation = true;
-                if (!mLocalAnimating) {
-                    if (WindowManagerService.DEBUG_ANIM) Slog.v(
-                        WindowManagerService.TAG, "Starting animation in " + this +
-                        " @ " + currentTime + ": ww=" + mFrame.width() +
-                        " wh=" + mFrame.height() +
-                        " dw=" + mAnimDw + " dh=" + mAnimDh +
-                        " scale=" + mService.mWindowAnimationScale);
-                    mAnimation.initialize(mFrame.width(), mFrame.height(), mAnimDw, mAnimDh);
-                    mAnimation.setStartTime(currentTime);
-                    mLocalAnimating = true;
-                    mAnimating = true;
-                }
-                if ((mAnimation != null) && mLocalAnimating) {
-                    if (stepAnimation(currentTime)) {
-                        return true;
-                    }
-                }
-                if (WindowManagerService.DEBUG_ANIM) Slog.v(
-                    WindowManagerService.TAG, "Finished animation in " + this +
-                    " @ " + currentTime);
-                //WindowManagerService.this.dump();
-            }
-            mHasLocalTransformation = false;
-            if ((!mLocalAnimating || mAnimationIsEntrance) && mAppToken != null
-                    && mAppToken.animation != null) {
-                // When our app token is animating, we kind-of pretend like
-                // we are as well.  Note the mLocalAnimating mAnimationIsEntrance
-                // part of this check means that we will only do this if
-                // our window is not currently exiting, or it is not
-                // locally animating itself.  The idea being that one that
-                // is exiting and doing a local animation should be removed
-                // once that animation is done.
-                mAnimating = true;
-                mHasTransformation = true;
-                mTransformation.clear();
-                return false;
-            } else if (mHasTransformation) {
-                // Little trick to get through the path below to act like
-                // we have finished an animation.
-                mAnimating = true;
-            } else if (isAnimating()) {
-                mAnimating = true;
-            }
-        } else if (mAnimation != null) {
-            // If the display is frozen, and there is a pending animation,
-            // clear it and make sure we run the cleanup code.
-            mAnimating = true;
-            mLocalAnimating = true;
-            mAnimation.cancel();
-            mAnimation = null;
-        }
-
-        if (!mAnimating && !mLocalAnimating) {
-            return false;
-        }
-
-        if (WindowManagerService.DEBUG_ANIM) Slog.v(
-            WindowManagerService.TAG, "Animation done in " + this + ": exiting=" + mExiting
-            + ", reportedVisible="
-            + (mAppToken != null ? mAppToken.reportedVisible : false));
-
-        mAnimating = false;
-        mLocalAnimating = false;
-        if (mAnimation != null) {
-            mAnimation.cancel();
-            mAnimation = null;
-        }
-        if (mService.mWindowDetachedWallpaper == this) {
-            mService.mWindowDetachedWallpaper = null;
-        }
-        mAnimLayer = mLayer;
-        if (mIsImWindow) {
-            mAnimLayer += mService.mInputMethodAnimLayerAdjustment;
-        } else if (mIsWallpaper) {
-            mAnimLayer += mService.mWallpaperAnimLayerAdjustment;
-        }
-        if (WindowManagerService.DEBUG_LAYERS) Slog.v(WindowManagerService.TAG, "Stepping win " + this
-                + " anim layer: " + mAnimLayer);
-        mHasTransformation = false;
-        mHasLocalTransformation = false;
-        if (mPolicyVisibility != mPolicyVisibilityAfterAnim) {
-            if (DEBUG_VISIBILITY) {
-                Slog.v(WindowManagerService.TAG, "Policy visibility changing after anim in " + this + ": "
-                        + mPolicyVisibilityAfterAnim);
-            }
-            mPolicyVisibility = mPolicyVisibilityAfterAnim;
-            mService.mLayoutNeeded = true;
-            if (!mPolicyVisibility) {
-                if (mService.mCurrentFocus == this) {
-                    mService.mFocusMayChange = true;
-                }
-                // Window is no longer visible -- make sure if we were waiting
-                // for it to be displayed before enabling the display, that
-                // we allow the display to be enabled now.
-                mService.enableScreenIfNeededLocked();
-            }
-        }
-        mTransformation.clear();
-        if (mHasDrawn
-                && mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING
-                && mAppToken != null
-                && mAppToken.firstWindowDrawn
-                && mAppToken.startingData != null) {
-            if (WindowManagerService.DEBUG_STARTING_WINDOW) Slog.v(WindowManagerService.TAG, "Finish starting "
-                    + mToken + ": first real window done animating");
-            mService.mFinishedStarting.add(mAppToken);
-            mService.mH.sendEmptyMessage(H.FINISHED_STARTING);
-        }
-
-        finishExit();
-        mService.mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
-
-        if (mAppToken != null) {
-            mAppToken.updateReportedVisibilityLocked();
-        }
-
-        return false;
-    }
-
-    void finishExit() {
-        if (WindowManagerService.DEBUG_ANIM) Slog.v(
-                WindowManagerService.TAG, "finishExit in " + this
-                + ": exiting=" + mExiting
-                + " remove=" + mRemoveOnExit
-                + " windowAnimating=" + isWindowAnimating());
-
-        final int N = mChildWindows.size();
-        for (int i=0; i<N; i++) {
-            mChildWindows.get(i).finishExit();
-        }
-
-        if (!mExiting) {
-            return;
-        }
-
-        if (isWindowAnimating()) {
-            return;
-        }
-
-        if (WindowManagerService.localLOGV) Slog.v(
-                WindowManagerService.TAG, "Exit animation finished in " + this
-                + ": remove=" + mRemoveOnExit);
-        if (mSurface != null) {
-            mService.mDestroySurface.add(this);
-            mDestroying = true;
-            if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(this, "HIDE (finishExit)", null);
-            mSurfaceShown = false;
-            try {
-                mSurface.hide();
-            } catch (RuntimeException e) {
-                Slog.w(WindowManagerService.TAG, "Error hiding surface in " + this, e);
-            }
-            mLastHidden = true;
-        }
-        mExiting = false;
-        if (mRemoveOnExit) {
-            mService.mPendingRemove.add(this);
-            mRemoveOnExit = false;
-        }
-    }
-
     boolean isIdentityMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
         if (dsdx < .99999f || dsdx > 1.00001f) return false;
         if (dtdy < .99999f || dtdy > 1.00001f) return false;
@@ -1190,10 +982,10 @@
     }
 
     void computeShownFrameLocked() {
-        final boolean selfTransformation = mHasLocalTransformation;
+        final boolean selfTransformation = mWinAnimator.mHasLocalTransformation;
         Transformation attachedTransformation =
-                (mAttachedWindow != null && mAttachedWindow.mHasLocalTransformation)
-                ? mAttachedWindow.mTransformation : null;
+                (mAttachedWindow != null && mAttachedWindow.mWinAnimator.mHasLocalTransformation)
+                ? mAttachedWindow.mWinAnimator.mTransformation : null;
         Transformation appTransformation =
                 (mAppToken != null && mAppToken.hasTransformation)
                 ? mAppToken.transformation : null;
@@ -1202,10 +994,10 @@
         // are currently targeting.
         if (mAttrs.type == TYPE_WALLPAPER && mService.mLowerWallpaperTarget == null
                 && mService.mWallpaperTarget != null) {
-            if (mService.mWallpaperTarget.mHasLocalTransformation &&
-                    mService.mWallpaperTarget.mAnimation != null &&
-                    !mService.mWallpaperTarget.mAnimation.getDetachWallpaper()) {
-                attachedTransformation = mService.mWallpaperTarget.mTransformation;
+            if (mService.mWallpaperTarget.mWinAnimator.mHasLocalTransformation &&
+                    mService.mWallpaperTarget.mWinAnimator.mAnimation != null &&
+                    !mService.mWallpaperTarget.mWinAnimator.mAnimation.getDetachWallpaper()) {
+                attachedTransformation = mService.mWallpaperTarget.mWinAnimator.mTransformation;
                 if (WindowManagerService.DEBUG_WALLPAPER && attachedTransformation != null) {
                     Slog.v(WindowManagerService.TAG, "WP target attached xform: " + attachedTransformation);
                 }
@@ -1221,8 +1013,8 @@
             }
         }
 
-        final boolean screenAnimation = mService.mScreenRotationAnimation != null
-                && mService.mScreenRotationAnimation.isAnimating();
+        final boolean screenAnimation = mService.mAnimator.mScreenRotationAnimation != null
+                && mService.mAnimator.mScreenRotationAnimation.isAnimating();
         if (selfTransformation || attachedTransformation != null
                 || appTransformation != null || screenAnimation) {
             // cache often used attributes locally
@@ -1251,7 +1043,7 @@
             }
             tmpMatrix.postScale(mGlobalScale, mGlobalScale);
             if (selfTransformation) {
-                tmpMatrix.postConcat(mTransformation.getMatrix());
+                tmpMatrix.postConcat(mWinAnimator.mTransformation.getMatrix());
             }
             tmpMatrix.postTranslate(frame.left + mXOffset, frame.top + mYOffset);
             if (attachedTransformation != null) {
@@ -1262,7 +1054,7 @@
             }
             if (screenAnimation) {
                 tmpMatrix.postConcat(
-                        mService.mScreenRotationAnimation.getEnterTransformation().getMatrix());
+                        mService.mAnimator.mScreenRotationAnimation.getEnterTransformation().getMatrix());
             }
 
             // "convert" it into SurfaceFlinger's format
@@ -1295,7 +1087,7 @@
                             && x == frame.left && y == frame.top))) {
                 //Slog.i(TAG, "Applying alpha transform");
                 if (selfTransformation) {
-                    mShownAlpha *= mTransformation.getAlpha();
+                    mShownAlpha *= mWinAnimator.mTransformation.getAlpha();
                 }
                 if (attachedTransformation != null) {
                     mShownAlpha *= attachedTransformation.getAlpha();
@@ -1305,7 +1097,7 @@
                 }
                 if (screenAnimation) {
                     mShownAlpha *=
-                        mService.mScreenRotationAnimation.getEnterTransformation().getAlpha();
+                        mService.mAnimator.mScreenRotationAnimation.getEnterTransformation().getAlpha();
                 }
             } else {
                 //Slog.i(TAG, "Not applying alpha transform");
@@ -1314,7 +1106,7 @@
             if (WindowManagerService.localLOGV) Slog.v(
                 WindowManagerService.TAG, "computeShownFrameLocked: Animating " + this +
                 ": " + mShownFrame +
-                ", alpha=" + mTransformation.getAlpha() + ", mShownAlpha=" + mShownAlpha);
+                ", alpha=" + mWinAnimator.mTransformation.getAlpha() + ", mShownAlpha=" + mShownAlpha);
             return;
         }
 
@@ -1365,7 +1157,7 @@
                 && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested)
                 && ((!mAttachedHidden && mViewVisibility == View.VISIBLE
                                 && !mRootToken.hidden)
-                        || mAnimation != null || animating);
+                        || mWinAnimator.mAnimation != null || animating);
     }
 
     /**
@@ -1422,10 +1214,10 @@
         if (atoken != null) {
             return mSurface != null && mPolicyVisibility && !mDestroying
                     && ((!mAttachedHidden && !atoken.hiddenRequested)
-                            || mAnimation != null || atoken.animation != null);
+                            || mWinAnimator.mAnimation != null || atoken.animation != null);
         } else {
             return mSurface != null && mPolicyVisibility && !mDestroying
-                    && (!mAttachedHidden || mAnimation != null);
+                    && (!mAttachedHidden || mWinAnimator.mAnimation != null);
         }
     }
 
@@ -1441,26 +1233,10 @@
         return mSurface != null && mPolicyVisibility && !mDestroying
                 && ((!mAttachedHidden && mViewVisibility == View.VISIBLE
                                 && !mRootToken.hidden)
-                        || mAnimation != null
+                        || mWinAnimator.mAnimation != null
                         || ((mAppToken != null) && (mAppToken.animation != null)));
     }
 
-    /** Is the window or its container currently animating? */
-    boolean isAnimating() {
-        final WindowState attached = mAttachedWindow;
-        final AppWindowToken atoken = mAppToken;
-        return mAnimation != null
-                || (attached != null && attached.mAnimation != null)
-                || (atoken != null &&
-                        (atoken.animation != null
-                                || atoken.inPendingTransaction));
-    }
-
-    /** Is this window currently animating? */
-    boolean isWindowAnimating() {
-        return mAnimation != null;
-    }
-
     /**
      * Like isOnScreen, but returns false if the surface hasn't yet
      * been drawn.
@@ -1470,7 +1246,7 @@
         return isDrawnLw() && mPolicyVisibility
             && ((!mAttachedHidden &&
                     (atoken == null || !atoken.hiddenRequested))
-                    || mAnimating);
+                    || mWinAnimator.mAnimating);
     }
 
     public boolean isGoneForLayoutLw() {
@@ -1499,7 +1275,7 @@
     boolean isOpaqueDrawn() {
         return (mAttrs.format == PixelFormat.OPAQUE
                         || mAttrs.type == TYPE_WALLPAPER)
-                && isDrawnLw() && mAnimation == null
+                && isDrawnLw() && mWinAnimator.mAnimation == null
                 && (mAppToken == null || mAppToken.animation == null);
     }
 
@@ -1509,11 +1285,10 @@
      * sense to call from performLayoutAndPlaceSurfacesLockedInner().)
      */
     boolean shouldAnimateMove() {
-        return mContentChanged && !mExiting && !mLastHidden && !mService.mDisplayFrozen
+        return mContentChanged && !mExiting && !mLastHidden && mService.okToDisplay()
                 && (mFrame.top != mLastFrame.top
                         || mFrame.left != mLastFrame.left)
-                && (mAttachedWindow == null || !mAttachedWindow.shouldAnimateMove())
-                && mService.mPolicy.isScreenOnFully();
+                && (mAttachedWindow == null || !mAttachedWindow.shouldAnimateMove());
     }
 
     boolean isFullscreen(int screenWidth, int screenHeight) {
@@ -1599,10 +1374,10 @@
         if (DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "Policy visibility true: " + this);
         if (doAnimation) {
             if (DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "doAnimation: mPolicyVisibility="
-                    + mPolicyVisibility + " mAnimation=" + mAnimation);
-            if (mService.mDisplayFrozen || !mService.mPolicy.isScreenOnFully()) {
+                    + mPolicyVisibility + " mAnimation=" + mWinAnimator.mAnimation);
+            if (!mService.okToDisplay()) {
                 doAnimation = false;
-            } else if (mPolicyVisibility && mAnimation == null) {
+            } else if (mPolicyVisibility && mWinAnimator.mAnimation == null) {
                 // Check for the case where we are currently visible and
                 // not animating; we do not want to do animation at such a
                 // point to become visible when we already are.
@@ -1626,7 +1401,7 @@
 
     boolean hideLw(boolean doAnimation, boolean requestAnim) {
         if (doAnimation) {
-            if (mService.mDisplayFrozen || !mService.mPolicy.isScreenOnFully()) {
+            if (!mService.okToDisplay()) {
                 doAnimation = false;
             }
         }
@@ -1638,7 +1413,7 @@
         }
         if (doAnimation) {
             mService.applyAnimationLocked(this, WindowManagerPolicy.TRANSIT_EXIT, false);
-            if (mAnimation == null) {
+            if (mWinAnimator.mAnimation == null) {
                 doAnimation = false;
             }
         }
@@ -1813,20 +1588,7 @@
                     pw.print(" last="); mLastVisibleInsets.printShortString(pw);
                     pw.println();
         }
-        if (mAnimating || mLocalAnimating || mAnimationIsEntrance
-                || mAnimation != null) {
-            pw.print(prefix); pw.print("mAnimating="); pw.print(mAnimating);
-                    pw.print(" mLocalAnimating="); pw.print(mLocalAnimating);
-                    pw.print(" mAnimationIsEntrance="); pw.print(mAnimationIsEntrance);
-                    pw.print(" mAnimation="); pw.println(mAnimation);
-        }
-        if (mHasTransformation || mHasLocalTransformation) {
-            pw.print(prefix); pw.print("XForm: has=");
-                    pw.print(mHasTransformation);
-                    pw.print(" hasLocal="); pw.print(mHasLocalTransformation);
-                    pw.print(" "); mTransformation.printShortString(pw);
-                    pw.println();
-        }
+        mWinAnimator.dump(pw, prefix, dumpAll);
         if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) {
             pw.print(prefix); pw.print("mShownAlpha="); pw.print(mShownAlpha);
                     pw.print(" mAlpha="); pw.print(mAlpha);
diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java
new file mode 100644
index 0000000..d86d411
--- /dev/null
+++ b/services/java/com/android/server/wm/WindowStateAnimator.java
@@ -0,0 +1,298 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+package com.android.server.wm;
+
+import android.util.Slog;
+import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+import com.android.server.wm.WindowManagerService.H;
+
+import java.io.PrintWriter;
+
+/**
+ * @author cmautner@google.com (Craig Mautner)
+ *
+ */
+class WindowStateAnimator {
+
+    final WindowManagerService mService;
+    final WindowState mWin;
+    final WindowState mAttachedWindow;
+
+    // Currently running animation.
+    boolean mAnimating;
+    boolean mLocalAnimating;
+    Animation mAnimation;
+    boolean mAnimationIsEntrance;
+    boolean mHasTransformation;
+    boolean mHasLocalTransformation;
+    final Transformation mTransformation = new Transformation();
+    boolean mWasAnimating;      // Were we animating going into the most recent animation step?
+
+    public WindowStateAnimator(final WindowManagerService service, final WindowState win,
+                               final WindowState attachedWindow) {
+        mService = service;
+        mWin = win;
+        mAttachedWindow = attachedWindow;
+    }
+
+    public void setAnimation(Animation anim) {
+        if (WindowManagerService.localLOGV) Slog.v(
+            WindowManagerService.TAG, "Setting animation in " + this + ": " + anim);
+        mAnimating = false;
+        mLocalAnimating = false;
+        mAnimation = anim;
+        mAnimation.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION);
+        mAnimation.scaleCurrentDuration(mService.mWindowAnimationScale);
+        // Start out animation gone if window is gone, or visible if window is visible.
+        mTransformation.clear();
+        mTransformation.setAlpha(mWin.mLastHidden ? 0 : 1);
+        mHasLocalTransformation = true;
+    }
+
+    public void clearAnimation() {
+        if (mAnimation != null) {
+            mAnimating = true;
+            mLocalAnimating = false;
+            mAnimation.cancel();
+            mAnimation = null;
+        }
+    }
+
+    /** Is the window or its container currently animating? */
+    boolean isAnimating() {
+        final WindowState attached = mAttachedWindow;
+        final AppWindowToken atoken = mWin.mAppToken;
+        return mAnimation != null
+                || (attached != null && attached.mWinAnimator.mAnimation != null)
+                || (atoken != null &&
+                        (atoken.animation != null
+                                || atoken.inPendingTransaction));
+    }
+
+    /** Is this window currently animating? */
+    boolean isWindowAnimating() {
+        return mAnimation != null;
+    }
+
+    // TODO: Fix and call finishExit() instead of cancelExitAnimationForNextAnimationLocked()
+    // for avoiding the code duplication.
+    void cancelExitAnimationForNextAnimationLocked() {
+        if (!mWin.mExiting) return;
+        if (mAnimation != null) {
+            mAnimation.cancel();
+            mAnimation = null;
+            mWin.destroySurfaceLocked();
+        }
+        mWin.mExiting = false;
+    }
+
+    private boolean stepAnimation(long currentTime) {
+        if ((mAnimation == null) || !mLocalAnimating) {
+            return false;
+        }
+        mTransformation.clear();
+        final boolean more = mAnimation.getTransformation(currentTime, mTransformation);
+        if (WindowManagerService.DEBUG_ANIM) Slog.v(
+            WindowManagerService.TAG, "Stepped animation in " + this +
+            ": more=" + more + ", xform=" + mTransformation);
+        return more;
+    }
+
+    // This must be called while inside a transaction.  Returns true if
+    // there is more animation to run.
+    boolean stepAnimationLocked(long currentTime) {
+        // Save the animation state as it was before this step so WindowManagerService can tell if
+        // we just started or just stopped animating by comparing mWasAnimating with isAnimating().
+        mWasAnimating = mAnimating;
+        if (mService.okToDisplay()) {
+            // We will run animations as long as the display isn't frozen.
+
+            if (mWin.isDrawnLw() && mAnimation != null) {
+                mHasTransformation = true;
+                mHasLocalTransformation = true;
+                if (!mLocalAnimating) {
+                    if (WindowManagerService.DEBUG_ANIM) Slog.v(
+                        WindowManagerService.TAG, "Starting animation in " + this +
+                        " @ " + currentTime + ": ww=" + mWin.mFrame.width() +
+                        " wh=" + mWin.mFrame.height() +
+                        " dw=" + mWin.mAnimDw + " dh=" + mWin.mAnimDh +
+                        " scale=" + mService.mWindowAnimationScale);
+                    mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(), mWin.mAnimDw,
+                        mWin.mAnimDh);
+                    mAnimation.setStartTime(currentTime);
+                    mLocalAnimating = true;
+                    mAnimating = true;
+                }
+                if ((mAnimation != null) && mLocalAnimating) {
+                    if (stepAnimation(currentTime)) {
+                        return true;
+                    }
+                }
+                if (WindowManagerService.DEBUG_ANIM) Slog.v(
+                    WindowManagerService.TAG, "Finished animation in " + this +
+                    " @ " + currentTime);
+                //WindowManagerService.this.dump();
+            }
+            mHasLocalTransformation = false;
+            if ((!mLocalAnimating || mAnimationIsEntrance) && mWin.mAppToken != null
+                    && mWin.mAppToken.animation != null) {
+                // When our app token is animating, we kind-of pretend like
+                // we are as well.  Note the mLocalAnimating mAnimationIsEntrance
+                // part of this check means that we will only do this if
+                // our window is not currently exiting, or it is not
+                // locally animating itself.  The idea being that one that
+                // is exiting and doing a local animation should be removed
+                // once that animation is done.
+                mAnimating = true;
+                mHasTransformation = true;
+                mTransformation.clear();
+                return false;
+            } else if (mHasTransformation) {
+                // Little trick to get through the path below to act like
+                // we have finished an animation.
+                mAnimating = true;
+            } else if (isAnimating()) {
+                mAnimating = true;
+            }
+        } else if (mAnimation != null) {
+            // If the display is frozen, and there is a pending animation,
+            // clear it and make sure we run the cleanup code.
+            mAnimating = true;
+            mLocalAnimating = true;
+            mAnimation.cancel();
+            mAnimation = null;
+        }
+
+        if (!mAnimating && !mLocalAnimating) {
+            return false;
+        }
+
+        if (WindowManagerService.DEBUG_ANIM) Slog.v(
+            WindowManagerService.TAG, "Animation done in " + this + ": exiting=" + mWin.mExiting
+            + ", reportedVisible="
+            + (mWin.mAppToken != null ? mWin.mAppToken.reportedVisible : false));
+
+        mAnimating = false;
+        mLocalAnimating = false;
+        if (mAnimation != null) {
+            mAnimation.cancel();
+            mAnimation = null;
+        }
+        if (mService.mWindowDetachedWallpaper == mWin) {
+            mService.mWindowDetachedWallpaper = null;
+        }
+        mWin.mAnimLayer = mWin.mLayer;
+        if (mWin.mIsImWindow) {
+            mWin.mAnimLayer += mService.mInputMethodAnimLayerAdjustment;
+        } else if (mWin.mIsWallpaper) {
+            mWin.mAnimLayer += mService.mWallpaperAnimLayerAdjustment;
+        }
+        if (WindowManagerService.DEBUG_LAYERS) Slog.v(WindowManagerService.TAG, "Stepping win " + this
+                + " anim layer: " + mWin.mAnimLayer);
+        mHasTransformation = false;
+        mHasLocalTransformation = false;
+        if (mWin.mPolicyVisibility != mWin.mPolicyVisibilityAfterAnim) {
+            if (WindowState.DEBUG_VISIBILITY) {
+                Slog.v(WindowManagerService.TAG, "Policy visibility changing after anim in " + this + ": "
+                        + mWin.mPolicyVisibilityAfterAnim);
+            }
+            mWin.mPolicyVisibility = mWin.mPolicyVisibilityAfterAnim;
+            mService.mLayoutNeeded = true;
+            if (!mWin.mPolicyVisibility) {
+                if (mService.mCurrentFocus == mWin) {
+                    mService.mFocusMayChange = true;
+                }
+                // Window is no longer visible -- make sure if we were waiting
+                // for it to be displayed before enabling the display, that
+                // we allow the display to be enabled now.
+                mService.enableScreenIfNeededLocked();
+            }
+        }
+        mTransformation.clear();
+        if (mWin.mHasDrawn
+                && mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING
+                && mWin.mAppToken != null
+                && mWin.mAppToken.firstWindowDrawn
+                && mWin.mAppToken.startingData != null) {
+            if (WindowManagerService.DEBUG_STARTING_WINDOW) Slog.v(WindowManagerService.TAG, "Finish starting "
+                    + mWin.mToken + ": first real window done animating");
+            mService.mFinishedStarting.add(mWin.mAppToken);
+            mService.mH.sendEmptyMessage(H.FINISHED_STARTING);
+        }
+
+        finishExit();
+        mService.mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+        if (WindowManagerService.DEBUG_LAYOUT_REPEATS) mService.debugLayoutRepeats("WindowState");
+
+        if (mWin.mAppToken != null) {
+            mWin.mAppToken.updateReportedVisibilityLocked();
+        }
+
+        return false;
+    }
+
+    void finishExit() {
+        if (WindowManagerService.DEBUG_ANIM) Slog.v(
+                WindowManagerService.TAG, "finishExit in " + this
+                + ": exiting=" + mWin.mExiting
+                + " remove=" + mWin.mRemoveOnExit
+                + " windowAnimating=" + isWindowAnimating());
+
+        final int N = mWin.mChildWindows.size();
+        for (int i=0; i<N; i++) {
+            mWin.mChildWindows.get(i).mWinAnimator.finishExit();
+        }
+
+        if (!mWin.mExiting) {
+            return;
+        }
+
+        if (isWindowAnimating()) {
+            return;
+        }
+
+        if (WindowManagerService.localLOGV) Slog.v(
+                WindowManagerService.TAG, "Exit animation finished in " + this
+                + ": remove=" + mWin.mRemoveOnExit);
+        if (mWin.mSurface != null) {
+            mService.mDestroySurface.add(mWin);
+            mWin.mDestroying = true;
+            if (WindowState.SHOW_TRANSACTIONS) WindowManagerService.logSurface(
+                mWin, "HIDE (finishExit)", null);
+            mWin.mSurfaceShown = false;
+            try {
+                mWin.mSurface.hide();
+            } catch (RuntimeException e) {
+                Slog.w(WindowManagerService.TAG, "Error hiding surface in " + this, e);
+            }
+            mWin.mLastHidden = true;
+        }
+        mWin.mExiting = false;
+        if (mWin.mRemoveOnExit) {
+            mService.mPendingRemove.add(mWin);
+            mWin.mRemoveOnExit = false;
+        }
+    }
+
+    public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        if (mAnimating || mLocalAnimating || mAnimationIsEntrance
+                || mAnimation != null) {
+            pw.print(prefix); pw.print("mAnimating="); pw.print(mAnimating);
+                    pw.print(" mLocalAnimating="); pw.print(mLocalAnimating);
+                    pw.print(" mAnimationIsEntrance="); pw.print(mAnimationIsEntrance);
+                    pw.print(" mAnimation="); pw.println(mAnimation);
+        }
+        if (mHasTransformation || mHasLocalTransformation) {
+            pw.print(prefix); pw.print("XForm: has=");
+                    pw.print(mHasTransformation);
+                    pw.print(" hasLocal="); pw.print(mHasLocalTransformation);
+                    pw.print(" "); mTransformation.printShortString(pw);
+                    pw.println();
+        }
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index b4fd55e..88ee867 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -53,6 +53,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
+import android.content.pm.UserInfo;
 import android.net.ConnectivityManager;
 import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
@@ -69,6 +70,7 @@
 import android.os.INetworkManagementService;
 import android.os.IPowerManager;
 import android.os.MessageQueue.IdleHandler;
+import android.os.UserId;
 import android.test.AndroidTestCase;
 import android.test.mock.MockPackageManager;
 import android.test.suitebuilder.annotation.LargeTest;
@@ -84,7 +86,9 @@
 import org.easymock.IAnswer;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
@@ -126,8 +130,16 @@
     private long mStartTime;
     private long mElapsedRealtime;
 
-    private static final int UID_A = android.os.Process.FIRST_APPLICATION_UID + 800;
-    private static final int UID_B = android.os.Process.FIRST_APPLICATION_UID + 801;
+    private static final int USER_ID = 0;
+    private static final int USER_ID_GUEST = 1;
+
+    private static final int APP_ID_A = android.os.Process.FIRST_APPLICATION_UID + 800;
+    private static final int APP_ID_B = android.os.Process.FIRST_APPLICATION_UID + 801;
+
+    private static final int UID_A = UserId.getUid(USER_ID, APP_ID_A);
+    private static final int UID_B = UserId.getUid(USER_ID, APP_ID_B);
+    private static final int UID_A_GUEST = UserId.getUid(USER_ID_GUEST, APP_ID_A);
+    private static final int UID_B_GUEST = UserId.getUid(USER_ID_GUEST, APP_ID_B);
 
     private static final int PID_1 = 400;
     private static final int PID_2 = 401;
@@ -161,6 +173,14 @@
                         info.signatures = new Signature[] { signature };
                         return info;
                     }
+
+                    @Override
+                    public List<UserInfo> getUsers() {
+                        final ArrayList<UserInfo> users = new ArrayList<UserInfo>();
+                        users.add(new UserInfo(USER_ID, "Primary", UserInfo.FLAG_PRIMARY));
+                        users.add(new UserInfo(USER_ID_GUEST, "Guest", 0));
+                        return users;
+                    }
                 };
             }
 
@@ -242,13 +262,13 @@
 
     @Suppress
     public void testPolicyChangeTriggersBroadcast() throws Exception {
-        mService.setUidPolicy(UID_A, POLICY_NONE);
+        mService.setAppPolicy(APP_ID_A, POLICY_NONE);
 
         // change background policy and expect broadcast
         final Future<Intent> backgroundChanged = mServiceContext.nextBroadcastIntent(
                 ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
 
-        mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
+        mService.setAppPolicy(APP_ID_A, POLICY_REJECT_METERED_BACKGROUND);
 
         backgroundChanged.get();
     }
@@ -302,6 +322,7 @@
 
     public void testScreenChangesRules() throws Exception {
         Future<Void> future;
+        Future<Void> futureGuest;
 
         expectSetUidNetworkRules(UID_A, false);
         expectSetUidForeground(UID_A, true);
@@ -314,10 +335,14 @@
         // push strict policy for foreground uid, verify ALLOW rule
         expectSetUidNetworkRules(UID_A, false);
         expectSetUidForeground(UID_A, true);
+        expectSetUidNetworkRules(UID_A_GUEST, true);
+        expectSetUidForeground(UID_A_GUEST, false);
         future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        futureGuest = expectRulesChanged(UID_A_GUEST, RULE_REJECT_METERED);
         replay();
-        mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
+        mService.setAppPolicy(APP_ID_A, POLICY_REJECT_METERED_BACKGROUND);
         future.get();
+        futureGuest.get();
         verifyAndReset();
 
         // now turn screen off and verify REJECT rule
@@ -343,6 +368,7 @@
 
     public void testPolicyNone() throws Exception {
         Future<Void> future;
+        Future<Void> futureGuest;
 
         expectSetUidNetworkRules(UID_A, false);
         expectSetUidForeground(UID_A, true);
@@ -355,10 +381,14 @@
         // POLICY_NONE should RULE_ALLOW in foreground
         expectSetUidNetworkRules(UID_A, false);
         expectSetUidForeground(UID_A, true);
+        expectSetUidNetworkRules(UID_A_GUEST, false);
+        expectSetUidForeground(UID_A_GUEST, false);
         future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        futureGuest = expectRulesChanged(UID_A_GUEST, RULE_ALLOW_ALL);
         replay();
-        mService.setUidPolicy(UID_A, POLICY_NONE);
+        mService.setAppPolicy(APP_ID_A, POLICY_NONE);
         future.get();
+        futureGuest.get();
         verifyAndReset();
 
         // POLICY_NONE should RULE_ALLOW in background
@@ -373,14 +403,19 @@
 
     public void testPolicyReject() throws Exception {
         Future<Void> future;
+        Future<Void> futureGuest;
 
         // POLICY_REJECT should RULE_ALLOW in background
         expectSetUidNetworkRules(UID_A, true);
         expectSetUidForeground(UID_A, false);
+        expectSetUidNetworkRules(UID_A_GUEST, true);
+        expectSetUidForeground(UID_A_GUEST, false);
         future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
+        futureGuest = expectRulesChanged(UID_A_GUEST, RULE_REJECT_METERED);
         replay();
-        mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
+        mService.setAppPolicy(APP_ID_A, POLICY_REJECT_METERED_BACKGROUND);
         future.get();
+        futureGuest.get();
         verifyAndReset();
 
         // POLICY_REJECT should RULE_ALLOW in foreground
@@ -404,33 +439,46 @@
 
     public void testPolicyRejectAddRemove() throws Exception {
         Future<Void> future;
+        Future<Void> futureGuest;
 
         // POLICY_NONE should have RULE_ALLOW in background
         expectSetUidNetworkRules(UID_A, false);
         expectSetUidForeground(UID_A, false);
+        expectSetUidNetworkRules(UID_A_GUEST, false);
+        expectSetUidForeground(UID_A_GUEST, false);
         future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        futureGuest = expectRulesChanged(UID_A_GUEST, RULE_ALLOW_ALL);
         replay();
         mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
-        mService.setUidPolicy(UID_A, POLICY_NONE);
+        mService.setAppPolicy(APP_ID_A, POLICY_NONE);
         future.get();
+        futureGuest.get();
         verifyAndReset();
 
         // adding POLICY_REJECT should cause RULE_REJECT
         expectSetUidNetworkRules(UID_A, true);
         expectSetUidForeground(UID_A, false);
+        expectSetUidNetworkRules(UID_A_GUEST, true);
+        expectSetUidForeground(UID_A_GUEST, false);
         future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
+        futureGuest = expectRulesChanged(UID_A_GUEST, RULE_REJECT_METERED);
         replay();
-        mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
+        mService.setAppPolicy(APP_ID_A, POLICY_REJECT_METERED_BACKGROUND);
         future.get();
+        futureGuest.get();
         verifyAndReset();
 
         // removing POLICY_REJECT should return us to RULE_ALLOW
         expectSetUidNetworkRules(UID_A, false);
         expectSetUidForeground(UID_A, false);
+        expectSetUidNetworkRules(UID_A_GUEST, false);
+        expectSetUidForeground(UID_A_GUEST, false);
         future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        futureGuest = expectRulesChanged(UID_A_GUEST, RULE_ALLOW_ALL);
         replay();
-        mService.setUidPolicy(UID_A, POLICY_NONE);
+        mService.setAppPolicy(APP_ID_A, POLICY_NONE);
         future.get();
+        futureGuest.get();
         verifyAndReset();
     }
 
@@ -599,25 +647,34 @@
 
     public void testUidRemovedPolicyCleared() throws Exception {
         Future<Void> future;
+        Future<Void> futureGuest;
 
         // POLICY_REJECT should RULE_REJECT in background
         expectSetUidNetworkRules(UID_A, true);
         expectSetUidForeground(UID_A, false);
+        expectSetUidNetworkRules(UID_A_GUEST, true);
+        expectSetUidForeground(UID_A_GUEST, false);
         future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
+        futureGuest = expectRulesChanged(UID_A_GUEST, RULE_REJECT_METERED);
         replay();
-        mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
+        mService.setAppPolicy(APP_ID_A, POLICY_REJECT_METERED_BACKGROUND);
         future.get();
+        futureGuest.get();
         verifyAndReset();
 
         // uninstall should clear RULE_REJECT
         expectSetUidNetworkRules(UID_A, false);
         expectSetUidForeground(UID_A, false);
+        expectSetUidNetworkRules(UID_A_GUEST, false);
+        expectSetUidForeground(UID_A_GUEST, false);
         future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+        futureGuest = expectRulesChanged(UID_A_GUEST, RULE_ALLOW_ALL);
         replay();
         final Intent intent = new Intent(ACTION_UID_REMOVED);
         intent.putExtra(EXTRA_UID, UID_A);
         mServiceContext.sendBroadcast(intent);
         future.get();
+        futureGuest.get();
         verifyAndReset();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
new file mode 100644
index 0000000..796372d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -0,0 +1,205 @@
+/*
+ * 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.pm;
+
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+
+import com.android.internal.content.PackageHelper;
+import com.android.internal.os.AtomicFile;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashSet;
+
+import android.os.Debug;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.storage.IMountService;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+public class PackageManagerSettingsTests extends AndroidTestCase {
+
+    private static final String PACKAGE_NAME_2 = "com.google.app2";
+    private static final String PACKAGE_NAME_3 = "com.android.app3";
+    private static final String PACKAGE_NAME_1 = "com.google.app1";
+    private static final boolean localLOGV = true;
+    public static final String TAG = "PackageManagerSettingsTests";
+    protected final String PREFIX = "android.content.pm";
+
+    private void writeFile(File file, byte[] data) {
+        file.mkdirs();
+        try {
+            AtomicFile aFile = new AtomicFile(file);
+            FileOutputStream fos = aFile.startWrite();
+            fos.write(data);
+            aFile.finishWrite(fos);
+        } catch (IOException ioe) {
+            Log.e(TAG, "Cannot write file " + file.getPath());
+        }
+    }
+
+    private void writePackagesXml() {
+        writeFile(new File(getContext().getFilesDir(), "system/packages.xml"),
+                ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<packages>"
+                + "<last-platform-version internal=\"15\" external=\"0\" />"
+                + "<permission-trees>"
+                + "<item name=\"com.google.android.permtree\" package=\"com.google.android.permpackage\" />"
+                + "</permission-trees>"
+                + "<permissions>"
+                + "<item name=\"android.permission.WRITE_CALL_LOG\" package=\"android\" protection=\"1\" />"
+                + "<item name=\"android.permission.ASEC_ACCESS\" package=\"android\" protection=\"2\" />"
+                + "<item name=\"android.permission.ACCESS_WIMAX_STATE\" package=\"android\" />"
+                + "<item name=\"android.permission.REBOOT\" package=\"android\" protection=\"18\" />"
+                + "</permissions>"
+                + "<package name=\"com.google.app1\" codePath=\"/system/app/app1.apk\" nativeLibraryPath=\"/data/data/com.google.app1/lib\" flags=\"1\" ft=\"1360e2caa70\" it=\"135f2f80d08\" ut=\"1360e2caa70\" version=\"1109\" sharedUserId=\"11000\">"
+                + "<sigs count=\"1\">"
+                + "<cert index=\"0\" key=\"308886\" />"
+                + "</sigs>"
+                + "</package>"
+                + "<package name=\"com.google.app2\" codePath=\"/system/app/app2.apk\" nativeLibraryPath=\"/data/data/com.google.app2/lib\" flags=\"1\" ft=\"1360e578718\" it=\"135f2f80d08\" ut=\"1360e578718\" version=\"15\" enabled=\"3\" userId=\"11001\">"
+                + "<sigs count=\"1\">"
+                + "<cert index=\"0\" />"
+                + "</sigs>"
+                + "</package>"
+                + "<package name=\"com.android.app3\" codePath=\"/system/app/app3.apk\" nativeLibraryPath=\"/data/data/com.android.app3/lib\" flags=\"1\" ft=\"1360e577b60\" it=\"135f2f80d08\" ut=\"1360e577b60\" version=\"15\" userId=\"11030\">"
+                + "<sigs count=\"1\">"
+                + "<cert index=\"1\" key=\"308366\" />"
+                + "</sigs>"
+                + "</package>"
+                + "<shared-user name=\"com.android.shared1\" userId=\"11000\">"
+                + "<sigs count=\"1\">"
+                + "<cert index=\"1\" />"
+                + "</sigs>"
+                + "<perms>"
+                + "<item name=\"android.permission.REBOOT\" />"
+                + "</perms>"
+                + "</shared-user>"
+                + "</packages>").getBytes());
+    }
+
+    private void writeStoppedPackagesXml() {
+        writeFile(new File(getContext().getFilesDir(), "system/packages-stopped.xml"),
+                ( "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<stopped-packages>"
+                + "<pkg name=\"com.google.app1\" nl=\"1\" />"
+                + "<pkg name=\"com.android.app3\" nl=\"1\" />"
+                + "</stopped-packages>")
+                .getBytes());
+    }
+
+    private void writePackagesList() {
+        writeFile(new File(getContext().getFilesDir(), "system/packages.list"),
+                ( "com.google.app1 11000 0 /data/data/com.google.app1"
+                + "com.google.app2 11001 0 /data/data/com.google.app2"
+                + "com.android.app3 11030 0 /data/data/com.android.app3")
+                .getBytes());
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    private void writeOldFiles() {
+        writePackagesXml();
+        writeStoppedPackagesXml();
+        writePackagesList();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testSettingsReadOld() {
+        // Debug.waitForDebugger();
+
+        // Write the package files and make sure they're parsed properly the first time
+        writeOldFiles();
+        Settings settings = new Settings(getContext().getFilesDir());
+        assertEquals(true, settings.readLPw(null));
+        assertNotNull(settings.peekPackageLPr(PACKAGE_NAME_3));
+        assertNotNull(settings.peekPackageLPr(PACKAGE_NAME_1));
+
+        PackageSetting ps = settings.peekPackageLPr(PACKAGE_NAME_1);
+        assertEquals(COMPONENT_ENABLED_STATE_DEFAULT, ps.getEnabled(0));
+        assertEquals(true, ps.getNotLaunched(0));
+
+        ps = settings.peekPackageLPr(PACKAGE_NAME_2);
+        assertEquals(false, ps.getStopped(0));
+        assertEquals(COMPONENT_ENABLED_STATE_DISABLED_USER, ps.getEnabled(0));
+        assertEquals(COMPONENT_ENABLED_STATE_DEFAULT, ps.getEnabled(1));
+    }
+
+    public void testNewPackageRestrictionsFile() {
+        // Write the package files and make sure they're parsed properly the first time
+        writeOldFiles();
+        Settings settings = new Settings(getContext().getFilesDir());
+        assertEquals(true, settings.readLPw(null));
+
+        // Create Settings again to make it read from the new files
+        settings = new Settings(getContext().getFilesDir());
+        assertEquals(true, settings.readLPw(null));
+
+        PackageSetting ps = settings.peekPackageLPr(PACKAGE_NAME_2);
+        assertEquals(COMPONENT_ENABLED_STATE_DISABLED_USER, ps.getEnabled(0));
+        assertEquals(COMPONENT_ENABLED_STATE_DEFAULT, ps.getEnabled(1));
+    }
+
+    public void testEnableDisable() {
+        // Write the package files and make sure they're parsed properly the first time
+        writeOldFiles();
+        Settings settings = new Settings(getContext().getFilesDir());
+        assertEquals(true, settings.readLPw(null));
+
+        // Enable/Disable a package
+        PackageSetting ps = settings.peekPackageLPr(PACKAGE_NAME_1);
+        ps.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, 0);
+        ps.setEnabled(COMPONENT_ENABLED_STATE_ENABLED, 1);
+        assertEquals(COMPONENT_ENABLED_STATE_DISABLED, ps.getEnabled(0));
+        assertEquals(COMPONENT_ENABLED_STATE_ENABLED, ps.getEnabled(1));
+
+        // Enable/Disable a component
+        HashSet<String> components = new HashSet<String>();
+        String component1 = PACKAGE_NAME_1 + "/.Component1";
+        components.add(component1);
+        ps.setDisabledComponents(components, 0);
+        HashSet<String> componentsDisabled = ps.getDisabledComponents(0);
+        assertEquals(1, componentsDisabled.size());
+        assertEquals(component1, componentsDisabled.toArray()[0]);
+        boolean hasEnabled =
+                ps.getEnabledComponents(0) != null && ps.getEnabledComponents(1).size() > 0;
+        assertEquals(false, hasEnabled);
+
+        // User 1 should not have any disabled components
+        boolean hasDisabled =
+                ps.getDisabledComponents(1) != null && ps.getDisabledComponents(1).size() > 0;
+        assertEquals(false, hasDisabled);
+        ps.setEnabledComponents(components, 1);
+        assertEquals(1, ps.getEnabledComponents(1).size());
+        hasEnabled = ps.getEnabledComponents(0) != null && ps.getEnabledComponents(0).size() > 0;
+        assertEquals(false, hasEnabled);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index e8188e7..d736ac1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -54,7 +54,7 @@
     public void testAddUser() throws Exception {
         final UserManager details = mUserManager;
 
-        UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null);
+        UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
         assertTrue(userInfo != null);
 
         List<UserInfo> list = details.getUsers();
@@ -73,8 +73,8 @@
     public void testAdd2Users() throws Exception {
         final UserManager details = mUserManager;
 
-        UserInfo user1 = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null);
-        UserInfo user2 = details.createUser("User 2", UserInfo.FLAG_ADMIN, null);
+        UserInfo user1 = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
+        UserInfo user2 = details.createUser("User 2", UserInfo.FLAG_ADMIN);
 
         assertTrue(user1 != null);
         assertTrue(user2 != null);
@@ -87,7 +87,7 @@
     public void testRemoveUser() throws Exception {
         final UserManager details = mUserManager;
 
-        UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null);
+        UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
 
         details.removeUser(userInfo.id);
 
diff --git a/telephony/java/com/android/internal/telephony/AdnRecordCache.java b/telephony/java/com/android/internal/telephony/AdnRecordCache.java
index a175d49..db5f4da 100644
--- a/telephony/java/com/android/internal/telephony/AdnRecordCache.java
+++ b/telephony/java/com/android/internal/telephony/AdnRecordCache.java
@@ -33,7 +33,7 @@
 public final class AdnRecordCache extends Handler implements IccConstants {
     //***** Instance Variables
 
-    PhoneBase phone;
+    private IccFileHandler mFh;
     private UsimPhoneBookManager mUsimPhoneBookManager;
 
     // Indexed by EF ID
@@ -56,9 +56,9 @@
 
 
 
-    public AdnRecordCache(PhoneBase phone) {
-        this.phone = phone;
-        mUsimPhoneBookManager = new UsimPhoneBookManager(phone, this);
+    public AdnRecordCache(IccFileHandler fh) {
+        mFh = fh;
+        mUsimPhoneBookManager = new UsimPhoneBookManager(mFh, this);
     }
 
     //***** Called from SIMRecords
@@ -155,7 +155,7 @@
 
         userWriteResponse.put(efid, response);
 
-        new AdnRecordLoader(phone).updateEF(adn, efid, extensionEF,
+        new AdnRecordLoader(mFh).updateEF(adn, efid, extensionEF,
                 recordIndex, pin2,
                 obtainMessage(EVENT_UPDATE_ADN_DONE, efid, recordIndex, adn));
     }
@@ -233,7 +233,7 @@
 
         userWriteResponse.put(efid, response);
 
-        new AdnRecordLoader(phone).updateEF(newAdn, efid, extensionEF,
+        new AdnRecordLoader(mFh).updateEF(newAdn, efid, extensionEF,
                 index, pin2,
                 obtainMessage(EVENT_UPDATE_ADN_DONE, efid, index, newAdn));
     }
@@ -296,7 +296,7 @@
             return;
         }
 
-        new AdnRecordLoader(phone).loadAllFromEF(efid, extensionEf,
+        new AdnRecordLoader(mFh).loadAllFromEF(efid, extensionEf,
             obtainMessage(EVENT_LOAD_ALL_ADN_LIKE_DONE, efid, 0));
     }
 
diff --git a/telephony/java/com/android/internal/telephony/AdnRecordLoader.java b/telephony/java/com/android/internal/telephony/AdnRecordLoader.java
index 55bdc06..084fae6 100644
--- a/telephony/java/com/android/internal/telephony/AdnRecordLoader.java
+++ b/telephony/java/com/android/internal/telephony/AdnRecordLoader.java
@@ -20,16 +20,17 @@
 
 import android.os.AsyncResult;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.util.Log;
 
 
 public class AdnRecordLoader extends Handler {
-    static String LOG_TAG;
+    final static String LOG_TAG = "RIL_AdnRecordLoader";
 
     //***** Instance Variables
 
-    PhoneBase phone;
+    private IccFileHandler mFh;
     int ef;
     int extensionEF;
     int pendingExtLoads;
@@ -56,13 +57,11 @@
 
     //***** Constructor
 
-    public AdnRecordLoader(PhoneBase phone) {
+    public AdnRecordLoader(IccFileHandler fh) {
         // The telephony unit-test cases may create AdnRecords
         // in secondary threads
-        super(phone.getHandler().getLooper());
-
-        this.phone = phone;
-        LOG_TAG = phone.getPhoneName();
+        super(Looper.getMainLooper());
+        mFh = fh;
     }
 
     /**
@@ -77,7 +76,7 @@
         this.recordNumber = recordNumber;
         this.userResponse = response;
 
-        phone.mIccFileHandler.loadEFLinearFixed(
+        mFh.loadEFLinearFixed(
                     ef, recordNumber,
                     obtainMessage(EVENT_ADN_LOAD_DONE));
 
@@ -95,7 +94,7 @@
         this.extensionEF = extensionEF;
         this.userResponse = response;
 
-        phone.mIccFileHandler.loadEFLinearFixedAll(
+        mFh.loadEFLinearFixedAll(
                     ef,
                     obtainMessage(EVENT_ADN_LOAD_ALL_DONE));
 
@@ -122,7 +121,7 @@
         this.userResponse = response;
         this.pin2 = pin2;
 
-        phone.mIccFileHandler.getEFLinearRecordSize( ef,
+        mFh.getEFLinearRecordSize( ef,
             obtainMessage(EVENT_EF_LINEAR_RECORD_SIZE_DONE, adn));
     }
 
@@ -163,7 +162,7 @@
                                 ar.exception);
                     }
 
-                    phone.mIccFileHandler.updateEFLinearFixed(ef, recordNumber,
+                    mFh.updateEFLinearFixed(ef, recordNumber,
                             data, pin2, obtainMessage(EVENT_UPDATE_RECORD_DONE));
 
                     pendingExtLoads = 1;
@@ -203,7 +202,7 @@
 
                         pendingExtLoads = 1;
 
-                        phone.mIccFileHandler.loadEFLinearFixed(
+                        mFh.loadEFLinearFixed(
                             extensionEF, adn.extRecord,
                             obtainMessage(EVENT_EXT_RECORD_LOAD_DONE, adn));
                     }
@@ -253,7 +252,7 @@
 
                             pendingExtLoads++;
 
-                            phone.mIccFileHandler.loadEFLinearFixed(
+                            mFh.loadEFLinearFixed(
                                 extensionEF, adn.extRecord,
                                 obtainMessage(EVENT_EXT_RECORD_LOAD_DONE, adn));
                         }
diff --git a/telephony/java/com/android/internal/telephony/IccCard.java b/telephony/java/com/android/internal/telephony/IccCard.java
index 965bafa..2139917 100644
--- a/telephony/java/com/android/internal/telephony/IccCard.java
+++ b/telephony/java/com/android/internal/telephony/IccCard.java
@@ -37,6 +37,7 @@
 import com.android.internal.telephony.CommandsInterface.RadioState;
 import com.android.internal.telephony.gsm.SIMFileHandler;
 import com.android.internal.telephony.gsm.SIMRecords;
+import com.android.internal.telephony.cat.CatService;
 import com.android.internal.telephony.cdma.CDMALTEPhone;
 import com.android.internal.telephony.cdma.CdmaLteUiccFileHandler;
 import com.android.internal.telephony.cdma.CdmaLteUiccRecords;
@@ -65,6 +66,8 @@
     protected PhoneBase mPhone;
     private IccRecords mIccRecords;
     private IccFileHandler mIccFileHandler;
+    private CatService mCatService;
+
     private RegistrantList mAbsentRegistrants = new RegistrantList();
     private RegistrantList mPinLockedRegistrants = new RegistrantList();
     private RegistrantList mNetworkLockedRegistrants = new RegistrantList();
@@ -185,14 +188,17 @@
                 mPhone.mCM, mHandler, EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED, null);
         if (phone.mCM.getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE
                 && phone instanceof CDMALTEPhone) {
-            mIccRecords = new CdmaLteUiccRecords(phone);
             mIccFileHandler = new CdmaLteUiccFileHandler(this, "", mPhone.mCM);
+            mIccRecords = new CdmaLteUiccRecords(this, mPhone.mContext, mPhone.mCM);
         } else {
-            mIccRecords = is3gpp ? new SIMRecords(phone) : new RuimRecords(phone);
             // Correct aid will be set later (when GET_SIM_STATUS returns)
             mIccFileHandler = is3gpp ? new SIMFileHandler(this, "", mPhone.mCM) :
                                        new RuimFileHandler(this, "", mPhone.mCM);
+            mIccRecords = is3gpp ? new SIMRecords(this, mPhone.mContext, mPhone.mCM) :
+                                   new RuimRecords(this, mPhone.mContext, mPhone.mCM);
         }
+        mCatService = CatService.getInstance(mPhone.mCM, mIccRecords,
+                mPhone.mContext, mIccFileHandler, this);
         mPhone.mCM.registerForOffOrNotAvailable(mHandler, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
         mPhone.mCM.registerForOn(mHandler, EVENT_RADIO_ON, null);
         mPhone.mCM.registerForIccStatusChanged(mHandler, EVENT_ICC_STATUS_CHANGED, null);
@@ -203,6 +209,7 @@
         mPhone.mCM.unregisterForIccStatusChanged(mHandler);
         mPhone.mCM.unregisterForOffOrNotAvailable(mHandler);
         mPhone.mCM.unregisterForOn(mHandler);
+        mCatService.dispose();
         mCdmaSSM.dispose(mHandler);
         mIccRecords.dispose();
         mIccFileHandler.dispose();
diff --git a/telephony/java/com/android/internal/telephony/IccRecords.java b/telephony/java/com/android/internal/telephony/IccRecords.java
index 6e82903..41c9d5a 100644
--- a/telephony/java/com/android/internal/telephony/IccRecords.java
+++ b/telephony/java/com/android/internal/telephony/IccRecords.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.content.Context;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
@@ -32,9 +33,16 @@
 
     protected static final boolean DBG = true;
     // ***** Instance Variables
+    protected boolean mDestroyed = false; // set to true once this object needs to be disposed of
+    protected Context mContext;
+    protected CommandsInterface mCi;
+    protected IccFileHandler mFh;
+    protected IccCard mParentCard;
 
-    protected PhoneBase phone;
     protected RegistrantList recordsLoadedRegistrants = new RegistrantList();
+    protected RegistrantList mRecordsEventsRegistrants = new RegistrantList();
+    protected RegistrantList mNewSmsRegistrants = new RegistrantList();
+    protected RegistrantList mNetworkSelectionModeAutomaticRegistrants = new RegistrantList();
 
     protected int recordsToLoad;  // number of pending load requests
 
@@ -71,6 +79,9 @@
 
     // ***** Event Constants
     protected static final int EVENT_SET_MSISDN_DONE = 30;
+    public static final int EVENT_MWI = 0;
+    public static final int EVENT_CFI = 1;
+    public static final int EVENT_SPN = 2;
 
     public static final int EVENT_GET_ICC_RECORD_DONE = 100;
 
@@ -91,15 +102,23 @@
     }
 
     // ***** Constructor
-
-    public IccRecords(PhoneBase p) {
-        this.phone = p;
+    public IccRecords(IccCard card, Context c, CommandsInterface ci) {
+        mContext = c;
+        mCi = ci;
+        mFh = card.getIccFileHandler();
+        mParentCard = card;
     }
 
     /**
      * Call when the IccRecords object is no longer going to be used.
      */
-    public abstract void dispose();
+    public void dispose() {
+        mDestroyed = true;
+        mParentCard = null;
+        mFh = null;
+        mCi = null;
+        mContext = null;
+    }
 
     protected abstract void onRadioOffOrNotAvailable();
     public abstract void onReady();
@@ -109,7 +128,15 @@
         return adnCache;
     }
 
+    public IccCard getIccCard() {
+        return mParentCard;
+    }
+
     public void registerForRecordsLoaded(Handler h, int what, Object obj) {
+        if (mDestroyed) {
+            return;
+        }
+
         Registrant r = new Registrant(h, what, obj);
         recordsLoadedRegistrants.add(r);
 
@@ -117,11 +144,35 @@
             r.notifyRegistrant(new AsyncResult(null, null, null));
         }
     }
-
     public void unregisterForRecordsLoaded(Handler h) {
         recordsLoadedRegistrants.remove(h);
     }
 
+    public void registerForRecordsEvents(Handler h, int what, Object obj) {
+        Registrant r = new Registrant (h, what, obj);
+        mRecordsEventsRegistrants.add(r);
+    }
+    public void unregisterForRecordsEvents(Handler h) {
+        mRecordsEventsRegistrants.remove(h);
+    }
+
+    public void registerForNewSms(Handler h, int what, Object obj) {
+        Registrant r = new Registrant (h, what, obj);
+        mNewSmsRegistrants.add(r);
+    }
+    public void unregisterForNewSms(Handler h) {
+        mNewSmsRegistrants.remove(h);
+    }
+
+    public void registerForNetworkSelectionModeAutomatic(
+            Handler h, int what, Object obj) {
+        Registrant r = new Registrant (h, what, obj);
+        mNetworkSelectionModeAutomaticRegistrants.add(r);
+    }
+    public void unregisterForNetworkSelectionModeAutomatic(Handler h) {
+        mNetworkSelectionModeAutomaticRegistrants.remove(h);
+    }
+
     /**
      * Get the International Mobile Subscriber ID (IMSI) on a SIM
      * for GSM, UMTS and like networks. Default is null if IMSI is
@@ -163,7 +214,7 @@
 
         AdnRecord adn = new AdnRecord(msisdnTag, msisdn);
 
-        new AdnRecordLoader(phone).updateEF(adn, EF_MSISDN, EF_EXT1, 1, null,
+        new AdnRecordLoader(mFh).updateEF(adn, EF_MSISDN, EF_EXT1, 1, null,
                 obtainMessage(EVENT_SET_MSISDN_DONE, onComplete));
     }
 
diff --git a/telephony/java/com/android/internal/telephony/MccTable.java b/telephony/java/com/android/internal/telephony/MccTable.java
index cdce841..e206783 100644
--- a/telephony/java/com/android/internal/telephony/MccTable.java
+++ b/telephony/java/com/android/internal/telephony/MccTable.java
@@ -18,6 +18,7 @@
 
 import android.app.ActivityManagerNative;
 import android.app.AlarmManager;
+import android.app.IActivityManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.net.wifi.WifiManager;
@@ -167,10 +168,10 @@
     /**
      * Updates MCC and MNC device configuration information for application retrieving
      * correct version of resources.  If either MCC or MNC is 0, they will be ignored (not set).
-     * @param phone PhoneBae to act on.
+     * @param context Context to act on.
      * @param mccmnc truncated imsi with just the MCC and MNC - MNC assumed to be from 4th to end
      */
-    public static void updateMccMncConfiguration(PhoneBase phone, String mccmnc) {
+    public static void updateMccMncConfiguration(Context context, String mccmnc) {
         if (!TextUtils.isEmpty(mccmnc)) {
             int mcc, mnc;
 
@@ -185,9 +186,9 @@
             Log.d(LOG_TAG, "updateMccMncConfiguration: mcc=" + mcc + ", mnc=" + mnc);
 
             if (mcc != 0) {
-                setTimezoneFromMccIfNeeded(phone, mcc);
-                setLocaleFromMccIfNeeded(phone, mcc);
-                setWifiCountryCodeFromMcc(phone, mcc);
+                setTimezoneFromMccIfNeeded(context, mcc);
+                setLocaleFromMccIfNeeded(context, mcc);
+                setWifiCountryCodeFromMcc(context, mcc);
             }
             try {
                 Configuration config = ActivityManagerNative.getDefault().getConfiguration();
@@ -205,16 +206,68 @@
     }
 
     /**
+     * Utility code to set the system locale if it's not set already
+     * @param context Context to act on.
+     * @param language Two character language code desired
+     * @param country Two character country code desired
+     *
+     *  {@hide}
+     */
+    public static void setSystemLocale(Context context, String language, String country) {
+        String l = SystemProperties.get("persist.sys.language");
+        String c = SystemProperties.get("persist.sys.country");
+
+        if (null == language) {
+            return; // no match possible
+        }
+        language = language.toLowerCase();
+        if (null == country) {
+            country = "";
+        }
+        country = country.toUpperCase();
+
+        if((null == l || 0 == l.length()) && (null == c || 0 == c.length())) {
+            try {
+                // try to find a good match
+                String[] locales = context.getAssets().getLocales();
+                final int N = locales.length;
+                String bestMatch = null;
+                for(int i = 0; i < N; i++) {
+                    // only match full (lang + country) locales
+                    if (locales[i]!=null && locales[i].length() >= 5 &&
+                            locales[i].substring(0,2).equals(language)) {
+                        if (locales[i].substring(3,5).equals(country)) {
+                            bestMatch = locales[i];
+                            break;
+                        } else if (null == bestMatch) {
+                            bestMatch = locales[i];
+                        }
+                    }
+                }
+                if (null != bestMatch) {
+                    IActivityManager am = ActivityManagerNative.getDefault();
+                    Configuration config = am.getConfiguration();
+                    config.locale = new Locale(bestMatch.substring(0,2),
+                                               bestMatch.substring(3,5));
+                    config.userSetLocale = true;
+                    am.updateConfiguration(config);
+                }
+            } catch (Exception e) {
+                // Intentionally left blank
+            }
+        }
+    }
+
+    /**
      * If the timezone is not already set, set it based on the MCC of the SIM.
-     * @param phone PhoneBase to act on (get context from).
+     * @param context Context to act on.
      * @param mcc Mobile Country Code of the SIM or SIM-like entity (build prop on CDMA)
      */
-    private static void setTimezoneFromMccIfNeeded(PhoneBase phone, int mcc) {
+    private static void setTimezoneFromMccIfNeeded(Context context, int mcc) {
         String timezone = SystemProperties.get(ServiceStateTracker.TIMEZONE_PROPERTY);
         if (timezone == null || timezone.length() == 0) {
             String zoneId = defaultTimeZoneForMcc(mcc);
             if (zoneId != null && zoneId.length() > 0) {
-                Context context = phone.getContext();
                 // Set time zone based on MCC
                 AlarmManager alarm =
                         (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
@@ -226,27 +279,31 @@
 
     /**
      * If the locale is not already set, set it based on the MCC of the SIM.
-     * @param phone PhoneBase to act on.
+     * @param context Context to act on.
      * @param mcc Mobile Country Code of the SIM or SIM-like entity (build prop on CDMA)
      */
-    private static void setLocaleFromMccIfNeeded(PhoneBase phone, int mcc) {
+    private static void setLocaleFromMccIfNeeded(Context context, int mcc) {
+        if (BaseCommands.getLteOnCdmaModeStatic() == Phone.LTE_ON_CDMA_TRUE) {
+            // Avoid system locale is set from MCC table if CDMALTEPhone is used.
+            // The locale will be picked up based on EFpl/EFli once CSIM records are loaded.
+            return;
+        }
         String language = MccTable.defaultLanguageForMcc(mcc);
         String country = MccTable.countryCodeForMcc(mcc);
 
         Log.d(LOG_TAG, "locale set to "+language+"_"+country);
-        phone.setSystemLocale(language, country, true);
+        setSystemLocale(context, language, country);
     }
 
     /**
      * If the number of allowed wifi channels has not been set, set it based on
      * the MCC of the SIM.
-     * @param phone PhoneBase to act on (get context from).
+     * @param context Context to act on.
      * @param mcc Mobile Country Code of the SIM or SIM-like entity (build prop on CDMA)
      */
-    private static void setWifiCountryCodeFromMcc(PhoneBase phone, int mcc) {
+    private static void setWifiCountryCodeFromMcc(Context context, int mcc) {
         String country = MccTable.countryCodeForMcc(mcc);
         if (!country.isEmpty()) {
-            Context context = phone.getContext();
             Log.d(LOG_TAG, "WIFI_COUNTRY_CODE set to " + country);
             WifiManager wM = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
             //persist
diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java
index 0b5a82c..3aa53eef 100644
--- a/telephony/java/com/android/internal/telephony/PhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/PhoneBase.java
@@ -43,6 +43,7 @@
 import com.android.internal.telephony.gsm.SIMRecords;
 
 import java.util.Locale;
+import java.util.concurrent.atomic.AtomicReference;
 
 
 /**
@@ -103,6 +104,10 @@
     protected static final int EVENT_EMERGENCY_CALLBACK_MODE_ENTER  = 25;
     protected static final int EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE = 26;
     protected static final int EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED = 27;
+    // other
+    protected static final int EVENT_SET_NETWORK_AUTOMATIC          = 28;
+    protected static final int EVENT_NEW_ICC_SMS                    = 29;
+    protected static final int EVENT_ICC_RECORD_EVENTS              = 30;
 
     // Key used to read/write current CLIR setting
     public static final String CLIR_KEY = "clir_key";
@@ -112,7 +117,6 @@
 
     /* Instance Variables */
     public CommandsInterface mCM;
-    protected IccFileHandler mIccFileHandler;
     boolean mDnsCheckDisabled;
     public DataConnectionTracker mDataConnectionTracker;
     boolean mDoesRilSendMultipleCallRing;
@@ -121,7 +125,7 @@
     public boolean mIsTheCurrentActivePhone = true;
     boolean mIsVoiceCapable = true;
     public IccRecords mIccRecords;
-    public IccCard mIccCard;
+    protected AtomicReference<IccCard> mIccCard = new AtomicReference<IccCard>();
     public SmsStorageMonitor mSmsStorageMonitor;
     public SmsUsageMonitor mSmsUsageMonitor;
     public SMSDispatcher mSMS;
@@ -264,7 +268,7 @@
         mSmsUsageMonitor = null;
         mSMS = null;
         mIccRecords = null;
-        mIccCard = null;
+        mIccCard.set(null);
         mDataConnectionTracker = null;
     }
 
@@ -600,7 +604,7 @@
                 if (l.length() >=5) {
                     country = l.substring(3, 5);
                 }
-                setSystemLocale(language, country, false);
+                MccTable.setSystemLocale(mContext, language, country);
 
                 if (!country.isEmpty()) {
                     try {
@@ -619,62 +623,6 @@
     }
 
     /**
-     * Utility code to set the system locale if it's not set already
-     * @param language Two character language code desired
-     * @param country Two character country code desired
-     * @param fromMcc Indicating whether the locale is set according to MCC table.
-     *                This flag wil be ignored by default implementation.
-     *                TODO: Use a source enumeration so that source of the locale
-     *                      can be prioritized.
-     *
-     *  {@hide}
-     */
-    public void setSystemLocale(String language, String country, boolean fromMcc) {
-        String l = SystemProperties.get("persist.sys.language");
-        String c = SystemProperties.get("persist.sys.country");
-
-        if (null == language) {
-            return; // no match possible
-        }
-        language = language.toLowerCase();
-        if (null == country) {
-            country = "";
-        }
-        country = country.toUpperCase();
-
-        if((null == l || 0 == l.length()) && (null == c || 0 == c.length())) {
-            try {
-                // try to find a good match
-                String[] locales = mContext.getAssets().getLocales();
-                final int N = locales.length;
-                String bestMatch = null;
-                for(int i = 0; i < N; i++) {
-                    // only match full (lang + country) locales
-                    if (locales[i]!=null && locales[i].length() >= 5 &&
-                            locales[i].substring(0,2).equals(language)) {
-                        if (locales[i].substring(3,5).equals(country)) {
-                            bestMatch = locales[i];
-                            break;
-                        } else if (null == bestMatch) {
-                            bestMatch = locales[i];
-                        }
-                    }
-                }
-                if (null != bestMatch) {
-                    IActivityManager am = ActivityManagerNative.getDefault();
-                    Configuration config = am.getConfiguration();
-                    config.locale = new Locale(bestMatch.substring(0,2),
-                                               bestMatch.substring(3,5));
-                    config.userSetLocale = true;
-                    am.updateConfiguration(config);
-                }
-            } catch (Exception e) {
-                // Intentionally left blank
-            }
-        }
-    }
-
-    /**
      * Get state
      */
     public abstract Phone.State getState();
@@ -682,7 +630,11 @@
     /**
      * Retrieves the IccFileHandler of the Phone instance
      */
-    public abstract IccFileHandler getIccFileHandler();
+    public IccFileHandler getIccFileHandler(){
+        IccCard iccCard = mIccCard.get();
+        if (iccCard == null) return null;
+        return iccCard.getIccFileHandler();
+    }
 
     /*
      * Retrieves the Handler of the Phone instance
@@ -707,7 +659,7 @@
 
     @Override
     public IccCard getIccCard() {
-        return mIccCard;
+        return mIccCard.get();
     }
 
     @Override
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
index 14a4b46..110d8bf 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
@@ -35,6 +35,7 @@
 import com.android.internal.telephony.PhoneProxy;
 import com.android.internal.telephony.SMSDispatcher;
 import com.android.internal.telephony.gsm.GsmSMSDispatcher;
+import com.android.internal.telephony.gsm.SmsMessage;
 import com.android.internal.telephony.ims.IsimRecords;
 import com.android.internal.telephony.uicc.UiccController;
 
@@ -62,17 +63,21 @@
     public CDMALTEPhone(Context context, CommandsInterface ci, PhoneNotifier notifier) {
         super(context, ci, notifier, false);
         m3gppSMS = new GsmSMSDispatcher(this, mSmsStorageMonitor, mSmsUsageMonitor);
+        mIccRecords.registerForNewSms(this, EVENT_NEW_ICC_SMS, null);
     }
 
     @Override
     public void handleMessage (Message msg) {
         AsyncResult ar;
-        Message onComplete;
         switch (msg.what) {
             // handle the select network completion callbacks.
             case EVENT_SET_NETWORK_MANUAL_COMPLETE:
                 handleSetSelectNetwork((AsyncResult) msg.obj);
                 break;
+            case EVENT_NEW_ICC_SMS:
+                ar = (AsyncResult)msg.obj;
+                m3gppSMS.dispatchMessage((SmsMessage)ar.result);
+                break;
             default:
                 super.handleMessage(msg);
         }
@@ -80,9 +85,8 @@
 
     @Override
     protected void initSstIcc() {
-        mIccCard = UiccController.getInstance(this).getIccCard();
-        mIccRecords = mIccCard.getIccRecords();
-        mIccFileHandler = mIccCard.getIccFileHandler();
+        mIccCard.set(UiccController.getInstance(this).getIccCard());
+        mIccRecords = mIccCard.get().getIccRecords();
         // CdmaLteServiceStateTracker registers with IccCard to know
         // when the card is ready. So create mIccCard before the ServiceStateTracker
         mSST = new CdmaLteServiceStateTracker(this);
@@ -93,6 +97,7 @@
         synchronized(PhoneProxy.lockForRadioTechnologyChange) {
             super.dispose();
             m3gppSMS.dispose();
+            mIccRecords.unregisterForNewSms(this);
         }
     }
 
@@ -214,15 +219,6 @@
         return false;
     }
 
-    @Override
-    public void setSystemLocale(String language, String country, boolean fromMcc) {
-        // Avoid system locale is set from MCC table if CDMALTEPhone is used.
-        // The locale will be picked up based on EFpl/EFli once CSIM records are loaded.
-        if (fromMcc) return;
-
-        super.setSystemLocale(language, country, false);
-    }
-
     // return IMSI from USIM as subscriber ID.
     @Override
     public String getSubscriberId() {
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
index e86e441..bb00d4b 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -103,7 +103,6 @@
     PhoneSubInfo mSubInfo;
     EriManager mEriManager;
     WakeLock mWakeLock;
-    CatService mCcatService;
 
     // mEriFileLoadedRegistrants are informed after the ERI text has been loaded
     private final RegistrantList mEriFileLoadedRegistrants = new RegistrantList();
@@ -150,9 +149,8 @@
     }
 
     protected void initSstIcc() {
-        mIccCard = UiccController.getInstance(this).getIccCard();
-        mIccRecords = mIccCard.getIccRecords();
-        mIccFileHandler = mIccCard.getIccFileHandler();
+        mIccCard.set(UiccController.getInstance(this).getIccCard());
+        mIccRecords = mIccCard.get().getIccRecords();
         // CdmaServiceStateTracker registers with IccCard to know
         // when the Ruim card is ready. So create mIccCard before the ServiceStateTracker
         mSST = new CdmaServiceStateTracker(this);
@@ -169,11 +167,9 @@
         mRuimSmsInterfaceManager = new RuimSmsInterfaceManager(this, mSMS);
         mSubInfo = new PhoneSubInfo(this);
         mEriManager = new EriManager(this, context, EriManager.ERI_FROM_XML);
-        mCcatService = CatService.getInstance(mCM, mIccRecords, mContext,
-                mIccFileHandler, mIccCard);
 
         mCM.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
-        mIccRecords.registerForRecordsLoaded(this, EVENT_RUIM_RECORDS_LOADED, null);
+        registerForRuimRecordEvents();
         mCM.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
         mCM.registerForOn(this, EVENT_RADIO_ON, null);
         mCM.setOnSuppServiceNotification(this, EVENT_SSN, null);
@@ -225,7 +221,7 @@
             log("dispose");
 
             //Unregister from all former registered events
-            mIccRecords.unregisterForRecordsLoaded(this); //EVENT_RUIM_RECORDS_LOADED
+            unregisterForRuimRecordEvents();
             mCM.unregisterForAvailable(this); //EVENT_RADIO_AVAILABLE
             mCM.unregisterForOffOrNotAvailable(this); //EVENT_RADIO_OFF_OR_NOT_AVAILABLE
             mCM.unregisterForOn(this); //EVENT_RADIO_ON
@@ -241,29 +237,24 @@
             mSST.dispose();
             mCdmaSSM.dispose(this);
             mSMS.dispose();
-            mIccFileHandler.dispose(); // instance of RuimFileHandler
-            mIccRecords.dispose();
             mRuimPhoneBookInterfaceManager.dispose();
             mRuimSmsInterfaceManager.dispose();
             mSubInfo.dispose();
             mEriManager.dispose();
-            mCcatService.dispose();
         }
     }
 
     @Override
     public void removeReferences() {
         log("removeReferences");
-        super.removeReferences();
         mRuimPhoneBookInterfaceManager = null;
         mRuimSmsInterfaceManager = null;
         mSubInfo = null;
-        mIccFileHandler = null;
         mCT = null;
         mSST = null;
         mEriManager = null;
-        mCcatService = null;
         mExitEcmRunnable = null;
+        super.removeReferences();
     }
 
     @Override
@@ -999,6 +990,11 @@
             }
             break;
 
+            case EVENT_ICC_RECORD_EVENTS:
+                ar = (AsyncResult)msg.obj;
+                processIccRecordEvents((Integer)ar.result);
+                break;
+
             case  EVENT_EXIT_EMERGENCY_CALLBACK_RESPONSE:{
                 handleExitEmergencyCallbackMode(msg);
             }
@@ -1063,10 +1059,22 @@
         }
     }
 
+    private void processIccRecordEvents(int eventCode) {
+        switch (eventCode) {
+            case RuimRecords.EVENT_MWI:
+                notifyMessageWaitingIndicator();
+                break;
+
+            default:
+                Log.e(LOG_TAG,"Unknown icc records event code " + eventCode);
+                break;
+        }
+    }
+
     /**
      * Handles the call to get the subscription source
      *
-     * @param holds the new CDMA subscription source value
+     * @param newSubscriptionSource holds the new CDMA subscription source value
      */
     private void handleCdmaSubscriptionSource(int newSubscriptionSource) {
         if (newSubscriptionSource != mCdmaSubscriptionSource) {
@@ -1117,13 +1125,6 @@
     }
 
     /**
-     * {@inheritDoc}
-     */
-    public IccFileHandler getIccFileHandler() {
-        return this.mIccFileHandler;
-    }
-
-    /**
      * Activate or deactivate cell broadcast SMS.
      *
      * @param activate 0 = activate, 1 = deactivate
@@ -1423,7 +1424,7 @@
                 getContext().getContentResolver().insert(uri, map);
 
                 // Updates MCC MNC device configuration information
-                MccTable.updateMccMncConfiguration(this, operatorNumeric);
+                MccTable.updateMccMncConfiguration(mContext, operatorNumeric);
 
                 return true;
             } catch (SQLException e) {
@@ -1456,6 +1457,16 @@
         return mEriManager.isEriFileLoaded();
     }
 
+    private void registerForRuimRecordEvents() {
+        mIccRecords.registerForRecordsEvents(this, EVENT_ICC_RECORD_EVENTS, null);
+        mIccRecords.registerForRecordsLoaded(this, EVENT_RUIM_RECORDS_LOADED, null);
+    }
+
+    private void unregisterForRuimRecordEvents() {
+        mIccRecords.unregisterForRecordsEvents(this);
+        mIccRecords.unregisterForRecordsLoaded(this);
+    }
+
     protected void log(String s) {
         if (DBG)
             Log.d(LOG_TAG, "[CDMAPhone] " + s);
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
index cad2e22..09008cd 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
@@ -349,7 +349,7 @@
                 ss.setOperatorAlphaLong(eriText);
             }
 
-            if (phone.mIccCard.getState() == IccCard.State.READY) {
+            if (phone.getIccCard().getState() == IccCard.State.READY) {
                 // SIM is found on the device. If ERI roaming is OFF, and SID/NID matches
                 // one configfured in SIM, use operator name  from CSIM record.
                 boolean showSpn =
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccRecords.java b/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccRecords.java
index ca1e96d..eaa2ede 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccRecords.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaLteUiccRecords.java
@@ -15,6 +15,7 @@
  */
 package com.android.internal.telephony.cdma;
 
+import android.content.Context;
 import android.os.AsyncResult;
 import android.os.SystemProperties;
 import android.util.Log;
@@ -22,6 +23,8 @@
 import com.android.internal.telephony.AdnRecordLoader;
 import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.IccCardApplication.AppType;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.IccCard;
 import com.android.internal.telephony.IccFileHandler;
 import com.android.internal.telephony.IccUtils;
 import com.android.internal.telephony.MccTable;
@@ -54,8 +57,8 @@
 
     private final IsimUiccRecords mIsimUiccRecords = new IsimUiccRecords();
 
-    public CdmaLteUiccRecords(PhoneBase p) {
-        super(p);
+    public CdmaLteUiccRecords(IccCard card, Context c, CommandsInterface ci) {
+        super(card, c, ci);
     }
 
     // Refer to ETSI TS 102.221
@@ -146,7 +149,7 @@
             }
             if (DBG) log("spn=" + spn);
             if (DBG) log("spnCondition=" + mCsimSpnDisplayCondition);
-            phone.setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, spn);
+            SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, spn);
         }
     }
 
@@ -262,55 +265,54 @@
 
     @Override
     protected void fetchSimRecords() {
-        IccFileHandler iccFh = phone.getIccFileHandler();
         recordsRequested = true;
 
-        phone.mCM.getIMSI(obtainMessage(EVENT_GET_IMSI_DONE));
+        mCi.getIMSIForApp(mParentCard.getAid(), obtainMessage(EVENT_GET_IMSI_DONE));
         recordsToLoad++;
 
-        iccFh.loadEFTransparent(EF_ICCID, obtainMessage(EVENT_GET_ICCID_DONE));
+        mFh.loadEFTransparent(EF_ICCID, obtainMessage(EVENT_GET_ICCID_DONE));
         recordsToLoad++;
 
-        iccFh.loadEFTransparent(EF_AD, obtainMessage(EVENT_GET_AD_DONE));
+        mFh.loadEFTransparent(EF_AD, obtainMessage(EVENT_GET_AD_DONE));
         recordsToLoad++;
 
-        iccFh.loadEFTransparent(EF_PL,
+        mFh.loadEFTransparent(EF_PL,
                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfPlLoaded()));
         recordsToLoad++;
 
-        new AdnRecordLoader(phone).loadFromEF(EF_MSISDN, EF_EXT1, 1,
+        new AdnRecordLoader(mFh).loadFromEF(EF_MSISDN, EF_EXT1, 1,
                 obtainMessage(EVENT_GET_MSISDN_DONE));
         recordsToLoad++;
 
-        iccFh.loadEFTransparent(EF_SST, obtainMessage(EVENT_GET_SST_DONE));
+        mFh.loadEFTransparent(EF_SST, obtainMessage(EVENT_GET_SST_DONE));
         recordsToLoad++;
 
-        iccFh.loadEFTransparent(EF_CSIM_LI,
+        mFh.loadEFTransparent(EF_CSIM_LI,
                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimLiLoaded()));
         recordsToLoad++;
 
-        iccFh.loadEFTransparent(EF_CSIM_SPN,
+        mFh.loadEFTransparent(EF_CSIM_SPN,
                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimSpnLoaded()));
         recordsToLoad++;
 
-        iccFh.loadEFLinearFixed(EF_CSIM_MDN, 1,
+        mFh.loadEFLinearFixed(EF_CSIM_MDN, 1,
                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimMdnLoaded()));
         recordsToLoad++;
 
-        iccFh.loadEFTransparent(EF_CSIM_IMSIM,
+        mFh.loadEFTransparent(EF_CSIM_IMSIM,
                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimImsimLoaded()));
         recordsToLoad++;
 
-        iccFh.loadEFLinearFixedAll(EF_CSIM_CDMAHOME,
+        mFh.loadEFLinearFixedAll(EF_CSIM_CDMAHOME,
                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimCdmaHomeLoaded()));
         recordsToLoad++;
 
-        iccFh.loadEFTransparent(EF_CSIM_EPRL,
+        mFh.loadEFTransparent(EF_CSIM_EPRL,
                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimEprlLoaded()));
         recordsToLoad++;
 
         // load ISIM records
-        recordsToLoad += mIsimUiccRecords.fetchIsimRecords(iccFh, this);
+        recordsToLoad += mIsimUiccRecords.fetchIsimRecords(mFh, this);
     }
 
     private int adjstMinDigits (int digits) {
@@ -354,7 +356,7 @@
                                     Integer.parseInt(imsi.substring(0,3)));
             }
             log("Setting locale to " + prefLang + "_" + country);
-            phone.setSystemLocale(prefLang, country, false);
+            MccTable.setSystemLocale(mContext, prefLang, country);
         } else {
             log ("No suitable CSIM selected locale");
         }
@@ -362,7 +364,7 @@
 
     private String findBestLanguage(byte[] languages) {
         String bestMatch = null;
-        String[] locales = phone.getContext().getAssets().getLocales();
+        String[] locales = mContext.getAssets().getLocales();
 
         if ((languages == null) || (locales == null)) return null;
 
@@ -436,23 +438,14 @@
             return true;
         }
 
-        if (phone == null || phone.mIccCard == null) {
+        if (mParentCard == null) {
             return false;
         }
 
-        if (phone.mIccCard.isApplicationOnIcc(AppType.APPTYPE_CSIM) &&
+        if (mParentCard.isApplicationOnIcc(AppType.APPTYPE_CSIM) &&
             ((mMdn == null) || (mMin == null))) {
             return false;
         }
         return true;
     }
-
-    /**
-     * Dispatch 3GPP format message. For CDMA/LTE phones,
-     * send the message to the secondary 3GPP format SMS dispatcher.
-     */
-    @Override
-    protected int dispatchGsmMessage(SmsMessageBase message) {
-        return ((CDMALTEPhone) phone).m3gppSMS.dispatchMessage(message);
-    }
 }
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
index 75f5d47..9ec56fc 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
@@ -209,8 +209,7 @@
         // Unregister for all events.
         cm.unregisterForRadioStateChanged(this);
         cm.unregisterForVoiceNetworkStateChanged(this);
-        phone.mIccCard.unregisterForReady(this);
-
+        phone.getIccCard().unregisterForReady(this);
         cm.unregisterForCdmaOtaProvision(this);
         phone.unregisterForEriFileLoaded(this);
         phone.mIccRecords.unregisterForRecordsLoaded(this);
@@ -498,7 +497,7 @@
             // NV is ready when subscription source is NV
             sendMessage(obtainMessage(EVENT_NV_READY));
         } else {
-            phone.mIccCard.registerForReady(this, EVENT_RUIM_READY, null);
+            phone.getIccCard().registerForReady(this, EVENT_RUIM_READY, null);
         }
     }
 
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimFileHandler.java b/telephony/java/com/android/internal/telephony/cdma/RuimFileHandler.java
index 9f30d36..f440935 100644
--- a/telephony/java/com/android/internal/telephony/cdma/RuimFileHandler.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimFileHandler.java
@@ -45,9 +45,6 @@
         super(card, aid, ci);
     }
 
-    public void dispose() {
-    }
-
     protected void finalize() {
         Log.d(LOG_TAG, "RuimFileHandler finalized");
     }
diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java b/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java
index 265dff7..3855515 100755
--- a/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java
+++ b/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY;
 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC;
+import android.content.Context;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
@@ -78,19 +79,19 @@
     private static final int EVENT_RUIM_REFRESH = 31;
 
 
-    public RuimRecords(PhoneBase p) {
-        super(p);
+    public RuimRecords(IccCard card, Context c, CommandsInterface ci) {
+        super(card, c, ci);
 
-        adnCache = new AdnRecordCache(phone);
+        adnCache = new AdnRecordCache(mFh);
 
         recordsRequested = false;  // No load request is made till SIM ready
 
         // recordsToLoad is set to 0 because no requests are made yet
         recordsToLoad = 0;
 
-        p.mCM.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
+        mCi.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
         // NOTE the EVENT_SMS_ON_RUIM is not registered
-        p.mCM.registerForIccRefresh(this, EVENT_RUIM_REFRESH, null);
+        mCi.registerForIccRefresh(this, EVENT_RUIM_REFRESH, null);
 
         // Start off by setting empty state
         onRadioOffOrNotAvailable();
@@ -99,9 +100,11 @@
 
     @Override
     public void dispose() {
+        if (DBG) log("Disposing RuimRecords " + this);
         //Unregister for all events
-        phone.mCM.unregisterForOffOrNotAvailable( this);
-        phone.mCM.unregisterForIccRefresh(this);
+        mCi.unregisterForOffOrNotAvailable( this);
+        mCi.unregisterForIccRefresh(this);
+        super.dispose();
     }
 
     @Override
@@ -196,7 +199,7 @@
 
         boolean isRecordLoadResponse = false;
 
-        if (!phone.mIsTheCurrentActivePhone) {
+        if (mDestroyed) {
             loge("Received message " + msg +
                     "[" + msg.what + "] while being destroyed. Ignoring.");
             return;
@@ -235,7 +238,7 @@
                 String operatorNumeric = getRUIMOperatorNumeric();
                 if (operatorNumeric != null) {
                     if(operatorNumeric.length() <= 6){
-                        MccTable.updateMccMncConfiguration(phone, operatorNumeric);
+                        MccTable.updateMccMncConfiguration(mContext, operatorNumeric);
                     }
                 }
             break;
@@ -314,6 +317,7 @@
         // One record loaded successfully or failed, In either case
         // we need to update the recordsToLoad count
         recordsToLoad -= 1;
+        if (DBG) log("RuimRecords:onRecordLoaded " + recordsToLoad + " requested: " + recordsRequested);
 
         if (recordsToLoad == 0 && recordsRequested == true) {
             onAllRecordsLoaded();
@@ -338,7 +342,7 @@
         }
         recordsLoadedRegistrants.notifyRegistrants(
             new AsyncResult(null, null, null));
-        phone.mIccCard.broadcastIccStateChangedIntent(
+        mParentCard.broadcastIccStateChangedIntent(
                 IccCard.INTENT_VALUE_ICC_LOADED, null);
     }
 
@@ -348,13 +352,12 @@
           READY is sent before IMSI ready
         */
 
-        phone.mIccCard.broadcastIccStateChangedIntent(
+        mParentCard.broadcastIccStateChangedIntent(
                 IccCard.INTENT_VALUE_ICC_READY, null);
 
         fetchRuimRecords();
 
-        phone.mCM.getCDMASubscription(obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_DONE));
-
+        mCi.getCDMASubscription(obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_DONE));
     }
 
 
@@ -363,13 +366,14 @@
 
         Log.v(LOG_TAG, "RuimRecords:fetchRuimRecords " + recordsToLoad);
 
-        phone.mCM.getIMSI(obtainMessage(EVENT_GET_IMSI_DONE));
+        mCi.getIMSI(obtainMessage(EVENT_GET_IMSI_DONE));
         recordsToLoad++;
 
-        phone.getIccFileHandler().loadEFTransparent(EF_ICCID,
+        mFh.loadEFTransparent(EF_ICCID,
                 obtainMessage(EVENT_GET_ICCID_DONE));
         recordsToLoad++;
 
+        log("RuimRecords:fetchRuimRecords " + recordsToLoad + " requested: " + recordsRequested);
         // Further records that can be inserted are Operator/OEM dependent
     }
 
@@ -401,7 +405,7 @@
         }
         countVoiceMessages = countWaiting;
 
-        ((CDMAPhone) phone).notifyMessageWaitingIndicator();
+        mRecordsEventsRegistrants.notifyResult(EVENT_MWI);
     }
 
     private void handleRuimRefresh(IccRefreshResponse refreshResponse) {
@@ -411,7 +415,7 @@
         }
 
         if (refreshResponse.aid != null &&
-                !refreshResponse.aid.equals(phone.getIccCard().getAid())) {
+                !refreshResponse.aid.equals(mParentCard.getAid())) {
             // This is for different app. Ignore.
             return;
         }
@@ -429,7 +433,7 @@
                 break;
             case IccRefreshResponse.REFRESH_RESULT_RESET:
                 if (DBG) log("handleRuimRefresh with SIM_REFRESH_RESET");
-                phone.mCM.setRadioPower(false, null);
+                mCi.setRadioPower(false, null);
                 /* Note: no need to call setRadioPower(true).  Assuming the desired
                 * radio power state is still ON (as tracked by ServiceStateTracker),
                 * ServiceStateTracker will call setRadioPower when it receives the
diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
index 5e9a4f2..be13c35 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -101,7 +101,6 @@
     // Instance Variables
     GsmCallTracker mCT;
     GsmServiceStateTracker mSST;
-    CatService mStkService;
     ArrayList <GsmMmiCode> mPendingMMIs = new ArrayList<GsmMmiCode>();
     SimPhoneBookInterfaceManager mSimPhoneBookIntManager;
     SimSmsInterfaceManager mSimSmsIntManager;
@@ -137,22 +136,20 @@
         }
 
         mCM.setPhoneType(Phone.PHONE_TYPE_GSM);
-        mIccCard = UiccController.getInstance(this).getIccCard();
+        mIccCard.set(UiccController.getInstance(this).getIccCard());
+        mIccRecords = mIccCard.get().getIccRecords();
         mCT = new GsmCallTracker(this);
         mSST = new GsmServiceStateTracker (this);
         mSMS = new GsmSMSDispatcher(this, mSmsStorageMonitor, mSmsUsageMonitor);
-        mIccFileHandler = mIccCard.getIccFileHandler();
-        mIccRecords = mIccCard.getIccRecords();
         mDataConnectionTracker = new GsmDataConnectionTracker (this);
         if (!unitTestMode) {
             mSimPhoneBookIntManager = new SimPhoneBookInterfaceManager(this);
             mSimSmsIntManager = new SimSmsInterfaceManager(this, mSMS);
             mSubInfo = new PhoneSubInfo(this);
         }
-        mStkService = CatService.getInstance(mCM, mIccRecords, mContext, mIccFileHandler, mIccCard);
 
         mCM.registerForAvailable(this, EVENT_RADIO_AVAILABLE, null);
-        mIccRecords.registerForRecordsLoaded(this, EVENT_SIM_RECORDS_LOADED, null);
+        registerForSimRecordEvents();
         mCM.registerForOffOrNotAvailable(this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
         mCM.registerForOn(this, EVENT_RADIO_ON, null);
         mCM.setOnUSSD(this, EVENT_USSD, null);
@@ -205,7 +202,7 @@
 
             //Unregister from all former registered events
             mCM.unregisterForAvailable(this); //EVENT_RADIO_AVAILABLE
-            mIccRecords.unregisterForRecordsLoaded(this); //EVENT_SIM_RECORDS_LOADED
+            unregisterForSimRecordEvents();
             mCM.unregisterForOffOrNotAvailable(this); //EVENT_RADIO_OFF_OR_NOT_AVAILABLE
             mCM.unregisterForOn(this); //EVENT_RADIO_ON
             mSST.unregisterForNetworkAttached(this); //EVENT_REGISTERED_TO_NETWORK
@@ -215,12 +212,9 @@
             mPendingMMIs.clear();
 
             //Force all referenced classes to unregister their former registered events
-            mStkService.dispose();
             mCT.dispose();
             mDataConnectionTracker.dispose();
             mSST.dispose();
-            mIccFileHandler.dispose(); // instance of SimFileHandler
-            mIccRecords.dispose();
             mSimPhoneBookIntManager.dispose();
             mSimSmsIntManager.dispose();
             mSubInfo.dispose();
@@ -230,15 +224,13 @@
     @Override
     public void removeReferences() {
         Log.d(LOG_TAG, "removeReferences");
-        super.removeReferences();
         mSimulatedRadioControl = null;
-        mStkService = null;
         mSimPhoneBookIntManager = null;
         mSimSmsIntManager = null;
         mSubInfo = null;
-        mIccFileHandler = null;
         mCT = null;
         mSST = null;
+        super.removeReferences();
     }
 
     protected void finalize() {
@@ -1288,6 +1280,21 @@
                 }
                 break;
 
+            case EVENT_NEW_ICC_SMS:
+                ar = (AsyncResult)msg.obj;
+                mSMS.dispatchMessage((SmsMessage)ar.result);
+                break;
+
+            case EVENT_SET_NETWORK_AUTOMATIC:
+                ar = (AsyncResult)msg.obj;
+                setNetworkSelectionModeAutomatic((Message)ar.result);
+                break;
+
+            case EVENT_ICC_RECORD_EVENTS:
+                ar = (AsyncResult)msg.obj;
+                processIccRecordEvents((Integer)ar.result);
+                break;
+
             // handle the select network completion callbacks.
             case EVENT_SET_NETWORK_MANUAL_COMPLETE:
             case EVENT_SET_NETWORK_AUTOMATIC_COMPLETE:
@@ -1311,7 +1318,18 @@
         }
     }
 
-    /**
+    private void processIccRecordEvents(int eventCode) {
+        switch (eventCode) {
+            case SIMRecords.EVENT_CFI:
+                notifyCallForwardingIndicator();
+                break;
+            case SIMRecords.EVENT_MWI:
+                notifyMessageWaitingIndicator();
+                break;
+        }
+    }
+
+   /**
      * Sets the "current" field in the telephony provider according to the SIM's operator
      *
      * @return true for success; false otherwise.
@@ -1420,13 +1438,6 @@
     }
 
     /**
-     * {@inheritDoc}
-     */
-    public IccFileHandler getIccFileHandler(){
-        return this.mIccFileHandler;
-    }
-
-    /**
      * Activate or deactivate cell broadcast SMS.
      *
      * @param activate 0 = activate, 1 = deactivate
@@ -1460,4 +1471,20 @@
     public boolean isCspPlmnEnabled() {
         return mIccRecords.isCspPlmnEnabled();
     }
+
+    private void registerForSimRecordEvents() {
+        mIccRecords.registerForNetworkSelectionModeAutomatic(
+                this, EVENT_SET_NETWORK_AUTOMATIC, null);
+        mIccRecords.registerForNewSms(this, EVENT_NEW_ICC_SMS, null);
+        mIccRecords.registerForRecordsEvents(this, EVENT_ICC_RECORD_EVENTS, null);
+        mIccRecords.registerForRecordsLoaded(this, EVENT_SIM_RECORDS_LOADED, null);
+    }
+
+    private void unregisterForSimRecordEvents() {
+        mIccRecords.unregisterForNetworkSelectionModeAutomatic(this);
+        mIccRecords.unregisterForNewSms(this);
+        mIccRecords.unregisterForRecordsEvents(this);
+        mIccRecords.unregisterForRecordsLoaded(this);
+    }
+
 }
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmMmiCode.java b/telephony/java/com/android/internal/telephony/gsm/GsmMmiCode.java
index 205c73d..9b3d5cd 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmMmiCode.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmMmiCode.java
@@ -765,7 +765,7 @@
                         // invalid length
                         handlePasswordError(com.android.internal.R.string.invalidPin);
                     } else if (sc.equals(SC_PIN) &&
-                               phone.mIccCard.getState() == IccCard.State.PUK_REQUIRED ) {
+                               phone.getIccCard().getState() == IccCard.State.PUK_REQUIRED ) {
                         // Sim is puk-locked
                         handlePasswordError(com.android.internal.R.string.needPuk);
                     } else {
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
index 148b139..080d90c 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -211,7 +211,7 @@
         cm.setOnNITZTime(this, EVENT_NITZ_TIME, null);
         cm.setOnSignalStrengthUpdate(this, EVENT_SIGNAL_STRENGTH_UPDATE, null);
         cm.setOnRestrictedStateChanged(this, EVENT_RESTRICTED_STATE_CHANGED, null);
-        phone.mIccCard.registerForReady(this, EVENT_SIM_READY, null);
+        phone.getIccCard().registerForReady(this, EVENT_SIM_READY, null);
 
         // system setting property AIRPLANE_MODE_ON is set in Settings.
         int airplaneMode = Settings.System.getInt(
@@ -244,7 +244,7 @@
         cm.unregisterForAvailable(this);
         cm.unregisterForRadioStateChanged(this);
         cm.unregisterForVoiceNetworkStateChanged(this);
-        phone.mIccCard.unregisterForReady(this);
+        phone.getIccCard().unregisterForReady(this);
         phone.mIccRecords.unregisterForRecordsLoaded(this);
         cm.unSetOnSignalStrengthUpdate(this);
         cm.unSetOnRestrictedStateChanged(this);
diff --git a/telephony/java/com/android/internal/telephony/gsm/SIMFileHandler.java b/telephony/java/com/android/internal/telephony/gsm/SIMFileHandler.java
index 455ee74..dcc9cfd 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SIMFileHandler.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SIMFileHandler.java
@@ -40,10 +40,6 @@
         super(card, aid, ci);
     }
 
-    public void dispose() {
-        super.dispose();
-    }
-
     protected void finalize() {
         Log.d(LOG_TAG, "SIMFileHandler finalized");
     }
diff --git a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
index 68d3b2a..b88af2c 100755
--- a/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SIMRecords.java
@@ -21,6 +21,7 @@
 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC;
 import android.content.Context;
 import android.os.AsyncResult;
+import android.os.Handler;
 import android.os.Message;
 import android.os.SystemProperties;
 import android.util.Log;
@@ -174,10 +175,10 @@
 
     // ***** Constructor
 
-    public SIMRecords(PhoneBase p) {
-        super(p);
+    public SIMRecords(IccCard card, Context c, CommandsInterface ci) {
+        super(card, c, ci);
 
-        adnCache = new AdnRecordCache(phone);
+        adnCache = new AdnRecordCache(mFh);
 
         mVmConfig = new VoiceMailConstants();
         mSpnOverride = new SpnOverride();
@@ -187,10 +188,10 @@
         // recordsToLoad is set to 0 because no requests are made yet
         recordsToLoad = 0;
 
-        p.mCM.registerForOffOrNotAvailable(
+        mCi.registerForOffOrNotAvailable(
                         this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
-        p.mCM.setOnSmsOnSim(this, EVENT_SMS_ON_SIM, null);
-        p.mCM.registerForIccRefresh(this, EVENT_SIM_REFRESH, null);
+        mCi.setOnSmsOnSim(this, EVENT_SMS_ON_SIM, null);
+        mCi.registerForIccRefresh(this, EVENT_SIM_REFRESH, null);
 
         // Start off by setting empty state
         onRadioOffOrNotAvailable();
@@ -199,9 +200,12 @@
 
     @Override
     public void dispose() {
+        if (DBG) log("Disposing SIMRecords " + this);
         //Unregister for all events
-        phone.mCM.unregisterForOffOrNotAvailable( this);
-        phone.mCM.unregisterForIccRefresh(this);
+        mCi.unregisterForOffOrNotAvailable( this);
+        mCi.unregisterForIccRefresh(this);
+        mCi.unSetOnSmsOnSim(this);
+        super.dispose();
     }
 
     protected void finalize() {
@@ -224,9 +228,9 @@
 
         adnCache.reset();
 
-        phone.setSystemProperty(PROPERTY_ICC_OPERATOR_NUMERIC, null);
-        phone.setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, null);
-        phone.setSystemProperty(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, null);
+        SystemProperties.set(PROPERTY_ICC_OPERATOR_NUMERIC, null);
+        SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, null);
+        SystemProperties.set(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, null);
 
         // recordsRequested is set to false indicating that the SIM
         // read requests made so far are not valid. This is set to
@@ -280,7 +284,7 @@
 
         AdnRecord adn = new AdnRecord(msisdnTag, msisdn);
 
-        new AdnRecordLoader(phone).updateEF(adn, EF_MSISDN, EF_EXT1, 1, null,
+        new AdnRecordLoader(mFh).updateEF(adn, EF_MSISDN, EF_EXT1, 1, null,
                 obtainMessage(EVENT_SET_MSISDN_DONE, onComplete));
     }
 
@@ -332,13 +336,13 @@
 
         if (mailboxIndex != 0 && mailboxIndex != 0xff) {
 
-            new AdnRecordLoader(phone).updateEF(adn, EF_MBDN, EF_EXT6,
+            new AdnRecordLoader(mFh).updateEF(adn, EF_MBDN, EF_EXT6,
                     mailboxIndex, null,
                     obtainMessage(EVENT_SET_MBDN_DONE, onComplete));
 
         } else if (isCphsMailboxEnabled()) {
 
-            new AdnRecordLoader(phone).updateEF(adn, EF_MAILBOX_CPHS,
+            new AdnRecordLoader(mFh).updateEF(adn, EF_MAILBOX_CPHS,
                     EF_EXT1, 1, null,
                     obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE, onComplete));
 
@@ -379,7 +383,7 @@
 
         countVoiceMessages = countWaiting;
 
-        phone.notifyMessageWaitingIndicator();
+        mRecordsEventsRegistrants.notifyResult(EVENT_MWI);
 
         try {
             if (efMWIS != null) {
@@ -398,7 +402,7 @@
                     efMWIS[1] = (byte) countWaiting;
                 }
 
-                phone.getIccFileHandler().updateEFLinearFixed(
+                mFh.updateEFLinearFixed(
                     EF_MWIS, 1, efMWIS, null,
                     obtainMessage (EVENT_UPDATE_DONE, EF_MWIS));
             }
@@ -408,7 +412,7 @@
                 efCPHS_MWI[0] = (byte)((efCPHS_MWI[0] & 0xf0)
                             | (countVoiceMessages == 0 ? 0x5 : 0xa));
 
-                phone.getIccFileHandler().updateEFTransparent(
+                mFh.updateEFTransparent(
                     EF_VOICE_MAIL_INDICATOR_CPHS, efCPHS_MWI,
                     obtainMessage (EVENT_UPDATE_DONE, EF_VOICE_MAIL_INDICATOR_CPHS));
             }
@@ -435,7 +439,7 @@
 
         callForwardingEnabled = enable;
 
-        phone.notifyCallForwardingIndicator();
+        mRecordsEventsRegistrants.notifyResult(EVENT_CFI);
 
         try {
             if (mEfCfis != null) {
@@ -449,7 +453,7 @@
                 // TODO: Should really update other fields in EF_CFIS, eg,
                 // dialing number.  We don't read or use it right now.
 
-                phone.getIccFileHandler().updateEFLinearFixed(
+                mFh.updateEFLinearFixed(
                         EF_CFIS, 1, mEfCfis, null,
                         obtainMessage (EVENT_UPDATE_DONE, EF_CFIS));
             }
@@ -463,7 +467,7 @@
                             | CFF_UNCONDITIONAL_DEACTIVE);
                 }
 
-                phone.getIccFileHandler().updateEFTransparent(
+                mFh.updateEFTransparent(
                         EF_CFF_CPHS, mEfCff,
                         obtainMessage (EVENT_UPDATE_DONE, EF_CFF_CPHS));
             }
@@ -516,7 +520,7 @@
 
         boolean isRecordLoadResponse = false;
 
-        if (!phone.mIsTheCurrentActivePhone) {
+        if (mDestroyed) {
             loge("Received message " + msg + "[" + msg.what + "] " +
                     " while being destroyed. Ignoring.");
             return;
@@ -574,9 +578,9 @@
 
                 if (mncLength != UNKNOWN && mncLength != UNINITIALIZED) {
                     // finally have both the imsi and the mncLength and can parse the imsi properly
-                    MccTable.updateMccMncConfiguration(phone, imsi.substring(0, 3 + mncLength));
+                    MccTable.updateMccMncConfiguration(mContext, imsi.substring(0, 3 + mncLength));
                 }
-                phone.mIccCard.broadcastIccStateChangedIntent(
+                mParentCard.broadcastIccStateChangedIntent(
                         IccCard.INTENT_VALUE_ICC_IMSI, null);
             break;
 
@@ -607,12 +611,12 @@
 
                 if (isValidMbdn) {
                     // Note: MBDN was not included in NUM_OF_SIM_RECORDS_LOADED
-                    new AdnRecordLoader(phone).loadFromEF(EF_MBDN, EF_EXT6,
+                    new AdnRecordLoader(mFh).loadFromEF(EF_MBDN, EF_EXT6,
                             mailboxIndex, obtainMessage(EVENT_GET_MBDN_DONE));
                 } else {
                     // If this EF not present, try mailbox as in CPHS standard
                     // CPHS (CPHS4_2.WW6) is a european standard.
-                    new AdnRecordLoader(phone).loadFromEF(EF_MAILBOX_CPHS,
+                    new AdnRecordLoader(mFh).loadFromEF(EF_MAILBOX_CPHS,
                             EF_EXT1, 1,
                             obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
                 }
@@ -644,7 +648,7 @@
                         // FIXME right now, only load line1's CPHS voice mail entry
 
                         recordsToLoad += 1;
-                        new AdnRecordLoader(phone).loadFromEF(
+                        new AdnRecordLoader(mFh).loadFromEF(
                                 EF_MAILBOX_CPHS, EF_EXT1, 1,
                                 obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
                     }
@@ -661,7 +665,7 @@
                     // FIXME should use SST to decide
                     // FIXME right now, only load line1's CPHS voice mail entry
                     recordsToLoad += 1;
-                    new AdnRecordLoader(phone).loadFromEF(
+                    new AdnRecordLoader(mFh).loadFromEF(
                             EF_MAILBOX_CPHS, EF_EXT1, 1,
                             obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
 
@@ -729,7 +733,7 @@
                     countVoiceMessages = -1;
                 }
 
-                phone.notifyMessageWaitingIndicator();
+                mRecordsEventsRegistrants.notifyResult(EVENT_MWI);
             break;
 
             case EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE:
@@ -758,7 +762,7 @@
                         countVoiceMessages = 0;
                     }
 
-                    phone.notifyMessageWaitingIndicator();
+                    mRecordsEventsRegistrants.notifyResult(EVENT_MWI);
                 }
             break;
 
@@ -839,7 +843,8 @@
                     if (imsi != null && mncLength != UNKNOWN) {
                         // finally have both imsi and the length of the mnc and can parse
                         // the imsi properly
-                        MccTable.updateMccMncConfiguration(phone, imsi.substring(0, 3 + mncLength));
+                        MccTable.updateMccMncConfiguration(mContext,
+                                imsi.substring(0, 3 + mncLength));
                     }
                 }
             break;
@@ -867,7 +872,7 @@
                     callForwardingEnabled =
                         ((data[0] & CFF_LINE1_MASK) == CFF_UNCONDITIONAL_ACTIVE);
 
-                    phone.notifyCallForwardingIndicator();
+                    mRecordsEventsRegistrants.notifyResult(EVENT_CFI);
                 }
                 break;
 
@@ -940,7 +945,7 @@
                             + ar.exception + " length " + index.length);
                 } else {
                     log("READ EF_SMS RECORD index=" + index[0]);
-                    phone.getIccFileHandler().loadEFLinearFixed(EF_SMS,index[0],
+                    mFh.loadEFLinearFixed(EF_SMS,index[0],
                             obtainMessage(EVENT_GET_SMS_DONE));
                 }
                 break;
@@ -1012,7 +1017,7 @@
                         onCphsCompleted = null;
                     }
 
-                    new AdnRecordLoader(phone).
+                    new AdnRecordLoader(mFh).
                             updateEF(adn, EF_MAILBOX_CPHS, EF_EXT1, 1, null,
                             obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE,
                                     onCphsCompleted));
@@ -1066,7 +1071,7 @@
                 // Refer TS 51.011 Section 10.3.46 for the content description
                 callForwardingEnabled = ((data[1] & 0x01) != 0);
 
-                phone.notifyCallForwardingIndicator();
+                mRecordsEventsRegistrants.notifyResult(EVENT_CFI);
                 break;
 
             case EVENT_GET_CSP_CPHS_DONE:
@@ -1103,18 +1108,18 @@
         switch(efid) {
             case EF_MBDN:
                 recordsToLoad++;
-                new AdnRecordLoader(phone).loadFromEF(EF_MBDN, EF_EXT6,
+                new AdnRecordLoader(mFh).loadFromEF(EF_MBDN, EF_EXT6,
                         mailboxIndex, obtainMessage(EVENT_GET_MBDN_DONE));
                 break;
             case EF_MAILBOX_CPHS:
                 recordsToLoad++;
-                new AdnRecordLoader(phone).loadFromEF(EF_MAILBOX_CPHS, EF_EXT1,
+                new AdnRecordLoader(mFh).loadFromEF(EF_MAILBOX_CPHS, EF_EXT1,
                         1, obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
                 break;
             case EF_CSP_CPHS:
                 recordsToLoad++;
                 log("[CSP] SIM Refresh for EF_CSP_CPHS");
-                phone.getIccFileHandler().loadEFTransparent(EF_CSP_CPHS,
+                mFh.loadEFTransparent(EF_CSP_CPHS,
                         obtainMessage(EVENT_GET_CSP_CPHS_DONE));
                 break;
             default:
@@ -1134,7 +1139,7 @@
         }
 
         if (refreshResponse.aid != null &&
-                !refreshResponse.aid.equals(phone.getIccCard().getAid())) {
+                !refreshResponse.aid.equals(mParentCard.getAid())) {
             // This is for different app. Ignore.
             return;
         }
@@ -1152,7 +1157,7 @@
                 break;
             case IccRefreshResponse.REFRESH_RESULT_RESET:
                 if (DBG) log("handleSimRefresh with SIM_REFRESH_RESET");
-                phone.mCM.setRadioPower(false, null);
+                mCi.setRadioPower(false, null);
                 /* Note: no need to call setRadioPower(true).  Assuming the desired
                 * radio power state is still ON (as tracked by ServiceStateTracker),
                 * ServiceStateTracker will call setRadioPower when it receives the
@@ -1174,7 +1179,8 @@
      * to send messages to the secondary 3GPP format SMS dispatcher.
      */
     protected int dispatchGsmMessage(SmsMessageBase message) {
-        return phone.mSMS.dispatchMessage(message);
+        mNewSmsRegistrants.notifyResult(message);
+        return 0;
     }
 
     private void handleSms(byte[] ba) {
@@ -1226,7 +1232,7 @@
                 ba[0] = 1;
 
                 if (false) { // XXX writing seems to crash RdoServD
-                    phone.getIccFileHandler().updateEFLinearFixed(EF_SMS,
+                    mFh.updateEFLinearFixed(EF_SMS,
                             i, ba, null, obtainMessage(EVENT_MARK_SMS_READ_DONE, i));
                 }
             }
@@ -1237,6 +1243,7 @@
         // One record loaded successfully or failed, In either case
         // we need to update the recordsToLoad count
         recordsToLoad -= 1;
+        if (DBG) log("onRecordLoaded " + recordsToLoad + " requested: " + recordsRequested);
 
         if (recordsToLoad == 0 && recordsRequested == true) {
             onAllRecordsLoaded();
@@ -1253,10 +1260,10 @@
 
         // Some fields require more than one SIM record to set
 
-        phone.setSystemProperty(PROPERTY_ICC_OPERATOR_NUMERIC, operator);
+        SystemProperties.set(PROPERTY_ICC_OPERATOR_NUMERIC, operator);
 
         if (imsi != null) {
-            phone.setSystemProperty(PROPERTY_ICC_OPERATOR_ISO_COUNTRY,
+            SystemProperties.set(PROPERTY_ICC_OPERATOR_ISO_COUNTRY,
                     MccTable.countryCodeForMcc(Integer.parseInt(imsi.substring(0,3))));
         }
         else {
@@ -1268,7 +1275,7 @@
 
         recordsLoadedRegistrants.notifyRegistrants(
             new AsyncResult(null, null, null));
-        phone.mIccCard.broadcastIccStateChangedIntent(
+        mParentCard.broadcastIccStateChangedIntent(
                 IccCard.INTENT_VALUE_ICC_LOADED, null);
     }
 
@@ -1294,7 +1301,7 @@
         /* broadcast intent SIM_READY here so that we can make sure
           READY is sent before IMSI ready
         */
-        phone.mIccCard.broadcastIccStateChangedIntent(
+        mParentCard.broadcastIccStateChangedIntent(
                 IccCard.INTENT_VALUE_ICC_READY, null);
 
         fetchSimRecords();
@@ -1302,31 +1309,30 @@
 
     protected void fetchSimRecords() {
         recordsRequested = true;
-        IccFileHandler iccFh = phone.getIccFileHandler();
 
-        logv("fetchSimRecords " + recordsToLoad);
+        if (DBG) log("fetchSimRecords " + recordsToLoad);
 
-        phone.mCM.getIMSIForApp(phone.getIccCard().getAid(), obtainMessage(EVENT_GET_IMSI_DONE));
+        mCi.getIMSIForApp(mParentCard.getAid(), obtainMessage(EVENT_GET_IMSI_DONE));
         recordsToLoad++;
 
-        iccFh.loadEFTransparent(EF_ICCID, obtainMessage(EVENT_GET_ICCID_DONE));
+        mFh.loadEFTransparent(EF_ICCID, obtainMessage(EVENT_GET_ICCID_DONE));
         recordsToLoad++;
 
         // FIXME should examine EF[MSISDN]'s capability configuration
         // to determine which is the voice/data/fax line
-        new AdnRecordLoader(phone).loadFromEF(EF_MSISDN, EF_EXT1, 1,
+        new AdnRecordLoader(mFh).loadFromEF(EF_MSISDN, EF_EXT1, 1,
                     obtainMessage(EVENT_GET_MSISDN_DONE));
         recordsToLoad++;
 
         // Record number is subscriber profile
-        iccFh.loadEFLinearFixed(EF_MBI, 1, obtainMessage(EVENT_GET_MBI_DONE));
+        mFh.loadEFLinearFixed(EF_MBI, 1, obtainMessage(EVENT_GET_MBI_DONE));
         recordsToLoad++;
 
-        iccFh.loadEFTransparent(EF_AD, obtainMessage(EVENT_GET_AD_DONE));
+        mFh.loadEFTransparent(EF_AD, obtainMessage(EVENT_GET_AD_DONE));
         recordsToLoad++;
 
         // Record number is subscriber profile
-        iccFh.loadEFLinearFixed(EF_MWIS, 1, obtainMessage(EVENT_GET_MWIS_DONE));
+        mFh.loadEFLinearFixed(EF_MWIS, 1, obtainMessage(EVENT_GET_MWIS_DONE));
         recordsToLoad++;
 
 
@@ -1334,39 +1340,39 @@
         // the same info as EF[MWIS]. If both exist, both are updated
         // but the EF[MWIS] data is preferred
         // Please note this must be loaded after EF[MWIS]
-        iccFh.loadEFTransparent(
+        mFh.loadEFTransparent(
                 EF_VOICE_MAIL_INDICATOR_CPHS,
                 obtainMessage(EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE));
         recordsToLoad++;
 
         // Same goes for Call Forward Status indicator: fetch both
         // EF[CFIS] and CPHS-EF, with EF[CFIS] preferred.
-        iccFh.loadEFLinearFixed(EF_CFIS, 1, obtainMessage(EVENT_GET_CFIS_DONE));
+        mFh.loadEFLinearFixed(EF_CFIS, 1, obtainMessage(EVENT_GET_CFIS_DONE));
         recordsToLoad++;
-        iccFh.loadEFTransparent(EF_CFF_CPHS, obtainMessage(EVENT_GET_CFF_DONE));
+        mFh.loadEFTransparent(EF_CFF_CPHS, obtainMessage(EVENT_GET_CFF_DONE));
         recordsToLoad++;
 
 
         getSpnFsm(true, null);
 
-        iccFh.loadEFTransparent(EF_SPDI, obtainMessage(EVENT_GET_SPDI_DONE));
+        mFh.loadEFTransparent(EF_SPDI, obtainMessage(EVENT_GET_SPDI_DONE));
         recordsToLoad++;
 
-        iccFh.loadEFLinearFixed(EF_PNN, 1, obtainMessage(EVENT_GET_PNN_DONE));
+        mFh.loadEFLinearFixed(EF_PNN, 1, obtainMessage(EVENT_GET_PNN_DONE));
         recordsToLoad++;
 
-        iccFh.loadEFTransparent(EF_SST, obtainMessage(EVENT_GET_SST_DONE));
+        mFh.loadEFTransparent(EF_SST, obtainMessage(EVENT_GET_SST_DONE));
         recordsToLoad++;
 
-        iccFh.loadEFTransparent(EF_INFO_CPHS, obtainMessage(EVENT_GET_INFO_CPHS_DONE));
+        mFh.loadEFTransparent(EF_INFO_CPHS, obtainMessage(EVENT_GET_INFO_CPHS_DONE));
         recordsToLoad++;
 
-        iccFh.loadEFTransparent(EF_CSP_CPHS,obtainMessage(EVENT_GET_CSP_CPHS_DONE));
+        mFh.loadEFTransparent(EF_CSP_CPHS,obtainMessage(EVENT_GET_CSP_CPHS_DONE));
         recordsToLoad++;
 
         // XXX should seek instead of examining them all
         if (false) { // XXX
-            iccFh.loadEFLinearFixedAll(EF_SMS, obtainMessage(EVENT_GET_ALL_SMS_DONE));
+            mFh.loadEFLinearFixedAll(EF_SMS, obtainMessage(EVENT_GET_ALL_SMS_DONE));
             recordsToLoad++;
         }
 
@@ -1379,9 +1385,10 @@
                          + "ffffffffffffffffffffffffffffff";
             byte[] ba = IccUtils.hexStringToBytes(sms);
 
-            iccFh.updateEFLinearFixed(EF_SMS, 1, ba, null,
+            mFh.updateEFLinearFixed(EF_SMS, 1, ba, null,
                             obtainMessage(EVENT_MARK_SMS_READ_DONE, 1));
         }
+        if (DBG) log("fetchSimRecords " + recordsToLoad + " requested: " + recordsRequested);
     }
 
     /**
@@ -1467,7 +1474,7 @@
             case INIT:
                 spn = null;
 
-                phone.getIccFileHandler().loadEFTransparent( EF_SPN,
+                mFh.loadEFTransparent(EF_SPN,
                         obtainMessage(EVENT_GET_SPN_DONE));
                 recordsToLoad++;
 
@@ -1481,11 +1488,11 @@
 
                     if (DBG) log("Load EF_SPN: " + spn
                             + " spnDisplayCondition: " + spnDisplayCondition);
-                    phone.setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, spn);
+                    SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, spn);
 
                     spnState = Get_Spn_Fsm_State.IDLE;
                 } else {
-                    phone.getIccFileHandler().loadEFTransparent( EF_SPN_CPHS,
+                    mFh.loadEFTransparent( EF_SPN_CPHS,
                             obtainMessage(EVENT_GET_SPN_DONE));
                     recordsToLoad++;
 
@@ -1503,11 +1510,11 @@
                             data, 0, data.length - 1 );
 
                     if (DBG) log("Load EF_SPN_CPHS: " + spn);
-                    phone.setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, spn);
+                    SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, spn);
 
                     spnState = Get_Spn_Fsm_State.IDLE;
                 } else {
-                    phone.getIccFileHandler().loadEFTransparent(
+                    mFh.loadEFTransparent(
                             EF_SPN_SHORT_CPHS, obtainMessage(EVENT_GET_SPN_DONE));
                     recordsToLoad++;
 
@@ -1521,7 +1528,7 @@
                             data, 0, data.length - 1);
 
                     if (DBG) log("Load EF_SPN_SHORT_CPHS: " + spn);
-                    phone.setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, spn);
+                    SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, spn);
                 }else {
                     if (DBG) log("No SPN loaded in either CHPS or 3GPP");
                 }
@@ -1638,7 +1645,7 @@
                      // Operator Selection menu should be disabled.
                      // Operator Selection Mode should be set to Automatic.
                      log("[CSP] Set Automatic Network Selection");
-                     phone.setNetworkSelectionModeAutomatic(null);
+                     mNetworkSelectionModeAutomaticRegistrants.notifyRegistrants();
                  }
                  return;
              }
diff --git a/telephony/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java b/telephony/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
index a9efc98..8f5a420 100755
--- a/telephony/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
+++ b/telephony/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
@@ -24,6 +24,7 @@
 import com.android.internal.telephony.AdnRecord;
 import com.android.internal.telephony.AdnRecordCache;
 import com.android.internal.telephony.IccConstants;
+import com.android.internal.telephony.IccFileHandler;
 import com.android.internal.telephony.IccUtils;
 import com.android.internal.telephony.PhoneBase;
 
@@ -42,7 +43,7 @@
     private static final boolean DBG = true;
     private PbrFile mPbrFile;
     private Boolean mIsPbrPresent;
-    private PhoneBase mPhone;
+    private IccFileHandler mFh;
     private AdnRecordCache mAdnCache;
     private Object mLock = new Object();
     private ArrayList<AdnRecord> mPhoneBookRecords;
@@ -74,8 +75,8 @@
     private static final int USIM_EFEMAIL_TAG = 0xCA;
     private static final int USIM_EFCCP1_TAG  = 0xCB;
 
-    public UsimPhoneBookManager(PhoneBase phone, AdnRecordCache cache) {
-        mPhone = phone;
+    public UsimPhoneBookManager(IccFileHandler fh, AdnRecordCache cache) {
+        mFh = fh;
         mPhoneBookRecords = new ArrayList<AdnRecord>();
         mPbrFile = null;
         // We assume its present, after the first read this is updated.
@@ -138,7 +139,7 @@
     }
 
     private void readPbrFileAndWait() {
-        mPhone.getIccFileHandler().loadEFLinearFixedAll(EF_PBR, obtainMessage(EVENT_PBR_LOAD_DONE));
+        mFh.loadEFLinearFixedAll(EF_PBR, obtainMessage(EVENT_PBR_LOAD_DONE));
         try {
             mLock.wait();
         } catch (InterruptedException e) {
@@ -165,7 +166,7 @@
                 }
             }
             // Read the EFEmail file.
-            mPhone.getIccFileHandler().loadEFLinearFixedAll(fileIds.get(USIM_EFEMAIL_TAG),
+            mFh.loadEFLinearFixedAll(fileIds.get(USIM_EFEMAIL_TAG),
                     obtainMessage(EVENT_EMAIL_LOAD_DONE));
             try {
                 mLock.wait();
@@ -183,7 +184,7 @@
     }
 
     private void readIapFileAndWait(int efid) {
-        mPhone.getIccFileHandler().loadEFLinearFixedAll(efid, obtainMessage(EVENT_IAP_LOAD_DONE));
+        mFh.loadEFLinearFixedAll(efid, obtainMessage(EVENT_IAP_LOAD_DONE));
         try {
             mLock.wait();
         } catch (InterruptedException e) {
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 351c771..5610134 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -45,6 +45,7 @@
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.RemoteException;
 
 import java.util.List;
 
@@ -530,6 +531,14 @@
      * @hide
      */
     @Override
+    public UserInfo getUser(int userId) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
     public boolean removeUser(int id) {
         throw new UnsupportedOperationException();
     }
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/mesh.rs b/tests/RenderScriptTests/tests/src/com/android/rs/test/mesh.rs
index 627ab99..1354897 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/mesh.rs
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/mesh.rs
@@ -11,29 +11,29 @@
 static bool test_mesh_getters() {
     bool failed = false;
 
-    _RS_ASSERT(rsMeshGetVertexAllocationCount(mesh) == 2);
-    _RS_ASSERT(rsMeshGetPrimitiveCount(mesh) == 3);
+    _RS_ASSERT(rsgMeshGetVertexAllocationCount(mesh) == 2);
+    _RS_ASSERT(rsgMeshGetPrimitiveCount(mesh) == 3);
 
-    rs_allocation meshV0 = rsMeshGetVertexAllocation(mesh, 0);
-    rs_allocation meshV1 = rsMeshGetVertexAllocation(mesh, 1);
-    rs_allocation meshV2 = rsMeshGetVertexAllocation(mesh, 2);
+    rs_allocation meshV0 = rsgMeshGetVertexAllocation(mesh, 0);
+    rs_allocation meshV1 = rsgMeshGetVertexAllocation(mesh, 1);
+    rs_allocation meshV2 = rsgMeshGetVertexAllocation(mesh, 2);
     _RS_ASSERT(meshV0.p == vertexAlloc0.p);
     _RS_ASSERT(meshV1.p == vertexAlloc1.p);
     _RS_ASSERT(!rsIsObject(meshV2));
 
-    rs_allocation meshI0 = rsMeshGetIndexAllocation(mesh, 0);
-    rs_allocation meshI1 = rsMeshGetIndexAllocation(mesh, 1);
-    rs_allocation meshI2 = rsMeshGetIndexAllocation(mesh, 2);
-    rs_allocation meshI3 = rsMeshGetIndexAllocation(mesh, 3);
+    rs_allocation meshI0 = rsgMeshGetIndexAllocation(mesh, 0);
+    rs_allocation meshI1 = rsgMeshGetIndexAllocation(mesh, 1);
+    rs_allocation meshI2 = rsgMeshGetIndexAllocation(mesh, 2);
+    rs_allocation meshI3 = rsgMeshGetIndexAllocation(mesh, 3);
     _RS_ASSERT(meshI0.p == indexAlloc0.p);
     _RS_ASSERT(!rsIsObject(meshI1));
     _RS_ASSERT(meshI2.p == indexAlloc2.p);
     _RS_ASSERT(!rsIsObject(meshI3));
 
-    rs_primitive p0 = rsMeshGetPrimitive(mesh, 0);
-    rs_primitive p1 = rsMeshGetPrimitive(mesh, 1);
-    rs_primitive p2 = rsMeshGetPrimitive(mesh, 2);
-    rs_primitive p3 = rsMeshGetPrimitive(mesh, 3);
+    rs_primitive p0 = rsgMeshGetPrimitive(mesh, 0);
+    rs_primitive p1 = rsgMeshGetPrimitive(mesh, 1);
+    rs_primitive p2 = rsgMeshGetPrimitive(mesh, 2);
+    rs_primitive p3 = rsgMeshGetPrimitive(mesh, 3);
 
     _RS_ASSERT(p0 == RS_PRIMITIVE_POINT);
     _RS_ASSERT(p1 == RS_PRIMITIVE_LINE);
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/sampler.rs b/tests/RenderScriptTests/tests/src/com/android/rs/test/sampler.rs
index ac9a549..ff1c0a7 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/sampler.rs
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/sampler.rs
@@ -9,35 +9,35 @@
 static bool test_sampler_getters() {
     bool failed = false;
 
-    _RS_ASSERT(rsgSamplerGetMagnification(minification) == RS_SAMPLER_NEAREST);
-    _RS_ASSERT(rsgSamplerGetMinification(minification) == RS_SAMPLER_LINEAR_MIP_LINEAR);
-    _RS_ASSERT(rsgSamplerGetWrapS(minification) == RS_SAMPLER_CLAMP);
-    _RS_ASSERT(rsgSamplerGetWrapT(minification) == RS_SAMPLER_CLAMP);
-    _RS_ASSERT(rsgSamplerGetAnisotropy(minification) == 1.0f);
+    _RS_ASSERT(rsSamplerGetMagnification(minification) == RS_SAMPLER_NEAREST);
+    _RS_ASSERT(rsSamplerGetMinification(minification) == RS_SAMPLER_LINEAR_MIP_LINEAR);
+    _RS_ASSERT(rsSamplerGetWrapS(minification) == RS_SAMPLER_CLAMP);
+    _RS_ASSERT(rsSamplerGetWrapT(minification) == RS_SAMPLER_CLAMP);
+    _RS_ASSERT(rsSamplerGetAnisotropy(minification) == 1.0f);
 
-    _RS_ASSERT(rsgSamplerGetMagnification(magnification) == RS_SAMPLER_LINEAR);
-    _RS_ASSERT(rsgSamplerGetMinification(magnification) == RS_SAMPLER_NEAREST);
-    _RS_ASSERT(rsgSamplerGetWrapS(magnification) == RS_SAMPLER_CLAMP);
-    _RS_ASSERT(rsgSamplerGetWrapT(magnification) == RS_SAMPLER_CLAMP);
-    _RS_ASSERT(rsgSamplerGetAnisotropy(magnification) == 1.0f);
+    _RS_ASSERT(rsSamplerGetMagnification(magnification) == RS_SAMPLER_LINEAR);
+    _RS_ASSERT(rsSamplerGetMinification(magnification) == RS_SAMPLER_NEAREST);
+    _RS_ASSERT(rsSamplerGetWrapS(magnification) == RS_SAMPLER_CLAMP);
+    _RS_ASSERT(rsSamplerGetWrapT(magnification) == RS_SAMPLER_CLAMP);
+    _RS_ASSERT(rsSamplerGetAnisotropy(magnification) == 1.0f);
 
-    _RS_ASSERT(rsgSamplerGetMagnification(wrapS) == RS_SAMPLER_NEAREST);
-    _RS_ASSERT(rsgSamplerGetMinification(wrapS) == RS_SAMPLER_NEAREST);
-    _RS_ASSERT(rsgSamplerGetWrapS(wrapS) == RS_SAMPLER_WRAP);
-    _RS_ASSERT(rsgSamplerGetWrapT(wrapS) == RS_SAMPLER_CLAMP);
-    _RS_ASSERT(rsgSamplerGetAnisotropy(wrapS) == 1.0f);
+    _RS_ASSERT(rsSamplerGetMagnification(wrapS) == RS_SAMPLER_NEAREST);
+    _RS_ASSERT(rsSamplerGetMinification(wrapS) == RS_SAMPLER_NEAREST);
+    _RS_ASSERT(rsSamplerGetWrapS(wrapS) == RS_SAMPLER_WRAP);
+    _RS_ASSERT(rsSamplerGetWrapT(wrapS) == RS_SAMPLER_CLAMP);
+    _RS_ASSERT(rsSamplerGetAnisotropy(wrapS) == 1.0f);
 
-    _RS_ASSERT(rsgSamplerGetMagnification(wrapT) == RS_SAMPLER_NEAREST);
-    _RS_ASSERT(rsgSamplerGetMinification(wrapT) == RS_SAMPLER_NEAREST);
-    _RS_ASSERT(rsgSamplerGetWrapS(wrapT) == RS_SAMPLER_CLAMP);
-    _RS_ASSERT(rsgSamplerGetWrapT(wrapT) == RS_SAMPLER_WRAP);
-    _RS_ASSERT(rsgSamplerGetAnisotropy(wrapT) == 1.0f);
+    _RS_ASSERT(rsSamplerGetMagnification(wrapT) == RS_SAMPLER_NEAREST);
+    _RS_ASSERT(rsSamplerGetMinification(wrapT) == RS_SAMPLER_NEAREST);
+    _RS_ASSERT(rsSamplerGetWrapS(wrapT) == RS_SAMPLER_CLAMP);
+    _RS_ASSERT(rsSamplerGetWrapT(wrapT) == RS_SAMPLER_WRAP);
+    _RS_ASSERT(rsSamplerGetAnisotropy(wrapT) == 1.0f);
 
-    _RS_ASSERT(rsgSamplerGetMagnification(anisotropy) == RS_SAMPLER_NEAREST);
-    _RS_ASSERT(rsgSamplerGetMinification(anisotropy) == RS_SAMPLER_NEAREST);
-    _RS_ASSERT(rsgSamplerGetWrapS(anisotropy) == RS_SAMPLER_CLAMP);
-    _RS_ASSERT(rsgSamplerGetWrapT(anisotropy) == RS_SAMPLER_CLAMP);
-    _RS_ASSERT(rsgSamplerGetAnisotropy(anisotropy) == 8.0f);
+    _RS_ASSERT(rsSamplerGetMagnification(anisotropy) == RS_SAMPLER_NEAREST);
+    _RS_ASSERT(rsSamplerGetMinification(anisotropy) == RS_SAMPLER_NEAREST);
+    _RS_ASSERT(rsSamplerGetWrapS(anisotropy) == RS_SAMPLER_CLAMP);
+    _RS_ASSERT(rsSamplerGetWrapT(anisotropy) == RS_SAMPLER_CLAMP);
+    _RS_ASSERT(rsSamplerGetAnisotropy(anisotropy) == 8.0f);
 
     if (failed) {
         rsDebug("test_sampler_getters FAILED", 0);
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java
index a3ae9be..065e86f 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java
@@ -37,10 +37,10 @@
     private static Paint whiteLabels;
 
     private static double viewportCoverage(TileData view, TileData tile) {
-        if (tile.left < view.right
-                && tile.right >= view.left
-                && tile.top < view.bottom
-                && tile.bottom >= view.top) {
+        if (tile.left < (view.right * view.scale)
+                && tile.right >= (view.left * view.scale)
+                && tile.top < (view.bottom * view.scale)
+                && tile.bottom >= (view.top * view.scale)) {
             return 1.0f;
         }
         return 0.0f;
@@ -215,7 +215,7 @@
         int lastBar = 0;
         for (int frameIndex = 0; frameIndex < data.frames.length; frameIndex++) {
             TileData frame[] = data.frames[frameIndex];
-            int newBar = (frame[0].top + frame[0].bottom) / 2;
+            int newBar = (int)((frame[0].top + frame[0].bottom) * frame[0].scale / 2.0f);
 
             MetricGen s = Metrics[metricIndex];
             double absoluteValue = s.getValue(frame);
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/RunData.java b/tests/TileBenchmark/src/com/test/tilebenchmark/RunData.java
index 2da61cc..5e48afd 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/RunData.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/RunData.java
@@ -46,7 +46,8 @@
 
         public String toString() {
             return "Tile (" + left + "," + top + ")->("
-                    + right + "," + bottom + ")";
+                    + right + "," + bottom + ")"
+                    + (isReady ? "ready" : "NOTready") + " at scale " + scale;
         }
     }
 
diff --git a/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java
index 4cacbc4..cceed16 100644
--- a/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java
@@ -39,7 +39,7 @@
     @SmallTest
 	public void testREORDER_TASKS() {
         try {
-            mAm.moveTaskToFront(0, 0);
+            mAm.moveTaskToFront(0, 0, null);
             fail("IActivityManager.moveTaskToFront did not throw SecurityException as"
                     + " expected");
         } catch (SecurityException e) {
diff --git a/tools/aapt/ZipFile.cpp b/tools/aapt/ZipFile.cpp
index 8057068..3994c31 100644
--- a/tools/aapt/ZipFile.cpp
+++ b/tools/aapt/ZipFile.cpp
@@ -20,8 +20,8 @@
 
 #define LOG_TAG "zip"
 
-#include <androidfw/ZipUtils.h>
 #include <utils/Log.h>
+#include <utils/ZipUtils.h>
 
 #include "ZipFile.h"
 
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
index 945b3cd7..5256b58 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
@@ -85,15 +85,18 @@
     // ------ Native Delegates ------
 
     @LayoutlibDelegate
-    /*package*/ static void nativeSetDefaultConfig(int nativeConfig) {
-        // pass
+    /*package*/ static Bitmap nativeDecodeStream(InputStream is, byte[] storage,
+            Rect padding, Options opts) {
+        return nativeDecodeStream(is, storage, padding, opts, false, 1.f);
     }
 
     @LayoutlibDelegate
-    /*package*/ static Bitmap nativeDecodeStream(InputStream is, byte[] storage,
-            Rect padding, Options opts) {
+    /*package*/ static  Bitmap nativeDecodeStream(InputStream is, byte[] storage,
+            Rect padding, Options opts, boolean applyScale, float scale) {
         Bitmap bm = null;
 
+        //TODO support rescaling
+
         Density density = Density.MEDIUM;
         if (opts != null) {
             density = Density.getEnum(opts.inDensity);
@@ -147,6 +150,13 @@
     }
 
     @LayoutlibDelegate
+    /*package*/ static Bitmap nativeDecodeAsset(int asset, Rect padding, Options opts,
+            boolean applyScale, float scale) {
+        opts.inBitmap = null;
+        return null;
+    }
+
+    @LayoutlibDelegate
     /*package*/ static Bitmap nativeDecodeByteArray(byte[] data, int offset,
             int length, Options opts) {
         opts.inBitmap = null;
diff --git a/tools/layoutlib/bridge/src/android/view/ViewRootImpl_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewRootImpl_Delegate.java
new file mode 100644
index 0000000..14b84ef
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/ViewRootImpl_Delegate.java
@@ -0,0 +1,34 @@
+/*
+ * 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.view;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link ViewRootImpl}
+ *
+ * Through the layoutlib_create tool, the original  methods of ViewRootImpl have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ */
+public class ViewRootImpl_Delegate {
+
+    @LayoutlibDelegate
+    /*package*/ static boolean isInTouchMode() {
+        return false; // this allows displaying selection.
+    }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java
index bef2c95..8b1d41a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowManager.java
@@ -340,6 +340,12 @@
     }
 
     @Override
+    public void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX, int startY,
+            IRemoteCallback startedCallback) throws RemoteException {
+        // TODO Auto-generated method stub
+    }
+
+    @Override
     public void pauseKeyDispatching(IBinder arg0) throws RemoteException {
         // TODO Auto-generated method stub
 
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 170cd6a..79e02c8 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -114,6 +114,7 @@
         "android.view.LayoutInflater#rInflate",
         "android.view.LayoutInflater#parseInclude",
         "android.view.View#isInEditMode",
+        "android.view.ViewRootImpl#isInTouchMode",
         "android.view.inputmethod.InputMethodManager#getInstance",
         "android.util.Log#println_native",
         "com.android.internal.util.XmlUtils#convertValueToInt",
diff --git a/voip/java/android/net/rtp/AudioStream.java b/voip/java/android/net/rtp/AudioStream.java
index d761214..b7874f7 100644
--- a/voip/java/android/net/rtp/AudioStream.java
+++ b/voip/java/android/net/rtp/AudioStream.java
@@ -158,7 +158,7 @@
             if (type < 96 || type > 127) {
                 throw new IllegalArgumentException("Invalid type");
             }
-            if (type == mCodec.type) {
+            if (mCodec != null && type == mCodec.type) {
                 throw new IllegalArgumentException("The type is used by codec");
             }
         }
diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp
index 1139577..b9bbd16 100644
--- a/voip/jni/rtp/AudioGroup.cpp
+++ b/voip/jni/rtp/AudioGroup.cpp
@@ -809,9 +809,9 @@
     AudioTrack track;
     AudioRecord record;
     if (track.set(AUDIO_STREAM_VOICE_CALL, sampleRate, AUDIO_FORMAT_PCM_16_BIT,
-        AUDIO_CHANNEL_OUT_MONO, output) != NO_ERROR || record.set(
-        AUDIO_SOURCE_VOICE_COMMUNICATION, sampleRate, AUDIO_FORMAT_PCM_16_BIT,
-        AUDIO_CHANNEL_IN_MONO, input) != NO_ERROR) {
+                AUDIO_CHANNEL_OUT_MONO, output) != NO_ERROR ||
+            record.set(AUDIO_SOURCE_VOICE_COMMUNICATION, sampleRate, AUDIO_FORMAT_PCM_16_BIT,
+                AUDIO_CHANNEL_IN_MONO, input) != NO_ERROR) {
         ALOGE("cannot initialize audio device");
         return false;
     }
diff --git a/wifi/java/android/net/wifi/NetworkUpdateResult.java b/wifi/java/android/net/wifi/NetworkUpdateResult.java
index 6b7b68b..234bbe1 100644
--- a/wifi/java/android/net/wifi/NetworkUpdateResult.java
+++ b/wifi/java/android/net/wifi/NetworkUpdateResult.java
@@ -22,6 +22,7 @@
     int netId;
     boolean ipChanged;
     boolean proxyChanged;
+    boolean isNewNetwork = false;
 
     public NetworkUpdateResult(int id) {
         netId = id;
@@ -58,4 +59,12 @@
     public boolean hasProxyChanged() {
         return proxyChanged;
     }
+
+    public boolean isNewNetwork() {
+        return isNewNetwork;
+    }
+
+    public void setIsNewNetwork(boolean isNew) {
+        isNewNetwork = isNew;
+    }
 }
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index c648568..a9dbd10 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -274,7 +274,8 @@
             mConfiguredNetworks.get(netId).status = Status.ENABLED;
         }
         mWifiNative.saveConfig();
-        sendConfiguredNetworksChangedBroadcast();
+        sendConfiguredNetworksChangedBroadcast(config, result.isNewNetwork() ?
+                WifiManager.CHANGE_REASON_ADDED : WifiManager.CHANGE_REASON_CONFIG_CHANGE);
         return result;
     }
 
@@ -308,13 +309,16 @@
     boolean forgetNetwork(int netId) {
         if (mWifiNative.removeNetwork(netId)) {
             mWifiNative.saveConfig();
+            WifiConfiguration target = null;
             WifiConfiguration config = mConfiguredNetworks.get(netId);
             if (config != null) {
-                mConfiguredNetworks.remove(netId);
+                target = mConfiguredNetworks.remove(netId);
                 mNetworkIds.remove(configKey(config));
             }
-            writeIpAndProxyConfigurations();
-            sendConfiguredNetworksChangedBroadcast();
+            if (target != null) {
+                writeIpAndProxyConfigurations();
+                sendConfiguredNetworksChangedBroadcast(target, WifiManager.CHANGE_REASON_REMOVED);
+            }
             return true;
         } else {
             loge("Failed to remove network " + netId);
@@ -333,7 +337,11 @@
      */
     int addOrUpdateNetwork(WifiConfiguration config) {
         NetworkUpdateResult result = addOrUpdateNetworkNative(config);
-        sendConfiguredNetworksChangedBroadcast();
+        if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
+            sendConfiguredNetworksChangedBroadcast(mConfiguredNetworks.get(result.getNetworkId()),
+                    result.isNewNetwork ? WifiManager.CHANGE_REASON_ADDED :
+                        WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+        }
         return result.getNetworkId();
     }
 
@@ -348,14 +356,17 @@
      */
     boolean removeNetwork(int netId) {
         boolean ret = mWifiNative.removeNetwork(netId);
+        WifiConfiguration config = null;
         if (ret) {
-            WifiConfiguration config = mConfiguredNetworks.get(netId);
+            config = mConfiguredNetworks.get(netId);
             if (config != null) {
-                mConfiguredNetworks.remove(netId);
+                config = mConfiguredNetworks.remove(netId);
                 mNetworkIds.remove(configKey(config));
             }
         }
-        sendConfiguredNetworksChangedBroadcast();
+        if (config != null) {
+            sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
+        }
         return ret;
     }
 
@@ -365,12 +376,24 @@
      * API. The more powerful selectNetwork()/saveNetwork() is used by the
      * state machine for connecting to a network
      *
-     * @param netId network to be removed
+     * @param netId network to be enabled
      * @return {@code true} if it succeeds, {@code false} otherwise
      */
     boolean enableNetwork(int netId, boolean disableOthers) {
         boolean ret = enableNetworkWithoutBroadcast(netId, disableOthers);
-        sendConfiguredNetworksChangedBroadcast();
+        if (disableOthers) {
+            sendConfiguredNetworksChangedBroadcast();
+        } else {
+            WifiConfiguration enabledNetwork = null;
+            synchronized(mConfiguredNetworks) {
+                enabledNetwork = mConfiguredNetworks.get(netId);
+            }
+            // check just in case the network was removed by someone else.
+            if (enabledNetwork != null) {
+                sendConfiguredNetworksChangedBroadcast(enabledNetwork,
+                        WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+            }
+        }
         return ret;
     }
 
@@ -403,13 +426,18 @@
      */
     boolean disableNetwork(int netId, int reason) {
         boolean ret = mWifiNative.disableNetwork(netId);
+        WifiConfiguration network = null;
         WifiConfiguration config = mConfiguredNetworks.get(netId);
         /* Only change the reason if the network was not previously disabled */
         if (config != null && config.status != Status.DISABLED) {
             config.status = Status.DISABLED;
             config.disableReason = reason;
+            network = config;
         }
-        sendConfiguredNetworksChangedBroadcast();
+        if (network != null) {
+            sendConfiguredNetworksChangedBroadcast(network,
+                    WifiManager.CHANGE_REASON_CONFIG_CHANGE);
+        }
         return ret;
     }
 
@@ -575,9 +603,29 @@
         return false;
     }
 
+    /**
+     * Should be called when a single network configuration is made.
+     * @param network The network configuration that changed.
+     * @param reason The reason for the change, should be one of WifiManager.CHANGE_REASON_ADDED,
+     * WifiManager.CHANGE_REASON_REMOVED, or WifiManager.CHANGE_REASON_CHANGE.
+     */
+    private void sendConfiguredNetworksChangedBroadcast(WifiConfiguration network,
+            int reason) {
+        Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, false);
+        intent.putExtra(WifiManager.EXTRA_WIFI_CONFIGURATION, network);
+        intent.putExtra(WifiManager.EXTRA_CHANGE_REASON, reason);
+        mContext.sendBroadcast(intent);
+    }
+
+    /**
+     * Should be called when multiple network configuration changes are made.
+     */
     private void sendConfiguredNetworksChangedBroadcast() {
         Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, true);
         mContext.sendBroadcast(intent);
     }
 
@@ -1136,6 +1184,7 @@
         mNetworkIds.put(configKey(currentConfig), netId);
 
         NetworkUpdateResult result = writeIpAndProxyConfigurationsOnChange(currentConfig, config);
+        result.setIsNewNetwork(newNetwork);
         result.setNetworkId(netId);
         return result;
     }
@@ -1235,7 +1284,8 @@
         if (ipChanged || proxyChanged) {
             currentConfig.linkProperties = linkProperties;
             writeIpAndProxyConfigurations();
-            sendConfiguredNetworksChangedBroadcast();
+            sendConfiguredNetworksChangedBroadcast(currentConfig,
+                    WifiManager.CHANGE_REASON_CONFIG_CHANGE);
         }
         return new NetworkUpdateResult(ipChanged, proxyChanged);
     }
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index d746810..8aa613b 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -294,12 +294,53 @@
 
     /**
      * Broadcast intent action indicating that the configured networks changed.
-     * This can be as a result of adding/updating/deleting a network
+     * This can be as a result of adding/updating/deleting a network. If
+     * {@link #EXTRA_MULTIPLE_NETWORKS_CHANGED} is set to true the new configuration
+     * can be retreived with the {@link #EXTRA_WIFI_CONFIGURATION} extra. If multiple
+     * Wi-Fi configurations changed, {@link #EXTRA_WIFI_CONFIGURATION} will not be present.
      * @hide
      */
     public static final String CONFIGURED_NETWORKS_CHANGED_ACTION =
         "android.net.wifi.CONFIGURED_NETWORKS_CHANGE";
     /**
+     * The lookup key for a (@link android.net.wifi.WifiConfiguration} object representing
+     * the changed Wi-Fi configuration when the {@link #CONFIGURED_NETWORKS_CHANGED_ACTION}
+     * broadcast is sent.
+     * @hide
+     */
+    public static final String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration";
+    /**
+     * Multiple network configurations have changed.
+     * @see #CONFIGURED_NETWORKS_CHANGED_ACTION
+     *
+     * @hide
+     */
+    public static final String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges";
+    /**
+     * The lookup key for an integer indicating the reason a Wi-Fi network configuration
+     * has changed. Only present if {@link #EXTRA_MULTIPLE_NETWORKS_CHANGED} is {@code false}
+     * @see #CONFIGURED_NETWORKS_CHANGED_ACTION
+     * @hide
+     */
+    public static final String EXTRA_CHANGE_REASON = "changeReason";
+    /**
+     * The configuration is new and was added.
+     * @hide
+     */
+    public static final int CHANGE_REASON_ADDED = 0;
+    /**
+     * The configuration was removed and is no longer present in the system's list of
+     * configured networks.
+     * @hide
+     */
+    public static final int CHANGE_REASON_REMOVED = 1;
+    /**
+     * The configuration has changed as a result of explicit action or because the system
+     * took an automated action such as disabling a malfunctioning configuration.
+     * @hide
+     */
+    public static final int CHANGE_REASON_CONFIG_CHANGE = 2;
+    /**
      * An access point scan has completed, and results are available from the supplicant.
      * Call {@link #getScanResults()} to obtain the results.
      */
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 05a8ca7..cbf7bf8 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -394,7 +394,7 @@
      * Starting and shutting down driver too quick causes problems leading to driver
      * being in a bad state. Delay driver stop.
      */
-    private static final int DELAYED_DRIVER_STOP_MS = 2 * 60 * 1000; /* 2 minutes */
+    private final int mDriverStopDelayMs;
     private int mDelayedStopCounter;
     private boolean mInDelayedStop = false;
 
@@ -563,6 +563,9 @@
         mDefaultSupplicantScanIntervalMs = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_wifi_supplicant_scan_interval);
 
+        mDriverStopDelayMs = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_wifi_driver_stop_delay);
+
         mContext.registerReceiver(
             new BroadcastReceiver() {
                 @Override
@@ -2589,7 +2592,7 @@
                     } else {
                         /* send regular delayed shut down */
                         sendMessageDelayed(obtainMessage(CMD_DELAYED_STOP_DRIVER,
-                                mDelayedStopCounter, 0), DELAYED_DRIVER_STOP_MS);
+                                mDelayedStopCounter, 0), mDriverStopDelayMs);
                     }
                     break;
                 case CMD_START_DRIVER: